diff --git a/pandora/settings.py b/pandora/settings.py index 3314ad40..0c33a85a 100644 --- a/pandora/settings.py +++ b/pandora/settings.py @@ -129,6 +129,7 @@ INSTALLED_APPS = ( 'place', 'text', 'torrent', + 'timeline', 'user', 'api', ) diff --git a/pandora/timeline/__init__.py b/pandora/timeline/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pandora/timeline/models.py b/pandora/timeline/models.py new file mode 100644 index 00000000..cd8dae5f --- /dev/null +++ b/pandora/timeline/models.py @@ -0,0 +1,51 @@ +# -*- 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.contrib.auth.models import User + + +class Timeline(models.Model): + + class Meta: + unique_together = ("user", "name") + + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + user = models.ForeignKey(User) + name = models.CharField(max_length=255) + public = models.BooleanField(default=False) + + duration = models.FloatField(default=0) + #FIXME: how to deal with width/height? + def __unicode__(self): + return u'%s (%s)' % (self.title, self.user) + + def editable(self, user): + #FIXME: make permissions work + if self.user == user or user.has_perm('Ox.admin'): + return True + return False + + ''' + #creating a new file from clips seams to work not to bad, needs testing for frame accuracy + ffmpeg -i 96p.webm -ss 123.33 -t 3 -vcodec copy -acodec copy 1.webm + ffmpeg -i 96p.webm -ss 323.33 -t 4 -vcodec copy -acodec copy 2.webm + ffmpeg -i 96p.webm -ss 423.33 -t 1 -vcodec copy -acodec copy 3.webm + mkvmerge 1.webm +2.webm +3.webm -o cutup.webm + ''' + + +class Clip(models.Model): + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + timeline = models.ForeignKey(Timeline) + position = models.IntegerField(default=0) #clip position + timeline_position = models.FloatField(default=0) #time on timeline + item = models.ForeignKey("item.Item") + start = models.FloatField(default=0) + end = models.FloatField(default=-1) + + def __unicode__(self): + return u'%s/%0.3f-%0.3f' % (self.item.itemId, self.start, self.end) diff --git a/pandora/timeline/tests.py b/pandora/timeline/tests.py new file mode 100644 index 00000000..2247054b --- /dev/null +++ b/pandora/timeline/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these 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.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/pandora/timeline/views.py b/pandora/timeline/views.py new file mode 100644 index 00000000..96648805 --- /dev/null +++ b/pandora/timeline/views.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division + +from ox.utils import json +from ox.django.decorators import login_required_json +from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response + +from api.actions import actions +import models + + +@login_required_json +def addClip(request): + ''' + param data + {timeline: timelineId, + item: itemId, + start: float, + end: float, + } + return {'status': {'code': int, 'text': string}, + 'data': {}} + ''' + data = json.loads(request.POST['data']) + list = get_object_or_404_json(models.Timeline, pk=data['list']) + if 'item' in data: + item = get_object_or_404_json(models.Item, pk=data['item']) + if list.editable(request.user): + list.add(item) + response = json_response(status=200, text='item added') + else: + response = json_response(status=403, text='not allowed') + elif 'query' in data: + response = json_response(status=501, text='not implemented') + + else: + response = json_response(status=501, text='not implemented') + return render_to_json_response(response) +actions.register(addClip) + + +@login_required_json +def removeClip(request): + ''' + param data + {timeline: timelineId, + clip: clipId, + } + return {'status': {'code': int, 'text': string}, + 'data': {}} + ''' + data = json.loads(request.POST['data']) + list = get_object_or_404_json(models.Timeline, pk=data['list']) + if 'item' in data: + item = get_object_or_404_json(models.Item, pk=data['item']) + if list.editable(request.user): + list.remove(item) + response = json_response(status=200, text='item removed') + else: + response = json_response(status=403, text='not allowed') + elif 'query' in data: + response = json_response(status=501, text='not implemented') + + else: + response = json_response(status=501, text='not implemented') + return render_to_json_response(response) +actions.register(removeClip) + + +def getTimeline(request): + ''' + param data + {name: value, user: user} + return { + 'status': {'code': int, 'text': string}, + 'data': { + fixme + } + } + +could be + timeline: { + 0: { + itemId:, start, end + }, + 123: { + itemId:, start, end + } + } +or implicit timeline position + timeline: [ + { + itemId:, start, end + }, + { + itemId:, start, end + } + ] + + ''' + response = json_response(status=501, text='not implemented') + return render_to_json_response(response) +actions.register(getTimeline) + + +@login_required_json +def addTimeline(request): + ''' + param data + {name: value} + return {'status': {'code': int, 'text': string}, + 'data': {}} + ''' + data = json.loads(request.POST['data']) + if models.Timeline.filter(name=data['name'], user=request.user).count() == 0: + list = models.Timeline(name=data['name'], user=request.user) + list.save() + response = json_response(status=200, text='created') + else: + response = json_response(status=200, text='list already exists') + response['data']['errors'] = { + 'name': 'List already exists' + } + return render_to_json_response(response) +actions.register(addTimeline) + + +@login_required_json +def editTimeline(request): + ''' + param data + {key: value} + keys: name, public + return {'status': {'code': int, 'text': string}, + 'data': {} + } + ''' + data = json.loads(request.POST['data']) + list = get_object_or_404_json(models.Timeline, pk=data['list']) + if list.editable(request.user): + for key in data: + if key in ('name', 'public'): + setattr(list, key, data['key']) + else: + response = json_response(status=403, text='not allowed') + return render_to_json_response(response) +actions.register(editTimeline) + + +@login_required_json +def removeTimeline(request): + ''' + param data + {key: value} + return {'status': {'code': int, 'text': string}, + 'data': {}} + ''' + data = json.loads(request.POST['data']) + list = get_object_or_404_json(models.Timeline, pk=data['list']) + if list.editable(request.user): + list.delete() + else: + response = json_response(status=403, text='not allowed') + return render_to_json_response(response) +actions.register(removeTimeline) diff --git a/static/js/pandora.js b/static/js/pandora.js index c3c437f2..ce6591d6 100755 --- a/static/js/pandora.js +++ b/static/js/pandora.js @@ -738,6 +738,7 @@ var pandora = new Ox.App({ }), callback); } }); + Ox.print('ZZZ') $.each(app.ui.groups, function(i_, group_) { if (i_ != i) { Ox.print('setting groups request', i, i_) @@ -838,7 +839,7 @@ var pandora = new Ox.App({ duration: video.duration, find: '', frameURL: function(position) { - return '/' + id + '/frame/' + video.width.toString() + '/' + position.toString() + '.jpg' + return '/' + app.user.ui.item + '/frame/' + video.width.toString() + '/' + position.toString() + '.jpg' }, height: app.$ui.contentPanel.size(1), id: 'editor', @@ -865,6 +866,7 @@ var pandora = new Ox.App({ { collapsible: true, element: app.$ui.annotations = ui.annotations(), + //.bindEvent('resize', function(event, data) { Ox.print('resize annotations', data); }), resizable: true, resize: [256, 384], size: 256