diff --git a/interfaces/WheelMenu/WheelMenu.py b/interfaces/WheelMenu/WheelMenu.py index 4f4fab2..41492e8 100644 --- a/interfaces/WheelMenu/WheelMenu.py +++ b/interfaces/WheelMenu/WheelMenu.py @@ -4,7 +4,7 @@ import gtk import pango import time import math -from ReflectionTexture import Texture_Reflection +from ui_elements.ReflectionTexture import Texture_Reflection from interfaces.MenuItem import MenuItem from InputQueue import InputQueue diff --git a/modules/dvd_player/dvd_player.py b/modules/dvd_player/dvd_player.py index dc404f7..b5f742e 100644 --- a/modules/dvd_player/dvd_player.py +++ b/modules/dvd_player/dvd_player.py @@ -1,6 +1,6 @@ import clutter from clutter import cluttergst -from VideoController import VideoController +from multimedia.VideoController import VideoController class Module: title = "DVD" diff --git a/modules/myth_tv_player/myth_tv_player.py b/modules/myth_tv_player/myth_tv_player.py index 2ce5d9d..d144e5a 100644 --- a/modules/myth_tv_player/myth_tv_player.py +++ b/modules/myth_tv_player/myth_tv_player.py @@ -9,8 +9,7 @@ from clutter import cluttergst from modules.myth_tv_player.MythBackendConn import MythBackendConnection from modules.myth_tv_player.tv_db_controller import tv_db_controller from SplashScr import SplashScr -#from Menu import Menu -from VideoController import VideoController +from multimedia.VideoController import VideoController class Module: title = "TV" diff --git a/modules/video_player/video_player.py b/modules/video_player/video_player.py index 5d052fa..25b1f9f 100644 --- a/modules/video_player/video_player.py +++ b/modules/video_player/video_player.py @@ -5,7 +5,7 @@ import pango import clutter import os from clutter import cluttergst -from VideoController import VideoController +from multimedia.VideoController import VideoController from modules.video_player.elements.cover_viewer import coverViewer from modules.video_player.elements.video_details import video_details from modules.video_player.elements.folder_menu import folderMenu @@ -248,6 +248,7 @@ class Module(): #if len(self.currentViewer.textureLibrary) == 0: if self.currentViewer is None: self.glossMgr.display_msg("Error: No videos", "There are no videos available in the library. This maybe caused by an empty library or a failed connection to the server.") + self.currentViewer = None self.stop() return @@ -321,6 +322,7 @@ class Module(): timeline_stop.start() def destroyPlugin(self, data): + self.stage.remove(self.backdrop) self.stage.remove(self.currentViewer) self.stage.remove(self.folderLibrary[self.folder_level]) self.stage.remove(self.video_details) diff --git a/VideoController.py b/multimedia/AudioController.py similarity index 100% rename from VideoController.py rename to multimedia/AudioController.py diff --git a/multimedia/MediaController.py b/multimedia/MediaController.py new file mode 100644 index 0000000..6a5d1fb --- /dev/null +++ b/multimedia/MediaController.py @@ -0,0 +1,42 @@ +import clutter +import gobject +from multimedia.MediaOSD import osd + +class MediaController(gobject.GObject): + + #Setup signals + __gsignals__ = { + "playing": ( + gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), + "stopped": ( + gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []) + } + + def __init__(self, glossMgr): + gobject.GObject.__init__(self) + self.stage = glossMgr.stage + #self.media_element = clutter.Media() + + self.use_osd = True + self.osd = osd(glossMgr) + + + #Skips the media forward the specified amount + def skip(self, amount): + if not self.media_element.get_can_seek(): + return + + #current_pos = self.video_texture.get_position() + current_pos = self.media_element.get_property("position") + new_pos = int(int(current_pos) + int(amount)) + + if new_pos >= self.media_element.get_duration(): + new_pos = self.media_element.get_duration()-1 + if new_pos <= 0: + new_pos = 1 + + # There's apparently a collision in the python bindings with the following method. Change this when its fixed in the bindings + #self.media_element.set_position(new_pos) + #Until then use: + self.media_element.set_property("position", int(new_pos)) + if self.use_osd: self.osd.shift_media(amount) \ No newline at end of file diff --git a/multimedia/MediaOSD.py b/multimedia/MediaOSD.py new file mode 100644 index 0000000..bd0b892 --- /dev/null +++ b/multimedia/MediaOSD.py @@ -0,0 +1,142 @@ +import clutter +import time + +class osd: + + def __init__(self, glossMgr): + self.glossMgr = glossMgr + self.stage = glossMgr.stage + self.timerRunning = False + self.setup_ui() + + self.bar_group = clutter.Group() + + + #self.background = clutter.Texture() + #self.background.set_pixbuf( gtk.gdk.pixbuf_new_from_file("ui/default/osd_bar3.png") ) + #self.background.set_opacity(255) + #self.background.set_width(stage.get_width()) + self.bar_group.add(self.background) + self.bar_group.show_all() + + def setup_ui(self): + self.background = self.glossMgr.themeMgr.get_texture("video_osd_bar", self.stage, None) + + def enter(self): + self.stage.add(self.bar_group) + self.bar_group.show() + + self.bar_group.set_position(0, self.stage.get_height()) + bar_position_y = int(self.stage.get_height() - self.background.get_height()) + + knots = (\ + (self.bar_group.get_x(), self.bar_group.get_y()),\ + (self.bar_group.get_x(), bar_position_y) \ + ) + + self.timeline = clutter.Timeline(25, 50) + self.alpha = clutter.Alpha(self.timeline, clutter.ramp_inc_func) + self.enter_behaviour_path = clutter.BehaviourPath(self.alpha, knots) + + self.enter_behaviour_path.apply(self.bar_group) + + self.timeline.start() + + self.timer = threading.Timer(3.0, self.exit) + self.timer.start() + + def exit(self): + + knots = (\ + (self.bar_group.get_x(), self.bar_group.get_y()),\ + (self.bar_group.get_x(), int(self.stage.get_height())) \ + ) + + self.timeline = clutter.Timeline(25, 50) + self.timeline.connect('completed', self.exit_end_event) + self.alpha = clutter.Alpha(self.timeline, clutter.ramp_inc_func) + self.exit_behaviour_path = clutter.BehaviourPath(self.alpha, knots) + + self.exit_behaviour_path.apply(self.bar_group) + self.timeline.start() + + def exit_end_event(self, data): + self.stage.remove(self.bar_group) + + #Is called when the media is skipped forwards or backwards + def shift_media(self, shift_amount): + #Firstly check whether the label is already there from last time + if self.timerRunning: + return + + shiftDistance = 100 + + self.shift_label = clutter.Label() + self.shift_label.set_font_name("Lucida Grande 60") + self.shift_label.set_opacity(0) + self.shift_label.set_color(clutter.color_parse('White')) + + #Set the string for the fast forward / rewind as well as the + if shift_amount > 0: + self.shift_label.set_text("+" + str(shift_amount) + "s >") + shift_label_x = int(self.stage.get_width() - self.shift_label.get_width() - shiftDistance) + direction = 1 + else: + self.shift_label.set_text("< " + str(shift_amount) + "s") + shift_label_x = int(0 + shiftDistance) + direction = -1 + + shift_label_y = int(self.stage.get_height() - self.shift_label.get_height()) + self.shift_label.set_position( shift_label_x, shift_label_y ) + incoming_label_knots = (\ + ( shift_label_x, shift_label_y ),\ + ( int(shift_label_x + (shiftDistance*direction)), shift_label_y )\ + ) + + self.incoming_text_timeline = clutter.Timeline(20, 60) + alpha = clutter.Alpha(self.incoming_text_timeline, clutter.ramp_inc_func) + self.behaviour1 = clutter.BehaviourPath(alpha, incoming_label_knots) + self.behaviour2 = clutter.BehaviourOpacity(opacity_start=0, opacity_end=120, alpha=alpha) + + self.behaviour1.apply(self.shift_label) + self.behaviour2.apply(self.shift_label) + self.stage.add(self.shift_label) + self.shift_label.show() + + #self.timer = threading.Timer(1.5, self.label_exit) + gobject.timeout_add(1500, self.label_exit) + self.timerRunning = True + #self.timer.start() + + self.incoming_text_timeline.start() + #print time.strftime("%H:%M:%S", time.gmtime(amount)) + + def label_exit(self): + self.timerRunning = False + #Check which way this label needs to go + if self.shift_label.get_text()[0] == "<": + end_x = int(self.shift_label.get_width() * -1) + else: + end_x = int(self.stage.get_width()) + + (starting_pos_x, starting_pos_y) = self.shift_label.get_abs_position() + outgoing_label_knots = (\ + ( starting_pos_x, starting_pos_y ),\ + ( end_x, starting_pos_y )\ + ) + + self.outgoing_text_timeline = clutter.Timeline(20, 60) + self.outgoing_text_timeline.connect('completed', self.removeLabel) + alpha = clutter.Alpha(self.outgoing_text_timeline, clutter.ramp_inc_func) + self.behaviour1 = clutter.BehaviourPath(alpha, outgoing_label_knots) + self.behaviour2 = clutter.BehaviourOpacity(opacity_start=self.shift_label.get_opacity() , opacity_end=0, alpha=alpha) + + self.behaviour1.apply(self.shift_label) + self.behaviour2.apply(self.shift_label) + + self.outgoing_text_timeline.start() + + return False + + def removeLabel(self, data): + self.stage.remove(self.shift_label) \ No newline at end of file diff --git a/multimedia/VideoController.py b/multimedia/VideoController.py new file mode 100644 index 0000000..ea74121 --- /dev/null +++ b/multimedia/VideoController.py @@ -0,0 +1,273 @@ +import sys, clutter, clutter.cluttergst, gst, pygst, gtk, pygtk, gobject +import threading +import os +from multimedia.MediaController import MediaController + +class VideoController(MediaController): + + def __init__(self, glossMgr): + MediaController.__init__(self, glossMgr) + self.overlay = None + self.blackdrop = None + + # Primary video texture & sink definition + self.video_texture = clutter.cluttergst.VideoTexture() + self.media_element = self.video_texture + self.video_sink = clutter.cluttergst.VideoSink(self.video_texture) + self.video_texture.connect('size-change', self.set_fullscreen) + self.video_texture.set_position(0,0) + + def on_key_press_event(self, event): + if event.keyval == clutter.keysyms.Left: + self.skip(-20) + if event.keyval == clutter.keysyms.Right: + self.skip(20) + + #self.osd.enter() + + def play_video(self, uri, player): + #self.customBin(uri) + #return + + self.player = player + self.video_texture.set_uri(uri) + + + #We need to connect to the message queue on the playbin to watch for any message (ie codec or file not found errors) + self.bin = self.video_texture.get_playbin() + #print "Queue: " + str(self.bin.get_property("queue_size")) + #print "Queue: " + str(self.bin.get_property("queue_threshold")) + #print "Queue: " + str(self.bin.get_property("queue_min_threshold")) + bus = self.video_texture.get_playbin().get_bus() + bus.add_signal_watch() + bus.connect('message', self.on_bus_message) + + #Now we can start the video + self.video_texture.set_playing(True) + #self.bin.set_state(gst.STATE_PAUSED) + self.bin.set_state(gst.STATE_PLAYING) + self.isPlaying = True + + #decodebin = self.bin.get_by_name("decodebin0") + #for element in decodebin.elements(): + # print "GST Element 1: " + str(element.get_name()) + #queue = decodebin.get_by_name("queue0") + #print queue.get_name() + #ypefind = decodebin.get_by_name("typefind") + + #decodebin.connect("pad-added", self.on_pad_added) + #vid = demuxer.get_by_name("video_00") + #self.queue1 = gst.element_factory_make("queue", "queue1") + #self.queue1.set_property("max-size-time", 50000) + #self.queue1.set_property("max-size-buffers", 0) + + #self.queue2 = gst.element_factory_make("queue", "queue2") + #self.bin.add(self.queue1) + #self.bin.add(self.queue2) + #decodebin.link(self.queue1) + #self.queue1.link(decodebin) + self.video_texture.set_opacity(255) + self.video_texture.set_position(0, 0) + self.video_texture.show() + #if self.video_texture.get_parent() is None: + self.stage.add(self.video_texture) + + self.emit("playing") + return self.video_texture + + #This handles any messages that are sent accross the playbin + #Currently this is checking two types of msgs: + # 1) A "codec not found" warning, at which stage playback is stopped + # 2) A Buffering msg. This pauses the video until the buffer is at 100% + def on_bus_message(self, bus, message): + t = message.type + #print "message type: " + str(t) + if t == gst.MESSAGE_ELEMENT: + #This occurs when an invalid codec is attempted to be played + #Need to insert some form of message to the user here + if self.player.glossMgr.debug: print "GStreamer Bus msg: " + message.structure.to_string() + struc = message.structure + if struc is None: + return + + if struc.get_name() == "missing-plugin": + print "GStreamer Error (missing-plugin): " + message.structure.to_string() + self.isPlaying = False + self.video_texture.set_playing(False) + self.player.stop_video() + elif t == gst.MESSAGE_BUFFERING: + percent = message.parse_buffering() + print "Buffer: " + str(percent) + if percent < 100: + self.bin.set_state(gst.STATE_PAUSED) + else: + if not self.bin.get_state() == gst.STATE_PLAYING: + self.bin.set_state(gst.STATE_PLAYING) + elif t == gst.MESSAGE_STATE_CHANGED: + prev, current, next = message.parse_state_changed() + #print "State Changed. Previous state: " + str(prev) + #print "State Changed. Current state: " + str(current) + elif t == gst.STREAM_ERROR: + #print "OHH NOES!" + print "GST Stream Error: " + message.structure.to_string() + else: + if not self.player is None: + if self.player.glossMgr.debug: print "GST Message: " + str(message) + + def stop_video(self): + if self.video_texture.get_playing(): + self.isPlaying = False + self.player.stop_video() + self.player = None + self.video_texture.set_playing(False) + + timeline = clutter.Timeline(15, 25) + timeline.connect('completed', self.end_video_event) + alpha = clutter.Alpha(timeline, clutter.ramp_inc_func) + self.behaviour = clutter.BehaviourOpacity(opacity_start=255, opacity_end=0, alpha=alpha) + self.behaviour.apply(self.video_texture) + if not (self.blackdrop is None): + self.behaviour.apply(self.blackdrop) + + timeline.start() + + def end_video_event(self, data): + self.stage.remove(self.video_texture) + if not (self.blackdrop is None): + self.stage.remove(self.blackdrop) + self.blackdrop = None + + def customBin(self, fd): + self.pipeline = gst.Pipeline("testPipeline") + #self.src = gst.element_factory_make("filesrc", "src") + #self.src.set_property("location", "test.mpg") + self.src = gst.element_factory_make("fdsrc", "src"); + self.src.set_property("fd", int(fd)) + self.demux = gst.element_factory_make("ffdemux_mpegts", "demux") + #self.demux = gst.element_factory_make("decodebin", "demux") + self.queue1 = gst.element_factory_make("queue", "queue1") + self.queue1.set_property("max-size-time", 500000) + self.queue1.set_property("max-size-buffers", 0) + self.queue2 = gst.element_factory_make("queue", "queue2") + #self.deinterlace = gst.element_factory_make("ffdeinterlace", "deinterlace") + self.vdecode = gst.element_factory_make("mpeg2dec", "vdecode") + self.adecode = gst.element_factory_make("mad", "adecode") + self.vsink = gst.element_factory_make("xvimagesink", "vsink") + #self.vsink = self.video_sink #cluttergst.VideoSink(self.video_texture) + self.asink = gst.element_factory_make("alsasink", "asink") + + bus = self.pipeline.get_bus() + bus.add_signal_watch() + bus.connect('message', self.on_bus_message) + + # add elements to the pipeline + self.pipeline.add(self.src) + self.pipeline.add(self.demux) + self.pipeline.add(self.queue1) + self.pipeline.add(self.queue2) + self.pipeline.add(self.vdecode) + #self.pipeline.add(self.deinterlace) + self.pipeline.add(self.adecode) + self.pipeline.add(self.vsink) + self.pipeline.add(self.asink) + + bus = self.pipeline.get_bus() + gst.Bus.add_signal_watch (bus) + + # we can't link demux until the audio and video pads are added + # we need to listen for "pad-added" signals + self.demux.connect("pad-added", self.on_pad_added) + + # link all elements apart from demux + print "linking..." + gst.element_link_many(self.src, self.demux) + gst.element_link_many(self.queue1, self.vdecode, self.vsink) #self.deinterlace, self.vsink) + gst.element_link_many(self.queue2, self.adecode, self.asink) + + self.pipeline.set_state(gst.STATE_PLAYING) + + def on_pad_added(self, element, src_pad): + caps = src_pad.get_caps() + name = caps[0].get_name() + # link demux to vdecode when video/mpeg pad added to demux + if name == "video/mpeg": + sink_pad = self.queue1.get_pad("sink") + elif name == "audio/mpeg": + sink_pad = self.queue2.get_pad("sink") + else: + return + if not sink_pad.is_linked(): + src_pad.link(sink_pad) + + def set_fullscreen(self, texture, width, height): + texture.set_property("sync-size", False) + texture.set_position(0, 0) + ratio = float(self.stage.get_width()) / float(width) + xy_ratio = float(width) / float(height) + #print "Width: " + str(width) + #print "Height: " + str(height) + #print "XY Ratio: " + str(ratio) + + width = int(self.stage.get_width()) + height = int ((height * ratio)) + #print "New Width: " + str(width) + #print "New Height: " + str(height) + + if height < self.stage.get_height(): + #Create a black backdrop that the video can sit on + self.blackdrop = clutter.Rectangle() + self.blackdrop.set_color(clutter.color_parse('Black')) + self.blackdrop.set_size(self.stage.get_width(), self.stage.get_height()) + self.stage.remove(self.video_texture) + self.stage.add(self.blackdrop) + self.stage.add(self.video_texture) + self.blackdrop.show() + + #And move the video into the vertical center + pos_y = int((self.stage.get_height() - height) / 2) + self.video_texture.set_position(0, pos_y) + + texture.set_size(width, height) + + def pause_video(self, use_backdrop): + if use_backdrop: + #Use the overlay to go over show + if self.overlay == None: + self.overlay = clutter.Rectangle() + self.overlay.set_color(clutter.color_parse('Black')) + self.overlay.set_size(self.stage.get_width(), self.stage.get_height()) + self.stage.add(self.overlay) + self.overlay.set_opacity(0) + self.overlay.show() + + + #self.video_texture.lower_actor(self.overlay) + #self.overlay.raise_actor(self.video_texture) + #Fade the overlay in + timeline_overlay = clutter.Timeline(10,30) + alpha = clutter.Alpha(timeline_overlay, clutter.ramp_inc_func) + self.overlay_behaviour = clutter.BehaviourOpacity(opacity_start=0, opacity_end=200, alpha=alpha) + self.overlay_behaviour.apply(self.overlay) + #video_behaviour.apply(self.video_texture) + timeline_overlay.start() + + #Pause the video + self.video_texture.set_playing(False) + + def unpause_video(self): + if not self.overlay is None: + #Fade the backdrop in + timeline_unpause = clutter.Timeline(10,30) + alpha = clutter.Alpha(timeline_unpause, clutter.ramp_inc_func) + self.overlay_behaviour = clutter.BehaviourOpacity(opacity_start=200, opacity_end=0, alpha=alpha) + self.overlay_behaviour.apply(self.overlay) + #video_behaviour.apply(self.video_texture) + timeline_unpause.start() + + #Resume the video + self.video_texture.set_playing(True) + + + + + diff --git a/multimedia/__init__.py b/multimedia/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/themeMgr.py b/themeMgr.py index bddb2c1..999f3b7 100644 --- a/themeMgr.py +++ b/themeMgr.py @@ -10,7 +10,7 @@ class ThemeMgr: defaultTheme = "default" currentTheme = "default" currentTheme = "Pear" - #currentTheme = "Mich" + currentTheme = "Mich" #currentTheme = "Gloxygen" def __init__(self, glossMgr): diff --git a/themes/Mich/music.xml b/themes/Mich/music.xml index 6f844e6..23c13ab 100644 --- a/themes/Mich/music.xml +++ b/themes/Mich/music.xml @@ -10,4 +10,8 @@ 0 + + + music/default_cover.png + diff --git a/themes/Mich/music/default_cover.png b/themes/Mich/music/default_cover.png new file mode 100755 index 0000000..1de9c14 Binary files /dev/null and b/themes/Mich/music/default_cover.png differ diff --git a/themes/default/music.xml b/themes/default/music.xml index de91a05..7fb0ed9 100644 --- a/themes/default/music.xml +++ b/themes/default/music.xml @@ -10,4 +10,8 @@ 0 + + + music/default_cover.png + diff --git a/themes/default/music/default_cover.png b/themes/default/music/default_cover.png new file mode 100755 index 0000000..1de9c14 Binary files /dev/null and b/themes/default/music/default_cover.png differ