diff --git a/pandora/item/models.py b/pandora/item/models.py index 7635fbc92..24d66b3e5 100644 --- a/pandora/item/models.py +++ b/pandora/item/models.py @@ -30,8 +30,9 @@ import ox.image import managers import utils import tasks -from archive import extract +from .timelines import join_timelines +from archive import extract from annotation.models import Annotation, Layer from person.models import get_name_sort from app.models import site_config @@ -863,7 +864,8 @@ class Item(models.Model): def make_timeline(self): streams = self.streams() if len(streams) > 1: - print "FIXME, needs to build timeline from parts" + timelines = [s.timeline_prefix for s in self.streams()] + join_timelines(timelines, self.timeline_prefix) def make_poster(self, force=False): if not self.poster or force: diff --git a/pandora/item/timelines.py b/pandora/item/timelines.py new file mode 100644 index 000000000..8e799beb0 --- /dev/null +++ b/pandora/item/timelines.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 + +from __future__ import division, with_statement + +from glob import glob + +import Image + +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 makeTiles(timeline_prefix, height=16, width=3600): + files = glob('%s.64.*.png' % timeline_prefix) + fps = 25 + part_step = 60 + output_width = width + 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] / fps) + 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 += 1 + +def makeTimelineOverview(timeline_prefix, width, inpoint=0, outpoint=0, duration=-1, height=16): + input_scale = 25 + + timeline_file = '%s.%s.png' % (timeline_prefix, height) + if outpoint > 0: + timeline_file = '%s.overview.%s.%d-%d.png' % (timeline_prefix, height, 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 join_timelines(timelines, prefix): + height = 64 + width = 1500 + + tiles = [] + for timeline in timelines: + tiles += sorted(glob('%s.%s.*.png'%(timeline, height))) + + tiles = map(Image.open, tiles) + duration = sum(map(lambda i: i.size[0], tiles)) + timeline = Image.new("RGB", (duration, height)) + pos = 0 + for tile in tiles: + timeline.paste(tile, (pos, 0, pos+tile.size[0], height)) + pos += tile.size[0] + + pos = 0 + i = 0 + while pos < timeline.size[0]: + end = min(pos+width, timeline.size[0]) + timeline_name = '%s.%s.%04d.png' % (prefix, timeline.size[1], i) + timeline.crop((pos, 0, end, timeline.size[1])).save(timeline_name) + pos += width + i += 1 + + makeTiles(prefix, 16, 3600) + makeTimelineOverview(prefix, 1920, height=16) + makeTimelineOverview(prefix, 1920, height=64) +