From 34f2c438324458bc1d97334aabf84e9275880835 Mon Sep 17 00:00:00 2001 From: noisymime Date: Wed, 29 Aug 2007 13:35:13 +0000 Subject: [PATCH] The 'Get the lead out' commit. Addition of some introduction code that will become the OSD Starting working on the cover browser in video module. --- TVPlayer.py | 60 +++------------- TVPlayer.pyc | Bin 4766 -> 4578 bytes VideoController.py | 133 ++++++++++++++++++++++++++++++++--- VideoPlayer.py | 168 +++++++++++++++++---------------------------- VideoPlayer.pyc | Bin 7725 -> 7198 bytes 5 files changed, 193 insertions(+), 168 deletions(-) diff --git a/TVPlayer.py b/TVPlayer.py index 9e87ce4..206a0b2 100644 --- a/TVPlayer.py +++ b/TVPlayer.py @@ -7,7 +7,7 @@ import clutter from clutter import cluttergst from myth.MythBackendConn import MythBackendConnection from Menu import Menu -from VideoController import VideoController +from VideoController import osd class TVPlayer: @@ -16,17 +16,8 @@ class TVPlayer: self.video.connect('size-change', self.video_size_change) self.stage = stage self.dbMgr = dbMgr - #audio.set_filename('cast1.avi') - #audio.show() - - #test = cluttercairo.Cairo() - #audio = cluttergst.Audio() - #audio.set_filename('test.mp3') - - #stage.add(audio) - #audio.set_playing(True) - - #self.db_conn = mythVideoDB() + self.isRunning = False + def begin(self, menuMgr): self.menuMgr = menuMgr @@ -35,20 +26,15 @@ class TVPlayer: (server, port) = self.dbMgr.get_backend_server() self.myConn = MythBackendConnection(self, server, port) self.myConn.start() - #vid = VideoController() + self.osd = osd(self.stage) #vid.begin(self.stage) - #self.begin_playback(self.buffer_file_reader.fileno()) + + self.isRunning = True def begin_playback(self, fd): - #self.video.set_filename("test.mpg") - #print "File Descriptor: " + str(fd) - #self.buffer_file = open("test.mpg","r") - #fd = self.buffer_file.fileno() - #print os.read(fd, 100) stage = self.menuMgr.get_stage() self.menuMgr.get_selector_bar().set_spinner(False) self.video.set_uri("fd://"+str(fd)) - #self.video.set_property("fullscreen", True) self.video.set_opacity(0) self.video.show() @@ -59,7 +45,6 @@ class TVPlayer: self.stage.add(self.video) self.video.set_playing(True) - self.video.set_size(400, 300) timeline.start() #return None @@ -73,39 +58,8 @@ class TVPlayer: height = int (width / xy_ratio) height = 768 - print "Width: " + str(width) - print "Height: " + str(height) - - self.video.set_size(width, height) - - - """ - playbin = self.video.get_playbin() .get_by_name("decodebin0") - for element in playbin.elements(): - print element.get_name() - - sink = playbin.elements().next() - deinterlace = gst.element_factory_make("ffdeinterlace", "deinterlace") - playbin.add(deinterlace) - #gst.element_link_many(sink, deinterlace) - self.video.set_size(stage.get_width(), stage.get_height()) - - #self.video.set_height(stage.get_height()) - - - - self.video_texture = clutter.Texture() - self.pipeline = gst.Pipeline("mypipeline") - self.pbin = gst.element_factory_make("playbin", "pbin"); - self.sink = cluttergst.video_sink_new(self.video_texture) - self.pbin.set_property("uri", "fd://"+str(fd)) - - # add elements to the pipeline - self.pipeline.add(self.pbin) - self.pipeline.set_state(gst.STATE_PLAYING) - """ def stop(self): if self.video.get_playing(): self.video.set_playing(False) @@ -124,6 +78,8 @@ class TVPlayer: self.stage.remove(self.video) def on_key_press_event (self, stage, event): + if self.isRunning: + self.osd.on_key_press_event(event) #print event.hardware_keycode """if event.keyval == clutter.keysyms.p: diff --git a/TVPlayer.pyc b/TVPlayer.pyc index 4b23dbba262f16b09381bd5ee117b3ff1826ce26..71fa8b482b948c8b509b6950c5e4a3889c72b42d 100644 GIT binary patch delta 997 zcmX|9OK1~e5T4m>vX5l5Nt(WEpO&UAc=1)OV5Fr7rM3O5(H619Y$yvg(e8>+3L+H+ zp@%XjFU5m5@l@~i;>C+6EnY>B9`qszf-`@L{FyKF&(1&d&0}xm>#()|%X43cpLVUC z2^Usez#s{M3xNes0JZ^^S5gqz5HLFbP3hf(8bUI}_*P0&xP7gLn6`>}^k*nNfA=GWZz)hOJo6(tJSwLz7t|DgUto!31b{9Uoc(v{hln0cX7=zl9cD_$WhIo%M2!nb>y(tB zgsTpr=4i@a^lT^X161I|G8Ul*P^&7fhB=!0@tbJUr0#gh(2VelG<^JQn|Uo zO))mBZ?5RPJh&>LnUp!#rG&7)PY1aUrh1qgNS5S#AN|cu48*d3qOek&X8xWMp0ymPF zhvemBc{D_2F zAAHzU?{;czN@Ltsm>pua%5F2Z&e-G>8bN03%+~(8^v%~3$rxj7+_jzJP_f{Zi}}1$ zh7{l6{qTbK!7gv)DyY&VEKAqYLCj@5#%sC8a}qvRRn`#xiwXEGjfZWRWz3bCdx()5m90yRuLhV~ zLyEY6P!MM1)^7J69_SuNFl(`Z6+!Scgc--n2roP1SW)tmR!>Galg$eUUdm5XadMF^ zL8a^y56qb3~i!25|ZGpFy1}dTg1F zTb0;4XLVUjNrfkB=V75@tqIrmX8R051cx1WX4dO7;GNpU z>tR<-^9Im^&0(t@qb41Xj0b`R%EQ6qh0Nc;@+Cj-!ye4#B?#9P?a6CQ4o%OP?9q)y!p{li;=oK^@ zjOVBwv}hdswoo0LACA8_tqb@Ve%lm8ERn7#J{e`9;rj%H$C%^P;QN7c&0& z;11#Br@KnoMd8@{V}`M2>_ry~e%$QXx7WdIKe(f^;b8iI5L?(^%IDpP#OJeaA+xaP zdS=I{i5i(nO~C%$*UuRqb-!51pt?+{>(@EgVK%bGH$aUJwuRAa z`EyO9XwfWKbPHAwK|jF-n2TPDBs5*C*E+SR7SO`*Hu_QV-@1$NI93ND+AC9~NAN4w zzZbBeKR(j(9G#U5+j~_W=50L%jH~;_u diff --git a/VideoController.py b/VideoController.py index e289b34..eef8b07 100644 --- a/VideoController.py +++ b/VideoController.py @@ -1,4 +1,5 @@ -import sys, clutter, clutter.cluttergst, gst +import sys, clutter, clutter.cluttergst, gst, pygst, gtk, pygtk +import threading class VideoController: @@ -6,21 +7,133 @@ class VideoController: def __init__(self): # Primary video texture & sink definition self.video_texture = clutter.cluttergst.VideoTexture() - video_sink = clutter.cluttergst.VideoSink(self.video_texture) + self.video_sink = clutter.cluttergst.VideoSink(self.video_texture) self.video_texture.set_position(0,0) - - # Gst pipeline def + self.pipeline = gst.Pipeline() + #self.tester() + """ src = gst.element_factory_make ("videotestsrc") - colorspace = gst.element_factory_make ("ffmpegcolorspace") - bus = self.pipeline.get_bus() - gst.Bus.add_signal_watch (bus) - gst.Bin.add (self.pipeline, src, colorspace, video_sink) - gst.element_link_many (src, colorspace, video_sink) + + gst.Bin.add (self.pipeline, filesrc, colorspace, deinterlace, video_sink) + gst.element_link_many (filesrc, colorspace, deinterlace, video_sink) + """ def begin(self, stage): + self.osd = osd(stage) + self.osd.enter() + + return None stage.add(self.video_texture) self.video_texture.show() self.pipeline.set_state(gst.STATE_PLAYING) - + + def tester(self): + self.src = gst.element_factory_make("filesrc", "src"); + self.src.set_property("location", "/home/josh/clutter/toys/gloss/test.mpg") + self.demux = gst.element_factory_make("ffdemux_mpegts", "demux") + self.queue1 = gst.element_factory_make("queue", "queue1") + 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") + + + # 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 + 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) + + 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) + +class osd: + + def __init__(self, stage): + self.stage = stage + + self.bar_group = clutter.Group() + + self.background = clutter.Texture() + self.background.set_pixbuf( gtk.gdk.pixbuf_new_from_file("ui/osd_bar.png") ) + self.background.set_opacity(120) + self.background.set_width(stage.get_width()) + self.bar_group.add(self.background) + self.bar_group.show_all() + + 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) + + def on_key_press_event(self, event): + self.enter() + + diff --git a/VideoPlayer.py b/VideoPlayer.py index 39181b1..86f72d8 100644 --- a/VideoPlayer.py +++ b/VideoPlayer.py @@ -11,7 +11,6 @@ class VideoPlayer(): def __init__(self, stage, dbMgr): self.stage = stage self.cover_viewer = coverViewer(self.stage) - self.videoLibrary = [] self.is_playing = False @@ -25,8 +24,8 @@ class VideoPlayer(): for record in results: tempVideo = videoItem() tempVideo.importFromMythObject(record) - self.videoLibrary.append(tempVideo) - self.cover_viewer.add_image(tempVideo.getCoverfile()) + self.cover_viewer.add_video(tempVideo) + #self.cover_viewer.add_image(tempVideo.getCoverfile()) #dbMgr.close_db() ################################################################################ @@ -38,6 +37,16 @@ class VideoPlayer(): self.pause() if event.keyval == clutter.keysyms.q: clutter.main_quit() + + up = clutter.keysyms.Up + down = clutter.keysyms.Down + left = clutter.keysyms.Left + right= clutter.keysyms.Right + if (event.keyval == up) or (event.keyval == down) or (event.keyval == left) or (event.keyval == right): + self.cover_viewer.on_cursor_press_event(event) + + + def begin(self, MenuMgr): @@ -144,19 +153,24 @@ class videoItem(): return self.coverfile class coverViewer(clutter.Group): + scaleFactor = 1.4 def __init__(self, stage): clutter.Group.__init__(self) self.stage = stage - self.covers = [] + self.videoLibrary = [] self.num_covers = 0 self.cover_size = 200 #A cover will be cover_size * cover_size (X * Y) - self.cover_gap = 10 + self.cover_gap = 1 - self.num_rows = 2 - self.num_columns = int(self.stage.get_width() / self.cover_size) + self.num_rows = 3 + self.num_columns = 4 #int(self.stage.get_width() / self.cover_size) - def add_image(self, imagePath): + self.currentSelection = 0 + + def add_video(self, video): + self.videoLibrary.append(video) + imagePath = video.getCoverfile() tempTexture = clutter.Texture() pixbuf = gtk.gdk.pixbuf_new_from_file(imagePath) tempTexture.set_pixbuf(pixbuf) @@ -175,104 +189,46 @@ class coverViewer(clutter.Group): self.add(tempTexture) self.num_covers = self.num_covers +1 + def select_item(self, incomingItem, outgoingItem): + outgoingTexture = self.get_nth_child(outgoingItem) + incomingTexture = self.get_nth_child(incomingItem) + + #Make sure the new texture is on the top + #incomingTexture.raise_top() + + self.timeline = clutter.Timeline(20,80) + alpha = clutter.Alpha(self.timeline, clutter.ramp_inc_func) + behaviourNew = clutter.BehaviourScale(alpha, 1, self.scaleFactor, clutter.GRAVITY_CENTER) + behaviourOld = clutter.BehaviourScale(alpha, self.scaleFactor, 1, clutter.GRAVITY_CENTER) + + behaviourNew.apply(incomingTexture) + behaviourOld.apply(outgoingTexture) + + self.currentSelection = incomingItem + + self.timeline.start() + + def on_cursor_press_event(self, event): + newItem = None + if event.keyval == clutter.keysyms.Left: + newItem = self.currentSelection - self.num_rows + if event.keyval == clutter.keysyms.Right: + newItem = self.currentSelection + self.num_rows + if event.keyval == clutter.keysyms.Down: + #Check if we're already on the bottom row + if not ((self.currentSelection % self.num_rows) == (self.num_rows-1)): + newItem = self.currentSelection + 1 + if event.keyval == clutter.keysyms.Up: + #Check if we're already on the top row + if not (self.currentSelection % self.num_rows) == 0: + newItem = self.currentSelection - 1 + + if (newItem < 0) and (not newItem == None): + newItem = self.currentSelection + #If there is movement, make the scale happen + if not newItem == None: + self.select_item(newItem, self.currentSelection) + - #Redo positioning on all textures to add new one :( - """for i in range(self.num_covers): - tempTexture = self.get_nth_child(i) - x = (self.cover_gap + self.cover_size) * i - y = (i % self.num_rows) * self.cover_size - tempTexture.set_position(x, y)""" - -class customBin: - def __init__(self): - self.gstInit() - - def gstInit(self): - self.texture = gobject.new(cluttergst.VideoTexture, tiled=False) # , "sync-size=False" - - #self.texture = clutter.Texture() #cluttergst.VideoTexture() - self.texture.set_property("sync-size", False) - - # declare our pipeline and GST elements - self.pipeline = gst.Pipeline("mypipeline") - """ - - self.src = gst.element_factory_make("filesrc", "src"); - self.src.set_property("location", "test.mpg") - self.demux = gst.element_factory_make("ffdemux_mpegts", "demux") - self.queue1 = gst.element_factory_make("queue", "queue1") - 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 = cluttergst.VideoSink(self.video_texture) - self.asink = gst.element_factory_make("alsasink", "asink") - """ - self.src = gst.element_factory_make("videotestsrc", "src") - #self.warp = gst.element_factory_make ("warptv", "warp") - self.colorspace = gst.element_factory_make ("ffmpegcolorspace", "color") - self.pipeline.add(self.colorspace) - #self.demux = gst.element_factory_make("ffdemux_mpegts", "demux") - self.sink = cluttergst.VideoSink (self.texture) - #self.sink = gst.element_factory_make("autovideosink", "vsink") - self.sink.set_property("qos", True) - self.sink.set_property("sync", True) - - # add elements to the pipeline - self.pipeline.add(self.src) - #self.pipeline.add(self.warp) - #self.pipeline.add(self.demux) - #self.pipeline.add(self.colorspace) - self.pipeline.add(self.sink) - - """ - 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) - - - # 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) - """ - self.texture.set_width(200) - self.texture.set_height(200) - #self.pipeline.add_signal_watch() - #self.pipeline.add_many(self.pipeline, self.src, self.warp, self.colorspace, self.sink) - gst.element_link_many(self.src, self.sink) #self.warp, self.colorspace, - # link all elements apart from demux - #gst.element_link_many(self.src, self.demux) - #gst.element_link_many(self.queue1, self.vsink) #self.vdecode, self.deinterlace, - #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 startPlayback(self): - # start playback - self.pipeline.set_state(gst.STATE_PLAYING) - - def get_texture(self): - return self.texture - diff --git a/VideoPlayer.pyc b/VideoPlayer.pyc index c7527b592e204ef5c55ca16e38c3698ac187e0de..d273a5778d7136e0a120fda692f58b2aa63d5e8a 100644 GIT binary patch delta 2862 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 GFaIBTj4Lq!