diff --git a/bin/vidref b/bin/vidref --- a/bin/vidref +++ b/bin/vidref @@ -8,7 +8,8 @@ gobject.threads_init() import gtk import sys, logging, optparse sys.path.append(".") -from light9.vidref.main import Main +from light9.vidref.main import Gui + # find replay dirs correctly. show multiple # replays. trash. reorder/pin. dump takes that are too short; they're @@ -26,6 +27,8 @@ log.handlers[0].setLevel(logging.DEBUG i logging.getLogger("restkit.client").setLevel(logging.WARN) -start=Main() + +start=Gui() + reactor.run() diff --git a/light9/vidref/main.py b/light9/vidref/main.py --- a/light9/vidref/main.py +++ b/light9/vidref/main.py @@ -6,93 +6,15 @@ dvcam test gst-launch dv1394src ! dvdemux name=d ! dvdec ! ffmpegcolorspace ! hqdn3d ! xvimagesink """ -import pygst -pygst.require("0.10") -import gst, gobject, time, logging, os, traceback +import gobject, logging, traceback import gtk from twisted.python.util import sibpath -import Image -from threading import Thread -from Queue import Queue -from light9.vidref.replay import ReplayViews, songDir, takeDir, framerate +from light9.vidref.replay import ReplayViews, framerate from light9.vidref.musictime import MusicTime - +from light9.vidref.videorecorder import Pipeline log = logging.getLogger() - -class VideoRecordSink(gst.Element): - _sinkpadtemplate = gst.PadTemplate ("sinkpadtemplate", - gst.PAD_SINK, - gst.PAD_ALWAYS, - gst.caps_new_any()) - - def __init__(self, musicTime, updateRecordingTo): - gst.Element.__init__(self) - self.updateRecordingTo = updateRecordingTo - self.sinkpad = gst.Pad(self._sinkpadtemplate, "sink") - self.add_pad(self.sinkpad) - self.sinkpad.set_chain_function(self.chainfunc) - self.lastTime = 0 - - self.musicTime = musicTime - - self.imagesToSave = Queue() - self.startBackgroundImageSaver(self.imagesToSave) - - def startBackgroundImageSaver(self, imagesToSave): - """do image saves in another thread to not block gst""" - def imageSaver(): - while True: - args = imagesToSave.get() - self.saveImg(*args) - imagesToSave.task_done() - - t = Thread(target=imageSaver) - t.setDaemon(True) - t.start() - def chainfunc(self, pad, buffer): - position = self.musicTime.getLatest() - - if not position['song']: - print "no song" # todo: this prints too much when the player has no song - return gst.FLOW_OK - - try: - cap = buffer.caps[0] - #print "cap", (cap['width'], cap['height']) - img = Image.fromstring('RGB', (cap['width'], cap['height']), - buffer.data) - self.imagesToSave.put((position, img, buffer.timestamp)) - except: - traceback.print_exc() - - return gst.FLOW_OK - - def saveImg(self, position, img, bufferTimestamp): - t1 = time.time() - outDir = takeDir(songDir(position['song']), position['started']) - outFilename = "%s/%08.03f.jpg" % (outDir, position['t']) - if os.path.exists(outFilename): # we're paused on one time - return - - try: - os.makedirs(outDir) - except OSError: - pass - - img.save(outFilename) - - now = time.time() - log.info("wrote %s delay of %.2fms, took %.2fms", - outFilename, - (now - self.lastTime) * 1000, - (now - t1) * 1000) - self.updateRecordingTo(outDir) - self.lastTime = now - -gobject.type_register(VideoRecordSink) - -class Main(object): +class Gui(object): def __init__(self): wtree = gtk.Builder() wtree.add_from_file(sibpath(__file__, "vidref.glade")) @@ -116,13 +38,16 @@ class Main(object): mainwin.show_all() vid3 = wtree.get_object("vid3") - self.liveVideoXid = vid3.window.xid + + self.pipeline = Pipeline(vid3.window.xid, self.musicTime, + self.recordingTo) + vid3.props.width_request = 360 vid3.props.height_request = 220 wtree.get_object("frame1").props.height_request = 220 - self.setInput('v4l') # auto seems to not search for dv + self.pipeline.setInput('v4l') # auto seems to not search for dv gobject.timeout_add(1000 // framerate, self.updateLoop) @@ -148,58 +73,9 @@ class Main(object): def getInputs(self): return ['auto', 'dv', 'video0'] - def setInput(self, name): - sourcePipe = { - "auto": "autovideosrc name=src1", - "testpattern" : "videotestsrc name=src1", - "dv": "dv1394src name=src1 ! dvdemux ! dvdec", - "v4l": "v4l2src device=/dev/video0 name=src1" , - }[name] - - cam = (sourcePipe + " ! " - "videorate ! video/x-raw-yuv,framerate=%s/1 ! " - "videoscale ! video/x-raw-yuv,width=640,height=480;video/x-raw-rgb,width=320,height=240 ! " - "videocrop left=160 top=180 right=120 bottom=80 ! " - "queue name=vid" % framerate) - - self.pipeline = gst.parse_launch(cam) - - def makeElem(t, n=None): - e = gst.element_factory_make(t, n) - self.pipeline.add(e) - return e - - sink = makeElem("xvimagesink") - def setRec(t): - # if you're selecting the text while gtk is updating it, - # you can get a crash in xcb_io - if getattr(self, '_lastRecText', None) == t: - return - with gtk.gdk.lock: - self.recordingTo.set_text(t) - self._lastRecText = t - recSink = VideoRecordSink(self.musicTime, setRec) - self.pipeline.add(recSink) - - tee = makeElem("tee") - - caps = makeElem("capsfilter") - caps.set_property('caps', gst.caps_from_string('video/x-raw-rgb')) - - gst.element_link_many(self.pipeline.get_by_name("vid"), tee, sink) - gst.element_link_many(tee, makeElem("ffmpegcolorspace"), caps, recSink) - sink.set_xwindow_id(self.liveVideoXid) - self.pipeline.set_state(gst.STATE_PLAYING) def on_liveVideoEnabled_toggled(self, widget): - if widget.get_active(): - self.pipeline.set_state(gst.STATE_PLAYING) - # this is an attempt to bring the dv1394 source back, but - # it doesn't work right. - self.pipeline.get_by_name("src1").seek_simple( - gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, 0 * gst.SECOND) - else: - self.pipeline.set_state(gst.STATE_READY) + self.pipeline.setLiveVideo(widget.get_active()) def on_liveFrameRate_value_changed(self, widget): print widget.get_value() diff --git a/light9/vidref/videorecorder.py b/light9/vidref/videorecorder.py new file mode 100644 --- /dev/null +++ b/light9/vidref/videorecorder.py @@ -0,0 +1,144 @@ +import pygst +pygst.require("0.10") +import gst, gobject, time, logging, os, traceback +import gtk +import Image +from threading import Thread +from Queue import Queue +from light9.vidref.replay import framerate, songDir, takeDir +log = logging.getLogger() + +class Pipeline(object): + def __init__(self, liveVideoXid, musicTime, recordingTo): + self.musicTime = musicTime + self.liveVideoXid = liveVideoXid + self.recordingTo = recordingTo + + def setInput(self, name): + sourcePipe = { + "auto": "autovideosrc name=src1", + "testpattern" : "videotestsrc name=src1", + "dv": "dv1394src name=src1 ! dvdemux ! dvdec", + "v4l": "v4l2src device=/dev/video0 name=src1" , + }[name] + + cam = (sourcePipe + " ! " + "videorate ! video/x-raw-yuv,framerate=%s/1 ! " + "videoscale ! video/x-raw-yuv,width=640,height=480;video/x-raw-rgb,width=320,height=240 ! " + "videocrop left=160 top=180 right=120 bottom=80 ! " + "queue name=vid" % framerate) + + print cam + self.pipeline = gst.parse_launch(cam) + + def makeElem(t, n=None): + e = gst.element_factory_make(t, n) + self.pipeline.add(e) + return e + + sink = makeElem("xvimagesink") + def setRec(t): + # if you're selecting the text while gtk is updating it, + # you can get a crash in xcb_io + if getattr(self, '_lastRecText', None) == t: + return + with gtk.gdk.lock: + self.recordingTo.set_text(t) + self._lastRecText = t + recSink = VideoRecordSink(self.musicTime, setRec) + self.pipeline.add(recSink) + + tee = makeElem("tee") + + caps = makeElem("capsfilter") + caps.set_property('caps', gst.caps_from_string('video/x-raw-rgb')) + + gst.element_link_many(self.pipeline.get_by_name("vid"), tee, sink) + gst.element_link_many(tee, makeElem("ffmpegcolorspace"), caps, recSink) + sink.set_xwindow_id(self.liveVideoXid) + self.pipeline.set_state(gst.STATE_PLAYING) + + def setLiveVideo(self, on): + + if on: + self.pipeline.set_state(gst.STATE_PLAYING) + # this is an attempt to bring the dv1394 source back, but + # it doesn't work right. + self.pipeline.get_by_name("src1").seek_simple( + gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, 0 * gst.SECOND) + else: + self.pipeline.set_state(gst.STATE_READY) + + +class VideoRecordSink(gst.Element): + _sinkpadtemplate = gst.PadTemplate ("sinkpadtemplate", + gst.PAD_SINK, + gst.PAD_ALWAYS, + gst.caps_new_any()) + + def __init__(self, musicTime, updateRecordingTo): + gst.Element.__init__(self) + self.updateRecordingTo = updateRecordingTo + self.sinkpad = gst.Pad(self._sinkpadtemplate, "sink") + self.add_pad(self.sinkpad) + self.sinkpad.set_chain_function(self.chainfunc) + self.lastTime = 0 + + self.musicTime = musicTime + + self.imagesToSave = Queue() + self.startBackgroundImageSaver(self.imagesToSave) + + def startBackgroundImageSaver(self, imagesToSave): + """do image saves in another thread to not block gst""" + def imageSaver(): + while True: + args = imagesToSave.get() + self.saveImg(*args) + imagesToSave.task_done() + + t = Thread(target=imageSaver) + t.setDaemon(True) + t.start() + + def chainfunc(self, pad, buffer): + position = self.musicTime.getLatest() + + if not position['song']: + print "no song" # todo: this prints too much when the player has no song + return gst.FLOW_OK + + try: + cap = buffer.caps[0] + #print "cap", (cap['width'], cap['height']) + img = Image.fromstring('RGB', (cap['width'], cap['height']), + buffer.data) + self.imagesToSave.put((position, img, buffer.timestamp)) + except: + traceback.print_exc() + + return gst.FLOW_OK + + def saveImg(self, position, img, bufferTimestamp): + t1 = time.time() + outDir = takeDir(songDir(position['song']), position['started']) + outFilename = "%s/%08.03f.jpg" % (outDir, position['t']) + if os.path.exists(outFilename): # we're paused on one time + return + + try: + os.makedirs(outDir) + except OSError: + pass + + img.save(outFilename) + + now = time.time() + log.info("wrote %s delay of %.2fms, took %.2fms", + outFilename, + (now - self.lastTime) * 1000, + (now - t1) * 1000) + self.updateRecordingTo(outDir) + self.lastTime = now + +gobject.type_register(VideoRecordSink)