import ID3import sysimport osimport os.pathimport wximport shutildef gen_ipod_structure(path_root = "E:\\"): ''' Returns the path on an iPod where music should reside''' return path_root + 'iPod_Control\\Music\\'def parse_ipod_structure(path): ''' Takes a path to an iPod-type iPod_control directory (really any directory with a bunch of mp3 files or subdirectories with mp3 files) and returns a list of tuples (ID3_tags_dict, path_to_music_file) ''' tagged_critters = [] untaggable_critters = [] for root, dirs, files in os.walk(path): for music_file in files: file_path = root + '\\' + music_file try: #keeping the ID3 object around keeps the file open tags = ID3.ID3(file_path).as_dict() tagged_critters.append((tags, file_path)) except ID3.InvalidTagError, message: untaggable_critters.append(file_path) return tagged_critters, untaggable_crittersclass ipod_explorer_frame(wx.Frame): columns = {0: ("Track Title", 'TITLE'), 1: ("Artist", "ARTIST"), 2: ("Album", "ALBUM")} ID_NEW_DRIVE = 101 ID_RIP = 102 def __init__(self): global parse_ipod_structure, gen_ipod_structure wx.Frame.__init__(self, None, 100, 'uPod Investigator', size=wx.Size(600, 600)) self.sz = wx.BoxSizer(wx.VERTICAL) # # Set up ListControl to show track info # self.lc = wx.ListCtrl(self, -1, style=wx.LC_REPORT, size=wx.Size(590, 500)) for i in self.columns: self.lc.InsertColumn(i, self.columns[i][0]) self.lc.SetColumnWidth(i, 570 / len(self.columns)) # # Start with our first iPod # self.new_drive() # # Create Menu Bar # menubar = wx.MenuBar() file = wx.Menu() edit = wx.Menu() # # Construct File menu # file.Append(self.ID_NEW_DRIVE, '&Open new iPod\tCTRL-O') file.Append(self.ID_RIP, '&Rip my unlicensed music!\tCTRL-R') file.AppendSeparator() file.Append(wx.ID_EXIT, 'E&xit\tCTRL-Q') self.Connect(wx.ID_EXIT, -1, wx.wxEVT_COMMAND_MENU_SELECTED, self.on_exit) self.Connect(self.ID_NEW_DRIVE, -1, wx.wxEVT_COMMAND_MENU_SELECTED, self.new_drive) self.Connect(self.ID_RIP, -1, wx.wxEVT_COMMAND_MENU_SELECTED, self.do_rip) menubar.Append(file, '&File') self.SetMenuBar(menubar) self.txt = wx.StaticText(self, -1, 'Select as many songs as you want from the list (CTRL for multiple), then go to file -> rip', style=wx.ALIGN_CENTER) self.sz.Add(self.txt) self.sz.Add(self.lc, wx.EXPAND) self.lc.Fit() self.sz.Fit(self) self.SetBackgroundColour(wx.WHITE) self.SetSizer(self.sz) self.Center() self.Show() def new_drive(self, *args): ''' Starts the app (over) with a new iPod. ''' root_dir = self.pick_drive_letter() # # Load all songs with ID3 tags in %(root_dir)s/iPod_Control/*/ # struct = gen_ipod_structure(root_dir) songs = parse_ipod_structure(struct)[0] # Eliminate songs that don't have proper ID3 tags songs = [i for i in songs if i[0].has_key('TITLE')] self.songs = songs self.load_song_list() def pick_drive_letter(self): ''' Prompts the user for their iPod's drive letter ''' root_dir = None while root_dir is None or not os.path.exists(root_dir + '\\iPod_Control\\Music\\'): if root_dir is not None: title = "Previous selection has no iPod_Control directory" else: title = "Select your iPod's drive letter" root = wx.DirDialog(self, title, "/", style=wx.EXPAND) root.ShowModal() root_dir = root.GetPath() return root_dir def on_exit(self, *args): self.Close(True) def load_song_list(self, sortby='TITLE'): ''' Reloads the song list from self.songs ''' self.lc.DeleteAllItems() self.sort_list(sortby) for i, tup in enumerate(self.songs): song, file = tup self.insert_row(song, file, i) def insert_row(self, song, file, index): ''' Inserts a row into the ListCtrl, accounting for which columns we want to use (modify self.columns) ''' last = self.lc.GetItemCount() self.lc.InsertStringItem(last, song.get(self.columns[0][1],'N/A')) #self.lc.SetStringItem(last, 1, song.get('ARTIST','N/A')) for i in self.columns: if i != 0: self.lc.SetStringItem(last, i, song.get(self.columns[i][1],'N/A')) self.lc.SetItemData(last, index) def do_rip(self, *args): ''' Walks the user through ripping their music to their computer''' to_rip = self.selected_songs() dest_dir = wx.DirDialog(self, 'Where do you want to rip to?', '/') dest_dir.ShowModal() dest_dir = dest_dir.GetPath() for song,file in to_rip: # Give each artist their own dir new_name = dest_dir + '\\%(ARTIST)s\\%(ALBUM)s\\%(TITLE)s.' % song # and add a file extension new_name += file.split('.')[-1] print 'COPYING %s -> %s...' % (file, new_name), try: head, tail = os.path.split(new_name) if not os.path.exists(head): os.makedirs(head) shutil.copy(file, new_name) print 'DONE.' except Exception, e: print 'ERROR!', e def selected_songs(self): ''' Returns all the songs currently selected on the ListCtrl''' songs = [] max = self.lc.GetItemCount() i = self.lc.GetNextSelected(-1) while i < max and i != -1: songs.append(self.songs[i]) i = self.lc.GetNextSelected(i) return songs def sort_list(self, by = 'ARTIST'): ''' Sorts the list by a particular dictionary key ''' def cmp_titles(x,y): x = x[0].get(by, 0) y = y[0].get(by, 0) if x < y: return -1 elif x == y: return 0 return 1 self.songs.sort(cmp_titles)app = wx.App(redirect=False)frame = ipod_explorer_frame()app.MainLoop()