217 lines
7 KiB
Python
217 lines
7 KiB
Python
|
#!/usr/bin/python
|
||
|
# -*- coding: utf-8 -*-
|
||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||
|
# GPL 2008
|
||
|
import gobject
|
||
|
gobject.threads_init()
|
||
|
|
||
|
from glob import glob
|
||
|
import math
|
||
|
import os
|
||
|
import time
|
||
|
|
||
|
import pygst
|
||
|
pygst.require("0.10")
|
||
|
import gst
|
||
|
import Image
|
||
|
|
||
|
from singledecodebin import SingleDecodeBin
|
||
|
from imagesink import ImageSink
|
||
|
from video import Video
|
||
|
|
||
|
|
||
|
class Timeline(Video):
|
||
|
def __init__(self, uri):
|
||
|
Video.__init__(self, uri)
|
||
|
|
||
|
bus = self.get_bus()
|
||
|
bus.add_signal_watch()
|
||
|
self.watch_id = bus.connect("message", self.onBusMessage)
|
||
|
|
||
|
self.mainloop = gobject.MainLoop()
|
||
|
|
||
|
def extract(self, prefix, width, height):
|
||
|
self.tile_width = width
|
||
|
self.tile_height = height
|
||
|
self.prefix = prefix
|
||
|
self.timeline_fps = 25
|
||
|
self.input_tile_width = int(math.ceil((float(self.framerate)/self.timeline_fps) * width))
|
||
|
ntiles = int(math.ceil(float(self.frames)/self.input_tile_width))
|
||
|
self.tiles = []
|
||
|
for i in range(ntiles):
|
||
|
tile = Image.new("RGB", (self.input_tile_width, height))
|
||
|
self.tiles.append(tile)
|
||
|
|
||
|
self.set_state(gst.STATE_PLAYING)
|
||
|
self.mainloop.run()
|
||
|
|
||
|
for i in range(ntiles):
|
||
|
tile = self.tiles[i]
|
||
|
if tile.size[0] != self.tile_width:
|
||
|
tile = tile.resize((self.tile_width, self.tile_height), Image.ANTIALIAS)
|
||
|
if i < (ntiles-1):
|
||
|
frames = self.input_tile_width
|
||
|
else:
|
||
|
frames = self.frames-((ntiles-1)*self.input_tile_width)
|
||
|
tile_width = int(math.ceil(self.timeline_fps*frames)/float(self.framerate))
|
||
|
if -2 < self.tile_width - tile_width < 2:
|
||
|
tile_width = self.tile_width
|
||
|
tile = tile.crop((0, 0, tile_width, self.tile_height))
|
||
|
filename = "%s.%s.%04d.png" % (self.prefix, self.tile_height, i)
|
||
|
tile.save(filename)
|
||
|
|
||
|
def done(self):
|
||
|
self.mainloop.quit()
|
||
|
|
||
|
def _sbinPadAddedCb(self, unused_sbin, pad):
|
||
|
self.log("pad : %s" % pad)
|
||
|
pad.link(self.csp.get_pad("sink"))
|
||
|
|
||
|
def _frameCb(self, unused_thsink, frame, timestamp):
|
||
|
self.log("image:%s, timestamp:%s" % (frame, gst.TIME_ARGS(timestamp)))
|
||
|
|
||
|
if not self._ready:
|
||
|
# we know we're prerolled when we get the initial thumbnail
|
||
|
self._ready = True
|
||
|
else:
|
||
|
framePos = int(math.ceil((float(timestamp) / (gst.SECOND) * float(self.framerate))))
|
||
|
tile = int(math.floor(float(framePos) / self.input_tile_width))
|
||
|
tilePos = framePos - (tile * self.input_tile_width)
|
||
|
frame = frame.resize((1, self.tile_height), Image.ANTIALIAS)
|
||
|
for i in range(self.tile_height):
|
||
|
self.tiles[tile].putpixel((tilePos, i), frame.getpixel((0, i)))
|
||
|
|
||
|
if self.mainloop and timestamp >= self.duration:
|
||
|
self.done()
|
||
|
|
||
|
def onBusMessage(self, bus, message):
|
||
|
if message.src == self and message.type == gst.MESSAGE_EOS:
|
||
|
self.done()
|
||
|
|
||
|
def loadTimeline(timeline_prefix, height=64):
|
||
|
files = sorted(glob('%s.%s.*.png' % (timeline_prefix, height)))
|
||
|
f = Image.open(files[0])
|
||
|
width = f.size[0]
|
||
|
f = Image.open(files[-1])
|
||
|
duration = f.size[0] + (len(files)-1)*width
|
||
|
timeline = Image.new("RGB", (duration, height))
|
||
|
pos = 0
|
||
|
for f in files:
|
||
|
part = Image.open(f)
|
||
|
timeline.paste(part, (pos, 0, pos + part.size[0], height))
|
||
|
pos += part.size[0]
|
||
|
return timeline
|
||
|
|
||
|
def createTimelineMultiline(timeline_prefix, width=600, height=16):
|
||
|
lineWidth = width
|
||
|
timlelineHeight = height
|
||
|
|
||
|
timeline = loadTimeline(timeline_prefix)
|
||
|
duration = timeline.size[0]
|
||
|
|
||
|
width = duration/25 #one pixel per second
|
||
|
timeline = timeline.resize((width, timlelineHeight), Image.ANTIALIAS).convert('RGBA')
|
||
|
|
||
|
lineHeight = timlelineHeight + 2 * 4
|
||
|
|
||
|
lines = int(math.ceil(width / lineWidth) + 1)
|
||
|
size = (lineWidth, lineHeight * lines)
|
||
|
|
||
|
timelineColor = (64, 64, 64)
|
||
|
i = Image.new("RGBA", size)
|
||
|
|
||
|
#padd end with nothing to fit to grid
|
||
|
t = Image.new("RGBA", (lineWidth * lines, timlelineHeight))
|
||
|
t.paste(timeline, (0, 0))
|
||
|
|
||
|
for currentLine in range(0, lines):
|
||
|
offset = currentLine * lineHeight + 4
|
||
|
toffset = currentLine * lineWidth
|
||
|
try:
|
||
|
tbox = t.crop((toffset, 0, toffset + lineWidth, timlelineHeight))
|
||
|
box = ((0, offset , tbox.size[0], offset + tbox.size[1]))
|
||
|
i.paste(tbox, box)
|
||
|
except:
|
||
|
broken = True
|
||
|
width = lineWidth
|
||
|
if currentLine == lines -1:
|
||
|
width = duration - (lines - 1) * lineWidth
|
||
|
box = ((0, offset , width, offset + timlelineHeight))
|
||
|
i.paste(timelineColor, box)
|
||
|
timeline_file = '%s.timeline.overview.png' % (timeline_prefix)
|
||
|
i.save(timeline_file, 'PNG')
|
||
|
|
||
|
def makeTimelineByFramesPerPixel(timeline_prefix, frames_per_pixel, inpoint=0, outpoint=0, height=16):
|
||
|
pos = 0
|
||
|
input_scale = 25
|
||
|
|
||
|
timeline_file = '%s.timeline.%s.png' % (timeline_prefix, width)
|
||
|
if outpoint > 0:
|
||
|
timeline_file = '%s.timeline.%s.%d-%d.png' % (timeline_prefix, width, inpoint, outpoint)
|
||
|
|
||
|
timeline = loadTimeline(timeline_prefix)
|
||
|
duration = timeline.size[0]
|
||
|
|
||
|
|
||
|
width = duration / frames_per_pixel
|
||
|
|
||
|
if inpoint<=0:
|
||
|
inpoint = 0
|
||
|
else:
|
||
|
inpoint = inpoint * input_scale
|
||
|
if outpoint<=0:
|
||
|
outpoint = pos
|
||
|
else:
|
||
|
outpoint = outpoint * input_scale
|
||
|
|
||
|
timeline = timeline.crop((inpoint, 0, outpoint, timeline.size[1])).resize((width, height), Image.ANTIALIAS)
|
||
|
timeline.save(timeline_file)
|
||
|
|
||
|
def makeTimelineOverview(timeline_prefix, width, inpoint=0, outpoint=0, duration=-1, height=16):
|
||
|
input_scale = 25
|
||
|
|
||
|
timeline_file = '%s.timeline.%s.png' % (timeline_prefix, width)
|
||
|
if outpoint > 0:
|
||
|
timeline_file = '%s.timeline.%s.%d-%d.png' % (timeline_prefix, width, inpoint, outpoint)
|
||
|
|
||
|
timeline = loadTimeline(timeline_prefix)
|
||
|
duration = timeline.size[0]
|
||
|
|
||
|
if inpoint<=0:
|
||
|
inpoint = 0
|
||
|
else:
|
||
|
inpoint = inpoint * input_scale
|
||
|
if outpoint<=0:
|
||
|
outpoint = duration
|
||
|
else:
|
||
|
outpoint = outpoint * input_scale
|
||
|
|
||
|
timeline = timeline.crop((inpoint, 0, outpoint, timeline.size[1])).resize((width, height), Image.ANTIALIAS)
|
||
|
timeline.save(timeline_file)
|
||
|
|
||
|
def makeTiles(timeline_prefix, height=16):
|
||
|
files = glob('%s.64.*.png' % timeline_prefix)
|
||
|
part_step = 60
|
||
|
output_width = 300
|
||
|
width = len(files) * part_step
|
||
|
timeline = Image.new("RGB", (width, height))
|
||
|
|
||
|
pos = 0
|
||
|
for f in sorted(files):
|
||
|
part = Image.open(f)
|
||
|
part_width = int(part.size[0] / 25)
|
||
|
part = part.resize((part_width, height), Image.ANTIALIAS)
|
||
|
timeline.paste(part, (pos, 0, pos+part_width, height))
|
||
|
pos += part_width
|
||
|
|
||
|
timeline = timeline.crop((0, 0, pos, height))
|
||
|
|
||
|
pos = 0
|
||
|
i = 0
|
||
|
while pos < timeline.size[0]:
|
||
|
end = min(pos+output_width, timeline.size[0])
|
||
|
timeline.crop((pos, 0, end, timeline.size[1])).save('%s.%s.%04d.png' % (timeline_prefix, timeline.size[1], i))
|
||
|
pos += output_width
|
||
|
i += 5
|
||
|
|