#!/usr/bin/env python ### # # RBCD - RadioBlogClub Downloader - Download songs from RadioBlogClub. # Copyright (C) 2006 foch # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # ### __version__ = "2.1.0" __date__ = "02/06/2007" import urllib, re, sys, gobject, gtk, threading, os # download folder result_folder = "~/rbcd_result" # encoding key key = "657ecb3231ac0b275497d4d6f00b61a1" # icon file icon_file = "/usr/share/icons/hicolor/24x24/apps/sound-juicer.png" (COLUMN_NUMBER, COLUMN_DOWNLOAD, COLUMN_NAME) = range(3) # min size for valid MP3 min_size = 20000 class UserInterface(gtk.Window): def __init__(self, parent=None): """ Create window and model """ # create window, etc gtk.Window.__init__(self) try: self.set_screen(parent.get_screen()) except AttributeError: self.connect('destroy', lambda *w: gtk.main_quit()) self.set_title('RadioBlogClub Downloader') self.set_border_width(8) self.set_default_size(300, 250) vbox = gtk.VBox(False, 8) self.add(vbox) # top part of window box_top = gtk.HBox(False, 5) self.label = gtk.Label('Enter keywords :') self.entry = gtk.Entry(max=0) self.search = gtk.Button(stock=gtk.STOCK_FIND) self.select_all = gtk.Button("Select all") self.select_none = gtk.Button("Select none") self.select_website = gtk.combo_box_new_text() self.select_website.append_text("RadioBlogClub"); box_top.pack_start(self.label, False, False) box_top.pack_start(self.entry, False, False) box_top.pack_start(self.search, False, False) box_top.pack_start(self.select_all , False, False) box_top.pack_start(self.select_none, False, False) box_top.pack_end(self.select_website, False, False) self.select_website.set_active(0) self.search.connect("clicked", self.searchSongs, None) self.entry.connect("activate", self.searchSongs, None) self.select_all.connect("clicked", self.selectAll, None) self.select_none.connect("clicked", self.selectNone, None) vbox.pack_start(box_top, False, False) # middle part of window sw = gtk.ScrolledWindow() sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) vbox.pack_start(sw) # create tree model self.model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_BOOLEAN, gobject.TYPE_STRING) # create tree view self.treeview = gtk.TreeView(self.model) self.treeview.set_rules_hint(True) self.treeview.set_search_column(COLUMN_NAME) sw.add(self.treeview) # down part of window box_down = gtk.HBox(False, 5) self.state_label = gtk.Label('') download = gtk.Button('Download') exitb = gtk.Button(stock=gtk.STOCK_QUIT) aboutb = gtk.Button(stock=gtk.STOCK_DIALOG_INFO) box_down.pack_end(exitb, False, False) box_down.pack_end(aboutb, False, False) box_down.pack_end(download, False, False) box_down.pack_start(self.state_label, False, False) exitb.connect_object("clicked", gtk.Widget.destroy, self) download.connect("clicked", self.download, None) aboutb.connect("clicked", self.about, None) vbox.pack_start(box_down, False, False) # create columns self.add_columns(self.treeview) # change window size self.resize(800, 500) # set icon #self.set_icon_from_file(icon_file) # print everything self.show_all() def add_columns(self, treeview): """ Add the 3 columns """ # column for fixed toggles renderer = gtk.CellRendererToggle() renderer.connect('toggled', self.select_toggled, self.model) column = gtk.TreeViewColumn('Select', renderer, active=COLUMN_DOWNLOAD) # set this column to a fixed sizing(of 50 pixels) column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) column.set_fixed_width(50) treeview.append_column(column) # column for description column = gtk.TreeViewColumn('#', gtk.CellRendererText(), text=COLUMN_NUMBER) column.set_sort_column_id(COLUMN_NUMBER) treeview.append_column(column) # column for description column = gtk.TreeViewColumn('Song name', gtk.CellRendererText(), text=COLUMN_NAME) column.set_sort_column_id(COLUMN_NAME) treeview.append_column(column) def select_toggled(self, cell, path, model): """ Change the select status """ # get toggled iter iter = model.get_iter((int(path),)) download = model.get_value(iter, COLUMN_DOWNLOAD) # do something with the value download = not download # set new value model.set(iter, COLUMN_DOWNLOAD, download) def searchSongs(self, widget, data=None): """ Start searching songs according to the given key words """ # get the selected website to use selected = self.select_website.get_active() if selected == 0: self.website = RadioBlogClub(self) try: search = Search(self, self.website, self.entry.get_text().split(" ")) self.setStateText("Starting research...") print "Starting research..." search.start() except AssertionError, detail: self.setStateText(str(detail)) print detail except IOError: self.setStateText("Could not connect.") def setSongList(self, songs, data): self.songs = songs self.data = data self.clearList() self.createList() self.setStateText("Research done.") print "Research done." def selectAll(self, widget, data=None): """ Select all the lines """ # iter on all the paths iter = self.model.get_iter_first() while iter is not None: self.model.set_value(iter, 1, 1) iter = self.model.iter_next(iter) def selectNone(self, widget, data=None): """ Select no line """ # iter on all the paths iter = self.model.get_iter_first() while iter is not None: self.model.set_value(iter, 1, 0) iter = self.model.iter_next(iter) def download(self, widget, data=None): """ Start downloading the selected songs """ try: down = Download(self, self.website, self.songs, self.model) down.start() except AssertionError, detail: self.setStateText(str(detail)) print detail def createList(self): """ Create the song list """ for item in self.data: iter = self.model.append() self.model.set(iter, COLUMN_NUMBER, item[COLUMN_NUMBER], COLUMN_DOWNLOAD, item[COLUMN_DOWNLOAD], COLUMN_NAME, Search.shortName(item[COLUMN_NAME])) def clearList(self): """ Clear the song list """ self.model.clear() def setStateText(self, text): """ Change state text """ self.state_label.set_text(text) def about(self, widget, data=None): """ Print an about dialog window""" dialog = gtk.AboutDialog() dialog.set_name("RBCD") dialog.set_comments("A program to download songs from RadioBlogClub") dialog.set_copyright("\302\251 Copyright 2006-2007 foch") dialog.set_website("http://www.dobitchu.info/blog") dialog.set_license('GNU General Public License version 2') dialog.set_version(__version__) dialog.set_authors(['Main programmer :', ' foch ', ' ', 'and contributors :' ,' Guillaume86', ' Zoolonly']) #dialog.set_icon_from_file(icon_file) # close dialog on user response dialog.connect("response", lambda d, r: d.destroy()) dialog.show() def main(self): """ Main function, one to rule them all """ # create download folder if not os.path.isdir(os.path.expanduser(result_folder)): os.mkdir (os.path.expanduser(result_folder)) gobject.threads_init() gtk.gdk.threads_enter() gtk.main() gtk.gdk.threads_leave() return 0 class RadioBlogClub: def __init__(self, parent): """ Init the thread """ self.parent = parent @staticmethod def rename(file): """ Change the RBS extension to MP3 """ name = file.split("/")[-1].split(".") # remove some extensions name = [part for part in name if not part.lower() in ("rbs", "mp3", "swf")] # the file is a mp3, add .mp3 at the end of the name name.append("mp3") return ".".join(name) def searchURL(self, i, params): """ Return the URL to search the MP3 """ url = "http://www.radioblogclub.com/search/" + str(i) + "/" + params try: return urllib.urlopen(url).read() except IOError: print "Error : could not connect" raise #reraise exception def search(self, text): """ Do a research """ # get the webpage data try: data = self.searchURL(0, "_".join(text)) except IOError: gobject.idle_add(self.parent.setStateText, "Error : could not connect") gtk.gdk.threads_leave() return lines = data.split('\n') # regular expression to find the number of songs reg1 = re.compile('^.*(.*) for .*$') n_songs = 0 # get the number of songs for line in lines: if reg1.match(line): n_songs = int(reg1.search(line).groups()[0]) break # if no song is found, exit if n_songs == 0: gobject.idle_add(self.parent.setSongList, [], []) gtk.gdk.threads_leave() return # get the number of pages to browse n_pages = n_songs / 50 + 1 # inform the user gobject.idle_add(self.parent.setStateText, \ "Search started... (about %i songs)" % (n_songs,)) # download the pages pages = [] pages.append(lines) numbers = [i * 50 for i in range(1, n_pages)] try: for i in numbers: pages.append(self.searchURL(i, "_".join(text)).split('\n')) except IOError: gobject.idle_add(self.parent.setStateText, "Error : could not connect") gtk.gdk.threads_leave() return # regular expression to find the song URLs reg2 = re.compile('^.*Track.start\(\'(.*)\'\).*(.*).*$') songs = [] data = [] i = 0 # parse the HTML data to find the songs and save in a list for page in pages: for line in page: if reg2.match(line): result = reg2.search(line).groups() try: url = unicode(result[0], "utf-8") name = unicode(result[1], "utf-8") songs.append((url, name)) data.append((i + 1, False, name)) i += 1 except UnicodeDecodeError: # encoding error: skip song print "%i : Encoding error. Skipping song." % (i,) gobject.idle_add(self.parent.setSongList, songs, data) def download(self, songs, model): """ Download the files """ # init the counters counter_ok = 0 counter_all = 0 # iter on all the paths iter = model.get_iter_first() while iter is not None: number = model.get_value(iter, 0) selected = model.get_value(iter, 1) iter = model.iter_next(iter) # if the song is selected for download... if selected: counter_all += 1 name = songs[number - 1][1] # change the extension to .mp3 name = RadioBlogClub.rename(name) state = "Downloading %s..." % (Search.shortName(name), ) gobject.idle_add(self.parent.setStateText, state) url = songs[number - 1][0]+ "\&k=" + key print "Downloading %s" % (name, ) print "from the RadioBlog :\n%s" % (url, ) # download the file print "Downloading to %s..." % (result_folder, ) result = os.system("cd %s && wget %s" % (result_folder, url)) if result == 0: counter_ok += 1 print "Download successful." else: print "Download failed." print # if we have downloaded songs, rename if counter_ok > 0: state = "Renaming files" gobject.idle_add(self.parent.setStateText, state) print state # renaming the rbs files to mp3 files = os.listdir(os.path.expanduser(result_folder)) files = [filename for filename in files if filename[0] != '.'] for file in files: os.rename(os.path.join(os.path.expanduser(result_folder), file), (os.path.join(os.path.expanduser(result_folder), RadioBlogClub.rename(file)))) # end state = "Downloaded %i song%sout of %i." % (counter_ok, counter_ok > 1 and 's ' or ' ', counter_all) gobject.idle_add(self.parent.setStateText, state) print state class Search(threading.Thread): instantiated = 0 # this class is a Singleton def __init__(self, parent, website, text): """ Init the thread """ Search.instantiated += 1 assert Search.instantiated == 1, \ "Search already started." assert Download.instantiated == 0, \ "Can't search while download in progress." threading.Thread.__init__(self) self.text = text self.parent = parent self.website = website def __del__(self): """ Destructor """ Search.instantiated -= 1 @staticmethod def shortName(name): """ Cut out too long name """ if len(name) > 80: name = name[0:79] + "..." return name def run(self): """ Thread main function """ gtk.gdk.threads_enter() self.website.search(self.text); gtk.gdk.threads_leave() class Download(threading.Thread): instantiated = 0 # this class is a Singleton def __init__(self, parent, website, songs, model): """ Init the thread """ Download.instantiated += 1 assert Download.instantiated == 1, \ "Download already started" assert Search.instantiated == 0, \ "Can't download while search in progress." threading.Thread.__init__(self) self.parent = parent self.songs = songs self.model = model self.website = website def __del__(self): """ Destructor """ Download.instantiated -= 1 def run(self): """ Thread main function """ gtk.gdk.threads_enter() self.website.download(self.songs, self.model) gtk.gdk.threads_leave() # program entry point if __name__ == "__main__": GUI = UserInterface() GUI.main()