Source code for vision.playout

import datetime
import importlib
import logging
import os
import weakref

import zope.interface
from twisted.internet import reactor
from twistedschedule.interfaces import ISchedule
from twistedschedule.task import ScheduledCall

from vision import clock
from vision import jukebox
from vision import playlist
from vision.configuration import configuration


# TODO: Ident, loop, etc should be full program obects and not be
# hardcoded in playout.py
# TODO: This part of the code should _only_ concern itself with relative paths.
IDENT_LENGTH = 27.0
#IDENT_FILENAME = os.path.join(configuration.ident_media_root,
#                              'FrikanalenVignett.avi')
#LOOP_FILENAME = os.path.join(configuration.ident_media_root,
#                             'FrikanalenLoop.avi')
IDENT_FILENAME = 'filler/FrikanalenVignett.avi'
LOOP_FILENAME = 'filler/FrikanalenLoop.avi'
# make sure we have the basic required files
#os.stat(IDENT_FILENAME)
#os.stat(LOOP_FILENAME)


[docs]@zope.interface.implementer(ISchedule) class Playout(object): def __init__(self, service, player_class=None): if player_class is None: player_class = configuration.player_class self.player = _get_class(player_class)(LOOP_FILENAME) self.random_provider = jukebox.RandomProvider() self.service = service self.service.playout = self self.schedule = None self.next_program = None self.playing_program = None # A reference to the timed callback which aborts programs self.duration_call = None self.delayed_start_call = None # Still filename, if being displayed self.still = None # Temporary stack for sequence of videos before going to on_idle self.on_end_call_stack=[]
[docs] def set_schedule(self, schedule): "Set schedule and start playing" self.schedule = schedule self.scheduler_task = ScheduledCall(self.cue_next_program) self.scheduler_task.start(self) self.service.on_set_schedule(schedule) if not self.playing_program: self.resume_playback()
[docs] def resume_current_program(self): current_program = self.playing_program if current_program: self.cue_program(current_program, current_program.seconds_since_playback())
[docs] def resume_playback(self): # TODO: Rename to resume_schedule current_program = self.schedule.get_current_program() if current_program: self.cue_program(current_program, current_program.seconds_since_playback()) else: self.on_idle()
[docs] def cue_next_program(self): """Starts the next program Set the next program with Playout.set_next_program""" if self.next_program: self.cue_program(self.next_program) self.next_program = None
def _cancel_pending_calls(self): """Stops any pending calls from starting in the future (and disrupt playback) This is used whenever a program is started and new calls will be registered """ if self.duration_call and not self.duration_call.called: self.duration_call.cancel() self.duration_call = None if self.delayed_start_call and not self.delayed_start_call.called: self.delayed_start_call.cancel() self.delayed_start_call = None
[docs] def cue_program(self, program, resume_offset=0): """Starts the given program""" self._cancel_pending_calls() duration_text = "Unknown" if program.playback_duration == float("inf"): duration_text = "Infinite" elif program.playback_duration: duration_text = "%i:%02i" % (program.playback_duration / 60, program.playback_duration % 60) # Schedule next call delta = program.playback_duration-resume_offset if delta <= 0.0: self.player.pause_screen() else: self.duration_call = reactor.callLater(delta, self.on_program_ended) logging.info("Playback video_id=%i, offset=%s+%ss name='%s' duration=%s" % ( program.media_id, str(program.playback_offset), str(resume_offset), program.title, duration_text)) # Start playback self.player.play_program(program, resume_offset=resume_offset) self.service.on_playback_started(program) self.playing_program = program
[docs] def set_next_program(self, program): self.next_program = program self.service.on_set_next_program(program) if program: logging.info("Next scheduled video_id=%i @ %s" % ( program.media_id, program.program_start)) else: logging.warning("Scheduled nothing")
# ISchedule.getDelayForNext
[docs] def getDelayForNext(self): # Queue next program = self.schedule.get_next_program() if program == None: self.scheduler_task.stop() self.set_next_program(None) # This will actually call cue_next_program once more. logging.warning("Program schedule empty") return 0.0 self.set_next_program(program) return program.seconds_until_playback()
[docs] def stop_schedule(self): if self.scheduler_task and self.scheduler_task.running: self.scheduler_task.stop()
#self.set_next_program(None)
[docs] def start_schedule(self): self.stop_schedule() self.scheduler_task = ScheduledCall(self.cue_next_program) self.scheduler_task.start(self)
[docs] def show_still(self, filename="stills/tekniskeprover.png"): self._cancel_pending_calls() self.stop_schedule() self.player.show_still(filename) self.service.on_still(filename) logging.info("Show still: %s", filename)
[docs] def cancel_still(self): logging.info("Cancel still") self.service.on_still("") self.start_schedule() # TODO: resume_current_program? self.resume_playback()
[docs] def on_program_ended(self): """ try: logging.debug("Video '%s' #%i ended with %.1fs left. " % ( self.playing_program.title, self.playing_program.media_id, self.player.seconds_until_end_of_playing_video()) ) pass # TODO: Add proper exception/exceptionlogging except: logging.warning("Excepted while trying to log on_program_ended") """ if self.on_end_call_stack: func = self.on_end_call_stack.pop(0) func() else: self.on_idle()
[docs] def play_jukebox(self): logging.info("Jukebox playback start") program = self.schedule.new_program() limit = 90*60 # 90 minutes long programs max if self.next_program: limit = min(limit, self.next_program.seconds_until_playback()) video = self.random_provider.get_random_video(limit) program.set_program( media_id=video["media_id"], program_start=clock.now(), playback_duration=video["duration"], title=video["name"]) self.cue_program(program)
[docs] def play_ident(self): logging.info("Ident playback start") program = self.schedule.new_program() program.set_program( media_id=-1, program_start=clock.now(), playback_duration=IDENT_LENGTH, title="Frikanalen Vignett", filename=IDENT_FILENAME) self.cue_program(program)
[docs] def on_idle(self): time_until_next = float("inf") if self.next_program: time_until_next = self.next_program.seconds_until_playback() # The rules. use_jukebox = configuration.jukebox use_jukebox &= time_until_next > (120+IDENT_LENGTH) use_jukebox &= self.random_provider.enough_room(time_until_next) if use_jukebox: loop_length = 12.0 PAUSE_LENGTH = IDENT_LENGTH+loop_length logging.info("Pause before jukebox: %.1fs" % PAUSE_LENGTH) program = self.schedule.new_program() program.set_program(-1, program_start=clock.now(), playback_duration=loop_length, title="Jukebox pause screen", filename=LOOP_FILENAME, loop=True) self.cue_program(program) self.on_end_call_stack.append(self.play_ident) self.on_end_call_stack.append(self.play_jukebox) elif time_until_next >= 12+IDENT_LENGTH: logging.info("Pause idle: %.1fs" % time_until_next) PAUSE_LENGTH = time_until_next program = self.schedule.new_program() program.set_program(-1, program_start=clock.now(), playback_duration=time_until_next-IDENT_LENGTH, title="Pause screen", filename=LOOP_FILENAME, loop=True) self.cue_program(program) self.on_end_call_stack.append(self.play_ident) else: logging.info("Short idle: %.1fs" % time_until_next) # Show pausescreen program = self.schedule.new_program() t = None if self.next_program: t = self.next_program.seconds_until_playback() program.set_program(-1, program_start=clock.now(), playback_duration=t, title="Pause screen", filename=LOOP_FILENAME, loop=True) #self.cue_program(program) # TODO: Doesn't handle looping self.player.pause_screen() self.playing_program = program self.service.on_playback_started(program)
[docs]class PlayoutService(object): def __init__(self): self.observers = weakref.WeakKeyDictionary()
[docs] def add_observer(self, observer): self.observers[observer] = True observer.on_playback_started(self.playout.playing_program) observer.on_set_next_program(self.playout.next_program) if self.playout.still: observer.on_still(self.playout.still)
[docs] def remove_observer(self, observer): del self.observers[observer]
[docs] def on_playback_started(self, program): for each in list(self.observers.keys()): each.on_playback_started(program)
[docs] def on_still(self, name): for each in list(self.observers.keys()): each.on_still(name)
[docs] def on_set_schedule(self, program): for each in list(self.observers.keys()): each.on_set_schedule(program)
[docs] def on_set_next_program(self, program): for each in list(self.observers.keys()): each.on_set_next_program(program)
[docs]def start_test_player(): log_fmt = ( "%(asctime)s %(levelname)s:%(name)s %(filename)s:%(lineno)d " "%(message)s") logging.basicConfig(level=logging.DEBUG, format=log_fmt) service = PlayoutService() schedule = playlist.Schedule() v = schedule.new_program() v.set_program(1758, clock.now() - datetime.timedelta(0, 5), title="First", playback_offset=10, playback_duration=10.0) schedule.add(v) for n in range(1): delta = datetime.timedelta(0, 6+(n)*10.0) v = schedule.new_program() v.set_program(1758, clock.now() + delta, title="No %i" % n, playback_offset=30+60*n, playback_duration=9.0) print(("Added %i @ %s" % (n, v.program_start))) schedule.add(v) player = Playout(service) player.set_schedule(schedule) # import playoutweb # playoutweb.start_web(None, playout_service=service, playout=player, # schedule=schedule, port=8888) reactor.callLater(4.0, player.show_still) reactor.callLater(7.0, player.cancel_still) return player
def _get_class(cls): mod_path, cls_name = cls.split(':') module = importlib.import_module(mod_path) return getattr(module, cls_name) if __name__ == '__main__': start_test_player() reactor.run()