merging changes
This commit is contained in:
commit
8d7ed0280a
10 changed files with 175 additions and 49 deletions
|
@ -133,6 +133,13 @@
|
||||||
"group": true,
|
"group": true,
|
||||||
"sort": "person"
|
"sort": "person"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "parts",
|
||||||
|
"title": "Parts",
|
||||||
|
"type": "integer",
|
||||||
|
"columnWidth": 60,
|
||||||
|
"rightsLevel": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "numberofactors",
|
"id": "numberofactors",
|
||||||
"title": "Number of Actors",
|
"title": "Number of Actors",
|
||||||
|
@ -561,6 +568,7 @@
|
||||||
},
|
},
|
||||||
"userLevels": ["guest", "member", "staff", "admin"],
|
"userLevels": ["guest", "member", "staff", "admin"],
|
||||||
"video": {
|
"video": {
|
||||||
|
"download": false,
|
||||||
"formats": ["webm", "mp4"],
|
"formats": ["webm", "mp4"],
|
||||||
"resolutions": [96]
|
"resolutions": [96]
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,6 +161,7 @@ class File(models.Model):
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.auto:
|
if self.auto:
|
||||||
self.set_state()
|
self.set_state()
|
||||||
|
self.available = self.streams.filter(source=None, available=True).count() > 0
|
||||||
super(File, self).save(*args, **kwargs)
|
super(File, self).save(*args, **kwargs)
|
||||||
|
|
||||||
#upload and data handling
|
#upload and data handling
|
||||||
|
@ -498,6 +499,7 @@ class Stream(models.Model):
|
||||||
resolution=resolution, format=f)
|
resolution=resolution, format=f)
|
||||||
if created:
|
if created:
|
||||||
derivative.source = self
|
derivative.source = self
|
||||||
|
derivative.save()
|
||||||
name = derivative.name()
|
name = derivative.name()
|
||||||
derivative.video.name = os.path.join(os.path.dirname(self.video.name), name)
|
derivative.video.name = os.path.join(os.path.dirname(self.video.name), name)
|
||||||
derivative.encode()
|
derivative.encode()
|
||||||
|
@ -528,6 +530,8 @@ class Stream(models.Model):
|
||||||
else:
|
else:
|
||||||
self.aspect_ratio = 128/80
|
self.aspect_ratio = 128/80
|
||||||
super(Stream, self).save(*args, **kwargs)
|
super(Stream, self).save(*args, **kwargs)
|
||||||
|
if self.available and not self.file.available:
|
||||||
|
self.file.save()
|
||||||
|
|
||||||
def json(self):
|
def json(self):
|
||||||
if settings.XSENDFILE or settings.XACCELREDIRECT:
|
if settings.XSENDFILE or settings.XACCELREDIRECT:
|
||||||
|
|
|
@ -91,10 +91,11 @@ def update_files(user, volume, files):
|
||||||
|
|
||||||
@task(queue="encoding")
|
@task(queue="encoding")
|
||||||
def process_stream(fileId):
|
def process_stream(fileId):
|
||||||
file = models.Stream.objects.get(id=fileId)
|
file = models.File.objects.get(id=fileId)
|
||||||
streams = file.streams.filter(source=None)
|
streams = file.streams.filter(source=None)
|
||||||
if streams.count() >0:
|
if streams.count() > 0:
|
||||||
stream = streams[0]
|
stream = streams[0]
|
||||||
stream.make_timeline()
|
stream.make_timeline()
|
||||||
stream.extract_derivatives()
|
stream.extract_derivatives()
|
||||||
|
file.item.update_timeline()
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -13,7 +13,7 @@ import unicodedata
|
||||||
from urllib import quote
|
from urllib import quote
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Sum, Count
|
from django.db.models import Count, Q, Sum
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.utils import simplejson as json
|
from django.utils import simplejson as json
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -30,14 +30,15 @@ import ox.image
|
||||||
import managers
|
import managers
|
||||||
import utils
|
import utils
|
||||||
import tasks
|
import tasks
|
||||||
from archive import extract
|
from .timelines import join_timelines
|
||||||
|
|
||||||
|
from archive import extract
|
||||||
from annotation.models import Annotation, Layer
|
from annotation.models import Annotation, Layer
|
||||||
from person.models import get_name_sort
|
from person.models import get_name_sort
|
||||||
from app.models import site_config
|
from app.models import site_config
|
||||||
|
|
||||||
|
|
||||||
def get_item(info, user=None):
|
def get_item(info, user=None, async=False):
|
||||||
'''
|
'''
|
||||||
info dict with:
|
info dict with:
|
||||||
imdbId, title, director, episode_title, season, series
|
imdbId, title, director, episode_title, season, series
|
||||||
|
@ -56,7 +57,10 @@ def get_item(info, user=None):
|
||||||
}
|
}
|
||||||
item.user = user
|
item.user = user
|
||||||
item.save()
|
item.save()
|
||||||
tasks.update_external.delay(item.itemId)
|
if async:
|
||||||
|
tasks.update_external.delay(item.itemId)
|
||||||
|
else:
|
||||||
|
item.update_external()
|
||||||
else:
|
else:
|
||||||
q = Item.objects.all()
|
q = Item.objects.all()
|
||||||
for key in ('title', 'director', 'year'):
|
for key in ('title', 'director', 'year'):
|
||||||
|
@ -789,17 +793,23 @@ class Item(models.Model):
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def streams(self):
|
def streams(self):
|
||||||
return [video.streams.filter(source=None)[0] for video in self.main_videos()]
|
return [video.streams.filter(source=None, available=True)[0]
|
||||||
|
for video in self.main_videos()]
|
||||||
|
|
||||||
def update_timeline(self, force=False):
|
def update_timeline(self, force=False):
|
||||||
|
config = site_config()
|
||||||
streams = self.streams()
|
streams = self.streams()
|
||||||
self.make_timeline()
|
self.make_timeline()
|
||||||
self.data['cuts'] = extract.cuts(self.timeline_prefix)
|
self.data['cuts'] = extract.cuts(self.timeline_prefix)
|
||||||
self.data['color'] = extract.average_color(self.timeline_prefix)
|
self.data['color'] = extract.average_color(self.timeline_prefix)
|
||||||
#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.make_local_poster()
|
self.make_local_poster()
|
||||||
self.make_poster()
|
self.make_poster()
|
||||||
self.make_icon()
|
self.make_icon()
|
||||||
|
if config['video']['download']:
|
||||||
|
self.make_torrent()
|
||||||
|
self.load_subtitles()
|
||||||
self.rendered = streams != []
|
self.rendered = streams != []
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
@ -855,7 +865,8 @@ class Item(models.Model):
|
||||||
def make_timeline(self):
|
def make_timeline(self):
|
||||||
streams = self.streams()
|
streams = self.streams()
|
||||||
if len(streams) > 1:
|
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):
|
def make_poster(self, force=False):
|
||||||
if not self.poster or force:
|
if not self.poster or force:
|
||||||
|
@ -960,6 +971,43 @@ class Item(models.Model):
|
||||||
os.unlink(f)
|
os.unlink(f)
|
||||||
return icon
|
return icon
|
||||||
|
|
||||||
|
def load_subtitles(self):
|
||||||
|
layer = Layer.objects.get(name='subtitles')
|
||||||
|
Annotation.objects.filter(layer=layer,item=self).delete()
|
||||||
|
offset = 0
|
||||||
|
language = ''
|
||||||
|
languages = [f.language for f in self.files.filter(is_main=True, is_subtitle=True,
|
||||||
|
available=True)]
|
||||||
|
if languages:
|
||||||
|
if 'en' in languages:
|
||||||
|
language = 'en'
|
||||||
|
elif '' in languages:
|
||||||
|
language = ''
|
||||||
|
else:
|
||||||
|
language = languages[0]
|
||||||
|
for f in self.files.filter(is_main=True, is_subtitle=True,
|
||||||
|
available=True, language=language).order_by('part'):
|
||||||
|
user = f.instances.all()[0].volume.user
|
||||||
|
for data in f.srt(offset):
|
||||||
|
annotation = Annotation(
|
||||||
|
item=f.item,
|
||||||
|
layer=layer,
|
||||||
|
start=data['in'],
|
||||||
|
end=data['out'],
|
||||||
|
value=data['value'],
|
||||||
|
user=user
|
||||||
|
)
|
||||||
|
annotation.save()
|
||||||
|
duration = self.files.filter(Q(is_audio=True)|Q(is_video=True)) \
|
||||||
|
.filter(is_main=True, available=True, part=f.part)
|
||||||
|
if duration:
|
||||||
|
duration = duration[0].duration
|
||||||
|
else:
|
||||||
|
Annotation.objects.filter(layer=layer,item=self).delete()
|
||||||
|
break
|
||||||
|
offset += duration
|
||||||
|
self.update_find()
|
||||||
|
|
||||||
def delete_item(sender, **kwargs):
|
def delete_item(sender, **kwargs):
|
||||||
i = kwargs['instance']
|
i = kwargs['instance']
|
||||||
i.delete_files()
|
i.delete_files()
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from celery.decorators import task, periodic_task
|
from celery.decorators import task, periodic_task
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
import models
|
import models
|
||||||
|
|
||||||
|
@ -32,39 +31,5 @@ def update_timeline(itemId):
|
||||||
|
|
||||||
def load_subtitles(itemId):
|
def load_subtitles(itemId):
|
||||||
item = models.Item.objects.get(itemId=itemId)
|
item = models.Item.objects.get(itemId=itemId)
|
||||||
layer = models.Layer.objects.get(name='subtitles')
|
item.load_subtitles()
|
||||||
models.Annotation.objects.filter(layer=layer,item=item).delete()
|
|
||||||
offset = 0
|
|
||||||
language = ''
|
|
||||||
languages = [f.language for f in item.files.filter(is_main=True, is_subtitle=True,
|
|
||||||
available=True)]
|
|
||||||
if languages:
|
|
||||||
if 'en' in languages:
|
|
||||||
language = 'en'
|
|
||||||
elif '' in languages:
|
|
||||||
language = ''
|
|
||||||
else:
|
|
||||||
language = languages[0]
|
|
||||||
for f in item.files.filter(is_main=True, is_subtitle=True,
|
|
||||||
available=True, language=language).order_by('part'):
|
|
||||||
user = f.instances.all()[0].volume.user
|
|
||||||
for data in f.srt(offset):
|
|
||||||
annotation = models.Annotation(
|
|
||||||
item=f.item,
|
|
||||||
layer=layer,
|
|
||||||
start=data['in'],
|
|
||||||
end=data['out'],
|
|
||||||
value=data['value'],
|
|
||||||
user=user
|
|
||||||
)
|
|
||||||
annotation.save()
|
|
||||||
duration = item.files.filter(Q(is_audio=True)|Q(is_video=True)) \
|
|
||||||
.filter(is_main=True, available=True, part=f.part)
|
|
||||||
if duration:
|
|
||||||
duration = duration[0].duration
|
|
||||||
else:
|
|
||||||
models.Annotation.objects.filter(layer=layer,item=item).delete()
|
|
||||||
break
|
|
||||||
offset += duration
|
|
||||||
item.update_find()
|
|
||||||
|
|
||||||
|
|
101
pandora/item/timelines.py
Normal file
101
pandora/item/timelines.py
Normal file
|
@ -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)
|
||||||
|
|
|
@ -446,7 +446,8 @@
|
||||||
},
|
},
|
||||||
"userLevels": ["guest", "member", "staff", "admin"],
|
"userLevels": ["guest", "member", "staff", "admin"],
|
||||||
"video": {
|
"video": {
|
||||||
"formats": ["webm", "h264"],
|
"download": true,
|
||||||
|
"formats": ["webm", "mp4"],
|
||||||
"resolutions": [480, 240, 96]
|
"resolutions": [480, 240, 96]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
js/jquery/
|
|
1
static/js/jquery/jquery.tmpl.min.js
vendored
1
static/js/jquery/jquery.tmpl.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -502,7 +502,7 @@ pandora.ui.infoView = function(data) {
|
||||||
$reflectionIcon.attr({src: src});
|
$reflectionIcon.attr({src: src});
|
||||||
iconSize = iconSize == 256 ? 512 : 256;
|
iconSize = iconSize == 256 ? 512 : 256;
|
||||||
iconRatio = pandora.user.ui.icons == 'posters'
|
iconRatio = pandora.user.ui.icons == 'posters'
|
||||||
? data.poster.width / data.poster.height : 1;
|
? data.posterRatio : 1;
|
||||||
toggleIconSize();
|
toggleIconSize();
|
||||||
pandora.user.level == 'admin' && $list.replaceWith($list = renderList());
|
pandora.user.level == 'admin' && $list.replaceWith($list = renderList());
|
||||||
};
|
};
|
||||||
|
@ -515,4 +515,4 @@ pandora.ui.infoView = function(data) {
|
||||||
|
|
||||||
return that;
|
return that;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue