#!/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.overview.png' % (timeline_prefix) i.save(timeline_file, 'PNG') timeline8_file = '%s.overview.8.png' % (timeline_prefix) if lines < 8: i.save(timeline8_file, 'PNG') else: i.crop((0,0,lineWidth, 8 * lineHeight)).save(timeline8_file, 'PNG') def makeTimelineByFramesPerPixel(timeline_prefix, frames_per_pixel, inpoint=0, outpoint=0, height=16): pos = 0 input_scale = 25 timeline_file = '%s.pixels.%s.png' % (timeline_prefix, width) if outpoint > 0: timeline_file = '%s.pixels.%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.overview.%s.png' % (timeline_prefix, width) if outpoint > 0: timeline_file = '%s.overview.%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