Author: duncan
Date: Mon Aug 20 14:06:28 2007
New Revision: 9829
Log:
[ 1776909 ] code cleanup and improving in usability
Patch from Tanja Kotthaus applied
Added:
branches/rel-1/freevo/src/tv/favoriteitem.py (contents, props changed)
branches/rel-1/freevo/src/tv/programitem.py (contents, props changed)
Modified:
branches/rel-1/freevo/src/tv/plugins/recordings_manager.py
branches/rel-1/freevo/src/tv/plugins/scheduled_recordings.py
branches/rel-1/freevo/src/tv/plugins/search_programs.py
branches/rel-1/freevo/src/tv/plugins/view_favorites.py
branches/rel-1/freevo/src/tv/program_display.py
branches/rel-1/freevo/src/tv/tvguide.py
branches/rel-1/freevo/src/tv/tvmenu.py
Added: branches/rel-1/freevo/src/tv/favoriteitem.py
==============================================================================
--- (empty file)
+++ branches/rel-1/freevo/src/tv/favoriteitem.py Mon Aug 20 14:06:28 2007
@@ -0,0 +1,387 @@
+# -*- coding: iso-8859-1 -*-
+# -----------------------------------------------------------------------
+# favoriteItem - Favorite handling.
+# -----------------------------------------------------------------------
+# $Id:
+#
+# Todo:
+# Notes:
+#
+# -----------------------------------------------------------------------
+#
+# Freevo - A Home Theater PC framework
+#
+# Copyright (C) 2002 Krister Lagerstrom, et al.
+#
+# 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 MER-
+# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# 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.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# ----------------------------------------------------------------------
+import time
+
+import menu, config, osd
+
+from item import Item
+
+from gui.InputBox import InputBox
+from gui.AlertBox import AlertBox
+from gui.PopupBox import PopupBox
+
+import tv.record_client as record_client
+
+class FavoriteItem(Item):
+ """
+ Item class for favorite items
+ """
+ def __init__(self, parent, fav, fav_action='edit'):
+ Item.__init__(self, parent, skin_type='video')
+ self.fav = fav
+ self.name = self.origname = fav.name
+ self.title = fav.title
+ self.fav_action = fav_action
+ if hasattr(fav,'allowDuplicates'):
+ self.allowDuplicates = fav.allowDuplicates
+ else:
+ self.allowDuplicates = 1
+ if hasattr(fav,'onlyNew'):
+ self.onlyNew = fav.onlyNew
+ else:
+ self.onlyNew = 0
+
+ self.week_days = (_('Mon'), _('Tue'), _('Wed'), _('Thu'),
+ _('Fri'), _('Sat'), _('Sun'))
+
+ if fav.channel == 'ANY':
+ self.channel = _('ANY CHANNEL')
+ else:
+ self.channel = fav.channel
+ if fav.dow == 'ANY':
+ self.dow = _('ANY DAY')
+ else:
+ self.dow = self.week_days[int(fav.dow)]
+ if fav.mod == 'ANY':
+ self.mod = _('ANY TIME')
+ else:
+ try:
+ self.mod = time.strftime(config.TV_TIMEFORMAT,
+ time.gmtime(float(int(fav.mod) * 60)))
+ except:
+ print 'Cannot add "%s" to favorites' % fav.name
+
+ # needed by the inputbox handler
+ self.menuw = None
+
+ def actions(self):
+ return [( self.display_submenu , _('Display favorite'))]
+
+
+ def display_submenu(self, arg=None, menuw=None):
+ """ Display menu for a favorite
+
+ With this menu the user can made a program a favorite.
+ All attributes of a favorite can be edited here and in the end
+ the user must select 'save changes' to finally create the favorite.
+ """
+
+ ### create menu items for editing the favorites attributes
+ items = []
+
+ items.append(menu.MenuItem(_('Modify name'),
+ action=self.mod_name))
+ items.append(menu.MenuItem(_('Modify channel'),
+ action=self.mod_channel))
+ items.append(menu.MenuItem(_('Modify day of week'),
+ action=self.mod_day))
+ items.append(menu.MenuItem(_('Modify time of day'),
+ action=self.mod_time))
+
+ if config.DUPLICATE_DETECTION:
+ items.append(menu.MenuItem(_('Modify duplicate flag'),
+ action=self.mod_dup))
+
+ if config.ONLY_NEW_DETECTION:
+ items.append(menu.MenuItem(_('Modify episodes flag'),
+ action=self.mod_new))
+
+ # XXX: priorities aren't quite supported yet
+ if 0:
+ (got_favs, favs) = record_client.getFavorites()
+ if got_favs and len(favs) > 1:
+ items.append(menu.MenuItem(_('Modify priority'),
+ action=self.mod_priority))
+
+
+ ### save favorite
+ items.append(menu.MenuItem(_('Save changes'),
+ action=self.save_changes))
+
+ ### remove this program from favorites
+ if not self.fav_action == 'add':
+ items.append(menu.MenuItem(_('Remove favorite'),
+ action=self.rem_favorite))
+
+ ### put the whole menu together
+ favorite_menu = menu.Menu(_('Favorite Menu'), items,
+ item_types = 'tv favorite menu')
+
+ favorite_menu.infoitem = self
+ favorite_menu.is_submenu = True
+ menuw.pushmenu(favorite_menu)
+ menuw.refresh()
+
+
+
+ ### Actions:
+
+ def mod_name(self, arg=None, menuw=None):
+ """ Modify name
+
+ This opens a input box to ask the user for a new name for this
favorite.
+ The default name of a favorite is the name of the program.
+ """
+ self.menuw = menuw
+ InputBox(text=_('Alter Name'), handler=self.alter_name,
+ width = osd.get_singleton().width - config.OSD_OVERSCAN_X -
20,
+ input_text=self.name).show()
+
+
+ def alter_name(self, name):
+ """ set the new name"""
+ if name:
+ self.name = self.fav.name = name.strip()
+
+ self.menuw.refresh()
+
+ def mod_dup(self, arg=None, menuw=None):
+ """ Modify duplication flag
+
+ This opens a submenu where the user can change the settings for the
+ duplication detection.
+ """
+ items = []
+ items.append(menu.MenuItem('Allow Duplicates', action=self.alter_prop,
+ arg=('dup', 'True')))
+ items.append(menu.MenuItem('Prevent Duplicates',
action=self.alter_prop,
+ arg=('dup', 'False')))
+ favorite_menu = menu.Menu(_('Modify Duplicate Flag'), items,
+ item_types = 'tv favorite menu')
+ favorite_menu.infoitem = self
+ menuw.pushmenu(favorite_menu)
+ menuw.refresh()
+
+
+ def mod_new(self, arg=None, menuw=None):
+ """ Modify new flag
+
+ This opens a submenu where the user can choose if all episodes of
+ a program should be recorded or only new ones.
+ """
+ items = []
+ items.append(menu.MenuItem('All Episodes', action=self.alter_prop,
+ arg=('new', 'False')))
+ items.append(menu.MenuItem('Only New Episodes', action=self.alter_prop,
+ arg=('new', 'True')))
+ favorite_menu = menu.Menu(_('Modify Only New Flag'), items,
+ item_types = 'tv favorite menu')
+ favorite_menu.infoitem = self
+ menuw.pushmenu(favorite_menu)
+ menuw.refresh()
+
+
+ def mod_channel(self, arg=None, menuw=None):
+ """Modify channel"""
+ items = []
+
+ items.append(menu.MenuItem('ANY CHANNEL', action=self.alter_prop,
+ arg=('channel', 'ANY')))
+
+ for chanline in config.TV_CHANNELS:
+ items.append(menu.MenuItem(chanline[1], action=self.alter_prop,
+ arg=('channel', chanline[1])))
+
+ favorite_menu = menu.Menu(_('Modify Channel'), items,
+ item_types = 'tv favorite menu')
+ favorite_menu.infoitem = self
+ menuw.pushmenu(favorite_menu)
+ menuw.refresh()
+
+
+
+ def mod_day(self, arg=None, menuw=None):
+ """ Modify day
+
+ Opens a submenu where the day of the week of a favorite can be
configured.
+ """
+ items = []
+
+ items.append(menu.MenuItem(_('ANY DAY'), action=self.alter_prop,
+ arg=('dow', 'ANY')))
+
+ for i in range(len(self.week_days)):
+ items.append(menu.MenuItem(self.week_days[i],
action=self.alter_prop,
+ arg=('dow', i)))
+
+ favorite_menu = menu.Menu(_('Modify Day'), items,
+ item_types = 'tv favorite menu')
+ favorite_menu.infoitem = self
+ menuw.pushmenu(favorite_menu)
+ menuw.refresh()
+
+
+ def mod_time(self, arg=None, menuw=None):
+ """ Modify time
+
+ Opens a submenu where the time of a favorite can be configured.
+ """
+ items = []
+
+ items.append(menu.MenuItem(_('ANY TIME'), action=self.alter_prop,
+ arg=('mod', 'ANY')))
+
+ for i in range(48):
+ mod = i * 30
+ items.append(menu.MenuItem(time.strftime(config.TV_TIMEFORMAT,
+ time.gmtime(float(mod * 60))),
+ action=self.alter_prop,
+ arg=('mod', mod)))
+
+ favorite_menu = menu.Menu(_('Modify Time'), items,
+ item_types = 'tv favorite menu')
+ favorite_menu.infoitem = self
+ menuw.pushmenu(favorite_menu)
+ menuw.refresh()
+
+
+
+ def alter_prop(self, arg=(None,None), menuw=None):
+ """ Alter a favorites property
+
+ This function is where the properties of a favorite really are changed.
+ """
+ (prop, val) = arg
+
+ if prop == 'channel':
+ if val == 'ANY':
+ self.channel = 'ANY CHANNEL'
+ self.fav.channel = 'ANY'
+ else:
+ self.channel = val
+ self.fav.channel = val
+
+ elif prop == 'dow':
+ if val == 'ANY':
+ self.dow = 'ANY DAY'
+ self.fav.dow = 'ANY'
+ else:
+ self.dow = self.week_days[val]
+ self.fav.dow = val
+
+ elif prop == 'mod':
+ if val == 'ANY':
+ self.mod = 'ANY TIME'
+ self.fav.mod = 'ANY'
+ else:
+ # self.mod = tv_util.minToTOD(val)
+ self.mod = time.strftime(config.TV_TIMEFORMAT,
+ time.gmtime(float(val * 60)))
+ self.fav.mod = val
+
+ elif prop == 'dup':
+ if val == 'True':
+ self.allowDuplicates=TRUE
+ self.fav.allowDuplicates=TRUE
+ else:
+ self.allowDuplicates=FALSE
+ self.fav.allowDuplicates=FALSE
+
+ elif prop == 'new':
+ if val == 'True':
+ self.onlyNew=TRUE
+ self.fav.onlyNew=TRUE
+ else:
+ self.onlyNew=FALSE
+ self.fav.onlyNew=FALSE
+
+ if menuw:
+ menuw.back_one_menu(arg='reload')
+
+
+ def save_changes(self, arg=None, menuw=None):
+ """
+ Save favorite
+ """
+ # this can take some time, as it means although to update the schedule
+ msgtext = _('Saving the changes to this favorite.')
+ msgtext+= _('This may take some time.')
+ pop = PopupBox(text=msgtext)
+ pop.show()
+
+
+ if self.fav_action == 'edit':
+ # first we remove the old favorite
+ (result, msg) = record_client.removeFavorite(self.origname)
+ elif self.fav_action =='add':
+ result = True
+
+ if result:
+ # create a new edited favorite
+ if not config.DUPLICATE_DETECTION or not hasattr(self.fav,
+
'allowDuplicates'):
+ self.fav.allowDuplicates = 1
+
+ if not config.ONLY_NEW_DETECTION or not hasattr(self.fav,
+ 'onlyNew'):
+ self.fav.onlyNew = 0
+
+ (result, msg) = record_client.addEditedFavorite(self.fav.name,
+ self.fav.title,
+ self.fav.channel,
+ self.fav.dow,
+ self.fav.mod,
+ self.fav.priority,
+
self.fav.allowDuplicates,
+ self.fav.onlyNew)
+ if result:
+ self.fav_action = 'edit'
+ if menuw:
+ # and reload the menu that we return to
+ menuw.back_one_menu(arg='reload')
+ pop.destroy()
+ else:
+ pop.destroy()
+ # it is important to show the user this error,
+ # because that means the favorite is removed,
+ # and must be created again
+ msgtext=_('Save failed, favorite was lost.')+(': %s' % msg)
+ AlertBox(text=msgtext).show()
+
+
+ def rem_favorite(self, arg=None, menuw=None):
+ """
+ Remove favorite
+ """
+ name = self.origname
+ (result, msg) = record_client.removeFavorite(name)
+ if result:
+ # if this is successfull
+ if menuw:
+ # reload the menu that we return to
+ menuw.back_one_menu(arg='reload')
+ # and show a short message of success
+ msgtext = text=_('"%s" has been removed from favorites') % name
+ AlertBox(text=msgtext).show()
+ else:
+ # if all fails then we should show an error
+ msgtext = _('Remove failed')+(': %s' % msg)
+ AlertBox(text=msgtext).show()
Modified: branches/rel-1/freevo/src/tv/plugins/recordings_manager.py
==============================================================================
--- branches/rel-1/freevo/src/tv/plugins/recordings_manager.py (original)
+++ branches/rel-1/freevo/src/tv/plugins/recordings_manager.py Mon Aug 20
14:06:28 2007
@@ -60,7 +60,6 @@
from gui import ConfirmBox, AlertBox, ProgressBox
from menu import MenuItem, Menu
from video import VideoItem
-from tv.program_display import ShowProgramDetails
disk_manager = None
@@ -152,7 +151,7 @@
self.settings_fxd = os.path.join(self.dir, 'folder.fxd')
self.load_settings()
- self.blue_action = ( self.configure, _('Configure directory'))
+ self.blue_action = (self.configure, _('Configure directory'))
# ======================================================================
# actions
@@ -162,8 +161,7 @@
"""
return a list of actions for this item
"""
- items = [ ( self.browse, _('Browse directory')) ,
- self.blue_action]
+ items = [ (self.browse, _('Browse directory')), self.blue_action]
return items
@@ -245,7 +243,7 @@
self.save_settings()
item = menuw.menustack[-1].selected
- item.name = item.name[:item.name.find(u'\t') + 1] +
self.configure_get_icon( eval(arg, globals()))
+ item.name = item.name[:item.name.find(u'\t') + 1] +
self.configure_get_icon(eval(arg, globals()))
copy_and_replace_menu_item(menuw, item)
menuw.init_page()
@@ -386,15 +384,18 @@
"""
return the default action
"""
- actions = [ ( self.play, _('Play') ),
- ( self.confirm_delete, _('Delete')),
- ( self.mark_to_keep, self.keep and _('Unmark to
Keep') or _('Mark to Keep')),
- ( self.mark_as_watched, self.watched and _('Unmark as
Watched') or _('Mark as Watched')),
- ( self.view_details, _('View Details'))]
+ actions = [
+ (self.play, _('Play')),
+ (self.view_details, _('Full Description')),
+ (self.confirm_delete, _('Delete')),
+ (self.mark_to_keep, self.keep and _('Unmark to Keep') or _('Mark
to Keep')),
+ (self.mark_as_watched, self.watched and _('Unmark as Watched') or
_('Mark as Watched'))
+ ]
if config.TVRM_ADD_VIDEO_ACTIONS:
actions += self.video_item.actions()[1:] # Remove 'Play' as we've
already got it.
return actions
+
def play(self, arg=None, menuw=None):
"""
Play the recorded program, and then mark it as watched.
@@ -402,11 +403,10 @@
self.video_item.play(menuw=menuw, arg=arg)
# Mark this programme as watched.
- self.update_fxd( True, self.keep)
+ self.update_fxd(True, self.keep)
self.set_icon()
-
def confirm_delete(self, arg=None, menuw=None):
"""
Confirm whether the user really wants to delete this program.
@@ -450,8 +450,21 @@
copy_and_replace_menu_item(menuw, self)
menuw.refresh(reload=True)
+
def view_details(self, arg=None, menuw=None):
- ShowProgramDetails(menuw, Details(self))
+ ShowDetails(menuw, self)
+
+
+ def display_submenu(self, arg=None, menuw=None):
+ """
+ Open the submenu for this item
+ """
+ if not menuw:
+ return
+ # this tries to imitated freevo's internal way of creating submenus
+ menuw.make_submenu(_('Recordings Menu'), self.actions(), self)
+ menuw.show()
+
def set_name_to_episode(self):
"""
@@ -575,10 +588,12 @@
"""
return the default action
"""
- return [ ( self.browse, _('Browse episodes')),
- ( self.confirm_delete, _('Delete all episodes')),
- ( self.mark_all_to_keep, _('Keep all episodes')),
- ( self.play_all, _('Play all episodes') )]
+ return [
+ (self.browse, _('Browse episodes')),
+ (self.confirm_delete, _('Delete all episodes')),
+ (self.mark_all_to_keep, _('Keep all episodes')),
+ (self.play_all, _('Play all episodes'))
+ ]
def browse(self, arg=None, menuw=None):
@@ -961,16 +976,112 @@
return order
-class Details:
- def __init__(self, episode):
- self.episode = episode
- self.time = episode.video_item['year']
- self.title = episode.name
- self.sub_title = episode.video_item['tagline']
- self.desc = episode.video_item['plot']
- self.ratings = {}
- self.advisories = []
- self.categories = []
+import skin
+# Create the skin_object object
+skin_object = skin.get_singleton()
+skin_object.register('tvguideinfo', ('screen', 'info', 'scrollabletext',
'plugin'))
+
+
+class ShowDetails:
+ """
+ Screen to show the details of the TV program
+ """
+ def __init__(self, menuw, episode):
+ if episode is None:
+ name = _('No Information Available')
+ sub_title = ''
+ time = ''
+ description = ''
+ else:
+ self.episode = episode
+ name = episode.name
+ sub_title = episode.video_item['tagline']
+ desc = episode.video_item['plot']
+ # gather the infos and construct the description text
+ if sub_title:
+ # subtitle + newline + description
+ description = u'"' + sub_title + u'"\n' + desc
+ else:
+ # or just the description, if there is no subtitle
+ description = desc
+
+ # maybe there is more infos to add (categories, advisories,
ratings)
+ if hasattr(episode.video_item, 'categories'):
+ description += u'\n'
+ for category in episode.video_item.categories:
+ description += u'\n' + _('Category : ') + category
+
+ if hasattr(episode.video_item, 'advisories'):
+ description += u'\n'
+ for advisory in episode.video_item.advisories:
+ description += u'\n' + _('Advisory : ') + advisory
+
+ if hasattr(episode.video_item, 'ratings'):
+ description += u'\n'
+ for system,value in episode.video_item.ratings.items():
+ description += u'\n' + _('Rating') + u'(' + system + u') :
' + value
+
+ # that's all, we can show this to the user
+ self.name = name
+ self.scrollable_text = skin.ScrollableText(description)
+ self.visible = True
+
+ self.menuw = menuw
+ self.menuw.hide(clear=False)
+
+ # this activates the eventhandler and the context of this class
+ rc.app(self)
+
+ skin_object.draw('tvguideinfo', self)
+
+
+
+ def getattr(self, name):
+ if name == 'title':
+ return self.name
- def getattr(self, attr):
- return getattr(self, attr)
+ if self.episode:
+ return self.episode.getattr(name)
+
+ return u''
+
+
+ def eventhandler(self, event, menuw=None):
+ """
+ eventhandler for the programm description display
+ """
+ if event in ('MENU_SELECT', 'MENU_BACK_ONE_MENU'):
+ # leave the description display and return to the previous menu
+ self.menuw.show()
+ # we do not need to call rc.app(None) here,
+ # because that is done by menuw.show(),
+ # but we need to set the context manually,
+ # because rc.app(None) sets it always to 'menu'
+ rc.set_context(self.menuw.get_event_context())
+ return True
+ elif event == 'MENU_SUBMENU':
+ if hasattr(self.menuw.menustack[-1],'is_submenu'):
+ # the last menu has been a submenu, we just have to show it
+ self.menuw.show()
+ rc.set_context(self.menuw.get_event_context())
+ else:
+ # we have to create the submenu
+ self.episode.display_submenu(menuw=self.menuw)
+ return True
+ elif event == 'MENU_UP':
+ # scroll the description up
+ self.scrollable_text.scroll(True)
+ skin_object.draw('tvguideinfo', self)
+ return True
+ elif event == 'MENU_DOWN':
+ # scroll the description down
+ self.scrollable_text.scroll(False)
+ skin_object.draw('tvguideinfo', self)
+ return True
+ elif event == 'MENU_PLAY_ITEM':
+ self.menuw.show()
+ rc.set_context(self.menuw.get_event_context())
+ self.episode.play(menuw=self.menuw)
+ return True
+ else:
+ return False
Modified: branches/rel-1/freevo/src/tv/plugins/scheduled_recordings.py
==============================================================================
--- branches/rel-1/freevo/src/tv/plugins/scheduled_recordings.py
(original)
+++ branches/rel-1/freevo/src/tv/plugins/scheduled_recordings.py Mon Aug
20 14:06:28 2007
@@ -36,7 +36,7 @@
from gui.AlertBox import AlertBox
from item import Item
-from tv.program_display import ProgramItem
+from tv.programitem import ProgramItem
class ScheduledRecordingsItem(Item):
Modified: branches/rel-1/freevo/src/tv/plugins/search_programs.py
==============================================================================
--- branches/rel-1/freevo/src/tv/plugins/search_programs.py (original)
+++ branches/rel-1/freevo/src/tv/plugins/search_programs.py Mon Aug 20
14:06:28 2007
@@ -50,7 +50,7 @@
from item import Item
from event import *
from menu import MenuItem, Menu
-from tv.program_display import ProgramItem
+from tv.programitem import ProgramItem
import tv.record_client as record_client
# Create the skin_object object
Modified: branches/rel-1/freevo/src/tv/plugins/view_favorites.py
==============================================================================
--- branches/rel-1/freevo/src/tv/plugins/view_favorites.py (original)
+++ branches/rel-1/freevo/src/tv/plugins/view_favorites.py Mon Aug 20
14:06:28 2007
@@ -34,7 +34,7 @@
import tv.record_client as record_client
from item import Item
-from tv.program_display import FavoriteItem
+from tv.favoriteitem import FavoriteItem
from gui.AlertBox import AlertBox
Modified: branches/rel-1/freevo/src/tv/program_display.py
==============================================================================
--- branches/rel-1/freevo/src/tv/program_display.py (original)
+++ branches/rel-1/freevo/src/tv/program_display.py Mon Aug 20 14:06:28 2007
@@ -29,6 +29,11 @@
#
# ----------------------------------------------------------------------
+print '======================================================'
+print 'WARNING: This file is deprecated.'
+print 'Please use favoriteitem.py and programitem.py instead.'
+print '======================================================'
+
import time, traceback
from time import gmtime, strftime
Added: branches/rel-1/freevo/src/tv/programitem.py
==============================================================================
--- (empty file)
+++ branches/rel-1/freevo/src/tv/programitem.py Mon Aug 20 14:06:28 2007
@@ -0,0 +1,462 @@
+# -*- coding: iso-8859-1 -*-
+# -----------------------------------------------------------------------
+# guide_ProgramItem - Information and actions for TvPrograms.
+# -----------------------------------------------------------------------
+# $Id:
+#
+# Todo:
+# Notes:
+#
+# -----------------------------------------------------------------------
+#
+# Freevo - A Home Theater PC framework
+#
+# Copyright (C) 2002 Krister Lagerstrom, et al.
+#
+# 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 MER-
+# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# 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.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# ----------------------------------------------------------------------
+
+import time, os
+
+import config, menu, rc, skin
+
+from item import Item
+from favoriteitem import FavoriteItem
+
+import util.tv_util as tv_util
+import tv.record_client as record_client
+from tv.channels import FreevoChannels
+from tv.record_types import Favorite
+
+
+from gui.PopupBox import PopupBox
+from gui.AlertBox import AlertBox
+
+
+class ProgramItem(Item):
+ """
+ Item class for program items
+
+ This is used in the tv guide
+ and in the list of schedules recordings.
+ """
+ def __init__(self, parent, prog, context='menu'):
+ Item.__init__(self, parent, skin_type='video')
+ # prog is a TvProgram object as we get it from the recordserver
+ self.prog = prog
+ self.context= context
+
+ if hasattr(prog, 'name'): self.name = self.title = prog.name
+ if hasattr(prog, 'title'): self.title = self.name = prog.title
+ if hasattr(prog,'sub_title'): self.sub_title = prog.sub_title
+ if hasattr(prog,'desc'): self.description = prog.desc
+
+ # categories
+ if hasattr(prog, 'categories'):self.categories = prog.categories
+ # ratings
+ if hasattr(prog, 'ratings'): self.ratings = prog.ratings
+ # advisories
+ if hasattr(prog, 'advisories'): self.advisories = prog.advisories
+
+ # channel where this program is running
+ self.channel = tv_util.get_chan_displayname(prog.channel_id)
+
+ # check if the prog is scheduled for recording
+ if hasattr(prog, 'scheduled'):
+ self.scheduled = prog.scheduled
+ else:
+ self.scheduled = False
+
+ # more infos from the TvProgam object
+ self.allowDuplicates = prog.allowDuplicates
+ self.onlyNew = prog.onlyNew
+ self.overlap = prog.overlap
+
+ # defaults to not favorite
+ self.favorite = False
+
+ # start time
+ self.start = time.strftime(config.TV_DATETIMEFORMAT,
+ time.localtime(prog.start))
+ # stop time
+ self.stop = time.strftime(config.TV_DATETIMEFORMAT,
+ time.localtime(prog.stop))
+
+ def actions(self):
+ """ List of actions """
+ #list of entries for the menu
+ items = []
+
+ ##'Play', if this programm is currently running or starts soon
+ # check the time
+ if self.context=='guide':
+ now = time.time()
+ if self.prog.start <= now+(7*60) and self.prog.stop > now:
+ items.append((self.play, _('Play')))
+
+ ## 'Show full description'
+ items.append((self.show_description, _('Full Description')))
+
+ ## 'Schedule for recording' OR 'Remove from schedule'
+ # check if this program is scheduled
+ (got_schedule, schedule) = record_client.getScheduledRecordings()
+ if got_schedule:
+ (result, message) = record_client.isProgScheduled(self.prog,
+ schedule.getProgramList())
+ if result:
+ self.scheduled = True
+ else:
+ self.scheduled = False
+
+ if self.scheduled:
+ items.append((self.remove_program, _('Remove from schedule')))
+ else:
+ items.append((self.schedule_program, _('Schedule for recording')))
+
+ ## 'Add to favorites' OR 'Remove from favorites'
+ # check if this program is a favorite
+ (result, message) = record_client.isProgAFavorite(self.prog)
+ if result:
+ self.favorite = True
+
+ if self.favorite:
+ items.append((self.edit_favorite, _('Edit favorite')))
+ else:
+ items.append((self.add_favorite, _('Add to favorites')))
+
+ ## 'Seach for more of this'
+ if not self.context == 'search':
+ items.append((self.find_more, _('Search for more of this
program')))
+
+ return items
+
+
+ ### Actions:
+
+ def play(self, arg=None, menuw=None):
+ """
+ Start watching TV
+ """
+ # watching TV should only be possible from the guide
+ if not self.context=='guide':
+ rc.post_event('MENU_SELECT')
+ return
+ now = time.time()
+ # Check if the selected program is >7 min in the future
+ if self.prog.start > now + (7*60):
+ if menuw: menuw.show()
+ # this program is in the future
+ msgtext = _('Sorry, you cannot watch this program now. ')
+ msgtext+= _('It starts in the future.')
+ AlertBox(text=msgtext).show()
+ return
+ elif self.prog.stop < now:
+ if menuw: menuw.show()
+ # this program is already over
+ msgtext = _('Sorry, you cannot watch this progam now. ')
+ msgtext+= _('This program is already over.')
+ AlertBox(text=msgtext).show()
+ return
+ else:
+ # check if the device is free
+ fc = FreevoChannels()
+ # for that we need the name of the lock file
+ suffix = fc.getVideoGroup(self.prog.channel_id, True).vdev
+ suffix = suffix.split('/')[-1]
+ tvlockfile = config.FREEVO_CACHEDIR + '/record.'+suffix
+ if os.path.exists(tvlockfile):
+ if menuw: menuw.show()
+ # XXX: In the future add the options to watch what we are
+ # recording or cancel it and watch TV.
+ msgtext = _('Sorry, you cannot watch TV while recording. ')
+ msgtext+= _('If this is not true then remove ')
+ msgtext+= tvlockfile + '.'
+ AlertBox(text=msgtext, height=200).show()
+ else:
+ # everything is ok, we can start watching!
+ self.parent.hide()
+ # return to the guide, which is self.parent
+ while not menuw.menustack[-1]==self.parent:
+ menuw.delete_menu
+ self.parent.player('tv', self.prog.channel_id)
+
+
+ def show_description(self, arg=None, menuw=None):
+ """
+ View a full scrollable description of the program.
+ """
+ ShowProgramDetails(menuw, self)
+
+
+ def toggle_rec(self, arg=None, menuw=None):
+ """
+ schedule or unschedule this program, depending on its current status
+ """
+ if self.scheduled:
+ # remove this program from schedule it it is already scheduled
+ self.remove_program(menuw=menuw)
+ else:
+ # otherwise add it to schedule without more questions
+ self.schedule_program(menuw=menuw)
+
+
+ def schedule_program(self, arg=None, menuw=None):
+ """
+ Add a program to schedule
+ """
+ # schedule the program
+ (result, msg) = record_client.scheduleRecording(self.prog)
+ if result:
+ menuw.delete_submenu()
+ if hasattr(self.parent, 'update'):
+ self.parent.update(force=True)
+ else:
+ menuw.refresh(reload=True)
+ msgtext= _('"%s" has been scheduled for recording') %self.name
+ pop = AlertBox(text=msgtext).show()
+ else:
+ # something went wrong
+ msgtext = _('Scheduling failed')+(': %s' % msg)
+ AlertBox(text=msgtext).show()
+
+
+ def remove_program(self, arg=None, menuw=None):
+ """
+ Remove a program from schedule
+ """
+ # remove the program
+ (result, msg) = record_client.removeScheduledRecording(self.prog)
+ if result:
+ menuw.delete_submenu()
+ if hasattr(self.parent, 'update'):
+ self.parent.update(force=True)
+ else:
+ menuw.refresh(reload=True)
+ msgtext = _('"%s" has been removed from schedule') % self.name
+ AlertBox(text=msgtext).show()
+ else:
+ # something went wrong
+ msgtext = _('Remove failed')+(': %s' % msg)
+ AlertBox(text=msgtext).show()
+
+
+ def add_favorite(self, arg=None, menuw=None):
+ """
+ Add a program to favorites
+ """
+ if menuw:
+ # we do not want to return to this menu,
+ # if we delete it here, then later back_one_menu
+ # brings us back to the tvguide
+ menuw.delete_menu()
+ # create a favorite
+ fav = Favorite(self.title, self.prog,
+ True, True, True, -1, True, False)
+ # and a favorite item which represents the submen
+ fav_item = FavoriteItem(self, fav, fav_action='add')
+ # and open that submenu
+ fav_item.display_submenu(menuw=menuw)
+
+
+ def edit_favorite(self, arg=None, menuw=None):
+ """
+ Edit the settings of a favorite
+ """
+ if menuw:
+ # we do not want to return to this menu,
+ # if we delete it here, then later back_one_menu
+ # brings us back to the tvguide
+ menuw.delete_menu()
+
+ # get the favorite from the record_client
+ (got_fav, fav) = record_client.getFavoriteObject(self.prog)
+ if got_fav:
+ # create a favorite item for the submenu
+ fav_item = FavoriteItem(self, fav, fav_action='edit')
+ # and open the submenu
+ fav_item.display_submenu(menuw=menuw)
+ else:
+ msgtext=_('getFavorites failed')+(':%s' % self.name)
+ AlertBox(text=msgtext).show()
+
+
+ def find_more(self, arg=None, menuw=None):
+ """
+ Find more of this program
+ """
+ _debug_(String('searching for: %s' % self.title),2)
+
+ # this might take some time, thus we open a popup messages
+ pop = PopupBox(text=_('Searching, please wait...'))
+ pop.show()
+ # do the search
+ (result, matches) = record_client.findMatches(self.title)
+ # we are ready -> kill the popup message
+ pop.destroy()
+ if result:
+ # we have been successful!
+ items = []
+ _debug_('search found %s matches' % len(matches), 2)
+ # sort by start times
+ f = lambda a, b: cmp(a.start, b.start)
+ matches.sort(f)
+ for prog in matches:
+ items.append(ProgramItem(self.parent, prog, context='search'))
+ elif matches == 'no matches':
+ # there have been no matches
+ msgtext = _('No matches found for %s') % self.title
+ AlertBox(text=msgtext).show()
+ return
+ else:
+ # something else went wrong
+ msgtext = _('findMatches failed') +(': %s' % matches)
+ AlertBox(text=msgtext).show()
+ return
+
+ # create a menu from the search result
+ search_menu = menu.Menu(_( 'Search Results' ), items,
+ item_types = 'tv program menu')
+ # do not return from the search list to the submenu
+ # where the search was initiated
+ search_menu.back_one_menu = 2
+ menuw.pushmenu(search_menu)
+ menuw.refresh()
+
+
+ def display_submenu(self, arg=None, menuw=None):
+ """
+ Open the submenu for this item
+ """
+ if not menuw:
+ return
+ # this tries to imitated freevo's internal way of creating submenus
+ menuw.make_submenu(_('Program Menu'), self.actions(), self)
+ menuw.show()
+
+
+
+# Create the skin_object object
+skin_object = skin.get_singleton()
+skin_object.register('tvguideinfo', ('screen', 'info', 'scrollabletext',
'plugin'))
+
+# Program Info screen
+class ShowProgramDetails:
+ """
+ Screen to show the details of the TV program
+ """
+ def __init__(self, menuw, prg):
+ if prg is None:
+ name = _('No Information Available')
+ sub_title = ''
+ time = ''
+ description = ''
+ else:
+ self.program = prg
+ name = prg.name
+ sub_title = prg.sub_title
+ # gather the infos and construct the description text
+ if sub_title:
+ # subtitle + newline + description
+ description = u'"' + sub_title + u'"\n' + prg.description
+ else:
+ # or just the description, if there is no subtitle
+ description = prg.description
+
+ # maybe there is more infos to add (categories, advisories,
ratings)
+ if prg.categories:
+ description += u'\n'
+ for category in prg.categories:
+ description += u'\n' + _('Category : ') + category
+
+ if prg.advisories:
+ description += u'\n'
+ for advisory in prg.advisories:
+ description += u'\n' + _('Advisory : ') + advisory
+
+ if prg.ratings:
+ description += u'\n'
+ for system,value in prg.ratings.items():
+ description += u'\n' + _('Rating') + u'(' + system + u') :
' + value
+
+ # that's all, we can show this to the user
+ self.name = name
+ self.scrollable_text = skin.ScrollableText(description)
+ self.visible = True
+
+ self.menuw = menuw
+ self.menuw.hide(clear=False)
+
+ self.app_mode = 'tvmenu' # context
+ # this activates the eventhandler and the context of this class
+ rc.app(self)
+
+ skin_object.draw('tvguideinfo', self)
+
+
+
+ def getattr(self, name):
+ if name == 'title':
+ return self.name
+
+ if self.program:
+ return self.program.getattr(name)
+
+ return u''
+
+
+ def eventhandler(self, event, menuw=None):
+ """
+ eventhandler for the programm description display
+ """
+ if event in ('MENU_SELECT', 'MENU_BACK_ONE_MENU'):
+ # leave the description display and return to the previous menu
+ self.menuw.show()
+ # we do not need to call rc.app(None) here,
+ # because that is done by menuw.show(),
+ # but we need to set the context manually,
+ # because rc.app(None) sets it always to 'menu'
+ rc.set_context(self.menuw.get_event_context())
+ return True
+ elif event == 'MENU_SUBMENU':
+ if hasattr(self.menuw.menustack[-1],'is_submenu'):
+ # the last menu has been a submenu, we just have to show it
+ self.menuw.show()
+ rc.set_context(self.menuw.get_event_context())
+ else:
+ # we have to create the submenu
+ self.program.display_submenu(menuw=self.menuw)
+ return True
+ elif event == 'MENU_UP':
+ # scroll the description up
+ self.scrollable_text.scroll(True)
+ skin_object.draw('tvguideinfo', self)
+ return True
+ elif event == 'MENU_DOWN':
+ # scroll the description down
+ self.scrollable_text.scroll(False)
+ skin_object.draw('tvguideinfo', self)
+ return True
+ elif event == 'PLAY':
+ # try to watch this program
+ self.program.play(menuw=self.menuw)
+ return True
+ elif event == 'TV_START_RECORDING':
+ self.menuw.show()
+ # short cut to change the schedule status of this program
+ self.program.toggle_rec(menuw=self.menuw)
+ return True
+ else:
+ return False
Modified: branches/rel-1/freevo/src/tv/tvguide.py
==============================================================================
--- branches/rel-1/freevo/src/tv/tvguide.py (original)
+++ branches/rel-1/freevo/src/tv/tvguide.py Mon Aug 20 14:06:28 2007
@@ -29,25 +29,19 @@
# -----------------------------------------------------------------------
-import os
-import time
+import os, time
-import config
-import rc
-import util
+
+import config, skin, util, rc
from gui.PopupBox import PopupBox
from gui.AlertBox import AlertBox
-import skin
+from item import Item
+from programitem import ProgramItem
from event import *
-# The Electronic Program Guide
import epg_xmltv, epg_types
-
-from item import Item
-from program_display import ProgramItem
-from tv.channels import FreevoChannels
import record_client as ri
skin = skin.get_singleton()
@@ -57,26 +51,34 @@
CHAN_NO_DATA = _('This channel has no data loaded')
class TVGuide(Item):
+ """
+ Class for TVGuide
+ """
def __init__(self, start_time, player, menuw):
Item.__init__(self)
+ # get skin definitions of the TVGuide
self.n_items, hours_per_page = skin.items_per_page(('tv', self))
+ # end of visible guide
stop_time = start_time + hours_per_page * 60 * 60
+ # constructing the guide takes some time
msgtext = _('Preparing the program guide')
guide = epg_xmltv.get_guide(PopupBox(text=msgtext))
+ # getting channels
channels = guide.GetPrograms(start=start_time+1, stop=stop_time-1)
if not channels:
AlertBox(text=_('TV Guide is corrupt!')).show()
return
+ # select the first available program
selected = None
for chan in channels:
if chan.programs:
selected = chan.programs[0]
break
- self.col_time = 30 # each col represents 30 minutes
+ self.col_time = 30 # each col represents 30 minutes
self.n_cols = (stop_time - start_time) / 60 / self.col_time
self.player = player
@@ -90,7 +92,6 @@
self.lastinput_time = None
self.update_schedules(force=True)
- self.fc = FreevoChannels()
self.rebuild(start_time, stop_time, guide.chan_list[0].id, selected)
self.event_context = 'tvmenu'
@@ -98,12 +99,17 @@
def update_schedules(self, force=False):
+ """
+ update schedule
+
+ reload the list of scheduled programs and check for overlapping
+ """
if not force and self.last_update + 60 > time.time():
return
# less than one second? Do not belive the force update
- if self.last_update + 1 > time.time():
- return
+ #if self.last_update + 1 > time.time():
+ # return
upsoon = '%s/upsoon' % (config.FREEVO_CACHEDIR)
if os.path.isfile(upsoon):
@@ -128,25 +134,10 @@
### event handler
def eventhandler(self, event, menuw=None):
- """ Handles events in the tv guide
-
- Events handled by this are:
- MENU_CHANGE_STYLE: ?
- MENU_UP: Move one channel up in the guide
- MENU_DOWN: Move one channel down in the guide
- MENU_LEFT: Move to the next program on this channel
- MENU_RIGHT: Move to previous programm on this channel
- MENU_PAGEUP: Moves to the first of the currently displayed channels
- MENU_PAGEDOWN: Move to the last of the currently displayed channels
- MENU_SUBMENU: Open a submenu for the selected program
- MENU_SELECT: Open a submenu for the selected program
- TV_START_RECORDING: Start to record this or put it on schedule
- PLAY: Start to watch the selected channel (if it is possible)
- PLAY_END: Show the guide again
- numerical INPUTs: Jump to a specific channel number
"""
-
- _debug_('TVGUIDE EVENT is %s' % event, 2)
+ Handles events in the tv guide
+ """
+ _debug_('TVGUIDE EVENT is %s' % event)
## MENU_CHANGE_STYLE
if event == MENU_CHANGE_STYLE:
@@ -187,66 +178,61 @@
## MENU_UP: Move one channel up in the guide
if event == MENU_UP:
- self.event_change_channel(-1)
+ self.change_channel(-1)
## MENU_DOWN: Move one channel down in the guide
elif event == MENU_DOWN:
- self.event_change_channel(1)
+ self.change_channel(1)
## MENU_LEFT: Move to the next program on this channel
elif event == MENU_LEFT:
- self.event_change_program(-1)
+ self.change_program(-1)
## MENU_RIGHT: Move to previous programm on this channel
elif event == MENU_RIGHT:
- self.event_change_program(1)
+ self.change_program(1)
## MENU_PAGEUP: Moves to the first of the currently displayed channels
elif event == MENU_PAGEUP:
- self.event_change_channel(-self.n_items)
+ self.change_channel(-self.n_items)
## MENU_PAGEDOWN: Move to the last of the currently displayed channels
elif event == MENU_PAGEDOWN:
- self.event_change_channel(self.n_items)
+ self.change_channel(self.n_items)
+
## MENU_SUBMENU: Open a submenu for the selected program
elif event == MENU_SUBMENU:
- self.event_submenu()
+ # create a ProgramItem for the selected program
+ pi = ProgramItem(self, prog=self.selected, context='guide')
+ #and show its submenu
+ pi.display_submenu(menuw=self.menuw)
- ## MENU_SELECT: Open a submenu for the selected program
+ ## MENU_SELECT: Show the description
elif event == MENU_SELECT:
- self.event_submenu()
+ # create a ProgramItem for the selected program
+ pi = ProgramItem(self, prog=self.selected, context='guide')
+ #and show selecte the first action in the actions list
+ pi.actions()[0][0](menuw=self.menuw)
- ## TV_START_RECORDING: Start to record this or put it on schedule
+ ## TV_START_RECORDING: add or remove this program from schedule
elif event == TV_START_RECORDING:
- self.event_record()
+ pi = ProgramItem(self, prog=self.selected, context='guide')
+ pi.toggle_rec(menuw=self.menuw)
## PLAY: Start to watch the selected channel (if it is possible)
elif event == PLAY:
- suffix = self.fc.getVideoGroup(self.selected.channel_id, True).vdev
- suffix = suffix.split('/')[-1]
- tvlockfile = config.FREEVO_CACHEDIR + '/record.'+suffix
-
- # Check if the selected program is >7 min in the future
- # if so, bring up the record dialog
- now = time.time() + (7*60)
- if self.selected.start > now:
- self.event_submenu()
- elif os.path.exists(tvlockfile):
- # XXX: In the future add the options to watch what we are
- # recording or cancel it and watch TV.
- AlertBox(text=_('Sorry, you cannot watch TV while recording.
')+ \
- _('If this is not true then remove ') + \
- tvlockfile + '.', height=200).show()
- return TRUE
- else:
- self.hide()
- self.player('tv', self.selected.channel_id)
+ # create a ProgramItem for the selected program
+ pi = ProgramItem(self, prog=self.selected, context='guide')
+ #and show its submenu
+ pi.play(menuw=self.menuw)
## PLAY_END: Show the guide again
elif event == PLAY_END:
self.show()
+ # FIX or REMOVE:
+ # the numerical INPUT events are not available in the tvmenu context
## numerical INPUT: Jump to a specific channel number
if str(event).startswith("INPUT_"):
# tune explicit channel
@@ -285,10 +271,11 @@
return TRUE
- ### actions
+ ### gui functions
def show(self):
""" show the guide"""
+ _debug_('show',2)
if not self.visible:
self.visible = 1
self.refresh()
@@ -296,6 +283,7 @@
def hide(self):
""" hide the guide"""
+ _debug_('hide',2)
if self.visible:
self.visible = 0
skin.clear()
@@ -307,7 +295,7 @@
This function is called automatically by freevo whenever this menu is
opened or reopened.
"""
- _debug_('Refresh',2)
+ _debug_('refresh',2)
if not self.menuw.children:
rc.set_context(self.event_context)
self.menuw.refresh()
@@ -315,12 +303,12 @@
def update(self, force=False):
- """ update the tv guide
+ """ update the guide
This function updates the scheduled and overlap flags for
all currently displayed programs.
"""
- _debug_('Update',2)
+ _debug_('update',2)
self.update_schedules(force)
if self.table:
for t in self.table:
@@ -345,14 +333,13 @@
self.menuw.refresh()
-
def rebuild(self, start_time, stop_time, start_channel, selected):
""" rebuild the guide
This is neccessary we change the set of programs that have to be
displayed, this is the case when the user moves around in the menu.
"""
- _debug_('Reload',2)
+ _debug_('reload',2)
self.guide = epg_xmltv.get_guide()
channels = self.guide.GetPrograms(start=start_time+1, stop=stop_time-1)
@@ -428,38 +415,10 @@
self.update()
- def event_record(self):
- """ Add to schedule or remove from schedule
-
- This function adds or removes the selected program to schedule,
- if the user presses REC inside of TVGuide.
- This is a kind of a short cut, which directly manipulates the schedule
- without opening the submenu for ProgramItems.
- """
- if self.selected.scheduled:
- # remove this program from schedule it it is already scheduled
- pi = ProgramItem(self, prog=self.selected).remove_program()
- else:
- # otherwise add it to schedule without more questions
- pi = ProgramItem(self, prog=self.selected).schedule_program()
- self.refresh()
-
-
- def event_submenu(self):
- """ Opens the submenu for ProgramItems
-
- The user can choose from this submenu what to do with the selected
- program, e.g. schedule a program for recording or search for more of
- that program.
+ def change_program(self, value, full_scan=False):
+ """
+ Move to the next program
"""
- # create a ProgramItem for the selected program
- pi = ProgramItem(self, prog=self.selected)
- # and show its submenu
- pi.display_program(menuw=self.menuw)
-
-
- def event_change_program(self, value, full_scan=False):
-
start_time = self.start_time
stop_time = self.stop_time
start_channel = self.start_channel
@@ -493,14 +452,14 @@
elif full_scan:
prg = programs[-1]
else:
- return self.event_change_program(value, True)
+ return self.change_program(value, True)
else:
if i+value >= 0:
prg = programs[i+value]
elif full_scan:
prg = programs[0]
else:
- return self.event_change_program(value, True)
+ return self.change_program(value, True)
if prg.sub_title:
procdesc = '"' + prg.sub_title + '"\n' + prg.desc
@@ -521,8 +480,6 @@
while prg.start + extra_space <= start_time:
start_time -= (self.col_time * 60)
stop_time -= (self.col_time * 60)
-
-
else:
prg = epg_types.TvProgram()
prg.channel_id = channel.id
@@ -535,7 +492,10 @@
self.rebuild(start_time, stop_time, start_channel, prg)
- def event_change_channel(self, value):
+ def change_channel(self, value):
+ """
+ Move to the next channel
+ """
start_time = self.start_time
stop_time = self.stop_time
start_channel = self.start_channel
Modified: branches/rel-1/freevo/src/tv/tvmenu.py
==============================================================================
--- branches/rel-1/freevo/src/tv/tvmenu.py (original)
+++ branches/rel-1/freevo/src/tv/tvmenu.py Mon Aug 20 14:06:28 2007
@@ -52,8 +52,6 @@
from gui.AlertBox import AlertBox
from gui.PopupBox import PopupBox
-import tv.program_display
-
DEBUG = config.DEBUG
TRUE = 1
-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems? Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >> http://get.splunk.com/
_______________________________________________
Freevo-cvslog mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog