use oxtimelines

This commit is contained in:
j 2012-05-17 11:38:59 +02:00
parent f09e4205cf
commit 96ec35e51a
19 changed files with 393 additions and 164 deletions

2
README
View file

@ -29,7 +29,7 @@ Get code from bazzar
cd /srv/ cd /srv/
bzr branch http://code.0x2620.org/pandora pandora bzr branch http://code.0x2620.org/pandora pandora
cd pandora cd pandora
virtualenv . virtualenv --system-site-packages .
pip -E . install -r requirements.txt pip -E . install -r requirements.txt
cd static cd static

View file

@ -16,6 +16,7 @@ import numpy as np
import Image import Image
import ox import ox
import ox.image import ox.image
from ox.utils import json
img_extension='jpg' img_extension='jpg'
@ -303,13 +304,27 @@ def resize_image(image_source, image_output, width=None, size=None):
output.save(image_output) output.save(image_output)
def timeline(video, prefix): def timeline(
cmd = ['oxtimeline', '-i', video, '-o', prefix] video, prefix,
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) modes=['antialias', 'slitscan', 'keyframes', 'audio', 'data'],
#modes=['antialias', 'slitscan', 'audio', 'data'],
size=[64, 16]
):
if isinstance(video, basestring):
video = [video]
cmd = ['../bin/oxtimelines',
'-s', ','.join(map(str, reversed(sorted(size)))),
'-m', ','.join(modes),
'-o', prefix,
'-c', os.path.join(prefix, 'cuts.json'),
] + video
#p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print cmd
p = subprocess.Popen(cmd)
p.wait() p.wait()
def average_color(prefix, start=0, end=0): def average_color(prefix, start=0, end=0, mode='antialias'):
height = 64 height = 64
frames = 0 frames = 0
pixels = [] pixels = []
@ -318,7 +333,9 @@ def average_color(prefix, start=0, end=0):
if end: if end:
start = int(start * 25) start = int(start * 25)
end = int(end * 25) end = int(end * 25)
timelines = sorted(filter(lambda t: t!= '%s%sp.png'%(prefix,height), glob("%s%sp*.png"%(prefix, height)))) mode = 'timeline' + mode
timelines = sorted(filter(lambda t: t!= '%s%s%sp.jpg'%(prefix, mode, height),
glob("%s%s%sp*.jpg"%(prefix, mode, height))))
for image in timelines: for image in timelines:
start_offset = 0 start_offset = 0
if start and frames + 1500 <= start: if start and frames + 1500 <= start:
@ -352,8 +369,7 @@ def average_color(prefix, start=0, end=0):
def average_volume(prefix, start=0, end=0): def average_volume(prefix, start=0, end=0):
#FIXME: actually compute volume return average_color(prefix, start, end, 'audio')[2]
return 0
def get_distance(rgb0, rgb1): def get_distance(rgb0, rgb1):
@ -363,31 +379,11 @@ def get_distance(rgb0, rgb1):
def cuts(prefix): def cuts(prefix):
cuts = [] fname = os.path.join(prefix, 'cuts.json')
distances = [0] if not os.path.exists(fname):
fps = 25 return []
frames = 0 with open(fname) as f:
height = 64 cuts = json.load(f)
width = 1500
pixels = []
timelines = sorted(filter(lambda t: t!= '%s%sp.png'%(prefix,height), glob("%s%sp*.png"%(prefix, height))))
for image in timelines:
timeline = Image.open(image)
frames += timeline.size[0]
pixels.append(timeline.load())
for frame in range(1, frames):
x = frame % width
distance = 0
image0 = int((frame - 1) / width)
image1 = int(frame / width)
for y in range(height):
rgb0 = pixels[image0][(x - 1) % width, y]
rgb1 = pixels[image1][x, y]
distance += get_distance(rgb0, rgb1)
distance = distance / height
distances.append(distance)
if distance >= 0.025 and abs(distance - distances[frame - 1]) >= 0.05:
cuts.append(frame / fps)
return cuts return cuts

View file

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os
import re
from django.core.management.base import BaseCommand
from django.conf import settings
import monkey_patch.models
from ... import models
class Command(BaseCommand):
"""
"""
help = 'migrate timelines to new path'
args = ''
def handle(self, **options):
for root, folders, files in os.walk(settings.MEDIA_ROOT):
for f in files:
f = os.path.join(root, f)
base, ext = os.path.splitext(os.path.basename(f))
if base.startswith('timeline') and ext == '.png':
if base in ('timeline.overview', 'timeline.overview.8'):
print 'delete', f
os.unlink(f)
else:
n = re.compile('timeline(\d+)p(\d+)').findall(base)
if not n:
n = re.compile('timeline(\d+)p').findall(base)
target = 'timelineantialias%sp.jpg' % n[0]
print f, target
target = os.path.join(os.path.dirname(f), target)
os.rename(f, target)
else:
n = tuple(map(int, n[0]))
target = 'timelineantialias%dp%d.jpg' % n
print f, target
target = os.path.join(os.path.dirname(f), target)
os.rename(f, target)

View file

@ -393,9 +393,11 @@ class Stream(models.Model):
cuts = fields.TupleField(default=[]) cuts = fields.TupleField(default=[])
color = fields.TupleField(default=[]) color = fields.TupleField(default=[])
volume = models.FloatField(default=0)
@property @property
def timeline_prefix(self): def timeline_prefix(self):
return os.path.join(settings.MEDIA_ROOT, self.path())
return os.path.join(settings.MEDIA_ROOT, self.path(), 'timeline') return os.path.join(settings.MEDIA_ROOT, self.path(), 'timeline')
def name(self): def name(self):
@ -438,6 +440,7 @@ class Stream(models.Model):
extract.timeline(self.video.path, self.timeline_prefix) extract.timeline(self.video.path, self.timeline_prefix)
self.cuts = tuple(extract.cuts(self.timeline_prefix)) self.cuts = tuple(extract.cuts(self.timeline_prefix))
self.color = tuple(extract.average_color(self.timeline_prefix)) self.color = tuple(extract.average_color(self.timeline_prefix))
self.volume= extract.average_volume(self.timeline_prefix)
self.save() self.save()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):

View file

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os
from os.path import join, dirname, basename, splitext, exists
import time
from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
import monkey_patch.models
from ... import models
class Command(BaseCommand):
"""
rebuild timeline for all items.
"""
help = 'rebuild all timeines(use after updating oxtimelines)'
args = ''
def handle(self, **options):
offset = 0
chunk = 50
count = pos = models.Item.objects.count()
while offset <= count:
for i in models.Item.objects.all().order_by('id')[offset:offset+chunk]:
print pos, i.itemId
for s in i.streams():
s.make_timeline()
i.update_timeline()
pos -= 1
offset += chunk
time.sleep(30) #keep load down

View file

