Changeset - cfd5d5be1b50
[Not reviewed]
default
0 4 0
drewp@bigasterisk.com - 15 years ago 2010-06-14 06:21:09
drewp@bigasterisk.com
vidref complete panels on each replay. replays load and delete pretty well
Ignore-this: 3c0eb5c77caf08b16ab557fae7b46418
4 files changed with 196 insertions and 52 deletions:
0 comments (0 inline, 0 general)
bin/vidref
Show inline comments
 
@@ -2,16 +2,23 @@
 
import gobject
 
gobject.threads_init()
 
import gtk
 
import sys, logging
 
import sys, logging, optparse
 
sys.path.append(".")
 
from light9.vidref.main import Main
 

	
 
 # find replay dirs correctly. show multiple replays. trash. reorder/pin.
 
                
 
 # find replay dirs correctly. show multiple
 
 # replays. trash. reorder/pin. dump takes that are too short; they're
 
 # just from seeking
 

	
 
parser = optparse.OptionParser()
 
parser.add_option("-v", "--verbose", action="store_true",
 
                  help="logging.DEBUG")
 
(options, args) = parser.parse_args()
 

	
 

	
 
logging.basicConfig()
 
log = logging.getLogger()
 
log.setLevel(logging.DEBUG)
 
log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
 
logging.getLogger("restkit.client").setLevel(logging.WARN)
 

	
 

	
light9/vidref/main.py
Show inline comments
 
@@ -9,14 +9,13 @@ gst-launch dv1394src ! dvdemux name=d ! 
 
import pygst
 
pygst.require("0.10")
 
import gst, gobject, time, jsonlib, restkit, logging, os, traceback
 
from decimal import Decimal
 
import gtk
 
from twisted.python.util import sibpath
 
import Image
 
from threading import Thread
 
from Queue import Queue
 
from light9 import networking
 
from light9.vidref.replay import ReplayViews, songDir, takeDir
 
from light9.vidref.replay import ReplayViews, songDir, takeDir, framerate
 

	
 
log = logging.getLogger()
 

	
 
