Determine episode duration (bug 811)
This patch adds two methods for detecting the episode length (time units, not bytes): * iTunes-specific "duration" in the RSS feed * GStreamer-based length detection after download The patch also adds duration information to the tooltip in the episode list as a first step for displaying this information in the UI.
This commit is contained in:
parent
7917c78ee8
commit
7c20ffd167
|
@ -237,25 +237,25 @@ class EpisodeListModel(gtk.ListStore):
|
||||||
show_missing = False
|
show_missing = False
|
||||||
status_icon = None
|
status_icon = None
|
||||||
status_icon_to_build_from_file = False
|
status_icon_to_build_from_file = False
|
||||||
tooltip = ''
|
tooltip = []
|
||||||
view_show_undeleted = True
|
view_show_undeleted = True
|
||||||
view_show_downloaded = False
|
view_show_downloaded = False
|
||||||
view_show_unplayed = False
|
view_show_unplayed = False
|
||||||
icon_theme = gtk.icon_theme_get_default()
|
icon_theme = gtk.icon_theme_get_default()
|
||||||
|
|
||||||
if downloading is not None and downloading(episode):
|
if downloading is not None and downloading(episode):
|
||||||
tooltip = _('Downloading')
|
tooltip.append(_('Downloading'))
|
||||||
status_icon = self.ICON_DOWNLOADING
|
status_icon = self.ICON_DOWNLOADING
|
||||||
view_show_downloaded = True
|
view_show_downloaded = True
|
||||||
view_show_unplayed = True
|
view_show_unplayed = True
|
||||||
else:
|
else:
|
||||||
if episode.state == gpodder.STATE_DELETED:
|
if episode.state == gpodder.STATE_DELETED:
|
||||||
tooltip = _('Deleted')
|
tooltip.append(_('Deleted'))
|
||||||
status_icon = self.ICON_DELETED
|
status_icon = self.ICON_DELETED
|
||||||
view_show_undeleted = False
|
view_show_undeleted = False
|
||||||
elif episode.state == gpodder.STATE_NORMAL and \
|
elif episode.state == gpodder.STATE_NORMAL and \
|
||||||
not episode.is_played:
|
not episode.is_played:
|
||||||
tooltip = _('New episode')
|
tooltip.append(_('New episode'))
|
||||||
status_icon = self.ICON_NEW
|
status_icon = self.ICON_NEW
|
||||||
view_show_downloaded = True
|
view_show_downloaded = True
|
||||||
view_show_unplayed = True
|
view_show_unplayed = True
|
||||||
|
@ -322,9 +322,14 @@ class EpisodeListModel(gtk.ListStore):
|
||||||
tooltip.append(_('deletion prevented'))
|
tooltip.append(_('deletion prevented'))
|
||||||
|
|
||||||
if episode.total_time > 0 and episode.current_position:
|
if episode.total_time > 0 and episode.current_position:
|
||||||
tooltip.append('%d%%' % (100.*float(episode.current_position)/float(episode.total_time)))
|
tooltip.append('%d%%' % (100.*float(episode.current_position)/float(episode.total_time),))
|
||||||
|
|
||||||
tooltip = ', '.join(tooltip)
|
if episode.total_time:
|
||||||
|
total_time = util.format_time(episode.total_time)
|
||||||
|
if total_time:
|
||||||
|
tooltip.append(total_time)
|
||||||
|
|
||||||
|
tooltip = ', '.join(tooltip)
|
||||||
|
|
||||||
if status_icon is not None:
|
if status_icon is not None:
|
||||||
status_icon = self._get_tree_icon(status_icon, show_bullet, \
|
status_icon = self._get_tree_icon(status_icon, show_bullet, \
|
||||||
|
|
|
@ -28,6 +28,7 @@ from gpodder import util
|
||||||
from gpodder import feedcore
|
from gpodder import feedcore
|
||||||
from gpodder import youtube
|
from gpodder import youtube
|
||||||
from gpodder import corestats
|
from gpodder import corestats
|
||||||
|
from gpodder import gstreamer
|
||||||
|
|
||||||
from gpodder.liblogger import log
|
from gpodder.liblogger import log
|
||||||
|
|
||||||
|
@ -690,6 +691,13 @@ class PodcastEpisode(PodcastModelObject):
|
||||||
episode.link = entry.get('link', '')
|
episode.link = entry.get('link', '')
|
||||||
episode.description = entry.get('summary', '')
|
episode.description = entry.get('summary', '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Parse iTunes-specific podcast duration metadata
|
||||||
|
total_time = util.parse_time(entry.get('itunes_duration', ''))
|
||||||
|
episode.total_time = total_time
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# Fallback to subtitle if summary is not available0
|
# Fallback to subtitle if summary is not available0
|
||||||
if not episode.description:
|
if not episode.description:
|
||||||
episode.description = entry.get('subtitle', '')
|
episode.description = entry.get('subtitle', '')
|
||||||
|
@ -804,7 +812,7 @@ class PodcastEpisode(PodcastModelObject):
|
||||||
# Time attributes
|
# Time attributes
|
||||||
self.total_time = 0
|
self.total_time = 0
|
||||||
self.current_position = 0
|
self.current_position = 0
|
||||||
self.current_position_updated = time.time()
|
self.current_position_updated = 0
|
||||||
|
|
||||||
def get_is_locked(self):
|
def get_is_locked(self):
|
||||||
return self._is_locked
|
return self._is_locked
|
||||||
|
@ -823,6 +831,22 @@ class PodcastEpisode(PodcastModelObject):
|
||||||
self.state = gpodder.STATE_DOWNLOADED
|
self.state = gpodder.STATE_DOWNLOADED
|
||||||
self.is_played = False
|
self.is_played = False
|
||||||
self.length = os.path.getsize(filename)
|
self.length = os.path.getsize(filename)
|
||||||
|
|
||||||
|
if not self.total_time:
|
||||||
|
try:
|
||||||
|
length = gstreamer.get_track_length(filename)
|
||||||
|
if length is not None:
|
||||||
|
length = int(length/1000)
|
||||||
|
log('Detected media length: %d seconds', length, \
|
||||||
|
sender=self)
|
||||||
|
self.total_time = length
|
||||||
|
self.db.save_episode(self)
|
||||||
|
self.db.commit()
|
||||||
|
return
|
||||||
|
except Exception, e:
|
||||||
|
log('Error while detecting media length: %s', str(e), \
|
||||||
|
sender=self)
|
||||||
|
|
||||||
self.db.save_downloaded_episode(self)
|
self.db.save_downloaded_episode(self)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
|
|
|
@ -1039,8 +1039,55 @@ def bluetooth_send_file(filename):
|
||||||
else:
|
else:
|
||||||
log('Cannot send file. Please install "bluetooth-sendto" or "gnome-obex-send".')
|
log('Cannot send file. Please install "bluetooth-sendto" or "gnome-obex-send".')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def format_time(value):
|
||||||
|
"""Format a seconds value to a string
|
||||||
|
|
||||||
|
>>> format_time(0)
|
||||||
|
'00:00'
|
||||||
|
>>> format_time(20)
|
||||||
|
'00:20'
|
||||||
|
>>> format_time(3600)
|
||||||
|
'01:00:00'
|
||||||
|
>>> format_time(10921)
|
||||||
|
'03:02:01'
|
||||||
|
"""
|
||||||
|
dt = datetime.datetime.utcfromtimestamp(value)
|
||||||
|
if dt.hour == 0:
|
||||||
|
return dt.strftime('%M:%S')
|
||||||
|
else:
|
||||||
|
return dt.strftime('%H:%M:%S')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_time(value):
|
||||||
|
"""Parse a time string into seconds
|
||||||
|
>>> parse_time('00:00')
|
||||||
|
0
|
||||||
|
>>> parse_time('00:00:00')
|
||||||
|
0
|
||||||
|
>>> parse_time('00:20')
|
||||||
|
20
|
||||||
|
>>> parse_time('00:00:20')
|
||||||
|
20
|
||||||
|
>>> parse_time('01:00:00')
|
||||||
|
3600
|
||||||
|
>>> parse_time('03:02:01')
|
||||||
|
10921
|
||||||
|
"""
|
||||||
|
if not value:
|
||||||
|
raise ValueError('Invalid value: %s' % (str(value),))
|
||||||
|
|
||||||
|
for format in ('%H:%M:%S', '%M:%S'):
|
||||||
|
try:
|
||||||
|
t = time.strptime(value, format)
|
||||||
|
return (t.tm_hour * 60 + t.tm_min) * 60 + t.tm_sec
|
||||||
|
except ValueError, ve:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
|
||||||
def format_seconds_to_hour_min_sec(seconds):
|
def format_seconds_to_hour_min_sec(seconds):
|
||||||
"""
|
"""
|
||||||
Take the number of seconds and format it into a
|
Take the number of seconds and format it into a
|
||||||
|
|
Loading…
Reference in a new issue