@ -16,7 +16,8 @@ class Command(BaseCommand):
""" """
rebuild sort/search cache for all items. rebuild sort/search cache for all items.
""" """
help = 'listen to rabbitmq and execute encoding tasks.' help = 'rebuild sort/search cache for all items.'
"""
args = '' args = ''
def handle(self, **options): def handle(self, **options):

View file

@ -29,7 +29,7 @@ import ox.image
import managers import managers
import utils import utils
import tasks import tasks
from .timelines import join_timelines from .timelines import join_tiles
from data_api import external_data from data_api import external_data
from archive import extract from archive import extract
@ -900,8 +900,8 @@ class Item(models.Model):
def timeline_prefix(self): def timeline_prefix(self):
videos = self.streams() videos = self.streams()
if len(videos) == 1: if len(videos) == 1:
return os.path.join(settings.MEDIA_ROOT, videos[0].path('timeline')) return os.path.join(settings.MEDIA_ROOT, videos[0].path(''))
return os.path.join(settings.MEDIA_ROOT, self.path(), 'timeline') return os.path.join(settings.MEDIA_ROOT, self.path())
def get_files(self, user): def get_files(self, user):
files = self.files.all().select_related() files = self.files.all().select_related()
@ -1025,19 +1025,20 @@ class Item(models.Model):
if streams.count() == 1: if streams.count() == 1:
self.data['color'] = streams[0].color self.data['color'] = streams[0].color
self.data['cuts'] = streams[0].cuts self.data['cuts'] = streams[0].cuts
self.data['volume'] = streams[0].volume
else: else:
#self.data['color'] = extract.average_color(self.timeline_prefix) #self.data['color'] = extract.average_color(self.timeline_prefix)
#self.data['cuts'] = extract.cuts(self.timeline_prefix) self.data['cuts'] = extract.cuts(self.timeline_prefix)
self.data['cuts'] = [] self.data['volume'] = 0
offset = 0 offset = 0
color = [0, 0, 0] color = [0, 0, 0]
n = streams.count() n = streams.count()
for s in streams: for s in streams:
for c in s.cuts: self.data['volume'] = s.volume * s.duration
self.data['cuts'].append(c+offset)
color = map(lambda a,b: (a+b)/n, color,ox.image.getRGB(s.color)) color = map(lambda a,b: (a+b)/n, color,ox.image.getRGB(s.color))
offset += s.duration offset += s.duration
self.data['color'] = ox.image.getHSL(color) self.data['color'] = ox.image.getHSL(color)
self.data['volume'] /= offset
#extract.timeline_strip(self, self.data['cuts'], stream.info, self.timeline_prefix[:-8]) #extract.timeline_strip(self, self.data['cuts'], stream.info, self.timeline_prefix[:-8])
self.select_frame() self.select_frame()
self.make_poster(True) self.make_poster(True)
@ -1082,7 +1083,7 @@ class Item(models.Model):
streams = self.streams() streams = self.streams()
if streams.count() > 1: if streams.count() > 1:
timelines = [s.timeline_prefix for s in self.streams()] timelines = [s.timeline_prefix for s in self.streams()]
join_timelines(timelines, self.timeline_prefix) join_tiles(timelines, self.timeline_prefix)
else: else:
#remove joined timeline if it was created at some point #remove joined timeline if it was created at some point
for f in glob(os.path.join(settings.MEDIA_ROOT, self.path(), 'timeline*.png')): for f in glob(os.path.join(settings.MEDIA_ROOT, self.path(), 'timeline*.png')):

View file

@ -1,114 +1,257 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
from __future__ import division, with_statement from __future__ import division, with_statement
import math
import os import os
from glob import glob import re
import Image import Image
from ox.utils import json
import ox
from utils import sorted_strings
def getTiles(timeline_prefix, height=64): def join_tiles(source_paths, target_path):
files = glob('%s%sp*.png' % (timeline_prefix, height)) '''
return sorted_strings(filter(lambda f: f!='%s%sp.png' % (timeline_prefix, height), files)) This is an implementation of a join_tiles function for new-style timelines.
Timelines of files will be read from source_paths, the timeline of the item will
be written to target_path.
'''
def loadTimeline(timeline_prefix, height=64): def divide(num, by):
files = getTiles(timeline_prefix, height) # divide(100, 3) -> [33, 33, 34]
f = Image.open(files[0]) arr = []
width = f.size[0] div = int(num / by)
f = Image.open(files[-1]) mod = num % by
duration = f.size[0] + (len(files)-1)*width for i in range(int(by)):
timeline = Image.new("RGB", (duration, height)) arr.append(div + (i > by - 1 - mod))
pos = 0 return arr
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): def get_file_info(file_name):
files = getTiles(timeline_prefix, 64) for mode in modes:
if re.match('^timeline' + mode + '64p\d+\.jpg', file_name):
return {
'file': file_name,
'mode': mode,
'index': int(file_name[11 + len(mode):-4])
}
return None
def save_and_open(data):
# whenever a large tile is done or needed,
# this function saves the previous large tile
# (if any) and opens the next one (if any).
# in between, whenever required, small tiles
# are opened, rendered and saved, and the
# large full tile is being generated.
# 'keyframes' are only rendered in large size,
# 'keyframeswide' only resized to small size.
image_mode = 'L' if mode == 'audio' else 'RGB'
small_mode = 'keyframes' if mode == 'keyframeswide' else mode
large_tile_i = int(target_w / large_tile_w)
# save previous large tile
if large_tile_i > 0:
large_tile_i -= 1
if mode != 'keyframeswide':
image_file = '%stimeline%s%dp%d.jpg' % (
target_path, mode, large_tile_h, large_tile_i
)
data['target_images']['large'].save(image_file)
#print image_file
if mode != 'keyframes':
# open small tile
small_tile_i = int(large_tile_i / 60)
small_tile_x = (large_tile_i % 60) * 60
if small_tile_x == 0:
if small_tile_i < small_tile_n - 1:
w = small_tile_w
else:
w = small_tile_last_w
data['target_images']['small'] = Image.new(image_mode, (w, small_tile_h))
# paste large tile into small tile
w = 60 if large_tile_i < large_tile_n - 1 else small_tile_last_w % 60
data['target_images']['large'] = data['target_images']['large'].resize(
(w, small_tile_h), Image.ANTIALIAS
)
data['target_images']['small'].paste(
data['target_images']['large'], (small_tile_x, 0)
)
# save small tile
if small_tile_x == small_tile_w - 60 or large_tile_i == large_tile_n - 1:
image_file = '%stimeline%s%dp%d.jpg' % (
target_path, small_mode, small_tile_h, small_tile_i
)
data['target_images']['small'].save(image_file)
print image_file
if mode == 'antialias':
# render full tile
resized = data['target_images']['large'].resize((
data['full_tile_widths'][0], large_tile_h
), Image.ANTIALIAS)
data['target_images']['full'].paste(resized, (data['full_tile_offset'], 0))
data['full_tile_offset'] += data['full_tile_widths'][0]
data['full_tile_widths'] = data['full_tile_widths'][1:]
large_tile_i += 1
# open next large tile
if large_tile_i < large_tile_n:
w = large_tile_w if large_tile_i < large_tile_n - 1 else large_tile_last_w
data['target_images']['large'] = Image.new(image_mode, (w, large_tile_h))
data = {}
fps = 25 fps = 25
part_step = 60 large_tile_w, large_tile_h = 1500, 64
output_width = width small_tile_w, small_tile_h = 3600, 16
width = len(files) * part_step full_tile_w = 1920
timeline = Image.new("RGB", (width, height)) modes = ['antialias', 'slitscan', 'keyframes', 'keyframeswide', 'audio']
source_files = {}
for mode in modes:
source_files[mode] = []
pos = 0 # read files
for f in sorted_strings(files): durations = [0] * len(source_paths)
part = Image.open(f) frame_n = 0
part_width = int(part.size[0] / fps) for i, path in enumerate(source_paths):
part = part.resize((part_width, height), Image.ANTIALIAS) file_info = map(get_file_info, os.listdir(path))
timeline.paste(part, (pos, 0, pos+part_width, height)) file_info = filter(lambda x: x != None, file_info)
pos += part_width for info in sorted(file_info, key=lambda x: x['index']):
mode = info['mode']
source_files[mode].append(path + info['file'])
if mode == modes[0]:
width = Image.open(source_files[mode][-1]).size[0]
durations[i] += width / fps
frame_n += width
large_tile_n = int(math.ceil(frame_n / large_tile_w))
large_tile_last_w = frame_n % large_tile_w
small_tile_n = int(math.ceil(frame_n / fps / small_tile_w))
small_tile_last_w = int(math.ceil(frame_n / fps)) % small_tile_w
timeline = timeline.crop((0, 0, pos, height)) # open full timeline
if large_tile_n == 1:
pos = 0 data['full_tile_widths'] = [large_tile_last_w]
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%sp%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%sp.png' % (timeline_prefix, height)
if outpoint > 0:
timeline_file = '%s%sp.%d-%d.png' % (timeline_prefix, height, inpoint, outpoint)
timeline = loadTimeline(timeline_prefix)
duration = timeline.size[0]
if inpoint<=0:
inpoint = 0
else: else:
inpoint = inpoint * input_scale w = full_tile_w
if outpoint<=0: n = large_tile_n
outpoint = duration if large_tile_last_w < large_tile_w:
else: factor = full_tile_w / frame_n
outpoint = outpoint * input_scale last_w = int(round(large_tile_last_w * factor))
w -= last_w
n -= 1
data['full_tile_widths'] = divide(w, n)
if large_tile_last_w < large_tile_w:
data['full_tile_widths'].append(last_w)
data['full_tile_offset'] = 0
full_tile_image = Image.new('RGB', (full_tile_w, large_tile_h))
timeline = timeline.crop((inpoint, 0, outpoint, timeline.size[1])).resize((width, height), Image.ANTIALIAS) # main loop
timeline.save(timeline_file) data['target_images'] = {'large': None, 'small': None, 'full': full_tile_image}
for mode in modes:
target_w = 0
for source_file in source_files[mode]:
source_image = Image.open(source_file)
source_w = source_image.size[0]
target_x = target_w % large_tile_w
if target_x == 0:
save_and_open(data)
data['target_images']['large'].paste(source_image, (target_x, 0))
target_w += source_w
if target_x + source_w > large_tile_w:
# target tile overflows into next source tile
save_and_open(data)
target_x -= large_tile_w
data['target_images']['large'].paste(source_image, (target_x, 0))
target_w += source_w
save_and_open(data)
def join_timelines(timelines, prefix): # save full timelines
height = 64 image_file = '%stimelineantialias%dp.jpg' % (target_path, large_tile_h)
width = 1500 data['target_images']['full'].save(image_file)
#print image_file
image_file = '%stimelineantialias%dp.jpg' % (target_path, small_tile_h)
data['target_images']['full'].resize(
(full_tile_w, small_tile_h), Image.ANTIALIAS
).save(image_file)
#print image_file
ox.makedirs(os.path.dirname(prefix)) # join cuts
for f in glob('%s*'%prefix): cuts = []
os.unlink(f) offset = 0
for i, path in enumerate(source_paths):
with open(os.path.join(path, 'cuts.json'), 'r') as f:
path_cuts = json.load(f)
if i > 0:
cuts.append(offset)
for cut in path_cuts:
cuts.append(offset + cut)
offset += durations[i]
with open(os.path.join(target_path, 'cuts.json'), 'w') as f:
# avoid float rounding artefacts
f.write('[' + ', '.join(map(lambda x: '%.2f' % x, cuts)) + ']')
tiles = [] def split_tiles(path, paths, durations):
for timeline in timelines:
tiles += getTiles(timeline, height)
timeline = Image.new("RGB", (2 * width, height)) def is_timeline_file(file_name):
return file_name.startswith('timeline') and file_name.endswith('.png')
pos = 0 file_names = filter(is_timeline_file, os.listdir(path))
i = 0 tiles = {}
for tile in tiles: for file_name in file_names:
tile = Image.open(tile) mode = re.split('\d+', file_name[8:])[0]
timeline.paste(tile, (pos, 0, pos+tile.size[0], height)) print file_name, mode
pos += tile.size[0] split = re.split('[a-z]+', file_name[8 + len(mode):-4])
if pos >= width: height, index = map(lambda x: int(x) if len(x) else -1, split)
timeline_name = '%s%sp%04d.png' % (prefix, height, i) if not mode in tiles:
timeline.crop((0, 0, width, height)).save(timeline_name) tiles[mode] = {}
i += 1 if not height in tiles[mode]:
if pos > width: tiles[mode][height] = 0
t = timeline.crop((width, 0, pos, height)) if index + 1 > tiles[mode][height]:
timeline.paste(t, (0, 0, t.size[0], height)) tiles[mode][height] = index + 1
pos -= width print tiles
if pos:
timeline_name = '%s%sp%04d.png' % (prefix, height, i)
timeline.crop((0, 0, pos, height)).save(timeline_name)
makeTiles(prefix, 16, 3600) # for each mode
makeTimelineOverview(prefix, 1920, height=16) for mode in tiles:
makeTimelineOverview(prefix, 1920, height=64) image_mode = 'L' if mode == 'audio' else 'RGB'
# and for each size of that mode
for i, height in enumerate(tiles[mode]):
tile_width = 1500 if i == 0 else 3600
px_per_sec = 25 if i == 0 else 1
target_images = []
target_data = []
# and for each split item
for item_index, duration in enumerate(durations):
tile_index = 0
px = int(math.ceil(duration * px_per_sec))
# create a flat list of all target images
# (and store the split item and tile index)
while px:
width = tile_width if px > tile_width else px
target_images.append(
Image.new(image_mode, (width, height))
)
target_data.append(
{'item': item_index, 'tile': tile_index}
)
tile_index += 1
px -= width
target_index = 0
offset = 0
# for each source tile
for source_index in range(tiles[mode][height]):
source_image = Image.open('%stimeline%s%dp%d.png' % (
path, mode, height, source_index
))
source_width = source_image.size[0]
target_width = target_images[target_index].size[0]
target_images[target_index].paste(source_image, (offset, 0))
# paste it into as many target tiles as needed
while source_width + offset > target_width:
offset -= target_width
target_index += 1
target_width = target_images[target_index].size[0]
target_images[target_index].paste(source_image, (offset, 0))
for i, target_image in enumerate(target_images):
file_name = '%stimeline%s%dp%d' % (
paths[target_data[i]['item']], mode, height, target_data[i]['tile']
)
# target_image.save(file_name)
print file_name, target_image.size

View file

@ -9,8 +9,8 @@ urlpatterns = patterns("item.views",
(r'^(?P<id>[A-Z0-9].*)/(?P<size>\d+)p(?P<position>[\d\.]*)\.jpg$', 'frame'), (r'^(?P<id>[A-Z0-9].*)/(?P<size>\d+)p(?P<position>[\d\.]*)\.jpg$', 'frame'),
#timelines #timelines
(r'^(?P<id>[A-Z0-9].*)/timeline(?P<size>\d+)p(?P<position>\d+)\.png$', 'timeline'), (r'^(?P<id>[A-Z0-9].*)/timeline(?P<mode>[a-z]*)(?P<size>\d+)p(?P<position>\d+)\.(?P<format>png|jpg)$', 'timeline'),
(r'^(?P<id>[A-Z0-9].*)/timeline(?P<size>\d+)p\.png$', 'timeline_overview'), (r'^(?P<id>[A-Z0-9].*)/timeline(?P<mode>[a-z]*)(?P<size>\d+)p\.(?P<format>png|jpg)$', 'timeline'),
#video #video
(r'^(?P<id>[A-Z0-9].*)/(?P<resolution>\d+)p(?P<index>\d*)\.(?P<format>webm|ogv|mp4)$', 'video'), (r'^(?P<id>[A-Z0-9].*)/(?P<resolution>\d+)p(?P<index>\d*)\.(?P<format>webm|ogv|mp4)$', 'video'),

View file

@ -713,20 +713,30 @@ def icon(request, id, size=None):
response['Cache-Control'] = 'no-cache' response['Cache-Control'] = 'no-cache'
return response return response
def timeline(request, id, size, position): def timeline(request, id, size, position=-1, format='jpg', mode=None):
item = get_object_or_404(models.Item, itemId=id) item = get_object_or_404(models.Item, itemId=id)
if not item.access(request.user): if not item.access(request.user):
return HttpResponseForbidden() return HttpResponseForbidden()
timeline = '%s%sp%04d.png' %(item.timeline_prefix, size, int(position))
return HttpFileResponse(timeline, content_type='image/png')
if not mode:
mode = 'antialias'
modes = [t['id'] for t in settings.CONFIG['timelines']]
if mode not in modes:
raise Http404
modes.pop(modes.index(mode))
def timeline_overview(request, id, size): prefix = os.path.join(item.timeline_prefix, 'timeline')
item = get_object_or_404(models.Item, itemId=id) def timeline():
if not item.access(request.user): timeline = '%s%s%sp' % (prefix, mode, size)
return HttpResponseForbidden() if position > -1:
timeline = '%s%sp.png' %(item.timeline_prefix, size) timeline += '%d' % int(position)
return HttpFileResponse(timeline, content_type='image/png') return timeline + '.jpg'
path = timeline()
while modes and not os.path.exists(path):
mode = modes.pop()
path = timeline()
return HttpFileResponse(path, content_type='image/jpeg')
def torrent(request, id, filename=None): def torrent(request, id, filename=None):
item = get_object_or_404(models.Item, itemId=id) item = get_object_or_404(models.Item, itemId=id)

View file

@ -1,7 +1,7 @@
-e svn+http://code.djangoproject.com/svn/django/branches/releases/1.3.X#egg=django -e svn+http://code.djangoproject.com/svn/django/branches/releases/1.3.X#egg=django
#South #South
-e bzr+http://code.0x2620.org/python-ox/#egg=python-ox -e bzr+http://code.0x2620.org/python-ox/#egg=python-ox
-e bzr+http://code.0x2620.org/oxtimeline/#egg=oxtimeline -e bzr+http://code.0x2620.org/oxtimelines/#egg=oxtimelines
simplejson simplejson
chardet chardet
celery>=2.4.2 celery>=2.4.2

View file

@ -28,12 +28,10 @@ pandora.ui.editor = function(data) {
return '/' + pandora.user.ui.item + '/' + pandora.user.ui.videoResolution + 'p' + position + '.jpg'; return '/' + pandora.user.ui.item + '/' + pandora.user.ui.videoResolution + 'p' + position + '.jpg';
}, },
getLargeTimelineURL: function(type, i) { getLargeTimelineURL: function(type, i) {
type = ''; return '/' + pandora.user.ui.item + '/timeline' + type + '64p' + i + '.jpg';
return '/' + pandora.user.ui.item + '/timeline' + type + '64p' + i + '.png';
}, },
getSmallTimelineURL: function(type, i) { getSmallTimelineURL: function(type, i) {
type = ''; return '/' + pandora.user.ui.item + '/timeline' + type + '16p' + i + '.jpg';
return '/' + pandora.user.ui.item + '/timeline' + type + '16p' + i + '.png';
}, },
height: pandora.$ui.contentPanel.size(1), height: pandora.$ui.contentPanel.size(1),
id: 'editor', id: 'editor',

View file

@ -309,8 +309,7 @@ pandora.ui.list = function() {
duration: data.duration, duration: data.duration,
find: isClipsQuery ? clipsQuery.conditions[0].value : '', find: isClipsQuery ? clipsQuery.conditions[0].value : '',
getImageURL: function(type, i) { getImageURL: function(type, i) {
type = ''; return '/' + data.id + '/timeline' + type + '16p' + i + '.jpg';
return '/' + data.id + '/timeline' + type + '16p' + i + '.png';
}, },
position: pandora.user.ui.videoPoints[data.id] position: pandora.user.ui.videoPoints[data.id]
? pandora.user.ui.videoPoints[data.id].position : 0, ? pandora.user.ui.videoPoints[data.id].position : 0,

View file

@ -23,8 +23,7 @@ pandora.ui.player = function(data) {
enableSubtitles: pandora.user.ui.videoSubtitles, enableSubtitles: pandora.user.ui.videoSubtitles,
find: pandora.user.ui.itemFind, find: pandora.user.ui.itemFind,
getLargeTimelineURL: function(type, i) { getLargeTimelineURL: function(type, i) {
type = ''; return '/' + pandora.user.ui.item + '/timeline' + type + '64p' + i + '.jpg';
return '/' + pandora.user.ui.item + '/timeline' + type + '64p' + i + '.png';
}, },
height: pandora.$ui.contentPanel.size(1), height: pandora.$ui.contentPanel.size(1),
'in': pandora.user.ui.videoPoints[pandora.user.ui.item]['in'], 'in': pandora.user.ui.videoPoints[pandora.user.ui.item]['in'],
@ -43,7 +42,7 @@ pandora.ui.player = function(data) {
showLayers: Ox.clone(pandora.user.ui.showLayers), showLayers: Ox.clone(pandora.user.ui.showLayers),
showUsers: pandora.site.annotations.showUsers, showUsers: pandora.site.annotations.showUsers,
showTimeline: pandora.user.ui.showTimeline, showTimeline: pandora.user.ui.showTimeline,
smallTimelineURL: '/' + pandora.user.ui.item + '/timeline16p.png', smallTimelineURL: '/' + pandora.user.ui.item + '/timeline16p.jpg',
subtitles: data.subtitles, subtitles: data.subtitles,
timeline: pandora.user.ui.videoTimeline, timeline: pandora.user.ui.videoTimeline,
tooltips: true, tooltips: true,

View file

@ -24,8 +24,7 @@ pandora.ui.timeline = function(data) {
return '/' + ui.item + '/' + ui.videoResolution + 'p' + position + '.jpg'; return '/' + ui.item + '/' + ui.videoResolution + 'p' + position + '.jpg';
}, },
getLargeTimelineURL: function(type, i) { getLargeTimelineURL: function(type, i) {
type = ''; return '/' + ui.item + '/timeline' + type + '64p' + i + '.jpg';
return '/' + ui.item + '/timeline' + type + '64p' + i + '.png';
}, },
height: pandora.$ui.contentPanel.size(1), height: pandora.$ui.contentPanel.size(1),
layers: data.annotations, layers: data.annotations,
@ -40,7 +39,7 @@ pandora.ui.timeline = function(data) {
showAnnotationsMap: ui.showAnnotationsMap, showAnnotationsMap: ui.showAnnotationsMap,
showLayers: Ox.clone(ui.showLayers), showLayers: Ox.clone(ui.showLayers),
showUsers: pandora.site.annotations.showUsers, showUsers: pandora.site.annotations.showUsers,
smallTimelineURL: '/' + ui.item + '/timeline16p.png', smallTimelineURL: '/' + ui.item + '/timeline16p.jpg',
timeline: ui.videoTimeline, timeline: ui.videoTimeline,
timelines: pandora.site.timelines, timelines: pandora.site.timelines,
video: data.video, video: data.video,

View file

@ -76,7 +76,7 @@ pandora.ui.tv = function() {
scaleToFill: pandora.user.ui.videoScale == 'fill', scaleToFill: pandora.user.ui.videoScale == 'fill',
subtitles: videoOptions.subtitles, subtitles: videoOptions.subtitles,
tooltips: true, tooltips: true,
timeline: '/' + result.data.item + '/timeline16p.png', timeline: '/' + result.data.item + '/timeline16p.jpg',
title: pandora.site.site.name + ' &mdash; ' + ( title: pandora.site.site.name + ' &mdash; ' + (
list || 'All ' + pandora.site.itemName.plural list || 'All ' + pandora.site.itemName.plural
) + ' &mdash; ' ) + ' &mdash; '

View file

@ -21,7 +21,7 @@ pandora.ui.videoPreview = function(data) {
height: data.height, height: data.height,
position: data.position, position: data.position,
scaleToFill: true, scaleToFill: true,
timeline: '/' + data.id + '/timeline16p.png', timeline: '/' + data.id + '/timeline16p.jpg',
width: data.width width: data.width
}); });
return that; return that;

View file

@ -2,6 +2,7 @@
pandora_repos=http://code.0x2620.org/pandora/ pandora_repos=http://code.0x2620.org/pandora/
oxjs_repos=http://code.0x2620.org/oxjs/ oxjs_repos=http://code.0x2620.org/oxjs/
python_ox_repos=http://code.0x2620.org/python-ox/ python_ox_repos=http://code.0x2620.org/python-ox/
oxtimelines_repos=http://code.0x2620.org/oxtimelines/
cd `dirname $0` cd `dirname $0`
base=`pwd` base=`pwd`
@ -29,6 +30,11 @@ if [ -e src/python-ox ]; then
new=$new`bzr revno` new=$new`bzr revno`
fi fi
cd $base cd $base
if [ -e src/oxtimelines ]; then
cd src/oxtimelines
bzr pull $oxtimelines_repos
fi
cd $base
if [ $current -ne $new ]; then if [ $current -ne $new ]; then
cd pandora cd pandora
./manage.py update_static ./manage.py update_static

View file

@ -2,6 +2,6 @@
CH="chroot $1" CH="chroot $1"
$CH bzr branch http://code.0x2620.org/pandora /srv/pandora $CH bzr branch http://code.0x2620.org/pandora /srv/pandora
$CH bzr branch http://code.0x2620.org/oxjs /srv/pandora/static/oxjs $CH bzr branch http://code.0x2620.org/oxjs /srv/pandora/static/oxjs
$CH virtualenv /srv/pandora $CH virtualenv --system-site-packages /srv/pandora
$CH pip -E /srv/pandora install -r /srv/pandora/requirements.txt $CH pip -E /srv/pandora install -r /srv/pandora/requirements.txt