@@ -26,7 +25,13 @@ class MusicTime(object):
 
    upon request, adjusted to be more precise with the system clock
 
    """
 
    def __init__(self, period=.2):
 
        """period is the seconds between http time requests."""
 
        """period is the seconds between http time requests.
 

	
 
        The choice of period doesn't need to be tied to framerate,
 
        it's more the size of the error you can tolerate (since we
 
        make up times between the samples, and we'll just run off the
 
        end of a song)
 
        """
 
        self.period = period
 
        self.musicResource = restkit.Resource(networking.musicUrl())
 
        t = Thread(target=self._timeUpdate)
 
@@ -48,7 +53,13 @@ class MusicTime(object):
 
        while True:
 
            position = jsonlib.loads(self.musicResource.get("time").body,
 
                                     use_float=True)
 

	
 
            # this is meant to be the time when the server gave me its
 
            # report, and I don't know if that's closer to the
 
            # beginning of my request or the end of it (or some
 
            # fraction of the way through)
 
            self.positionFetchTime = time.time()
 
            
 
            self.position = position
 
            time.sleep(self.period)
 
        
 
@@ -58,15 +69,14 @@ class VideoRecordSink(gst.Element):
 
                                        gst.PAD_ALWAYS,
 
                                        gst.caps_new_any())
 

	
 
    def __init__(self, replay):
 
    def __init__(self, musicTime):
 
        gst.Element.__init__(self)
 
        self.replay = replay
 
        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.musicTime = musicTime
 

	
 
        self.imagesToSave = Queue()
 
        self.startBackgroundImageSaver(self.imagesToSave)
 
@@ -84,9 +94,6 @@ class VideoRecordSink(gst.Element):
 
        t.start()
 

	
 
    def chainfunc(self, pad, buffer):
 
        global nextImageCb
 
        self.info("%s timestamp(buffer):%d" % (pad, buffer.timestamp))
 

	
 
        position = self.musicTime.getLatest()
 

	
 
        if not position['song']:
 
@@ -101,14 +108,10 @@ class VideoRecordSink(gst.Element):
 
        except:
 
            traceback.print_exc()
 

	
 
        try:
 
            self.replay.update(position)
 
        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
 
@@ -122,23 +125,24 @@ class VideoRecordSink(gst.Element):
 
        img.save(outFilename)
 

	
 
        now = time.time()
 
        log.info("wrote %s delay of %.2fms %s",
 
                 outFilename,
 
                 (now - self.lastTime) * 1000,
 
                 bufferTimestamp)
 
        log.debug("wrote %s delay of %.2fms, took %.2fms",
 
                  outFilename,
 
                  (now - self.lastTime) * 1000,
 
                  (now - t1) * 1000)
 
        self.lastTime = now
 

	
 
gobject.type_register(VideoRecordSink)
 

	
 
class Main(object):
 
    def __init__(self):
 
        self.musicTime = MusicTime()
 
        wtree = gtk.Builder()
 
        wtree.add_from_file(sibpath(__file__, "vidref.glade"))
 
        mainwin = wtree.get_object("MainWindow")
 
        mainwin.connect("destroy", gtk.main_quit)
 
        wtree.connect_signals(self)
 

	
 
        wtree.get_object("replayPanel").show()
 
        # wtree.get_object("replayPanel").show() # demo only
 
        rp = wtree.get_object("replayVbox")
 
        self.replayViews = ReplayViews(rp)
 

	
 
@@ -147,6 +151,17 @@ class Main(object):
 

	
 
        self.setInput('dv')
 

	
 
        gobject.timeout_add(1000 // framerate, self.updateLoop)
 

	
 
    def updateLoop(self):
 
        position = self.musicTime.getLatest()
 
        try:
 
            with gtk.gdk.lock:
 
                self.replayViews.update(position)
 
        except:
 
            traceback.print_exc()
 
        return True
 

	
 
    def getInputs(self):
 
        return ['auto', 'dv', 'video0']
 

	
 
@@ -159,9 +174,9 @@ class Main(object):
 
            }[name]
 

	
 
        cam = (sourcePipe + " ! "
 
              "videorate ! video/x-raw-yuv,framerate=15/1 ! "
 
              "videorate ! video/x-raw-yuv,framerate=%s/1 ! "
 
              "videoscale ! video/x-raw-yuv,width=320,height=240;video/x-raw-rgb,width=320,height=240 ! "
 
              "queue name=vid")
 
              "queue name=vid" % framerate)
 

	
 
        self.pipeline = gst.parse_launch(cam)
 

	
 
@@ -171,7 +186,7 @@ class Main(object):
 
            return e
 
        
 
        sink = makeElem("xvimagesink")
 
        recSink = VideoRecordSink(self.replayViews)
 
        recSink = VideoRecordSink(self.musicTime)
 
        self.pipeline.add(recSink)
 

	
 
        tee = makeElem("tee")
light9/vidref/replay.py
Show inline comments
 
import os, gtk
 
from __future__ import division
 
import os, gtk, shutil, logging, time
 
from bisect import bisect_left
 
from decimal import Decimal
 
log = logging.getLogger()
 

	
 
framerate = 15
 

	
 
def songDir(song):
 
    return "/tmp/vidref/play-%s" % song.split('://')[-1].replace('/','_')
 
@@ -20,7 +23,7 @@ class ReplayViews(object):
 
    def __init__(self, parent):
 
        # today, parent is the vbox the replay windows should appear in
 
        self.parent = parent
 
        self.lastSong = None
 
        self.lastStart = None
 

	
 
        self.views = []
 
     
 
@@ -29,22 +32,36 @@ class ReplayViews(object):
 
        freshen all replay windows. We get called this about every
 
        time there's a new live video frame.
 

	
 
        may be responsible for making new children if we change song
 
        Calls loadViewsForSong if we change songs, or even if we just
 
        restart the playback of the current song (since there could be
 
        a new replay view)
 
        """
 
        if position['song'] != self.lastSong:
 
        t1 = time.time()
 
        if position['started'] != self.lastStart:
 
            self.loadViewsForSong(position['song'])
 
            self.lastSong = position['song']
 
            self.lastStart = position['started']
 
        for v in self.views:
 
            v.updatePic(position)
 

	
 
        log.debug("update %s views in %.2fms",
 
                  len(self.views), (time.time() - t1) * 1000)
 

	
 
    def loadViewsForSong(self, song):
 
        # remove previous ones
 
        
 
        takes = os.listdir(songDir(song))
 
        """
 
        replace previous views, and cleanup short ones
 
        """
 
        for v in self.views:
 
            v.destroy()
 
        self.views[:] = []
 

	
 
        takes = sorted(os.listdir(songDir(song)))
 
        for take in takes:
 
            td = takeDir(songDir(song), take)
 
            rv = ReplayView(self.parent, Replay(td))
 
            r = Replay(td)
 
            if r.tooShort():
 
                log.warn("cleaning up %s; too short" % r.takeDir)
 
                r.deleteDir()
 
                continue
 
            rv = ReplayView(self.parent, r)
 
            self.views.append(rv)
 

	
 
