findClips

This commit is contained in:
j 2011-10-02 20:16:28 +02:00
parent 4442a9aaf3
commit 78b07dd64a
11 changed files with 228 additions and 14 deletions

View file

@ -6,9 +6,11 @@ from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
import ox import ox
from archive import extract
from clip.models import Clip
import utils import utils
import managers import managers
from archive import extract
def load_layers(layers): def load_layers(layers):
@ -79,6 +81,7 @@ class Annotation(models.Model):
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User) user = models.ForeignKey(User)
item = models.ForeignKey('item.Item', related_name='annotations') item = models.ForeignKey('item.Item', related_name='annotations')
clip = models.ForeignKey('clip.Clip', null=True, related_name='annotations')
public_id = models.CharField(max_length=128, unique=True) public_id = models.CharField(max_length=128, unique=True)
#seconds #seconds
@ -127,14 +130,26 @@ class Annotation(models.Model):
self.set_public_id() self.set_public_id()
if self.duration != self.end - self.start: if self.duration != self.end - self.start:
self.update_calculated_values() self.update_calculated_values()
if not self.clip and not self.layer.private:
self.clip, created = Clip.objects.get_or_create(item=self.item,
start=self.start,
end=self.end)
if created:
#FIXME, only clips should have those values
self.clip.duration = self.duration
self.clip.hue = self.hue
self.clip.saturation = self.saturation
self.clip.lightness = self.lightness
self.clip.volume = self.volume
self.clip.save()
super(Annotation, self).save(*args, **kwargs) super(Annotation, self).save(*args, **kwargs)
def json(self, layer=False, keys=None): def json(self, layer=False, keys=None):
j = { j = {
'user': self.user.username, 'user': self.user.username,
} }
for field in ('id', 'in', 'out', 'value', 'created', 'modified', for field in ('id', 'in', 'out', 'value', 'created', 'modified'):
'hue', 'saturation', 'lightness', 'volume'):
j[field] = getattr(self, { j[field] = getattr(self, {
'id': 'public_id', 'id': 'public_id',
'in': 'start', 'in': 'start',

View file

@ -318,6 +318,9 @@ def average_color(prefix, start=0, end=0):
color = list(map(float, color)) color = list(map(float, color))
return ox.image.getHSL(color) return ox.image.getHSL(color)
def average_volume(prefix, start=0, end=0):
#FIXME: actually compute volume
return 0
def get_distance(rgb0, rgb1): def get_distance(rgb0, rgb1):
dst = math.sqrt(pow(rgb0[0] - rgb1[0], 2) + pow(rgb0[0] - rgb1[0], 2) + pow(rgb0[0] - rgb1[0], 2)) dst = math.sqrt(pow(rgb0[0] - rgb1[0], 2) + pow(rgb0[0] - rgb1[0], 2) + pow(rgb0[0] - rgb1[0], 2))

0
pandora/clip/__init__.py Normal file
View file

85
pandora/clip/models.py Normal file
View file

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import division, with_statement
from django.db import models
from django.conf import settings
import ox
from archive import extract
import managers
class Clip(models.Model):
class Meta:
unique_together = ("item", "start", "end")
objects = managers.ClipManager()
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
public_id = models.CharField(max_length=128, unique=True)
item = models.ForeignKey('item.Item', related_name='clips')
#seconds
start = models.FloatField(default=-1)
end = models.FloatField(default=-1)
duration = models.FloatField(default=0)
#get from annotation
duration = models.FloatField(default=0, db_index=True)
hue = models.FloatField(default=0, db_index=True)
saturation = models.FloatField(default=0, db_index=True)
lightness = models.FloatField(default=0, db_index=True)
volume = models.FloatField(default=0, db_index=True)
def update_calculated_values(self):
self.duration = self.end - self.start
if self.duration > 0:
self.hue, self.saturation, self.lightness = extract.average_color(
self.item.timeline_prefix, self.start, self.end)
self.volume = extract.average_volume(self.item.timeline_prefix, self.start, self.end)
else:
self.hue = self.saturation = self.lightness = 0
self.volume = 0
def save(self, *args, **kwargs):
self.public_id = u"%s/%s-%s" %(self.item.itemId, self.start, self.end)
if self.duration != self.end - self.start:
self.update_calculated_values()
super(Clip, self).save(*args, **kwargs)
def set_public_id(self):
self.public_id = u"%s/%s-%s" %(self.item.itemId, self.start, self.end)
def json(self, keys=None):
j = {}
for field in ('id', 'in', 'out', 'created', 'modified',
'hue', 'saturation', 'lightness', 'volume'):
j[field] = getattr(self, {
'id': 'public_id',
'in': 'start',
'out': 'end',
}.get(field, field))
if keys:
_j = {}
for key in keys:
if key in j:
_j[key] = j[key]
j = _j
if 'videoRatio' in keys:
streams = self.item.streams()
if streams:
j['videoRatio'] = streams[0].aspect_ratio
public_layers = [l['id']
for l in filter(lambda l: not l.get('private', False),
settings.CONFIG['layers'])]
for layer in filter(lambda l: l in keys, public_layers):
j[layer] = [a.json(keys=['value'])['value']
for a in self.annotations.filter(layer__name=layer)]
return j
def __unicode__(self):
return u"%s/%s-%s" %(self.item, self.start, self.end)

16
pandora/clip/tests.py Normal file
View file

@ -0,0 +1,16 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

93
pandora/clip/views.py Normal file
View file

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import division
from ox.utils import json
from ox.django.shortcuts import render_to_json_response, json_response
from api.actions import actions
from item.models import Item
from item import utils
import models
def parse_query(data, user):
query = {}
query['range'] = [0, 100]
query['sort'] = [{'key':'in', 'operator':'+'}]
for key in ('keys', 'group', 'range', 'sort', 'query'):
if key in data:
query[key] = data[key]
query['qs'] = models.Clip.objects.find(query, user)
if 'itemQuery' in data:
item_query = Item.objects.find({'query': data['itemQuery']}, user)
query['qs'] = query['qs'].filter(item__in=item_query)
return query
def order_query(qs, sort):
order_by = []
for e in sort:
operator = e['operator']
if operator != '-':
operator = ''
key = {
'in': 'start',
'out': 'end',
}.get(e['key'], e['key'])
if key.startswith('clip:'):
key = e['key'][len('clip:'):]
key = {
'text': 'annotations__value',
'position': 'start',
}.get(key, key)
elif key not in ('start', 'end', 'annotations__value'):
#key mgith need to be changed, see order_sort in item/views.py
key = "item__sort__%s" % key
order = '%s%s' % (operator, key)
order_by.append(order)
if order_by:
qs = qs.order_by(*order_by, nulls_last=True)
qs = qs.distinct()
return qs
def findClips(request):
'''
param data {
query: ...
itemQuery: ...
}
return {
'status': {'code': int, 'text': string}
'data': {
items = [{..}, {...}, ...]
}
}
'''
data = json.loads(request.POST['data'])
response = json_response()
query = parse_query(data, request.user)
qs = order_query(query['qs'], query['sort'])
if 'keys' in data:
qs = qs[query['range'][0]:query['range'][1]]
response['data']['items'] = [p.json(keys=data['keys']) for p in qs]
elif 'position' in query:
ids = [i.public_id for i in qs]
data['conditions'] = data['conditions'] + {
'value': data['position'],
'key': query['sort'][0]['key'],
'operator': '^'
}
query = parse_query(data, request.user)
qs = order_query(query['qs'], query['sort'])
if qs.count() > 0:
response['data']['position'] = utils.get_positions(ids, [qs[0].itemId])[0]
elif 'positions' in data:
ids = [i.public_id for i in qs]
response['data']['positions'] = utils.get_positions(ids, data['positions'])
else:
response['data']['items'] = qs.count()
return render_to_json_response(response)
actions.register(findClips)

View file

@ -123,6 +123,7 @@ INSTALLED_APPS = (
'app', 'app',
'annotation', 'annotation',
'clip',
'archive', 'archive',
'event', 'event',
'item', 'item',

View file

@ -24,12 +24,13 @@ pandora.ui.clipList = function(videoRatio) {
info = ['hue', 'saturation', 'lightness'].indexOf(sortKey) > -1 info = ['hue', 'saturation', 'lightness'].indexOf(sortKey) > -1
? Ox.formatColor(data[sortKey], sortKey) ? Ox.formatColor(data[sortKey], sortKey)
: Ox.formatDuration(data['in'], 'short') + ' - ' : Ox.formatDuration(data['in'], 'short') + ' - '
+ Ox.formatDuration(data['out'], 'short'); + Ox.formatDuration(data['out'], 'short'),
title = data.subtitles[0]; //fixme: could be other layer
return { return {
height: height, height: height,
id: data.id, id: data.id,
info: info, info: info,
title: data.value, title: title,
url: url, url: url,
width: width width: width
}; };
@ -65,13 +66,13 @@ pandora.ui.clipList = function(videoRatio) {
// we'll need something like itemFind (vs. listFind) // we'll need something like itemFind (vs. listFind)
query = {conditions: [], operator: '&'}; query = {conditions: [], operator: '&'};
} }
pandora.api.findAnnotations(Ox.extend({ pandora.api.findClips(Ox.extend({
itemQuery: itemQuery, itemQuery: itemQuery,
query: query query: query
}, data), callback); }, data), callback);
}, },
keys: Ox.merge( keys: Ox.merge(
['id', 'in', 'out', 'value'], ['id', 'in', 'out', 'subtitles'], //fixme: could be other layer
!ui.item ? ['videoRatio'] : [] !ui.item ? ['videoRatio'] : []
), ),
max: 1, max: 1,

View file

@ -60,7 +60,7 @@ pandora.ui.item = function() {
select: function(event) { select: function(event) {
pandora.$ui.clips.options({ pandora.$ui.clips.options({
items: function(data, callback) { items: function(data, callback) {
pandora.api.findAnnotations(Ox.extend(data, { pandora.api.findClips(Ox.extend(data, {
query: { query: {
conditions:[{ conditions:[{
key: 'event', key: 'event',

View file

@ -245,11 +245,11 @@ pandora.ui.list = function() {
query.conditions.push({key: 'value', value: q.value, operator: q.operator}); query.conditions.push({key: 'value', value: q.value, operator: q.operator});
} }
}); });
pandora.api.findAnnotations(Ox.extend({ pandora.api.findClips(Ox.extend({
query: query, query: query,
itemQuery: itemQuery itemQuery: itemQuery
}, range ? { }, range ? {
keys: ['id', 'in', 'out'], keys: ['id', 'in', 'out', 'subtitles'],
range: range, range: range,
sort: pandora.user.ui.listSort sort: pandora.user.ui.listSort
} : {}), function(result) { } : {}), function(result) {
@ -355,7 +355,7 @@ pandora.ui.list = function() {
select: function(event) { select: function(event) {
pandora.$ui.clips.options({ pandora.$ui.clips.options({
items: function(data, callback) { items: function(data, callback) {
return pandora.api.findAnnotations(Ox.extend(data, { return pandora.api.findClips(Ox.extend(data, {
query: { query: {
conditions:[{key: 'event', conditions:[{key: 'event',
value: event.id, value: event.id,

View file

@ -54,7 +54,7 @@ pandora.ui.mapView = function(videoRatio) {
operator: '&' operator: '&'
}; };
} }
return pandora.api.findAnnotations(Ox.extend({ return pandora.api.findClips(Ox.extend({
itemQuery: itemQuery, itemQuery: itemQuery,
query: { query: {
conditions: [{key: 'place', value: id, operator:'=='}], conditions: [{key: 'place', value: id, operator:'=='}],