diff options
-rw-r--r-- | bindings/python/Makefile.am | 6 | ||||
-rwxr-xr-x | bindings/python/examples/add_song.py | 50 | ||||
-rwxr-xr-x | bindings/python/examples/playwith_ipod_api.py | 14 | ||||
-rw-r--r-- | bindings/python/gpod.i | 46 | ||||
-rw-r--r-- | bindings/python/ipod.py | 203 |
5 files changed, 266 insertions, 53 deletions
diff --git a/bindings/python/Makefile.am b/bindings/python/Makefile.am index 1a6a396..381df7e 100644 --- a/bindings/python/Makefile.am +++ b/bindings/python/Makefile.am @@ -3,7 +3,8 @@ SUBDIRS = examples EXTRA_DIST = \ gpod.i \ ipod.py \ - __init__.py + __init__.py \ + gtkpod.py CLEANFILES = \ *.py* \ @@ -34,9 +35,12 @@ install-pythonDATA: $(python_DATA) $(INSTALL_PROGRAM) _gpod.so $(DESTDIR)$(pythondir)/gpod/_gpod.so $(INSTALL_DATA) __init__.py $(DESTDIR)$(pythondir)/gpod/__init__.py $(INSTALL_DATA) gpod.py $(DESTDIR)$(pythondir)/gpod/gpod.py + $(INSTALL_DATA) gtkpod.py $(DESTDIR)$(pythondir)/gpod/gtkpod.py $(INSTALL_DATA) ipod.py $(DESTDIR)$(pythondir)/gpod/ipod.py $(PYTHON) -c 'from py_compile import compile; compile("$(DESTDIR)$(pythondir)/gpod/gpod.py")' $(PYTHON) -O -c 'from py_compile import compile; compile("$(DESTDIR)$(pythondir)/gpod/gpod.py")' + $(PYTHON) -c 'from py_compile import compile; compile("$(DESTDIR)$(pythondir)/gpod/gtkpod.py")' + $(PYTHON) -O -c 'from py_compile import compile; compile("$(DESTDIR)$(pythondir)/gpod/gtkpod.py")' $(PYTHON) -c 'from py_compile import compile; compile("$(DESTDIR)$(pythondir)/gpod/ipod.py")' $(PYTHON) -O -c 'from py_compile import compile; compile("$(DESTDIR)$(pythondir)/gpod/ipod.py")' diff --git a/bindings/python/examples/add_song.py b/bindings/python/examples/add_song.py index acca621..a913d1d 100755 --- a/bindings/python/examples/add_song.py +++ b/bindings/python/examples/add_song.py @@ -26,10 +26,10 @@ import os, os.path import gpod import sys from optparse import OptionParser -import eyeD3 import urlparse, urllib2 import tempfile import shutil +import eyeD3 def download(path): print "Downloading %s" % path @@ -70,11 +70,7 @@ parser.add_option("-p", "--podcast", if len(args) == 0: parser.error("Requires an mp3 to add.") -itdb = gpod.itdb_parse(options.mountpoint, None) -if not itdb: - print "Failed to read iPod at %s" % options.mountpoint - sys.exit(2) -itdb.mountpoint = options.mountpoint +db = gpod.Database(options.mountpoint) for path in args: deleteWhenDone = [] @@ -90,42 +86,28 @@ for path in args: deleteWhenDone.pop() continue - track = gpod.itdb_track_new() - audiofile = eyeD3.Mp3AudioFile(path) - tag = audiofile.getTag() - - track.artist= str(tag.getArtist()) - track.album = str(tag.getAlbum()) - track.title = str(tag.getTitle()) - track.filetype = 'mp3' - track.tracklen = audiofile.getPlayTime() * 1000 # important to add!, iPod uses ms. - + track = gpod.Track(from_file=path, podcast=options.ispodcast) + db.add(track) + if options.ispodcast: - track.flag2 = 0x01 # skip when shuffling - track.flag3 = 0x01 # remember playback position - track.flag4 = 0x01 # Show Title/Album on the 'Now Playing' page - playlists = [gpod.itdb_playlist_podcasts(itdb)] - print "Adding Podcast %s (Title: %s)" % (path,track.title) + playlists = [db.Podcasts] + print "Adding Podcast %s (%s)" % (path,track) else: - track.flag2 = 0x00 # do not skip when shuffling - track.flag3 = 0x00 # do not remember playback position - track.flag4 = 0x00 # Show Title/Album/Artist on the 'New Playing' page - playlists = [gpod.itdb_playlist_mpl(itdb)] - print "Adding Song %s (Title: %s)" % (path,track.title) - - gpod.itdb_track_add(itdb, track, -1) + playlists = [db.Master] + print "Adding Song %s (%s)" % (path,track) for playlist in playlists: - gpod.itdb_playlist_add_track(playlist, track, -1) + print " adding to playlist %s" % playlist + playlist.add(track) + print " added to playlist %s" % playlist - if gpod.itdb_cp_track_to_ipod(track, path, None) == 1: - print "Copied to %s" % gpod.itdb_filename_on_ipod(track) - else: - print "Copy failed" + print " added Song %s (%s)" % (path,track) + + track.copy_to_ipod() [os.unlink(f) for f in deleteWhenDone] -gpod.itdb_write(itdb, None) +db.close() print "Saved db" diff --git a/bindings/python/examples/playwith_ipod_api.py b/bindings/python/examples/playwith_ipod_api.py index 544de5b..4780f73 100755 --- a/bindings/python/examples/playwith_ipod_api.py +++ b/bindings/python/examples/playwith_ipod_api.py @@ -1,14 +1,16 @@ #!/usr/bin/python -import ipod +import gpod -db = ipod.Database() +db = gpod.Database() + +print db for track in db[4:20]: print track print track['title'] -filename = "/mp3/Blondie/No_Exit/Blondie_-_Maria.mp3" -t = ipod.Track(from_file=filename) -print t - +for pl in db.Playlists: + print pl + for track in pl: + print " ", track diff --git a/bindings/python/gpod.i b/bindings/python/gpod.i index 39de9c1..1d5f231 100644 --- a/bindings/python/gpod.i +++ b/bindings/python/gpod.i @@ -94,6 +94,48 @@ PyObject* sw_get_playlists(Itdb_iTunesDB *itdb) { } return list; } + + void sw__track_extra_destroy (PyObject *data) { + Py_XDECREF(data); + } + + PyObject *sw__track_extra_duplicate (PyObject *data) { + if (data == Py_None) { + Py_INCREF(Py_None); + return Py_None; + } else { + return PyDict_Copy(data); + } + } + + PyObject *sw_set_track_userdata(Itdb_Track *track, PyObject *data) { + Py_INCREF(data); + if ((PyDict_Check(data)) || (data == Py_None)) { + if (track->userdata) { + Py_DECREF((PyObject *)track->userdata); + } + track->userdata = (gpointer) data; + track->userdata_duplicate = (ItdbUserDataDuplicateFunc)sw__track_extra_duplicate; + track->userdata_destroy = (ItdbUserDataDestroyFunc)sw__track_extra_destroy; + Py_INCREF(Py_None); + return Py_None; + } else { + PyErr_SetString(PyExc_TypeError, "userdata must be a Dictionary"); + return NULL; + } + } + + PyObject* sw_get_track_userdata(Itdb_Track *track) { + if (track->userdata) { + Py_INCREF((PyObject *)track->userdata); + return (PyObject *)track->userdata; + } else { + Py_INCREF(Py_None); + return Py_None; + } + } + + %} # be nicer to decode these utf8 strings into Unicode objects in the C @@ -101,6 +143,7 @@ PyObject* sw_get_playlists(Itdb_iTunesDB *itdb) { # them utf8 encoded Strings. typedef char gchar; + %typemap(in) guint8 { unsigned long ival; ival = PyInt_AsUnsignedLongMask($input); @@ -221,4 +264,7 @@ PyObject* sw_get_rule(GList *list, gint index); PyObject* sw_get_list_len(GList *list); PyObject* sw_get_playlists(Itdb_iTunesDB *itdb); PyObject* sw_get_playlist_tracks(Itdb_Playlist *pl); +PyObject* sw_set_track_userdata(Itdb_Track *track, PyObject *data); +PyObject* sw_get_track_userdata(Itdb_Track *track); + %include "../../src/itdb.h" diff --git a/bindings/python/ipod.py b/bindings/python/ipod.py index 3e98310..020ed20 100644 --- a/bindings/python/ipod.py +++ b/bindings/python/ipod.py @@ -3,6 +3,8 @@ import gpod import types import eyeD3 +import gtkpod +import os class DatabaseException(RuntimeError): pass @@ -19,9 +21,29 @@ class Database: else: self._itdb.mountpoint = mountpoint + self._load_gtkpod_extended_info() + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return "<Database Filename:%s Playlists:%s Tracks:%s>" % ( + repr(self._itdb.filename), + gpod.sw_get_list_len(self._itdb.playlists), + len(self)) + + def _load_gtkpod_extended_info(self): + self._itdb_file = os.path.join(self._itdb.mountpoint, + "iPod_Control","iTunes","iTunesDB") + self._itdbext_file = os.path.join(self._itdb.mountpoint, + "iPod_Control","iTunes","iTunesDB.ext") + if os.path.exists(self._itdb_file) and os.path.exists(self._itdbext_file): + gtkpod.parse(self._itdbext_file, self, self._itdb_file) + def close(self): if not gpod.itdb_write(self._itdb, None): raise DatabaseException("Unable to save iTunes database at %s" % mountpoint) + gtkpod.write(self._itdbext_file, self, self._itdb_file) def __getitem__(self, index): if type(index) == types.SliceType: @@ -39,9 +61,37 @@ class Database: track.copy_to_ipod() gpod.itdb_playlist_add_track(gpod.itdb_playlist_mpl(self._itdb), track._track, -1) - + + def __del__(self): + gpod.itdb_free(self._itdb) + + def add(self, track, pos=-1): + gpod.itdb_track_add(self._itdb, track._track, pos) + + def get_master(self): + return Playlist(self,proxied_playlist=gpod.itdb_playlist_mpl(self._itdb)) + + def get_podcasts(self): + return Playlist(self,proxied_playlist=gpod.itdb_playlist_podcasts(self._itdb)) + + def get_playlists(self): + l = [] + for playlist in gpod.sw_get_playlists(self._itdb): + l.append(Playlist(self,proxied_playlist=playlist)) + return tuple(l) + + Master = property(get_master) + Podcasts = property(get_podcasts) + Playlists= property(get_playlists) + + def smart_update(self): + gpod.itdb_spl_update_all(self._itdb) class Track: + # Note we don't free the underlying structure, as it's still used + # by the itdb. + + _proxied_attributes = ("title","ipod_path","album","artist","genre","filetype", "comment","category","composer","grouping","description", "podcasturl","podcastrss","chapterdata","subtitle","id", @@ -56,16 +106,15 @@ class Track: "time_released","has_artwork","flag1","flag2","flag3","flag4", "lyrics_flag","movie_flag","mark_unplayed","samplecount", "chapterdata_raw","chapterdata_raw_length","artwork", - "usertype","userdata","userdata_duplicate","userdata_destroy") + "usertype") def __init__(self, from_file=None, proxied_track=None, podcast=False): - self._ondiskfilename = None if from_file: - self._ondiskfilename = from_file self._track = gpod.itdb_track_new() - audiofile = eyeD3.Mp3AudioFile(self._ondiskfilename) + self['userdata'] = {'filename_locale': from_file, + 'transferred': 0} + audiofile = eyeD3.Mp3AudioFile(self['userdata']['filename_locale']) tag = audiofile.getTag() - print dir(tag) for func, attrib in (('getArtist','artist'), ('getTitle','title'), ('getBPM','BPM'), @@ -86,6 +135,15 @@ class Track: if of is not None: self['tracks'] = of self['tracklen'] = audiofile.getPlayTime() * 1000 + if podcast: + self['flag2'] = 0x01 # skip when shuffling + self['flag3'] = 0x01 # remember playback position + self['flag4'] = 0x01 # Show Title/Album on the 'Now Playing' page + else: + self['flag2'] = 0x00 # do not skip when shuffling + self['flag3'] = 0x00 # do not remember playback position + self['flag4'] = 0x00 # Show Title/Album/Artist on the 'New Playing' page + elif proxied_track: self._track = proxied_track else: @@ -93,9 +151,17 @@ class Track: self.set_podcast(podcast) def copy_to_ipod(self): - if gpod.itdb_cp_track_to_ipod(self._track, self._ondiskfilename, None) != 1: - raise TrackException('Unable to copy %s to iPod as %s' % (self._ondiskfilename, - self)) + self['userdata']['md5_hash'] = gtkpod.sha1_hash( + self['userdata']['filename_locale']) + self['userdata']['transferred'] = 1 + if gpod.itdb_cp_track_to_ipod(self._track, + self['userdata']['filename_locale'], None) != 1: + raise TrackException('Unable to copy %s to iPod as %s' % ( + self['userdata']['filename_locale'], + self)) + + def ipod_filename(self): + return gpod.itdb_filename_on_ipod(self._track) def set_podcast(self, value): if value: @@ -128,13 +194,18 @@ class Track: return [(k, self[k]) for k in self._proxied_attributes] def __getitem__(self, item): - if item in self._proxied_attributes: + if item == "userdata": + return gpod.sw_get_track_userdata(self._track) + elif item in self._proxied_attributes: return getattr(self._track, item) else: raise KeyError('No such key: %s' % item) def __setitem__(self, item, value): #print item, value + if item == "userdata": + gpod.sw_set_track_userdata(self._track, value) + return if type(value) == types.UnicodeType: value = value.encode() if item in self._proxied_attributes: @@ -142,6 +213,114 @@ class Track: else: raise KeyError('No such key: %s' % item) - +_playlist_sorting = { + 1:'playlist', + 2:'unknown2', + 3:'songtitle', + 4:'album', + 5:'artist', + 6:'bitrate', + 7:'genre', + 8:'kind', + 9:'modified', + 10:'track', + 11:'size', + 12:'time', + 13:'year', + 14:'rate', + 15:'comment', + 16:'added', + 17:'equalizer', + 18:'composer', + 19:'unknown19', + 20:'count', + 21:'last', + 22:'disc', + 23:'rating', + 24:'release', + 25:'BPM', + 26:'grouping', + 27:'category', + 28:'description'} + class Playlist: - pass + def __init__(self, parent_db, proxied_playlist=None, + title="New Playlist", smart=False, pos=-1): + self._db = parent_db + if proxied_playlist: + self._pl = proxied_playlist + else: + if smart: + smart = 1 + else: + smart = 0 + self._pl = gpod.itdb_playlist_new(title, smart) + gpod.itdb_playlist_add(self._db._itdb, self._pl, pos) + + def smart_update(self): + gpod.itdb_spl_update(self._pl) + + def randomize(self): + gpod.itdb_playlist_randomize(self._pl) + + def get_name(self): + return self._pl.name + def set_name(self, name): + self._pl.name = name + def get_id(self): + return self._pl.id + def get_smart(self): + if self._pl.is_spl == 1: + return True + return False + def get_master(self): + if gpod.itdb_playlist_is_mpl(self._pl) == 1: + return True + return False + def get_podcast(self): + if gpod.itdb_playlist_is_podcasts(self._pl) == 1: + return True + return False + def get_sort(self): + return _playlist_sorting[self._pl.sortorder] + def set_sort(self, order): + order = order.lower() + for k, v in _playlist_sorting.items(): + if v == order: + self._pl.sortorder = v + return + return ValueError("Unknown playlist sorting '%s'" % order) + + name = property(get_name, set_name) + id = property(get_id) + smart = property(get_smart) + master = property(get_master) + podcast= property(get_podcast) + order = property(get_sort, set_sort) + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return "<Playlist Title:%s Sort:%s Smart:%s Master:%s Podcast:%s Tracks:%d>" % ( + repr(self.name), + repr(self.order), + repr(self.smart), + repr(self.master), + repr(self.podcast), + len(self)) + + def __getitem__(self, index): + if type(index) == types.SliceType: + return [self[i] for i in xrange(*index.indices(len(self)))] + else: + if index < 0: + index += len(self) + return Track(proxied_track=gpod.sw_get_track(self._pl.members, index)) + + def __len__(self): + #return self._pl.num # Always 0 ? + return gpod.sw_get_list_len(self._pl.members) + + def add(self, track, pos=-1): + gpod.itdb_playlist_add_track(self._pl, track._track, pos) |