class ReplayView(object):
 
@@ -53,17 +70,100 @@ class ReplayView(object):
 
    """
 
    def __init__(self, parent, replay):
 
        self.replay = replay
 
        self.enabled = True
 

	
 
        # this *should* be a composite widget from glade
 
        img = gtk.Image()
 
        img.set_size_request(320, 240)
 
        parent.pack_end(img, False, False)
 
        img.show()
 
        self.picWidget = img
 
        
 
#        self.picWidget = parent.get_children()[0].get_child()
 

	
 
        delImage = gtk.Image()
 
        delImage.set_visible(True)
 
        delImage.set_from_stock("gtk-delete", gtk.ICON_SIZE_BUTTON)
 

	
 
        def withLabel(cls, label):
 
            x = cls()
 
            x.set_visible(True)
 
            x.set_label(label)
 
            return x
 

	
 
        def labeledProperty(key, value, width=12):
 
            lab = withLabel(gtk.Label, key)
 

	
 
            ent = gtk.Entry()
 
            ent.set_visible(True)
 
            ent.props.editable = False
 
            ent.props.width_chars = width
 
            ent.props.text = value
 

	
 
            cols = gtk.HBox()
 
            cols.set_visible(True)
 
            cols.add(lab)
 
            cols.add(ent)
 
            return cols
 

	
 
        replayPanel = gtk.HBox()
 
        replayPanel.set_visible(True)
 
        if True:
 
            af = gtk.AspectFrame()
 
            af.set_visible(True)
 
            af.set_size_request(320, 240)
 
            af.set_shadow_type(gtk.SHADOW_OUT)
 
            af.props.obey_child = True
 

	
 
            img = gtk.Image()
 
            img.set_visible(True)
 
            img.set_size_request(320, 240)
 
            self.picWidget = img
 

	
 
            af.add(img)
 
            replayPanel.pack_start(af, False, False, 0)
 

	
 
        if True:
 
            rows = []
 
            rows.append(labeledProperty("Started:", self.replay.getTitle()))
 
            rows.append(labeledProperty("Seconds:", self.replay.getDuration()))
 
            if True:
 
                en = withLabel(gtk.ToggleButton, "Enabled")
 
                en.set_active(True)
 
                def tog(w):
 
                    self.enabled = w.get_active()
 
                en.connect("toggled", tog)
 
                rows.append(en)
 
            if True:
 
                d = withLabel(gtk.Button, "Delete")
 
                d.props.image = delImage
 
                def onClicked(w):
 
                    self.replay.deleteDir()
 
                    self.destroy()
 
                d.connect("clicked", onClicked)
 
                rows.append(d)
 
            if True:
 
                pin = withLabel(gtk.CheckButton, "Pin to top")
 
                pin.props.draw_indicator = True
 
                rows.append(pin)
 

	
 
            stack = gtk.VBox()
 
            stack.set_visible(True)
 
            for r in rows:
 
                stack.add(r)
 
                stack.set_child_packing(r, False, False, 0, gtk.PACK_START)
 
            
 
            replayPanel.pack_start(stack, False, False, 0)
 

	
 
        parent.pack_start(replayPanel, False, False)
 
        self.replayPanel = replayPanel
 

	
 
    def destroy(self):
 
        self.replayPanel.destroy()
 
        self.enabled = False
 
        
 
    def updatePic(self, position):
 

	
 
        # this should skip updating off-screen widgets! maybe that is
 
        # done by declaring the widget dirty and then reacting to a
 
        # paint message if one comes
 

	
 
        if not self.enabled:
 
            return
 
        
 
        inPic = self.replay.findClosestFrame(position['t']+.25)
 
        with gtk.gdk.lock:
 
            self.picWidget.set_from_file(inPic)
 
@@ -78,11 +178,34 @@ class Replay(object):
 
    """
 
    def __init__(self, takeDir):
 
        self.takeDir = takeDir
 
        self.existingFrames = sorted([Decimal(f.split('.jpg')[0])
 
                                 for f in os.listdir(self.takeDir)])
 

	
 
    def tooShort(self, minSeconds=5):
 
        return len(self.existingFrames) < (minSeconds * framerate)
 

	
 
    def deleteDir(self):
 
        shutil.rmtree(self.takeDir)
 

	
 
    def getTitle(self):
 
        tm = time.localtime(int(os.path.basename(self.takeDir)))
 
        return time.strftime("%a %H:%M:%S", tm)
 

	
 
    def getDuration(self):
 
        """total number of seconds represented, which is most probably
 
        a continuous section, but we aren't saying where in the song
 
        that is"""
 
        return "%.1f" % (len(self.existingFrames) / framerate)
 

	
 
    def findClosestFrame(self, t):
 
        existingFrames = sorted([Decimal(f.split('.jpg')[0])
 
                                 for f in os.listdir(self.takeDir)])
 
        i = bisect_left(existingFrames, Decimal(str(t)))
 
        if i >= len(existingFrames):
 
            i = len(existingFrames) - 1
 
        return os.path.join(self.takeDir, "%08.03f.jpg" % existingFrames[i])
 
        # this is weird to be snapping our playback time to the frames
 
        # on disk. More efficient and accurate would be to schedule
 
        # the disk frames to playback exactly as fast as they want
 
        # to. This might spread cpu load since the recorded streams
 
        # might be a little more out of phase. It would also
 
        # accomodate changes in framerate between playback streams.
 
        i = bisect_left(self.existingFrames, Decimal(str(t)))
 
        if i >= len(self.existingFrames):
 
            i = len(self.existingFrames) - 1
 
        return os.path.join(self.takeDir, "%08.03f.jpg" %
 
                            self.existingFrames[i])
light9/vidref/vidref.glade
Show inline comments
 
@@ -3,9 +3,8 @@
 
  <requires lib="gtk+" version="2.16"/>
 
  <!-- interface-naming-policy project-wide -->
 
  <object class="GtkWindow" id="MainWindow">
 
    <property name="width_request">990</property>
 
    <property name="height_request">709</property>
 
    <property name="default_width">500</property>
 
    <property name="title" translatable="yes">vidref</property>
 
    <property name="default_width">690</property>
 
    <property name="default_height">500</property>
 
    <child>
 
      <object class="GtkHBox" id="hbox3">
0 comments (0 inline, 0 general)