Changeset - 9ee42b88299b
[Not reviewed]
default
0 2 0
Drew Perttula - 6 years ago 2019-06-06 02:56:46
drewp@bigasterisk.com
vidref now has some stats
Ignore-this: ef1184d85c09bead29fb4d1f2245f180
2 files changed with 42 insertions and 4 deletions:
0 comments (0 inline, 0 general)
bin/vidref
Show inline comments
 
@@ -24,19 +24,27 @@ from light9 import networking, showconfi
 
from light9.vidref import videorecorder
 
from rdflib import URIRef
 
from light9.newtypes import Song
 
from rdfdb.syncedgraph import SyncedGraph
 
from cycloneerr import PrettyErrorHandler
 
from typing import cast
 
from greplin import scales
 
from greplin.scales.cyclonehandler import StatsHandler
 

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

	
 
log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
 

	
 
stats = scales.collection(
 
    '/webServer',
 
    scales.RecentFpsStat('liveWebsocketFrameFps'),
 
    scales.IntStat('liveClients'),
 
    
 
)
 

	
 
class Snapshot(cyclone.web.RequestHandler):
 

	
 
    @defer.inlineCallbacks
 
    def post(self):
 
        # save next pic
 
@@ -65,19 +73,23 @@ pipeline = videorecorder.GstSource(
 

	
 

	
 
class Live(cyclone.websocket.WebSocketHandler):
 

	
 
    def connectionMade(self, *args, **kwargs):
 
        pipeline.liveImages.subscribe(on_next=self.onFrame)
 
        stats.liveClients += 1
 

	
 
    def connectionLost(self, reason):
 
        0  #self.subj.dispose()
 
        #self.subj.dispose()
 
        stats.liveClients -= 1
 

	
 
    def onFrame(self, cf: videorecorder.CaptureFrame):
 
        if cf is None: return
 

	
 
        stats.liveWebsocketFrameFps.mark()
 

	
 
        self.sendMessage(
 
            json.dumps({
 
                'jpeg': base64.b64encode(cf.asJpeg()).decode('ascii'),
 
                'description': f't={cf.t}',
 
            }))
 

	
 
@@ -105,14 +117,15 @@ class Clips(PrettyErrorHandler, cyclone.
 

	
 
class ReplayMap(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        song = Song(self.get_argument('song'))
 
        clips = []
 
        for vid in glob.glob(os.path.join(videorecorder.songDir(song),
 
                                          b'*.mp4')):
 
        videoPaths = glob.glob(os.path.join(videorecorder.songDir(song),
 
                                            b'*.mp4'))
 
        for vid in videoPaths:
 
            pts = []
 
            for line in open(vid.replace(b'.mp4', b'.timing'), 'rb'):
 
                _v, vt, _eq, _song, st = line.split()
 
                pts.append([float(st), float(vt)])
 

	
 
            url = vid[len(os.path.dirname(os.path.dirname(showconfig.root()))
 
@@ -156,12 +169,16 @@ reactor.listenTCP(
 
            (r'/replayMap', ReplayMap),
 
            (r'/snapshot', Snapshot),
 
            (r'/snapshot/(.*)', SnapshotPic, {
 
                "path": 'todo',
 
            }),
 
            (r'/time', Time),
 
            (r'/stats/(.*)', StatsHandler, {
 
                'serverName': 'vidref'
 
            }),
 

	
 
        ],
 
        debug=True,
 
    ))
 
log.info("serving on %s" % port)
 

	
 
reactor.run()
light9/vidref/videorecorder.py
Show inline comments
 
@@ -11,28 +11,40 @@ import PIL.Image
 
from gi.repository import Gst
 
from rx.subject import BehaviorSubject
 
from twisted.internet import threads
 
from rdflib import URIRef
 
import moviepy.editor
 
import numpy
 
from greplin import scales
 

	
 
from light9.ascoltami.musictime_client import MusicTime
 
from light9.newtypes import Song
 
from light9 import showconfig
 

	
 
log = logging.getLogger()
 

	
 
stats = scales.collection(
 
    '/recorder',
 
    scales.PmfStat('jpegEncode', recalcPeriod=1),
 
    scales.IntStat('deletes'),
 
    scales.PmfStat('waitForNextImg', recalcPeriod=1),
 
    scales.PmfStat('crop', recalcPeriod=1),
 
    scales.RecentFpsStat('encodeFrameFps'),
 
    scales.RecentFpsStat('queueGstFrameFps'),
 
    
 
)
 

	
 
@dataclass
 
class CaptureFrame:
 
    img: PIL.Image
 
    song: Song
 
    t: float
 
    isPlaying: bool
 
    imgJpeg: Optional[bytes] = None
 

	
 
    @stats.jpegEncode.time()
 
    def asJpeg(self):
 
        if not self.imgJpeg:
 
            output = BytesIO()
 
            self.img.save(output, 'jpeg', quality=80)
 
            self.imgJpeg = output.getvalue()
 
        return self.imgJpeg
 
@@ -54,12 +66,13 @@ def deleteClip(uri: URIRef):
 
    # uri http://light9.bigasterisk.com/show/dance2019/song6/take_155
 
    # path show/dance2019/video/light9.bigasterisk.com_show_dance2019_song6/take_155.*
 
    w = uri.split('/')[-4:]
 
    path = '/'.join([w[0], w[1], 'video',
 
                     f'light9.bigasterisk.com_{w[0]}_{w[1]}_{w[2]}', w[3]])
 
    log.info(f'deleting {uri} {path}')
 
    stats.deletes += 1
 
    for fn in [path + '.mp4', path + '.timing']:
 
        os.remove(fn)
 

	
 
class FramesToVideoFiles:
 
    """
 

	
 
@@ -153,24 +166,27 @@ class FramesToVideoFiles:
 
        if self.currentClipFrameCount < 400:
 
            log.info('too small- deleting')
 
            deleteClip(takeUri(self.outMp4.encode('ascii')))
 
        
 

	
 
    def _bg_make_frame(self, video_time_secs):
 
        stats.encodeFrameFps.mark()
 
        if self.nextWriteAction == 'close':
 
            raise StopIteration  # the one in write_videofile
 
        elif self.nextWriteAction == 'notWritingClip':
 
            raise NotImplementedError
 
        elif self.nextWriteAction == 'saveFrames':
 
            pass
 
        else:
 
            raise NotImplementedError(self.nextWriteAction)
 

	
 
        # should be a little queue to miss fewer frames
 
        t1 = time.time()
 
        while self.nextImg is None:
 
            time.sleep(.015)
 
        stats.waitForNextImg = time.time() - t1
 
        cf, self.nextImg = self.nextImg, None
 

	
 
        self.frameMap.write(f'video {video_time_secs:g} = song {cf.t:g}\n')
 
        self.currentClipFrameCount += 1
 
        return numpy.asarray(cf.img)
 

	
 
@@ -218,27 +234,32 @@ class GstSource:
 
            (result, mapinfo) = buf.map(Gst.MapFlags.READ)
 
            try:
 
                img = PIL.Image.frombytes(
 
                    'RGB', (caps.get_structure(0).get_value('width'),
 
                            caps.get_structure(0).get_value('height')),
 
                    mapinfo.data)
 
                img = img.crop((0, 100, 640, 380))
 
                img = self.crop(img)
 
            finally:
 
                buf.unmap(mapinfo)
 
            # could get gst's frame time and pass it to getLatest
 
            latest = self.musicTime.getLatest()
 
            if 'song' in latest:
 
                stats.queueGstFrameFps.mark()
 
                self.liveImages.on_next(
 
                    CaptureFrame(img=img,
 
                                 song=Song(latest['song']),
 
                                 t=latest['t'],
 
                                 isPlaying=latest['playing']))
 
        except Exception:
 
            traceback.print_exc()
 
        return Gst.FlowReturn.OK
 

	
 
    @stats.crop.time()
 
    def crop(self, img):
 
        return img.crop((0, 100, 640, 380))
 
    
 
    def setupPipelineError(self, pipe, cb):
 
        bus = pipe.get_bus()
 

	
 
        def onBusMessage(bus, msg):
 

	
 
            print('nusmsg', msg)
0 comments (0 inline, 0 general)