From c6ab72c7e0acda4bba874e8e9a4c149b66921292 Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Mon, 15 Oct 2012 22:08:40 +0200 Subject: [PATCH 01/13] sendtoipod: initial version --- gpodder_extensions/sendtoipod.py | 120 +++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 gpodder_extensions/sendtoipod.py diff --git a/gpodder_extensions/sendtoipod.py b/gpodder_extensions/sendtoipod.py new file mode 100644 index 0000000..61612d8 --- /dev/null +++ b/gpodder_extensions/sendtoipod.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# Send files to iPod using libgpod +# Copyright (c) 2012-10-15 Paul Ortyl +# Licensed under the same terms as gPodder itself +''' +Extention to gPodder for sending files to iPod from context menu +''' + +# missing features: +# * refresh of UI/model after episode gets deleted +# * user feedback +# * automatic conversion to file format supported by iPod (assume mp3) + +# path to the ipod, hardcoded until autodetection or configuration gets implemented +_IPOD_PATH = '/media/ortyl/PO' + +import os +import gpodder +import logging +logger = logging.getLogger(__name__) + +_are_libraries_available = True +try: + import gpod + import eyeD3 +except Exception, e: + _are_libraries_available = False + +_ = gpodder.gettext + +__title__ = _('Send file(s) to iPod from context menu') +__description__ = _('Send file(s) to iPod from context menu') +__authors__ = 'Paul Ortyl ' + +DefaultConfig = { + 'context_menu': True, # Show item in context menu +} + +class gPodderExtension: + def __init__(self, container): + + self.container = container + self.config = self.container.config + + def on_episode_downloaded(self, episode): + True + + def on_episodes_context_menu(self, episodes): + if not _are_libraries_available: + return None + + if not self.config.context_menu: + return None + + if 'audio/mpeg' not in [e.mime_type for e in episodes + if e.mime_type is not None and e.file_exists()]: + return None + + return [(_('Send To iPod'), self._send_to_ipod)] + + def _send_to_ipod(self, episodes): + ipod_mount = _IPOD_PATH + itdb = gpod.itdb_parse(ipod_mount, None) + if not itdb: + logger.error('Could not open iPod database at %s' % ipod_mount) + return + + itdb_modified = False + + for episode in episodes: + filename = episode.local_filename(create=False) + if filename is None: + return + + extension = os.path.splitext(filename)[1] + + if episode.file_type() != 'audio': + return + + if extension.lower() != '.mp3': + return + + modified = self.send_file_to_ipod(itdb, filename) + itdb_modified |= modified + if modified: + episode.mark_old() + episode.delete_from_disk() + + if itdb_modified: + gpod.itdb_write(itdb, None) + gpod.itdb_free(itdb) + + def send_file_to_ipod(self, itdb, fname): + if eyeD3.isMp3File(fname): + logger.debug("Copying file '%s' to iPod..." % fname) + podcasts = gpod.itdb_playlist_podcasts(itdb) + af = eyeD3.Mp3AudioFile(fname, eyeD3.ID3_ANY_VERSION) + tag = af.getTag() + track = gpod.itdb_track_new() + track.visible = 1 + track.filetype = "mp3" + track.ipod_path = fname + track.tracklen = af.getPlayTime() * 1000 + track.album = str(tag.getAlbum()) + track.artist = str(tag.getArtist()) + track.title = str(tag.getTitle()) + track.genre = str(tag.getGenre()) + track.playcount = 0 + gpod.itdb_track_add(itdb, track, -1) + gpod.itdb_playlist_add_track(podcasts, track, -1) + is_copied = gpod.itdb_cp_track_to_ipod(track, fname, None) + if is_copied: + logger.info("File '%s' has been successfully copied to iPod" % fname) + else: + logger.error("File '%s' could not be copied to iPod" % fname) + return is_copied + else: + logger.error("File format for %s is not mp3, skipping" % fname) + + return False From f32a3f89a63114569e6576b848d56e883a521d6a Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Tue, 16 Oct 2012 21:54:01 +0200 Subject: [PATCH 02/13] sendtoipod: autodetection of ipod mountpoint (linux only) --- gpodder_extensions/sendtoipod.py | 36 +++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/gpodder_extensions/sendtoipod.py b/gpodder_extensions/sendtoipod.py index 61612d8..7c84f12 100644 --- a/gpodder_extensions/sendtoipod.py +++ b/gpodder_extensions/sendtoipod.py @@ -11,8 +11,6 @@ # * user feedback # * automatic conversion to file format supported by iPod (assume mp3) -# path to the ipod, hardcoded until autodetection or configuration gets implemented -_IPOD_PATH = '/media/ortyl/PO' import os import gpodder @@ -41,6 +39,8 @@ def __init__(self, container): self.container = container self.config = self.container.config + # use default evironment variable as defined for gtkpod + self.ipod_mount = os.getenv('IPOD_MOUNTPOINT') def on_episode_downloaded(self, episode): True @@ -56,13 +56,16 @@ def on_episodes_context_menu(self, episodes): if e.mime_type is not None and e.file_exists()]: return None - return [(_('Send To iPod'), self._send_to_ipod)] + if self._find_ipod(): + return [(_('Send To iPod'), self._send_to_ipod)] + else: + return None def _send_to_ipod(self, episodes): - ipod_mount = _IPOD_PATH - itdb = gpod.itdb_parse(ipod_mount, None) + itdb = gpod.itdb_parse(self.ipod_mount, None) if not itdb: - logger.error('Could not open iPod database at %s' % ipod_mount) + logger.error('Could not open iPod database at %s' % self.ipod_mount) + self.ipod_mount = None return itdb_modified = False @@ -116,5 +119,24 @@ def send_file_to_ipod(self, itdb, fname): return is_copied else: logger.error("File format for %s is not mp3, skipping" % fname) - return False + + def _find_ipod(self): + '''Try to autodetect mount point of ipod and set the internal path to ipod''' + if self.ipod_mount: return True + + # find first vfat mounted directory with iPod_Control subdir + try: + with open('/proc/mounts', 'r') as f: + for line in f.readlines(): + tokens = line.split(' ', 4) + if tokens and 'vfat' == tokens[2] and os.path.exists(tokens[1] + '/iPod_Control'): + self.ipod_mount = tokens[1] + return True + except IOError as e: pass # in case we are not on standard linux + + return False + + + + From 812e44633902d4b833d3eb614a7527027fce1d33 Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Tue, 16 Oct 2012 22:23:13 +0200 Subject: [PATCH 03/13] movetoipod: change the name to 'Move' instead of copy, flush the ipod database after each file --- gpodder_extensions/sendtoipod.py | 38 ++++++++++++++------------------ 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/gpodder_extensions/sendtoipod.py b/gpodder_extensions/sendtoipod.py index 7c84f12..9aa5ab8 100644 --- a/gpodder_extensions/sendtoipod.py +++ b/gpodder_extensions/sendtoipod.py @@ -3,7 +3,7 @@ # Copyright (c) 2012-10-15 Paul Ortyl # Licensed under the same terms as gPodder itself ''' -Extention to gPodder for sending files to iPod from context menu +Extention to gPodder for sending (moving) files to iPod from context menu ''' # missing features: @@ -26,8 +26,8 @@ _ = gpodder.gettext -__title__ = _('Send file(s) to iPod from context menu') -__description__ = _('Send file(s) to iPod from context menu') +__title__ = _('Move file(s) to iPod from context menu') +__description__ = _('Move file(s) to iPod from context menu') __authors__ = 'Paul Ortyl ' DefaultConfig = { @@ -57,7 +57,7 @@ def on_episodes_context_menu(self, episodes): return None if self._find_ipod(): - return [(_('Send To iPod'), self._send_to_ipod)] + return [(_('Move To iPod'), self._send_to_ipod)] else: return None @@ -68,30 +68,25 @@ def _send_to_ipod(self, episodes): self.ipod_mount = None return - itdb_modified = False - for episode in episodes: filename = episode.local_filename(create=False) if filename is None: - return + return extension = os.path.splitext(filename)[1] if episode.file_type() != 'audio': - return + return if extension.lower() != '.mp3': - return + return - modified = self.send_file_to_ipod(itdb, filename) - itdb_modified |= modified - if modified: - episode.mark_old() - episode.delete_from_disk() + if self.send_file_to_ipod(itdb, filename): + episode.mark_old() + episode.delete_from_disk() + logger.info("File '%s' has been deleted from gPoddes database and filesystem" % filename) - if itdb_modified: - gpod.itdb_write(itdb, None) - gpod.itdb_free(itdb) + gpod.itdb_free(itdb) def send_file_to_ipod(self, itdb, fname): if eyeD3.isMp3File(fname): @@ -115,7 +110,12 @@ def send_file_to_ipod(self, itdb, fname): if is_copied: logger.info("File '%s' has been successfully copied to iPod" % fname) else: + # roll back logger.error("File '%s' could not be copied to iPod" % fname) + gpod.itdb_playlist_remove_track(podcasts, track) + gpod.itdb_track_remove(track) + track = None + gpod.itdb_write(itdb, None) return is_copied else: logger.error("File format for %s is not mp3, skipping" % fname) @@ -136,7 +136,3 @@ def _find_ipod(self): except IOError as e: pass # in case we are not on standard linux return False - - - - From 98b427f88017230bae789da3d2b740c1bcd607c3 Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Tue, 16 Oct 2012 22:23:57 +0200 Subject: [PATCH 04/13] movetoipod: rename the file --- gpodder_extensions/{sendtoipod.py => movetoipod.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gpodder_extensions/{sendtoipod.py => movetoipod.py} (100%) diff --git a/gpodder_extensions/sendtoipod.py b/gpodder_extensions/movetoipod.py similarity index 100% rename from gpodder_extensions/sendtoipod.py rename to gpodder_extensions/movetoipod.py From afa5397a7d00a7ec449747ca790809cb015b8037 Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Sun, 21 Oct 2012 22:10:02 +0200 Subject: [PATCH 05/13] movetoipod: fix gui update, support conversion from .ogg files --- gpodder_extensions/movetoipod.py | 141 +++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 44 deletions(-) diff --git a/gpodder_extensions/movetoipod.py b/gpodder_extensions/movetoipod.py index 9aa5ab8..1832443 100644 --- a/gpodder_extensions/movetoipod.py +++ b/gpodder_extensions/movetoipod.py @@ -1,26 +1,25 @@ # -*- coding: utf-8 -*- # Send files to iPod using libgpod -# Copyright (c) 2012-10-15 Paul Ortyl +# Copyright (c) 2012-10-21 Paul Ortyl # Licensed under the same terms as gPodder itself ''' Extention to gPodder for sending (moving) files to iPod from context menu ''' # missing features: -# * refresh of UI/model after episode gets deleted # * user feedback -# * automatic conversion to file format supported by iPod (assume mp3) - import os import gpodder import logging +import subprocess logger = logging.getLogger(__name__) _are_libraries_available = True try: import gpod - import eyeD3 + from mutagen.oggvorbis import OggVorbis + from mutagen.mp3 import MP3 except Exception, e: _are_libraries_available = False @@ -29,21 +28,63 @@ __title__ = _('Move file(s) to iPod from context menu') __description__ = _('Move file(s) to iPod from context menu') __authors__ = 'Paul Ortyl ' +__only_for__ = 'gtk' DefaultConfig = { 'context_menu': True, # Show item in context menu } + +def _convert_to_mp3(filename): + '''Convert file to temporary mp3 + @param filename: filename of the audio file to be converted + @return: file name of the temporary file in mp3 format, this file should be deleted afterwards + ''' + fnameMP3 = filename + '.mp3' + # remove file so that avconv wont get confused + if (os.path.exists(fnameMP3)): os.unlink(fnameMP3) + cmd = ['avconv', '-i', filename, fnameMP3] + avconv = subprocess.Popen(cmd) + avconv.communicate() + + if 0 == avconv.returncode: + return fnameMP3 + else: + os.unlink(fnameMP3) + return None + +def _get_MP3_tags(filename): + tagsMP3 = MP3(filename) + tags = {} + tags['album'] = tagsMP3['TALB'] + tags['title'] = tagsMP3['TIT2'] + tags['artist'] = tagsMP3['TPE1'] + tags['length'] = tagsMP3.info.length * 1000 + tags['genre'] = tagsMP3['TCON'] + return tags + +def _get_OGG_tags(filename): + tagsOGG = OggVorbis(filename) + tags = {} + tags['album'] = tagsOGG['album'] + tags['title'] = tagsOGG['title'] + tags['artist'] = tagsOGG['artist'] + tags['length'] = tagsOGG.info.length * 1000 + tags['genre'] = 'Podcast' + return tags + class gPodderExtension: def __init__(self, container): self.container = container self.config = self.container.config + self.ui = None # use default evironment variable as defined for gtkpod self.ipod_mount = os.getenv('IPOD_MOUNTPOINT') - def on_episode_downloaded(self, episode): - True + def on_ui_object_available(self, name, ui_object): + '''Get reference onto UI object, needed for triggering UI update after move''' + if name == 'gpodder-gtk': self.ui = ui_object def on_episodes_context_menu(self, episodes): if not _are_libraries_available: @@ -52,8 +93,12 @@ def on_episodes_context_menu(self, episodes): if not self.config.context_menu: return None - if 'audio/mpeg' not in [e.mime_type for e in episodes - if e.mime_type is not None and e.file_exists()]: + mpeg = 'audio/mpeg' in [e.mime_type for e in episodes + if e.mime_type is not None and e.file_exists()] + ogg = 'audio/ogg' in [e.mime_type for e in episodes + if e.mime_type is not None and e.file_exists()] + + if not mpeg and not ogg: return None if self._find_ipod(): @@ -78,48 +123,56 @@ def _send_to_ipod(self, episodes): if episode.file_type() != 'audio': return - if extension.lower() != '.mp3': + sent = False # set to true if file transfer was successful + if extension.lower() == '.mp3': + sent = self.send_file_to_ipod(itdb, filename, _get_MP3_tags(filename)) + elif extension.lower() == '.ogg': + tmpname = _convert_to_mp3(filename) + if tmpname: + sent = self.send_file_to_ipod(itdb, tmpname, _get_OGG_tags(filename)) + os.unlink(tmpname); + else: return - if self.send_file_to_ipod(itdb, filename): + if sent: episode.mark_old() - episode.delete_from_disk() - logger.info("File '%s' has been deleted from gPoddes database and filesystem" % filename) + logger.info("File '%s' has been marked 'old'" % filename) + if not episode.archive: + episode.delete_from_disk() + logger.info("File '%s' has been deleted from gPodder database and file system" + % filename) + # update UI + if self.ui: + self.ui.episode_list_status_changed([episode]) gpod.itdb_free(itdb) - def send_file_to_ipod(self, itdb, fname): - if eyeD3.isMp3File(fname): - logger.debug("Copying file '%s' to iPod..." % fname) - podcasts = gpod.itdb_playlist_podcasts(itdb) - af = eyeD3.Mp3AudioFile(fname, eyeD3.ID3_ANY_VERSION) - tag = af.getTag() - track = gpod.itdb_track_new() - track.visible = 1 - track.filetype = "mp3" - track.ipod_path = fname - track.tracklen = af.getPlayTime() * 1000 - track.album = str(tag.getAlbum()) - track.artist = str(tag.getArtist()) - track.title = str(tag.getTitle()) - track.genre = str(tag.getGenre()) - track.playcount = 0 - gpod.itdb_track_add(itdb, track, -1) - gpod.itdb_playlist_add_track(podcasts, track, -1) - is_copied = gpod.itdb_cp_track_to_ipod(track, fname, None) - if is_copied: - logger.info("File '%s' has been successfully copied to iPod" % fname) - else: - # roll back - logger.error("File '%s' could not be copied to iPod" % fname) - gpod.itdb_playlist_remove_track(podcasts, track) - gpod.itdb_track_remove(track) - track = None - gpod.itdb_write(itdb, None) - return is_copied + def send_file_to_ipod(self, itdb, fname, tags): + logger.debug("Copying file '%s' to iPod..." % fname) + podcasts = gpod.itdb_playlist_podcasts(itdb) + track = gpod.itdb_track_new() + track.visible = 1 + track.filetype = "mp3" + track.ipod_path = fname + track.album = str(tags['album']) + track.artist = str(tags['artist']) + track.title = str(tags['title']) + track.genre = str(tags['genre']) + track.tracklen = tags['length'] + track.playcount = 0 + gpod.itdb_track_add(itdb, track, -1) + gpod.itdb_playlist_add_track(podcasts, track, -1) + is_copied = gpod.itdb_cp_track_to_ipod(track, fname, None) + if is_copied: + logger.info("File '%s' has been successfully copied to iPod" % fname) else: - logger.error("File format for %s is not mp3, skipping" % fname) - return False + # roll back + logger.error("File '%s' could not be copied to iPod" % fname) + gpod.itdb_playlist_remove_track(podcasts, track) + gpod.itdb_track_remove(track) + track = None + gpod.itdb_write(itdb, None) + return is_copied def _find_ipod(self): '''Try to autodetect mount point of ipod and set the internal path to ipod''' From 22bb781fbae684789b55edc236ab55fb35fc8f54 Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Mon, 22 Oct 2012 21:32:53 +0200 Subject: [PATCH 06/13] movetoipod: fix retrieval of tags --- gpodder_extensions/movetoipod.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gpodder_extensions/movetoipod.py b/gpodder_extensions/movetoipod.py index 1832443..6b1223c 100644 --- a/gpodder_extensions/movetoipod.py +++ b/gpodder_extensions/movetoipod.py @@ -56,19 +56,19 @@ def _convert_to_mp3(filename): def _get_MP3_tags(filename): tagsMP3 = MP3(filename) tags = {} - tags['album'] = tagsMP3['TALB'] - tags['title'] = tagsMP3['TIT2'] - tags['artist'] = tagsMP3['TPE1'] + tags['album'] = tagsMP3.get('TALB', '') + tags['title'] = tagsMP3.get('TIT2', '') + tags['artist'] = tagsMP3.get('TPE1', '') tags['length'] = tagsMP3.info.length * 1000 - tags['genre'] = tagsMP3['TCON'] + tags['genre'] = tagsMP3.get('TCON', '') return tags def _get_OGG_tags(filename): tagsOGG = OggVorbis(filename) tags = {} - tags['album'] = tagsOGG['album'] - tags['title'] = tagsOGG['title'] - tags['artist'] = tagsOGG['artist'] + tags['album'] = tagsOGG.get('album', '') + tags['title'] = tagsOGG.get('title', '') + tags['artist'] = tagsOGG.get('artist', '') tags['length'] = tagsOGG.info.length * 1000 tags['genre'] = 'Podcast' return tags From 0d4ebd13893475528ccda1847aa5ea1acdc127d2 Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Mon, 22 Oct 2012 21:34:21 +0200 Subject: [PATCH 07/13] movetoipod: reverse sequence of selected episodes (make it from the oldest to newest) --- gpodder_extensions/movetoipod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpodder_extensions/movetoipod.py b/gpodder_extensions/movetoipod.py index 6b1223c..37c28cd 100644 --- a/gpodder_extensions/movetoipod.py +++ b/gpodder_extensions/movetoipod.py @@ -113,7 +113,7 @@ def _send_to_ipod(self, episodes): self.ipod_mount = None return - for episode in episodes: + for episode in reversed(episodes): filename = episode.local_filename(create=False) if filename is None: return From 8665da53340ba6949b7bab9cb884e25111b46593 Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Mon, 22 Oct 2012 21:35:01 +0200 Subject: [PATCH 08/13] movetoipod: record unresolved issues --- gpodder_extensions/movetoipod.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gpodder_extensions/movetoipod.py b/gpodder_extensions/movetoipod.py index 37c28cd..619e62b 100644 --- a/gpodder_extensions/movetoipod.py +++ b/gpodder_extensions/movetoipod.py @@ -8,6 +8,8 @@ # missing features: # * user feedback +# * BUG: UI does not get updates after enabling of this extention, restart of gpodder is necessary +# * BUG: UI does not get updated until the complete batch of files gets processed import os import gpodder From 62e4a19c61acdfffc7fb3c2498e8308c31aa6b55 Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Mon, 29 Oct 2012 14:58:45 +0100 Subject: [PATCH 09/13] movetoipod: fix some issues with missing/malformed id3 tags --- gpodder_extensions/movetoipod.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/gpodder_extensions/movetoipod.py b/gpodder_extensions/movetoipod.py index 619e62b..9d5b26a 100644 --- a/gpodder_extensions/movetoipod.py +++ b/gpodder_extensions/movetoipod.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Send files to iPod using libgpod -# Copyright (c) 2012-10-21 Paul Ortyl +# Copyright (c) 2012-10-29 Paul Ortyl # Licensed under the same terms as gPodder itself ''' Extention to gPodder for sending (moving) files to iPod from context menu @@ -8,7 +8,7 @@ # missing features: # * user feedback -# * BUG: UI does not get updates after enabling of this extention, restart of gpodder is necessary +# * BUG: UI does not get updates after enabling of this extension, restart of gpodder is necessary # * BUG: UI does not get updated until the complete batch of files gets processed import os @@ -68,9 +68,9 @@ def _get_MP3_tags(filename): def _get_OGG_tags(filename): tagsOGG = OggVorbis(filename) tags = {} - tags['album'] = tagsOGG.get('album', '') - tags['title'] = tagsOGG.get('title', '') - tags['artist'] = tagsOGG.get('artist', '') + tags['album'] = ''.join(tagsOGG.get('album', [''])) + tags['title'] = ''.join(tagsOGG.get('title', [''])) + tags['artist'] = ''.join(tagsOGG.get('artist', [''])) tags['length'] = tagsOGG.info.length * 1000 tags['genre'] = 'Podcast' return tags @@ -120,14 +120,24 @@ def _send_to_ipod(self, episodes): if filename is None: return - extension = os.path.splitext(filename)[1] + extension = episode.extension() + basename = os.path.splitext(os.path.basename(filename))[0] if episode.file_type() != 'audio': return sent = False # set to true if file transfer was successful if extension.lower() == '.mp3': - sent = self.send_file_to_ipod(itdb, filename, _get_MP3_tags(filename)) + tags = _get_MP3_tags(filename) + if not tags['album']: + tags['album'] = episode.parent.title + if not tags['title'] or basename == tags['title']: + tags['title'] = episode.title + if not tags['title']: + tags['title'] = episode.basename() + if not tags['genre']: + tags['genre'] = 'Podcast' + sent = self.send_file_to_ipod(itdb, filename, tags) elif extension.lower() == '.ogg': tmpname = _convert_to_mp3(filename) if tmpname: From 76877fe72046d4075aa8b2053ed0b0dfa5cd6895 Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Mon, 29 Oct 2012 14:59:41 +0100 Subject: [PATCH 10/13] movetoipod: skip unsupported files instead of aborting --- gpodder_extensions/movetoipod.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gpodder_extensions/movetoipod.py b/gpodder_extensions/movetoipod.py index 9d5b26a..0936aa4 100644 --- a/gpodder_extensions/movetoipod.py +++ b/gpodder_extensions/movetoipod.py @@ -118,13 +118,13 @@ def _send_to_ipod(self, episodes): for episode in reversed(episodes): filename = episode.local_filename(create=False) if filename is None: - return + continue extension = episode.extension() basename = os.path.splitext(os.path.basename(filename))[0] if episode.file_type() != 'audio': - return + continue sent = False # set to true if file transfer was successful if extension.lower() == '.mp3': @@ -144,7 +144,7 @@ def _send_to_ipod(self, episodes): sent = self.send_file_to_ipod(itdb, tmpname, _get_OGG_tags(filename)) os.unlink(tmpname); else: - return + continue if sent: episode.mark_old() From 62f0fdfcecdfc89eb7c93ac2178d4a55877a2aed Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Wed, 14 Nov 2012 17:43:23 +0100 Subject: [PATCH 11/13] movetoipod: support "other" audio formats as long as avconv it supports --- gpodder_extensions/movetoipod.py | 57 +++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/gpodder_extensions/movetoipod.py b/gpodder_extensions/movetoipod.py index 0936aa4..4ac468f 100644 --- a/gpodder_extensions/movetoipod.py +++ b/gpodder_extensions/movetoipod.py @@ -1,15 +1,19 @@ # -*- coding: utf-8 -*- # Send files to iPod using libgpod -# Copyright (c) 2012-10-29 Paul Ortyl +# Copyright (c) 2012-11-01 Paul Ortyl # Licensed under the same terms as gPodder itself ''' Extention to gPodder for sending (moving) files to iPod from context menu ''' # missing features: -# * user feedback +# * user feedback (at the moment only via logging to console, start gpodder with '-v' option) # * BUG: UI does not get updates after enabling of this extension, restart of gpodder is necessary # * BUG: UI does not get updated until the complete batch of files gets processed +# missing goodies, ideas: +# * extract audio from video tracks and send it to iPod +# * two menu entries, one for audio and one for video +# * stop file transfers as soon as free space in iPod gets below predefined threshold import os import gpodder @@ -75,13 +79,29 @@ def _get_OGG_tags(filename): tags['genre'] = 'Podcast' return tags +def _fix_tags(tags, episode, basename): + if not tags.has_key('album') or not tags['album']: + tags['album'] = episode.parent.title + if not tags.has_key('title') or not tags['title'] or basename == tags['title']: + tags['title'] = episode.title + if not tags.has_key('title') or not tags['title']: + tags['title'] = basename + if not tags.has_key('genre') or not tags['genre']: + tags['genre'] = 'Podcast' + if not tags.has_key('artist') or not tags['artist']: + tags['artist'] = '' + if not tags.has_key('length') or not tags['length']: + tags['length'] = 0 # FIXME: get length from converted mp3 file! + + return tags + class gPodderExtension: def __init__(self, container): self.container = container self.config = self.container.config self.ui = None - # use default evironment variable as defined for gtkpod + # use default environment variable as defined for gtkpod self.ipod_mount = os.getenv('IPOD_MOUNTPOINT') def on_ui_object_available(self, name, ui_object): @@ -95,12 +115,10 @@ def on_episodes_context_menu(self, episodes): if not self.config.context_menu: return None - mpeg = 'audio/mpeg' in [e.mime_type for e in episodes - if e.mime_type is not None and e.file_exists()] - ogg = 'audio/ogg' in [e.mime_type for e in episodes + audio = 'audio' in [e.file_type() for e in episodes if e.mime_type is not None and e.file_exists()] - if not mpeg and not ogg: + if not audio: return None if self._find_ipod(): @@ -127,22 +145,25 @@ def _send_to_ipod(self, episodes): continue sent = False # set to true if file transfer was successful + # for each file type: + # 1. extract (id3) tags + # 2. fix missing tags using available information + # 3. convert file to mp3 if necessary + # 4. send to iPod if extension.lower() == '.mp3': tags = _get_MP3_tags(filename) - if not tags['album']: - tags['album'] = episode.parent.title - if not tags['title'] or basename == tags['title']: - tags['title'] = episode.title - if not tags['title']: - tags['title'] = episode.basename() - if not tags['genre']: - tags['genre'] = 'Podcast' - sent = self.send_file_to_ipod(itdb, filename, tags) + sent = self.send_file_to_ipod(itdb, filename, _fix_tags(tags, episode, basename)) elif extension.lower() == '.ogg': tmpname = _convert_to_mp3(filename) if tmpname: sent = self.send_file_to_ipod(itdb, tmpname, _get_OGG_tags(filename)) os.unlink(tmpname); + elif extension.lower() == '.wav': + tags = {} + tmpname = _convert_to_mp3(filename) + if tmpname: + sent = self.send_file_to_ipod(itdb, tmpname, _fix_tags({}, episode, basename)) + os.unlink(tmpname); else: continue @@ -160,6 +181,10 @@ def _send_to_ipod(self, episodes): gpod.itdb_free(itdb) def send_file_to_ipod(self, itdb, fname, tags): + if not os.path.exists(fname): + logger.error("File '%s' does not exist" % fname) + return False + logger.debug("Copying file '%s' to iPod..." % fname) podcasts = gpod.itdb_playlist_podcasts(itdb) track = gpod.itdb_track_new() From 32b03a15d2c8a6e6a0e6337273f21e26d15948da Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Tue, 22 Jan 2013 19:42:05 +0100 Subject: [PATCH 12/13] movetoipod: be verbose about missing libraries, update documentation --- gpodder_extensions/movetoipod.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/gpodder_extensions/movetoipod.py b/gpodder_extensions/movetoipod.py index 4ac468f..bc102ed 100644 --- a/gpodder_extensions/movetoipod.py +++ b/gpodder_extensions/movetoipod.py @@ -1,11 +1,30 @@ # -*- coding: utf-8 -*- # Send files to iPod using libgpod -# Copyright (c) 2012-11-01 Paul Ortyl +# Copyright (c) 2013-01-22 Paul Ortyl # Licensed under the same terms as gPodder itself ''' Extention to gPodder for sending (moving) files to iPod from context menu ''' +# HOWTO HOWTO HOWTO HOWTO HOWTO +# * copy the file 'movetoipod.py' to share/gpodder/extensions/. +# * make sure gpod and mutagen libraries (and python wrappers) are installed +# to install it on Ubuntu suffices: +# sudo apt-get install python-gpod python-mutagen +# * restart gpodder ( gpodder -v to see diagnostics) +# * enable this extension in preferences/extentions +# * restart gpodder again (just to make sure all hooks fired at gpodder startup) +# * connect iPod (make sure it is mounted, and mounted as vfat, use "mount" command to check it) +# * new entry "Move to iPod" should appear in the context menu of episodes +# * use it for transferring _audio_ files to the end of Podcast playlist on the iPod +# +# Other info:: +# The UI is blocked while the files are transferred. +# For more info start gpodder with '-v' to see detailed logging. +# This script has been tested (and is dogfooded) with iPod Shuffle 2G (and a little on 4G) +# Video files are ignored/skipped (there is no use for them on iPod Shuffle) +# For audio file formats other than mp3 'avconv' is used, (sudo apt-get install libav-tools) + # missing features: # * user feedback (at the moment only via logging to console, start gpodder with '-v' option) # * BUG: UI does not get updates after enabling of this extension, restart of gpodder is necessary @@ -24,9 +43,15 @@ _are_libraries_available = True try: import gpod +except Exception, e: + logger.error('gpod library is missing') + _are_libraries_available = False + +try: from mutagen.oggvorbis import OggVorbis from mutagen.mp3 import MP3 except Exception, e: + logger.error('mutagen library is missing') _are_libraries_available = False _ = gpodder.gettext From cf752a62fe1e47c1e0e264e77758d6550c05c0d8 Mon Sep 17 00:00:00 2001 From: Paul Ortyl Date: Wed, 23 Jan 2013 19:05:28 +0100 Subject: [PATCH 13/13] movetoipod: fix parsing of /proc/mounts, add some more documentation --- gpodder_extensions/movetoipod.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/gpodder_extensions/movetoipod.py b/gpodder_extensions/movetoipod.py index bc102ed..dad35c3 100644 --- a/gpodder_extensions/movetoipod.py +++ b/gpodder_extensions/movetoipod.py @@ -24,6 +24,8 @@ # This script has been tested (and is dogfooded) with iPod Shuffle 2G (and a little on 4G) # Video files are ignored/skipped (there is no use for them on iPod Shuffle) # For audio file formats other than mp3 'avconv' is used, (sudo apt-get install libav-tools) +# If autodetection of iPod fails, you can always set IPOD_MOUNTPOINT to the desired path: +# export IPOD_MOUNTPOINT=/paht/to/ipod ; gpodder -v # missing features: # * user feedback (at the moment only via logging to console, start gpodder with '-v' option) @@ -245,9 +247,16 @@ def _find_ipod(self): with open('/proc/mounts', 'r') as f: for line in f.readlines(): tokens = line.split(' ', 4) - if tokens and 'vfat' == tokens[2] and os.path.exists(tokens[1] + '/iPod_Control'): - self.ipod_mount = tokens[1] - return True + if tokens and 'vfat' == tokens[2]: + # replace the following escapes (see man 3 getmntent): + # space (\040), tab (\011), newline (\012) and backslash (\134) + path = tokens[1] + path = path.replace('\\040','\040').replace('\\011','\011') + path = path.replace('\\012','\012').replace('\\134','\134') + if os.path.exists(path + '/iPod_Control'): + self.ipod_mount = path + logger.info("iPod mountpoint found at '%s'" % self.ipod_mount) + return True except IOError as e: pass # in case we are not on standard linux return False