2007-01-28 10:21:39 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
2006-04-07 22:22:30 +02:00
|
|
|
|
|
|
|
#
|
|
|
|
# gPodder (a media aggregator / podcast client)
|
2006-12-29 16:52:52 +01:00
|
|
|
# Copyright (C) 2005-2007 Thomas Perl <thp at perli.net>
|
2006-04-07 22:22:30 +02:00
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU General Public License
|
|
|
|
# as published by the Free Software Foundation; either version 2
|
|
|
|
# of the License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# 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.
|
2006-04-06 16:11:03 +02:00
|
|
|
#
|
2006-04-07 22:22:30 +02:00
|
|
|
# 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., 51 Franklin Street, Fifth Floor, Boston,
|
|
|
|
# MA 02110-1301, USA.
|
2006-04-06 16:11:03 +02:00
|
|
|
#
|
|
|
|
|
|
|
|
#
|
|
|
|
# libipodsync.py -- sync localdb contents with ipod playlist
|
|
|
|
# thomas perl <thp@perli.net> 20060405
|
|
|
|
#
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
# variable tells if ipod functions are to be enabled
|
|
|
|
enable_ipod_functions = True
|
|
|
|
|
2006-11-22 17:05:15 +01:00
|
|
|
# possible mp3 length detection mechanisms
|
|
|
|
MAD = 1
|
|
|
|
EYED3 = 2
|
|
|
|
|
2006-12-28 19:04:03 +01:00
|
|
|
# default length (our "educated guess") is 60 minutes
|
|
|
|
DEFAULT_LENGTH = 60*60*1000
|
|
|
|
|
|
|
|
# command line for mplayer
|
|
|
|
MPLAYER_COMMAND = 'mplayer -msglevel all=-1 -identify -vo null -ao null -frames 0 "%s" 2>/dev/null'
|
|
|
|
|
2007-08-07 20:11:31 +02:00
|
|
|
from gpodder import util
|
|
|
|
|
2006-11-22 17:05:15 +01:00
|
|
|
from liblogger import log
|
2006-04-06 16:11:03 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
import gpod
|
|
|
|
except:
|
2007-06-11 07:30:01 +02:00
|
|
|
log( '(ipodsync) Could not find python-gpod. iPod functions will be disabled.')
|
|
|
|
log( '(ipodsync) Please install the "python-gpod" package if you want iPod support.')
|
2006-04-06 16:11:03 +02:00
|
|
|
enable_ipod_functions = False
|
|
|
|
|
2007-06-11 07:30:01 +02:00
|
|
|
try:
|
|
|
|
import mad
|
|
|
|
log( '(ipodsync) Found pymad')
|
|
|
|
except:
|
|
|
|
log( '(ipodsync) Could not find pymad.')
|
|
|
|
|
|
|
|
try:
|
|
|
|
import eyeD3
|
|
|
|
log( '(ipodsync) Found eyeD3')
|
|
|
|
except:
|
|
|
|
log( '(ipodsync) Coulld not find eyeD3.')
|
|
|
|
|
2006-11-30 23:15:17 +01:00
|
|
|
|
|
|
|
# are we going to use python-id3 for cover art extraction?
|
|
|
|
use_pyid3 = False
|
|
|
|
|
|
|
|
try:
|
|
|
|
# try to load PyID3
|
2007-07-22 23:41:30 +02:00
|
|
|
import id3
|
2006-11-30 23:15:17 +01:00
|
|
|
use_pyid3 = True
|
|
|
|
log('(ipodsync) Found PyID3, will try to extract cover art from mp3 metadata')
|
|
|
|
except:
|
2007-07-22 23:41:30 +02:00
|
|
|
log('(ipodsync) PyID3 not found - falling back to channel cover for iPod cover art')
|
2006-11-30 23:15:17 +01:00
|
|
|
|
|
|
|
|
2006-04-06 16:11:03 +02:00
|
|
|
import os
|
2006-12-17 02:21:36 +01:00
|
|
|
import os.path
|
2007-03-03 14:10:21 +01:00
|
|
|
import glob
|
2006-12-17 02:21:36 +01:00
|
|
|
import shutil
|
2006-04-06 16:11:03 +02:00
|
|
|
import sys
|
2006-04-07 03:43:06 +02:00
|
|
|
import time
|
2007-04-05 09:11:59 +02:00
|
|
|
import string
|
2006-11-13 12:12:32 +01:00
|
|
|
import email.Utils
|
2006-04-06 16:11:03 +02:00
|
|
|
|
|
|
|
import liblocaldb
|
|
|
|
import libpodcasts
|
2007-03-18 19:28:17 +01:00
|
|
|
import libgpodder
|
2007-03-19 20:08:00 +01:00
|
|
|
import libconverter
|
2007-04-01 19:53:04 +02:00
|
|
|
import libtagupdate
|
2006-04-06 16:11:03 +02:00
|
|
|
|
2006-04-07 03:43:06 +02:00
|
|
|
import gobject
|
2006-04-06 16:11:03 +02:00
|
|
|
|
|
|
|
# do we provide iPod functions to the user?
|
|
|
|
def ipod_supported():
|
|
|
|
global enable_ipod_functions
|
|
|
|
return enable_ipod_functions
|
|
|
|
|
2006-06-09 12:43:42 +02:00
|
|
|
# file extensions that are handled as video
|
2007-08-28 00:19:43 +02:00
|
|
|
video_extensions = [ "mov", "mp4", "m4v", "divx" ]
|
2006-04-06 16:11:03 +02:00
|
|
|
|
2006-12-28 19:04:03 +01:00
|
|
|
# is mplayer available for finding track length?
|
|
|
|
use_mplayer = False
|
2007-06-11 07:30:01 +02:00
|
|
|
if not os.system("which mplayer >/dev/null 2>&1"):
|
2006-12-28 19:04:03 +01:00
|
|
|
use_mplayer = True
|
2007-06-11 07:30:01 +02:00
|
|
|
log('(ipodsync) Found mplayer, using it to find track length of files')
|
2006-12-28 19:04:03 +01:00
|
|
|
else:
|
|
|
|
log('(ipodsync) mplayer not found - length of video files will be guessed')
|
2006-04-09 18:18:47 +02:00
|
|
|
|
2006-12-17 02:21:36 +01:00
|
|
|
class gPodderSyncMethod:
|
|
|
|
def __init__( self, callback_progress = None, callback_status = None, callback_done = None):
|
|
|
|
self.callback_progress = callback_progress
|
|
|
|
self.callback_status = callback_status
|
|
|
|
self.callback_done = callback_done
|
2007-03-22 17:35:51 +01:00
|
|
|
self.cancelled = False
|
|
|
|
self.can_cancel = False
|
2007-07-12 15:52:54 +02:00
|
|
|
self.errors = []
|
2007-03-18 19:28:17 +01:00
|
|
|
|
|
|
|
def open( self):
|
|
|
|
return False
|
2006-12-17 02:21:36 +01:00
|
|
|
|
|
|
|
def set_progress( self, pos, max):
|
|
|
|
if self.callback_progress:
|
|
|
|
gobject.idle_add( self.callback_progress, pos, max)
|
2007-03-19 20:08:00 +01:00
|
|
|
|
|
|
|
def set_progress_overall( self, pos, max):
|
|
|
|
if self.callback_progress:
|
|
|
|
gobject.idle_add( self.callback_progress, pos, max, True)
|
|
|
|
|
|
|
|
def set_progress_sub_episode( self, pos, max):
|
|
|
|
if self.callback_progress:
|
|
|
|
gobject.idle_add( self.callback_progress, pos, max, False, True)
|
|
|
|
|
|
|
|
def set_episode_status( self, episode):
|
|
|
|
self.set_status( episode = _('Copying %s') % episode)
|
|
|
|
|
|
|
|
def set_channel_status( self, channel):
|
|
|
|
self.set_status( channel = _('Synchronizing %s') % channel)
|
|
|
|
|
|
|
|
def set_episode_convert_status( self, episode, percentage):
|
|
|
|
self.set_progress_sub_episode( int(percentage), 100)
|
|
|
|
self.set_status( episode = _('Converting %s (%s%%)') % ( episode, str(percentage), ))
|
2006-12-17 02:21:36 +01:00
|
|
|
|
2007-03-22 17:35:51 +01:00
|
|
|
def set_status( self, episode = None, channel = None, progressbar_text = None, title = None, header = None, body = None):
|
2006-12-17 02:21:36 +01:00
|
|
|
if self.callback_status:
|
2007-03-22 17:35:51 +01:00
|
|
|
gobject.idle_add( self.callback_status, episode, channel, progressbar_text, title, header, body)
|
2006-12-17 02:21:36 +01:00
|
|
|
|
2007-04-23 17:18:31 +02:00
|
|
|
def sync_channel( self, channel, episodes = None, sync_played_episodes = True):
|
2007-03-22 17:35:51 +01:00
|
|
|
if not channel.sync_to_devices and episodes == None or self.cancelled:
|
2006-12-17 02:21:36 +01:00
|
|
|
return False
|
|
|
|
|
2007-03-18 19:28:17 +01:00
|
|
|
if episodes == None:
|
|
|
|
episodes = channel
|
|
|
|
|
|
|
|
max = len( episodes)
|
2007-03-19 20:08:00 +01:00
|
|
|
pos = 0
|
2006-12-17 02:21:36 +01:00
|
|
|
|
2007-03-18 19:28:17 +01:00
|
|
|
for episode in episodes:
|
2007-03-22 17:35:51 +01:00
|
|
|
if self.cancelled:
|
|
|
|
return False
|
2006-12-17 02:21:36 +01:00
|
|
|
self.set_progress( pos, max)
|
2007-08-22 01:00:49 +02:00
|
|
|
if episode.is_downloaded() and episode.file_type() in ( 'audio', 'video' ) and (sync_played_episodes or not channel.is_played( episode)):
|
2007-07-12 15:52:54 +02:00
|
|
|
if not self.add_episode_from_channel( channel, episode):
|
|
|
|
return False
|
2006-12-17 02:21:36 +01:00
|
|
|
pos = pos + 1
|
2007-03-19 20:08:00 +01:00
|
|
|
self.set_progress( pos, max)
|
2006-12-17 02:21:36 +01:00
|
|
|
|
2007-03-19 20:08:00 +01:00
|
|
|
self.set_status( channel = _('Completed %s') % channel.title)
|
2006-12-17 02:21:36 +01:00
|
|
|
time.sleep(1)
|
2007-03-19 20:08:00 +01:00
|
|
|
|
2006-12-17 02:21:36 +01:00
|
|
|
return True
|
|
|
|
|
|
|
|
def add_episode_from_channel( self, channel, episode):
|
|
|
|
channeltext = channel.title
|
|
|
|
|
|
|
|
if channel.is_music_channel:
|
|
|
|
channeltext = _('%s (to "%s")') % ( channel.title, channel.device_playlist_name )
|
|
|
|
|
2007-03-19 20:08:00 +01:00
|
|
|
self.set_episode_status( episode.title)
|
|
|
|
self.set_channel_status( channeltext)
|
2006-12-17 02:21:36 +01:00
|
|
|
|
2007-07-12 15:52:54 +02:00
|
|
|
return True
|
|
|
|
|
2007-03-22 17:35:51 +01:00
|
|
|
def close( self, success = True, access_error = False, cleaned = False):
|
|
|
|
try:
|
|
|
|
self.set_status( channel = _('Writing data to disk'))
|
|
|
|
os.system('sync')
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if self.callback_done:
|
2007-07-12 15:52:54 +02:00
|
|
|
gobject.idle_add( self.callback_done, success, access_error, cleaned, self.errors)
|
2006-12-17 02:21:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
class gPodder_iPodSync( gPodderSyncMethod):
|
2006-04-06 16:11:03 +02:00
|
|
|
itdb = None
|
|
|
|
ipod_mount = '' # mountpoint for ipod
|
2006-04-07 20:11:31 +02:00
|
|
|
playlist_name = 'gpodder' # name of playlist to sync to
|
2006-04-06 16:11:03 +02:00
|
|
|
pl_master = None
|
2006-04-08 11:09:15 +02:00
|
|
|
pl_podcasts = None
|
2006-04-07 03:43:06 +02:00
|
|
|
|
2007-03-18 19:28:17 +01:00
|
|
|
def __init__( self, callback_progress = None, callback_status = None, callback_done = None):
|
2006-04-06 16:11:03 +02:00
|
|
|
if not ipod_supported():
|
2006-11-17 15:26:10 +01:00
|
|
|
log( '(ipodsync) iPod functions not supported. (libgpod + eyed3 needed)')
|
2007-03-18 19:28:17 +01:00
|
|
|
gl = libgpodder.gPodderLib()
|
|
|
|
self.ipod_mount = gl.ipod_mount
|
2006-12-17 02:21:36 +01:00
|
|
|
gPodderSyncMethod.__init__( self, callback_progress, callback_status, callback_done)
|
2006-04-09 18:18:47 +02:00
|
|
|
|
2006-04-06 16:11:03 +02:00
|
|
|
def open( self):
|
|
|
|
if not ipod_supported():
|
|
|
|
return False
|
2007-03-22 17:35:51 +01:00
|
|
|
tries = 0
|
|
|
|
while self.itdb == None and not self.cancelled:
|
|
|
|
if os.path.isdir( self.ipod_mount):
|
|
|
|
self.itdb = gpod.itdb_parse( str( self.ipod_mount), None)
|
|
|
|
if self.itdb:
|
|
|
|
self.itdb.mountpoint = str(self.ipod_mount)
|
|
|
|
self.pl_master = gpod.sw_get_playlists( self.itdb)[0]
|
|
|
|
self.pl_podcasts = gpod.itdb_playlist_podcasts( self.itdb)
|
|
|
|
if not self.pl_master or not self.pl_podcasts:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
header = _('Connect your iPod')
|
|
|
|
body = _('To start the synchronization process, please connect your iPod to the computer.')
|
|
|
|
if tries > 30:
|
|
|
|
return False
|
|
|
|
elif tries > 15:
|
|
|
|
self.set_status( episode = _('Have you set up your iPod correctly?'), header = header, body = body)
|
|
|
|
else:
|
|
|
|
self.set_status( channel = _('Please connect your iPod'), episode = _('Waiting for iPod'), header = header, body = body)
|
2006-04-06 16:11:03 +02:00
|
|
|
|
2007-03-22 17:35:51 +01:00
|
|
|
time.sleep( 1)
|
|
|
|
tries += 1
|
|
|
|
|
|
|
|
return self.itdb != None
|
|
|
|
|
|
|
|
def close( self, success = True, access_error = False, cleaned = False):
|
2006-04-06 16:11:03 +02:00
|
|
|
if not ipod_supported():
|
|
|
|
return False
|
2007-03-18 19:28:17 +01:00
|
|
|
if self.itdb:
|
2007-03-19 20:08:00 +01:00
|
|
|
self.set_status( channel = _('Saving iPod database'))
|
2007-03-18 19:28:17 +01:00
|
|
|
gpod.itdb_write( self.itdb, None)
|
2006-04-06 16:11:03 +02:00
|
|
|
self.itdb = None
|
2007-03-22 17:35:51 +01:00
|
|
|
gPodderSyncMethod.close( self, success, access_error, cleaned)
|
2006-04-07 20:11:31 +02:00
|
|
|
return True
|
2006-04-06 16:11:03 +02:00
|
|
|
|
2006-04-08 11:09:15 +02:00
|
|
|
def remove_from_ipod( self, track, playlists):
|
2006-04-06 16:11:03 +02:00
|
|
|
if not ipod_supported():
|
|
|
|
return False
|
2006-11-17 15:26:10 +01:00
|
|
|
log( '(ipodsync) Removing track from iPod: %s', track.title)
|
2007-03-19 20:08:00 +01:00
|
|
|
status_text = _('Removing %s') % ( track.title, )
|
|
|
|
self.set_status( channel = status_text)
|
2006-04-06 16:11:03 +02:00
|
|
|
fname = gpod.itdb_filename_on_ipod( track)
|
2006-04-08 11:09:15 +02:00
|
|
|
for playlist in playlists:
|
|
|
|
try:
|
|
|
|
gpod.itdb_playlist_remove_track( playlist, track)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
2006-04-06 16:11:03 +02:00
|
|
|
gpod.itdb_track_unlink( track)
|
2006-04-07 03:43:06 +02:00
|
|
|
try:
|
|
|
|
os.unlink( fname)
|
|
|
|
except:
|
|
|
|
# suppose we've already deleted it or so..
|
|
|
|
pass
|
2006-04-06 16:11:03 +02:00
|
|
|
|
2006-04-08 11:09:15 +02:00
|
|
|
def get_playlist_by_name( self, playlistname = 'gPodder'):
|
2006-04-06 16:11:03 +02:00
|
|
|
if not ipod_supported():
|
|
|
|
return False
|
|
|
|
for playlist in gpod.sw_get_playlists( self.itdb):
|
2006-04-08 11:09:15 +02:00
|
|
|
if playlist.name == playlistname:
|
2006-11-17 15:26:10 +01:00
|
|
|
log( '(ipodsync) Found old playlist: %s', playlist.name)
|
2006-04-06 16:11:03 +02:00
|
|
|
return playlist
|
|
|
|
|
2006-11-17 15:26:10 +01:00
|
|
|
log( '(ipodsync) New playlist: %s', playlistname)
|
2006-04-08 11:09:15 +02:00
|
|
|
new_playlist = gpod.itdb_playlist_new( str(playlistname), False)
|
2006-04-06 16:11:03 +02:00
|
|
|
gpod.itdb_playlist_add( self.itdb, new_playlist, -1)
|
|
|
|
return new_playlist
|
|
|
|
|
2006-04-08 11:09:15 +02:00
|
|
|
def get_playlist_for_channel( self, channel):
|
|
|
|
if channel.is_music_channel:
|
|
|
|
return self.get_playlist_by_name( channel.device_playlist_name)
|
|
|
|
else:
|
|
|
|
return self.pl_podcasts
|
|
|
|
|
2006-04-07 03:43:06 +02:00
|
|
|
def episode_is_on_ipod( self, channel, episode):
|
|
|
|
if not ipod_supported():
|
|
|
|
return False
|
2006-12-06 21:25:26 +01:00
|
|
|
pl = self.get_playlist_for_channel( channel)
|
|
|
|
if not pl:
|
|
|
|
return False
|
|
|
|
for track in gpod.sw_get_playlist_tracks( pl):
|
2006-04-07 20:11:31 +02:00
|
|
|
if episode.title == track.title and channel.title == track.album:
|
2007-04-03 13:21:12 +02:00
|
|
|
gl = libgpodder.gPodderLib()
|
|
|
|
|
|
|
|
# Mark as played locally if played on iPod
|
|
|
|
if track.playcount > 0:
|
|
|
|
log( 'Episode has been played %d times on iPod: %s', track.playcount, episode.title, sender = self)
|
|
|
|
gl.history_mark_played( episode.url)
|
|
|
|
|
|
|
|
# Mark as played on iPod if played locally (and set podcast flags)
|
|
|
|
self.set_podcast_flags( track, episode)
|
|
|
|
|
2006-04-07 03:43:06 +02:00
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
2006-04-06 16:11:03 +02:00
|
|
|
def clean_playlist( self):
|
|
|
|
if not ipod_supported():
|
|
|
|
return False
|
2006-04-08 11:09:15 +02:00
|
|
|
for track in gpod.sw_get_playlist_tracks( self.pl_podcasts):
|
2006-11-17 15:26:10 +01:00
|
|
|
log( '(ipodsync) Trying to remove: %s', track.title)
|
2006-04-08 11:09:15 +02:00
|
|
|
self.remove_from_ipod( track, [ self.pl_podcasts ])
|
2006-04-06 16:11:03 +02:00
|
|
|
|
2007-04-03 13:21:12 +02:00
|
|
|
def set_podcast_flags( self, track, episode):
|
2006-04-06 16:11:03 +02:00
|
|
|
if not ipod_supported():
|
|
|
|
return False
|
2006-04-09 14:12:19 +02:00
|
|
|
try:
|
2006-11-18 11:43:59 +01:00
|
|
|
# Add blue bullet next to unplayed tracks on 5G iPods
|
2007-04-03 13:21:12 +02:00
|
|
|
# (only if the podcast has not been played locally already
|
|
|
|
gl = libgpodder.gPodderLib()
|
|
|
|
if gl.history_is_played( episode.url):
|
|
|
|
track.mark_unplayed = 0x01
|
2007-04-05 07:50:21 +02:00
|
|
|
# Increment playcount if it's played locally
|
|
|
|
# but still has zero playcount on iPod
|
|
|
|
if track.playcount == 0:
|
|
|
|
track.playcount = 1
|
2007-04-03 13:21:12 +02:00
|
|
|
else:
|
|
|
|
track.mark_unplayed = 0x02
|
2006-11-18 11:43:59 +01:00
|
|
|
|
|
|
|
# Podcast flags (for new iPods?)
|
|
|
|
track.remember_playback_position = 0x01
|
|
|
|
|
|
|
|
# Podcast flags (for old iPods?)
|
2006-04-09 14:12:19 +02:00
|
|
|
track.flag1 = 0x02
|
|
|
|
track.flag2 = 0x01
|
|
|
|
track.flag3 = 0x01
|
|
|
|
track.flag4 = 0x01
|
2007-01-28 09:27:21 +01:00
|
|
|
|
2006-04-09 14:12:19 +02:00
|
|
|
except:
|
2006-11-17 15:26:10 +01:00
|
|
|
log( '(ipodsync) Seems like your python-gpod is out-of-date.')
|
2006-11-30 23:15:17 +01:00
|
|
|
|
|
|
|
def set_channel_art(self, track, local_filename):
|
|
|
|
cover_filename = os.path.dirname( local_filename) + '/cover'
|
|
|
|
if os.path.isfile( cover_filename):
|
|
|
|
gpod.itdb_track_set_thumbnails( track, cover_filename)
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def set_cover_art(self, track, local_filename):
|
|
|
|
if not ipod_supported():
|
|
|
|
return False
|
|
|
|
if use_pyid3:
|
|
|
|
try:
|
|
|
|
cover_filename = local_filename + '.cover.jpg'
|
2007-07-22 23:41:30 +02:00
|
|
|
id3v2_tags = id3.ID3v2( local_filename )
|
2006-11-30 23:15:17 +01:00
|
|
|
for frame in id3v2_tags.frames:
|
|
|
|
if frame.id == 'APIC':
|
|
|
|
cover_file = file(cover_filename, 'w')
|
|
|
|
cover_file.write(frame.image)
|
|
|
|
cover_file.close()
|
|
|
|
if os.path.isfile( cover_filename):
|
|
|
|
gpod.itdb_track_set_thumbnails( track, cover_filename)
|
|
|
|
return True
|
|
|
|
except:
|
|
|
|
log( '(ipodsync) Error reading ID3v2 information for %s' % ( local_filename, ))
|
|
|
|
# If we haven't yet found cover art, fall back to channel cover
|
|
|
|
return self.set_channel_art( track, local_filename)
|
2006-04-06 16:11:03 +02:00
|
|
|
|
|
|
|
def add_episode_from_channel( self, channel, episode):
|
2006-12-28 19:04:03 +01:00
|
|
|
global DEFAULT_LENGTH
|
|
|
|
global MPLAYER_COMMAND
|
|
|
|
|
2006-04-06 16:11:03 +02:00
|
|
|
if not ipod_supported():
|
|
|
|
return False
|
2006-12-17 02:21:36 +01:00
|
|
|
|
|
|
|
gPodderSyncMethod.add_episode_from_channel( self, channel, episode)
|
2006-04-07 03:43:06 +02:00
|
|
|
|
|
|
|
if self.episode_is_on_ipod( channel, episode):
|
2007-05-24 16:48:20 +02:00
|
|
|
status_text = _('Already on iPod: %s') % ( episode.title, )
|
2007-03-19 20:08:00 +01:00
|
|
|
self.set_status( episode = status_text)
|
2006-04-07 03:43:06 +02:00
|
|
|
return True
|
|
|
|
|
2006-11-17 15:26:10 +01:00
|
|
|
log( '(ipodsync) Adding item: %s from %s', episode.title, channel.title)
|
2007-08-22 01:00:49 +02:00
|
|
|
original_filename = str( episode.local_filename())
|
2007-03-19 20:08:00 +01:00
|
|
|
local_filename = original_filename
|
|
|
|
if libconverter.converters.has_converter( os.path.splitext( original_filename)[1][1:]):
|
|
|
|
log('(ipodsync) Converting: %s', original_filename)
|
|
|
|
callback_status = lambda percentage: self.set_episode_convert_status( episode.title, percentage)
|
|
|
|
local_filename = str( libconverter.converters.convert( original_filename, callback = callback_status))
|
2007-04-01 19:53:04 +02:00
|
|
|
if not libtagupdate.update_metadata_on_file( local_filename, title = episode.title, artist = channel.title):
|
|
|
|
log('(ipodsync) Could not set metadata on converted file %s', local_filename)
|
2007-03-19 20:08:00 +01:00
|
|
|
self.set_episode_status( episode.title)
|
|
|
|
if not local_filename:
|
|
|
|
log('(ipodsync) Error while converting file %s', original_filename)
|
|
|
|
return False
|
2006-12-28 19:04:03 +01:00
|
|
|
|
|
|
|
# if we cannot get the track length, make an educated guess (default value)
|
|
|
|
track_length = DEFAULT_LENGTH
|
2007-06-11 07:30:01 +02:00
|
|
|
track_length_found = False
|
2006-12-28 19:04:03 +01:00
|
|
|
|
2007-06-11 07:30:01 +02:00
|
|
|
if use_mplayer:
|
|
|
|
try:
|
|
|
|
log( 'Using mplayer to get file length', sender = self)
|
|
|
|
mplayer_output = os.popen( MPLAYER_COMMAND % local_filename).read()
|
|
|
|
track_length = int(float(mplayer_output[mplayer_output.index('ID_LENGTH'):].splitlines()[0][10:]) * 1000)
|
|
|
|
track_length_found = True
|
|
|
|
except:
|
|
|
|
log( 'Warning: cannot get length for %s', episode.title, sender = self)
|
|
|
|
else:
|
|
|
|
log( 'Please try installing the "mplayer" package for track length detection.', sender = self)
|
|
|
|
|
|
|
|
if not track_length_found:
|
|
|
|
try:
|
|
|
|
log( 'Using pymad to get file length', sender = self)
|
2006-11-22 17:05:15 +01:00
|
|
|
mad_info = mad.MadFile( local_filename)
|
|
|
|
track_length = mad_info.total_time()
|
2007-06-11 07:30:01 +02:00
|
|
|
track_length_found = True
|
|
|
|
except:
|
|
|
|
log( 'Warning: cannot get length for %s', episode.title, sender = self)
|
|
|
|
|
|
|
|
if not track_length_found:
|
|
|
|
try:
|
|
|
|
log( 'Using eyeD3 to get file length', sender = self)
|
2006-11-22 17:05:15 +01:00
|
|
|
eyed3_info = eyeD3.Mp3AudioFile( local_filename)
|
|
|
|
track_length = eyed3_info.getPlayTime() * 1000
|
2007-06-11 07:30:01 +02:00
|
|
|
track_length_found = True
|
|
|
|
except:
|
|
|
|
log( 'Warning: cannot get length for %s', episode.title, sender = self)
|
|
|
|
|
|
|
|
if not track_length_found:
|
|
|
|
log( 'I was not able to find a correct track length, defaulting to %d', track_length, sender = self)
|
2006-04-06 16:11:03 +02:00
|
|
|
|
|
|
|
track = gpod.itdb_track_new()
|
2006-04-09 14:12:19 +02:00
|
|
|
|
2007-04-01 19:53:04 +02:00
|
|
|
track.artist = str(channel.title)
|
2007-04-03 13:21:12 +02:00
|
|
|
self.set_podcast_flags( track, episode)
|
2006-04-09 14:12:19 +02:00
|
|
|
|
2006-11-13 12:12:32 +01:00
|
|
|
# Add release time to track if pubDate is parseable
|
|
|
|
ipod_date = email.Utils.parsedate(episode.pubDate)
|
|
|
|
if ipod_date != None:
|
2007-07-07 17:04:15 +02:00
|
|
|
try:
|
|
|
|
# libgpod>= 0.5.x uses a new timestamp format
|
|
|
|
track.time_released = gpod.itdb_time_host_to_mac(int(time.mktime(ipod_date)))
|
|
|
|
except:
|
|
|
|
# old (pre-0.5.x) libgpod versions expect mactime, so
|
|
|
|
# we're going to manually build a good mactime timestamp here :)
|
|
|
|
#
|
|
|
|
# + 2082844800 for unixtime => mactime (1970 => 1904)
|
|
|
|
track.time_released = int(time.mktime(ipod_date) + 2082844800)
|
2006-11-13 12:12:32 +01:00
|
|
|
|
2006-04-09 14:12:19 +02:00
|
|
|
track.title = str(episode.title)
|
2006-04-07 20:11:31 +02:00
|
|
|
track.album = str(channel.title)
|
2006-12-06 21:25:26 +01:00
|
|
|
track.tracklen = int(track_length)
|
2006-04-06 16:11:03 +02:00
|
|
|
track.description = str(episode.description)
|
2006-04-07 21:23:35 +02:00
|
|
|
track.podcasturl = str(episode.url)
|
|
|
|
track.podcastrss = str(channel.url)
|
2006-11-20 12:00:14 +01:00
|
|
|
track.size = os.path.getsize( local_filename)
|
2007-04-23 17:18:31 +02:00
|
|
|
track.filetype = 'mp3'
|
|
|
|
# For audio podcasts (thanks to José Luis Fustel)
|
|
|
|
try:
|
|
|
|
track.mediatype = 0x00000004
|
|
|
|
except:
|
|
|
|
log( '(ipodsync) Seems like your python-gpod is out-of-date.')
|
|
|
|
track.unk208 = 0x00000004
|
|
|
|
|
2006-04-06 16:11:03 +02:00
|
|
|
gpod.itdb_track_add( self.itdb, track, -1)
|
2006-04-08 11:09:15 +02:00
|
|
|
playlist = self.get_playlist_for_channel( channel)
|
|
|
|
gpod.itdb_playlist_add_track( playlist, track, -1)
|
2006-06-09 12:43:42 +02:00
|
|
|
|
2006-11-30 23:15:17 +01:00
|
|
|
self.set_cover_art( track, local_filename)
|
2007-04-23 17:18:31 +02:00
|
|
|
|
2006-06-09 12:43:42 +02:00
|
|
|
# dirty hack to get video working, seems to work
|
|
|
|
for ext in video_extensions:
|
|
|
|
if local_filename.lower().endswith( '.%s' % ext):
|
2007-04-23 17:18:31 +02:00
|
|
|
track.filetype = 'm4v' # Doesn't seem to matter if it's mp4 or m4v
|
2006-12-28 19:04:03 +01:00
|
|
|
try:
|
|
|
|
# documented on http://ipodlinux.org/ITunesDB
|
|
|
|
track.mediatype = 0x00000002
|
|
|
|
except:
|
|
|
|
# for old libgpod versions, "mediatype" is "unk208"
|
2007-04-23 17:18:31 +02:00
|
|
|
log( '(ipodsync) Seems like your python-gpod is out-of-date.')
|
2006-12-28 19:04:03 +01:00
|
|
|
track.unk208 = 0x00000002
|
2006-06-09 12:43:42 +02:00
|
|
|
|
2006-04-08 11:09:15 +02:00
|
|
|
# if it's a music channel, also sync to master playlist
|
|
|
|
if channel.is_music_channel:
|
|
|
|
gpod.itdb_playlist_add_track( self.pl_master, track, -1)
|
2006-04-06 16:11:03 +02:00
|
|
|
|
|
|
|
if gpod.itdb_cp_track_to_ipod( track, local_filename, None) != 1:
|
2006-11-17 15:26:10 +01:00
|
|
|
log( '(ipodsync) Could not add %s', episode.title)
|
2006-04-06 16:11:03 +02:00
|
|
|
else:
|
2006-11-17 15:26:10 +01:00
|
|
|
log( '(ipodsync) Added %s', episode.title)
|
2007-04-23 17:18:31 +02:00
|
|
|
|
2007-05-24 16:48:20 +02:00
|
|
|
status_text = _('Done: %s') % ( episode.title, )
|
2007-03-19 20:08:00 +01:00
|
|
|
self.set_status( episode = status_text)
|
|
|
|
|
|
|
|
if local_filename != original_filename:
|
|
|
|
log('(ipodsync) Removing temporary file: %s', local_filename)
|
|
|
|
try:
|
|
|
|
os.unlink( local_filename)
|
|
|
|
except:
|
|
|
|
log('(ipodsync) Could not remove temporary file %s', local_filename)
|
|
|
|
|
2007-08-09 13:31:08 +02:00
|
|
|
return True
|
2006-12-17 02:21:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
class gPodder_FSSync( gPodderSyncMethod):
|
2007-03-22 17:35:51 +01:00
|
|
|
BUFFER = 1024*1024*1 # 1MB
|
|
|
|
|
2007-03-18 19:28:17 +01:00
|
|
|
def __init__( self, callback_progress = None, callback_status = None, callback_done = None):
|
|
|
|
gl = libgpodder.gPodderLib()
|
|
|
|
self.destination = gl.mp3_player_folder
|
2006-12-17 02:21:36 +01:00
|
|
|
gPodderSyncMethod.__init__( self, callback_progress, callback_status, callback_done)
|
2007-03-22 17:35:51 +01:00
|
|
|
self.can_cancel = True
|
2007-03-18 19:28:17 +01:00
|
|
|
|
|
|
|
def open( self):
|
|
|
|
gpl = libgpodder.gPodderLib()
|
2007-08-07 20:11:31 +02:00
|
|
|
return util.directory_is_writable( self.destination)
|
2006-12-17 02:21:36 +01:00
|
|
|
|
|
|
|
def add_episode_from_channel( self, channel, episode):
|
2007-04-05 09:11:59 +02:00
|
|
|
allowed_chars = set( string.lowercase + string.uppercase + string.digits + ' _.-')
|
2007-01-22 13:17:46 +01:00
|
|
|
|
2006-12-17 02:21:36 +01:00
|
|
|
gPodderSyncMethod.add_episode_from_channel( self, channel, episode)
|
|
|
|
|
2007-04-05 09:11:59 +02:00
|
|
|
folder_src = channel.title
|
|
|
|
folder = ''
|
|
|
|
for ch in folder_src:
|
|
|
|
if ch in allowed_chars:
|
|
|
|
folder = folder + ch
|
|
|
|
else:
|
|
|
|
folder = folder + '_'
|
2007-01-22 13:17:46 +01:00
|
|
|
folder = os.path.join( self.destination, folder)
|
|
|
|
|
2007-08-22 01:00:49 +02:00
|
|
|
from_file = episode.local_filename()
|
2006-12-17 02:21:36 +01:00
|
|
|
|
2007-04-05 09:11:59 +02:00
|
|
|
to_file_src = episode.title + os.path.splitext( from_file)[1].lower()
|
|
|
|
to_file = ''
|
|
|
|
for ch in to_file_src:
|
|
|
|
if ch in allowed_chars:
|
|
|
|
to_file = to_file + ch
|
|
|
|
else:
|
|
|
|
to_file = to_file + '_'
|
2007-08-13 22:59:32 +02:00
|
|
|
|
|
|
|
# dirty workaround: on bad (empty) episode titles,
|
|
|
|
# we simply use the from_file basename
|
|
|
|
# (please, podcast authors, FIX YOUR RSS FEEDS!)
|
2007-08-19 08:48:01 +02:00
|
|
|
if os.path.splitext( to_file)[0] == '':
|
2007-08-13 22:59:32 +02:00
|
|
|
to_file = os.path.basename( from_file)
|
|
|
|
|
2006-12-17 02:21:36 +01:00
|
|
|
to_file = os.path.join( folder, to_file)
|
|
|
|
|
|
|
|
try:
|
|
|
|
os.makedirs( folder)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if not os.path.exists( to_file):
|
|
|
|
log( 'Copying: %s => %s', os.path.basename( from_file), to_file)
|
2007-07-12 15:52:54 +02:00
|
|
|
if not self.copy_file_progress( from_file, to_file):
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
2007-03-22 17:35:51 +01:00
|
|
|
|
|
|
|
def copy_file_progress( self, from_file, to_file):
|
2007-07-12 15:52:54 +02:00
|
|
|
try:
|
|
|
|
out_file = open( to_file, 'wb')
|
|
|
|
except IOError, ioerror:
|
|
|
|
self.errors.append( _('Error opening %s: %s') % ( ioerror.filename, ioerror.strerror, ))
|
|
|
|
self.cancelled = True
|
|
|
|
return False
|
|
|
|
try:
|
|
|
|
in_file = open( from_file, 'rb')
|
|
|
|
except IOError, ioerror:
|
|
|
|
self.errors.append( _('Error opening %s: %s') % ( ioerror.filename, ioerror.strerror, ))
|
|
|
|
self.cancelled = True
|
|
|
|
return False
|
|
|
|
|
2007-03-22 17:35:51 +01:00
|
|
|
in_file.seek( 0, 2)
|
|
|
|
bytes = in_file.tell()
|
|
|
|
bytes_read = 0
|
|
|
|
in_file.seek( 0)
|
|
|
|
s = in_file.read( self.BUFFER)
|
|
|
|
bytes_read += len(s)
|
|
|
|
self.set_progress_sub_episode( bytes_read, bytes)
|
|
|
|
while s:
|
|
|
|
bytes_read += len(s)
|
2007-07-12 15:52:54 +02:00
|
|
|
try:
|
|
|
|
out_file.write( s)
|
|
|
|
except IOError, ioerror:
|
|
|
|
self.errors.append( ioerror.strerror)
|
|
|
|
try:
|
|
|
|
out_file.close()
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
try:
|
|
|
|
log( 'Trying to remove partially copied file: %s' % ( to_file, ), sender = self)
|
|
|
|
os.unlink( to_file)
|
|
|
|
log( 'Yeah! Unlinked %s at least..' % ( to_file, ), sender = self)
|
|
|
|
except:
|
|
|
|
log( 'Error while trying to unlink %s. OH MY!' % ( to_file, ), sender = self)
|
|
|
|
self.cancelled = True
|
|
|
|
return False
|
2007-03-22 17:35:51 +01:00
|
|
|
self.set_progress_sub_episode( bytes_read, bytes)
|
|
|
|
s = in_file.read( self.BUFFER)
|
|
|
|
out_file.close()
|
|
|
|
in_file.close()
|
2007-07-12 15:52:54 +02:00
|
|
|
|
|
|
|
return True
|
2007-03-03 14:10:21 +01:00
|
|
|
|
|
|
|
def clean_playlist( self):
|
|
|
|
folders = glob.glob( os.path.join( self.destination, '*'))
|
|
|
|
for folder in range( len( folders)):
|
2007-03-22 17:35:51 +01:00
|
|
|
if self.cancelled:
|
|
|
|
return
|
2007-03-19 20:08:00 +01:00
|
|
|
self.set_progress_overall( folder+1, len( folders))
|
|
|
|
self.set_channel_status( os.path.basename( folders[folder]))
|
|
|
|
self.set_status( episode = _('Removing files'))
|
2007-03-03 14:10:21 +01:00
|
|
|
log( 'deleting: %s', folders[folder])
|
|
|
|
shutil.rmtree( folders[folder])
|
|
|
|
try:
|
|
|
|
os.system('sync')
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
2006-12-17 02:21:36 +01:00
|
|
|
|