From 78b07dd64aa0d947a5d0cb35835ab02fd65a46fd Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Sun, 2 Oct 2011 20:16:28 +0200 Subject: [PATCH] findClips --- pandora/annotation/models.py | 21 ++++++-- pandora/archive/extract.py | 3 ++ pandora/clip/__init__.py | 0 pandora/clip/models.py | 85 +++++++++++++++++++++++++++++ pandora/clip/tests.py | 16 ++++++ pandora/clip/views.py | 93 ++++++++++++++++++++++++++++++++ pandora/settings.py | 1 + static/js/pandora/ui/clipList.js | 11 ++-- static/js/pandora/ui/item.js | 2 +- static/js/pandora/ui/list.js | 6 +-- static/js/pandora/ui/mapView.js | 4 +- 11 files changed, 228 insertions(+), 14 deletions(-) create mode 100644 pandora/clip/__init__.py create mode 100644 pandora/clip/models.py create mode 100644 pandora/clip/tests.py create mode 100644 pandora/clip/views.py diff --git a/pandora/annotation/models.py b/pandora/annotation/models.py index f048828b..0dbeb057 100644 --- a/pandora/annotation/models.py +++ b/pandora/annotation/models.py @@ -6,9 +6,11 @@ from django.db import models from django.contrib.auth.models import User import ox +from archive import extract +from clip.models import Clip + import utils import managers -from archive import extract def load_layers(layers): @@ -79,6 +81,7 @@ class Annotation(models.Model): modified = models.DateTimeField(auto_now=True) user = models.ForeignKey(User) 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) #seconds @@ -127,14 +130,26 @@ class Annotation(models.Model): self.set_public_id() if self.duration != self.end - self.start: 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) def json(self, layer=False, keys=None): j = { 'user': self.user.username, } - for field in ('id', 'in', 'out', 'value', 'created', 'modified', - 'hue', 'saturation', 'lightness', 'volume'): + for field in ('id', 'in', 'out', 'value', 'created', 'modified'): + j[field] = getattr(self, { 'id': 'public_id', 'in': 'start', diff --git a/pandora/archive/extract.py b/pandora/archive/extract.py index 61088edb..2fd42a51 100644 --- a/pandora/archive/extract.py +++ b/pandora/archive/extract.py @@ -318,6 +318,9 @@ def average_color(prefix, start=0, end=0): color = list(map(float, 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): dst = math.sqrt(pow(rgb0[0] - rgb1[0], 2) + pow(rgb0[0] - rgb1[0], 2) + pow(rgb0[0] - rgb1[0], 2)) diff --git a/pandora/clip/__init__.py b/pandora/clip/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pandora/clip/models.py b/pandora/clip/models.py new file mode 100644 index 00000000..aec284af --- /dev/null +++ b/pandora/clip/models.py @@ -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) + diff --git a/pandora/clip/tests.py b/pandora/clip/tests.py new file mode 100644 index 00000000..501deb77 --- /dev/null +++ b/pandora/clip/tests.py @@ -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) diff --git a/pandora/clip/views.py b/pandora/clip/views.py new file mode 100644 index 00000000..05a3884b --- /dev/null +++ b/pandora/clip/views.py @@ -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) diff --git a/pandora/settings.py b/pandora/settings.py index c1f4658f..7a68aec1 100644 --- a/pandora/settings.py +++ b/pandora/settings.py @@ -123,6 +123,7 @@ INSTALLED_APPS = ( 'app', 'annotation', + 'clip', 'archive', 'event', 'item', diff --git a/static/js/pandora/ui/clipList.js b/static/js/pandora/ui/clipList.js index 759325b8..7cb71f48 100644 --- a/static/js/pandora/ui/clipList.js +++ b/static/js/pandora/ui/clipList.js @@ -24,12 +24,13 @@ pandora.ui.clipList = function(videoRatio) { info = ['hue', 'saturation', 'lightness'].indexOf(sortKey) > -1 ? Ox.formatColor(data[sortKey], sortKey) : 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 { height: height, id: data.id, info: info, - title: data.value, + title: title, url: url, width: width }; @@ -65,13 +66,13 @@ pandora.ui.clipList = function(videoRatio) { // we'll need something like itemFind (vs. listFind) query = {conditions: [], operator: '&'}; } - pandora.api.findAnnotations(Ox.extend({ + pandora.api.findClips(Ox.extend({ itemQuery: itemQuery, query: query }, data), callback); }, keys: Ox.merge( - ['id', 'in', 'out', 'value'], + ['id', 'in', 'out', 'subtitles'], //fixme: could be other layer !ui.item ? ['videoRatio'] : [] ), max: 1, @@ -178,4 +179,4 @@ pandora.ui.clipList = function(videoRatio) { return that; -} \ No newline at end of file +} diff --git a/static/js/pandora/ui/item.js b/static/js/pandora/ui/item.js index 0283d5d9..e0d19b8b 100644 --- a/static/js/pandora/ui/item.js +++ b/static/js/pandora/ui/item.js @@ -60,7 +60,7 @@ pandora.ui.item = function() { select: function(event) { pandora.$ui.clips.options({ items: function(data, callback) { - pandora.api.findAnnotations(Ox.extend(data, { + pandora.api.findClips(Ox.extend(data, { query: { conditions:[{ key: 'event', diff --git a/static/js/pandora/ui/list.js b/static/js/pandora/ui/list.js index d8514e40..dd3e5ffc 100644 --- a/static/js/pandora/ui/list.js +++ b/static/js/pandora/ui/list.js @@ -245,11 +245,11 @@ pandora.ui.list = function() { query.conditions.push({key: 'value', value: q.value, operator: q.operator}); } }); - pandora.api.findAnnotations(Ox.extend({ + pandora.api.findClips(Ox.extend({ query: query, itemQuery: itemQuery }, range ? { - keys: ['id', 'in', 'out'], + keys: ['id', 'in', 'out', 'subtitles'], range: range, sort: pandora.user.ui.listSort } : {}), function(result) { @@ -355,7 +355,7 @@ pandora.ui.list = function() { select: function(event) { pandora.$ui.clips.options({ items: function(data, callback) { - return pandora.api.findAnnotations(Ox.extend(data, { + return pandora.api.findClips(Ox.extend(data, { query: { conditions:[{key: 'event', value: event.id, diff --git a/static/js/pandora/ui/mapView.js b/static/js/pandora/ui/mapView.js index 6459f96e..323879dc 100644 --- a/static/js/pandora/ui/mapView.js +++ b/static/js/pandora/ui/mapView.js @@ -54,7 +54,7 @@ pandora.ui.mapView = function(videoRatio) { operator: '&' }; } - return pandora.api.findAnnotations(Ox.extend({ + return pandora.api.findClips(Ox.extend({ itemQuery: itemQuery, query: { conditions: [{key: 'place', value: id, operator:'=='}], @@ -221,4 +221,4 @@ pandora.ui.mapView = function(videoRatio) { return that; -}; \ No newline at end of file +};