testing interface, more work on backend

This commit is contained in:
j 2009-10-05 00:00:08 +02:00
parent b1d4411dc2
commit 88c5c3d9ac
20 changed files with 1883 additions and 311 deletions

0
app/__init__.py Normal file
View file

3
app/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

23
app/tests.py Normal file
View file

@ -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
"""}

10
app/views.py Normal file
View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404
from django.template import RequestContext
def index(request):
context = RequestContext(request, {})
return render_to_response('index.html', context)

18
backend/decorators.py Normal file
View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from django.contrib.auth.decorators import user_passes_test
def login_required_json(function=None):
"""
Decorator for views that checks that the user is logged in
return json error if not logged in.
"""
actual_decorator = user_passes_test(
lambda u: u.is_authenticated(),
login_url='/json/login',
)
if function:
return actual_decorator(function)
return actual_decorator

View file

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
import random
import os.path import os.path
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -26,8 +27,15 @@ def loadIMDb(imdbId):
try: try:
movie = models.Movie.byImdbId(imdbId) movie = models.Movie.byImdbId(imdbId)
except models.Movie.DoesNotExist: except models.Movie.DoesNotExist:
#this shound not happen, just in case previous imports failed
try:
imdb = models.MovieImdb.objects.get(imdbId=imdbId)
except models.MovieImdb.DoesNotExist:
imdb = models.MovieImdb()
imdb.imdbId = imdbId
imdb.save()
movie = models.Movie() movie = models.Movie()
movie.imdbId = imdbId movie.imdb = imdb
info = oxweb.imdb.getMovieInfo(imdbId) info = oxweb.imdb.getMovieInfo(imdbId)
for key in ('title', for key in ('title',
@ -40,7 +48,7 @@ def loadIMDb(imdbId):
'season', 'season',
'episode'): 'episode'):
if key in info: if key in info:
setattr(movie, key, info[key]) setattr(movie.imdb, key, info[key])
debug(key, info[key]) debug(key, info[key])
_info_map = { _info_map = {
'episode title': 'episode_title', 'episode title': 'episode_title',
@ -48,18 +56,20 @@ def loadIMDb(imdbId):
} }
for key in _info_map.keys(): for key in _info_map.keys():
if key in info: if key in info:
setattr(movie, _info_map.get(key, key), info[key]) setattr(movie.imdb, _info_map.get(key, key), info[key])
movie.plot = oxweb.imdb.getMoviePlot(imdbId) movie.imdb.plot = oxweb.imdb.getMoviePlot(imdbId)
debug("plot", movie.plot) debug("plot", movie.imdb.plot)
movie.runtime = oxweb.imdb.getMovieRuntimeSeconds(imdbId) movie.imdb.runtime = oxweb.imdb.getMovieRuntimeSeconds(imdbId)
business = oxweb.imdb.getMovieBusinessSum(imdbId) business = oxweb.imdb.getMovieBusinessSum(imdbId)
for key in ('gross', 'profit', 'budget'): for key in ('gross', 'profit', 'budget'):
setattr(movie, key, business[key]) setattr(movie.imdb, key, business[key])
movie.imdb.save()
movie.oxdbId = "__init__%s" % random.randint(0, 100000)
movie.save() movie.save()
models.AlternativeTitle.objects.filter(movie=movie).delete() models.AlternativeTitle.objects.filter(movie=movie, manual=False).delete()
for i in oxweb.imdb.getMovieAKATitles(imdbId): for i in oxweb.imdb.getMovieAKATitles(imdbId):
t = models.AlternativeTitle() t = models.AlternativeTitle()
t.movie = movie t.movie = movie
@ -69,7 +79,7 @@ def loadIMDb(imdbId):
#FIXME: related tables should be cleaned to not accumulate cruft #FIXME: related tables should be cleaned to not accumulate cruft
#Country #Country
models.MovieCountry.objects.filter(movie=movie).delete() models.MovieCountry.objects.filter(movie=movie, manual=False).delete()
position = 0 position = 0
if 'country' in info: if 'country' in info:
for i in info['country']: for i in info['country']:
@ -79,7 +89,7 @@ def loadIMDb(imdbId):
position += 1 position += 1
#Language #Language
models.MovieLanguage.objects.filter(movie=movie).delete() models.MovieLanguage.objects.filter(movie=movie, manual=False).delete()
position = 0 position = 0
if 'language' in info: if 'language' in info:
for i in info['language']: for i in info['language']:
@ -89,7 +99,7 @@ def loadIMDb(imdbId):
position += 1 position += 1
#Location #Location
movie.locations.all().delete() movie.locations_all.filter(manual=False).delete()
locations = oxweb.imdb.getMovieLocations(imdbId) locations = oxweb.imdb.getMovieLocations(imdbId)
for i in locations: for i in locations:
debug("add location", i) debug("add location", i)
@ -97,7 +107,7 @@ def loadIMDb(imdbId):
location.movies.add(movie) location.movies.add(movie)
#Genre #Genre
movie.genres.all().delete() movie.genres_all.filter(manual=False).delete()
if 'genre' in info: if 'genre' in info:
for i in info['genre']: for i in info['genre']:
debug("add genre", i) debug("add genre", i)
@ -105,14 +115,14 @@ def loadIMDb(imdbId):
genre.movies.add(movie) genre.movies.add(movie)
#Keyword #Keyword
movie.keywords.all().delete() movie.keywords_all.filter(manual=False).delete()
keywords = oxweb.imdb.getMovieKeywords(imdbId) keywords = oxweb.imdb.getMovieKeywords(imdbId)
for g in keywords: for g in keywords:
debug("add keyword", g) debug("add keyword", g)
keyword = models.Keyword.get_or_create(g) keyword = models.Keyword.get_or_create(g)
keyword.movies.add(movie) keyword.movies.add(movie)
movie.trivia.all().delete() movie.trivia_all.filter(manual=False).delete()
position = 0 position = 0
trivia = oxweb.imdb.getMovieTrivia(imdbId) trivia = oxweb.imdb.getMovieTrivia(imdbId)
for i in trivia: for i in trivia:
@ -125,6 +135,7 @@ def loadIMDb(imdbId):
position += 1 position += 1
position = 0 position = 0
models.Cast.objects.filter(movie=movie).filter(manual=False).delete()
credits = oxweb.imdb.getMovieCredits(imdbId) credits = oxweb.imdb.getMovieCredits(imdbId)
for role in credits: for role in credits:
for p in credits[role]: for p in credits[role]:
@ -137,18 +148,19 @@ def loadIMDb(imdbId):
models.Cast.link(movie, person, role, character, position) models.Cast.link(movie, person, role, character, position)
position += 1 position += 1
movie.connections.all().delete() movie.connections_all.filter(manual=False).delete()
connections = oxweb.imdb.getMovieConnections(imdbId) connections = oxweb.imdb.getMovieConnections(imdbId)
for relation in connections: for relation in connections:
for otherId in connections[relation]: for otherId in connections[relation]:
try: try:
object = models.Movie.objects.get(imdbId=otherId) object = models.Movie.objects.get(imdb__imdbId=otherId)
debug("add connection", relation, object) debug("add connection", relation, object)
models.Connection.get_or_create(movie, relation, object) models.Connection.get_or_create(movie, relation, object)
except models.Movie.DoesNotExist: except models.Movie.DoesNotExist:
pass pass
reviews = oxweb.imdb.getMovieExternalReviews(imdbId) reviews = oxweb.imdb.getMovieExternalReviews(imdbId)
movie.reviews_all.filter(manual=False).delete()
for r in reviews: for r in reviews:
debug("add review", r) debug("add review", r)
review = models.Review.get_or_create(movie, r) review = models.Review.get_or_create(movie, r)

View file

@ -10,6 +10,7 @@ from django.db.models import Q, Manager
import models import models
def keyType(key): def keyType(key):
if key in ('released'): if key in ('released'):
return "date" return "date"
@ -29,6 +30,7 @@ class MovieManager(Manager):
also checks for lists. also checks for lists.
range and order must be applied later range and order must be applied later
''' '''
q = ''
for i in request.META['QUERY_STRING'].split('&'): for i in request.META['QUERY_STRING'].split('&'):
if i.startswith('q='): if i.startswith('q='):
q = i[2:] q = i[2:]
@ -96,6 +98,8 @@ class MovieManager(Manager):
#join query with operator #join query with operator
qs = self.get_query_set() qs = self.get_query_set()
#only include movies that have hard metadata
qs = qs.filter(available=True)
if conditions: if conditions:
q = conditions[0] q = conditions[0]
for c in conditions[1:]: for c in conditions[1:]:
@ -122,15 +126,27 @@ class MovieManager(Manager):
qs = qs.filter(listitem__list__id=lqs[0].id) qs = qs.filter(listitem__list__id=lqs[0].id)
return qs return qs
class FileManager(Manager):
def get_query_set(self):
return super(FileManager, self).get_query_set()
def movie_files(self, movie):
q = self.get_query_set()
return q.filter(is_video=True, movie=movie)
class ArchiveFileManager(Manager): class ArchiveFileManager(Manager):
def get_query_set(self): def get_query_set(self):
return super(UserFileManager, self).get_query_set() return super(ArchiveFileManager, self).get_query_set()
def movie_files(self, movie):
q = self.get_query_set()
return q.filter(file__is_video=True, file__movie=movie)
def by_oshash(self, oshash): def by_oshash(self, oshash):
q = self.get_query_set() q = self.get_query_set()
q.filter(movie_file__oshash=oshash) q.filter(movie_file__oshash=oshash)
if q.count() == 0: if q.count() == 0:
raise models.UserFile.DoesNotExist("%s matching oshash %s does not exist." % raise models.ArchiveFile.DoesNotExist("%s matching oshash %s does not exist." %
(models.UserFile._meta.object_name, oshash)) (models.ArchiveFile._meta.object_name, oshash))
return q[0] return q[0]

View file

@ -2,10 +2,11 @@
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
import re import re
import os.path import os.path
import random
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
from django.contrib.auth.models import User from django.contrib.auth.models import User
import oxlib import oxlib
from oxlib import stripTags from oxlib import stripTags
from oxlib.normalize import canonicalTitle, canonicalName from oxlib.normalize import canonicalTitle, canonicalName
@ -14,91 +15,166 @@ import utils
import managers import managers
class Movie(models.Model): class MovieImdb(models.Model):
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
accessed = models.DateTimeField(null=True, blank=True)
movieId = models.CharField(max_length=128, unique=True, blank=True) imdbId = models.CharField(max_length=7, unique=True)
imdbId = models.CharField(max_length=7, unique=True, blank=True)
oxdbId = models.CharField(max_length=42, unique=True, blank=True)
title = models.CharField(max_length=1000) title = models.CharField(max_length=1000)
year = models.CharField(max_length=4) year = models.CharField(max_length=4)
runtime = models.IntegerField(null=True, blank=True) runtime = models.IntegerField(null=True, blank=True)
release_date = models.DateField(null=True, blank=True) release_date = models.DateField(null=True, blank=True)
tagline = models.TextField(blank=True) tagline = models.TextField(blank=True)
plot = models.TextField(blank=True) plot = models.TextField(blank=True)
plot_outline = models.TextField(blank=True) plot_outline = models.TextField(blank=True)
rating = models.IntegerField(null=True, blank=True) rating = models.FloatField(null=True, blank=True)
votes = models.IntegerField(null=True, blank=True) votes = models.IntegerField(null=True, blank=True)
budget = models.IntegerField(null=True, blank=True) budget = models.IntegerField(null=True, blank=True)
gross = models.IntegerField(null=True, blank=True) gross = models.IntegerField(null=True, blank=True)
profit = models.IntegerField(null=True, blank=True) profit = models.IntegerField(null=True, blank=True)
#FIXME: how to deal with files now?
#files =
files_modified = models.DateTimeField(auto_now=True)
filename = models.TextField(blank=True)
extracted = models.IntegerField(null=True, blank=True)
#length = models.IntegerField(null=True, blank=True)
duration = models.FloatField(null=True, blank=True)
objects = managers.MovieManager()
#FIXME: should this be a done via a manager for person?
def directors(self):
return self.people.filter(cast__role='directors').order_by('cast__position')
def writers(self):
return self.people.filter(cast__role='writers').order_by('cast__position')
def editors(self):
return self.people.filter(cast__role='editors').order_by('cast__position')
def producers(self):
return self.people.filter(cast__role='producers').order_by('cast__position')
def cinematographers(self):
return self.people.filter(cast__role='cinematographers').order_by('cast__position')
#FIXME: include role and character
def cast(self):
cast = []
for c in Cast.objects.filter(movie=self, role='cast').order_by('position'):
cast.append((c.person.name, c.character))
return tuple(cast)
#return self.person.filter(cast__role='cast').order_by('cast__position')
def connections_json(self):
connections = {}
for connection in self.connections.all():
if connection.relation not in connections:
connections[connection.relation] = []
connections[connection.relation].append(connection.object.movieId)
return connections
def filtered_reviews(self):
whitelist = ReviewWhitelist.objects.all()
q = Q(id=-1)
for w in whitelist:
q = q | Q(url__contains=w.url)
return self.reviews.filter(q)
rights_level = models.IntegerField(default=-1)
#FIXME: use data.0xdb.org
tpb_id = models.CharField(max_length=128, blank=True)
kg_id = models.CharField(max_length=128, blank=True)
open_subtitle_id = models.IntegerField(null=True, blank=True)
wikipedia_url = models.TextField(blank=True)
#Series information
series_imdb = models.CharField(max_length=7, default='') series_imdb = models.CharField(max_length=7, default='')
series_title = models.TextField(blank=True, default='') series_title = models.TextField(blank=True, default='')
episode_title = models.TextField(blank=True, default='') episode_title = models.TextField(blank=True, default='')
season = models.IntegerField(default=-1) season = models.IntegerField(default=-1)
episode = models.IntegerField(default=-1) episode = models.IntegerField(default=-1)
class MovieOxdb(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
title = models.CharField(max_length=1000)
year = models.CharField(max_length=4)
runtime = models.IntegerField(null=True, blank=True)
release_date = models.DateField(null=True, blank=True)
tagline = models.TextField(blank=True)
plot = models.TextField(blank=True)
plot_outline = models.TextField(blank=True)
rating = models.FloatField(null=True, blank=True)
votes = models.IntegerField(null=True, blank=True)
budget = models.IntegerField(null=True, blank=True)
gross = models.IntegerField(null=True, blank=True)
profit = models.IntegerField(null=True, blank=True)
series_imdb = models.CharField(max_length=7, default='')
series_title = models.TextField(blank=True, default='')
episode_title = models.TextField(blank=True, default='')
season = models.IntegerField(default=-1)
episode = models.IntegerField(default=-1)
def newMovie(title, director, year):
movie = Movie()
oxdb = MovieOxdb()
oxdb.save()
movie.oxdb = oxdb
movie.oxdb.title = title
movie.oxdb.year = str(year)
movie.oxdb.save()
movie.oxdbId = "__init__%s" % random.randint(0, 100000)
movie.save()
movie.oxdbId = movie.oxid()
movie.save()
return movie
class Movie(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
#only movies that have metadata from files are available,
#this is indicated by setting available to True
available = models.BooleanField(default=False)
movieId = models.CharField(max_length=128, unique=True, blank=True)
oxdbId = models.CharField(max_length=42, unique=True, blank=True)
imdb = models.OneToOneField('MovieImdb', null=True, related_name='movie')
oxdb = models.OneToOneField('MovieOxdb', null=True, related_name='movie')
objects = managers.MovieManager()
def get(self, key, default=None):
if self.oxdb and getattr(self.oxdb, key):
return getattr(self.oxdb, key)
if self.imdb:
return getattr(self.imdb, key)
return default
def _manual(self, qs, f='manual'):
if qs.filter(**{f:True}).count() > 0:
return qs.exclude(**{f:False})
return qs.exclude(**{f:True})
def directors(self):
qs = self.people.filter(cast__role='directors').order_by('cast__position')
return self._manual(qs, 'cast__manual')
def writers(self):
qs = self.people.filter(cast__role='writers').order_by('cast__position')
return self._manual(qs, 'cast__manual')
def editors(self):
qs = self.people.filter(cast__role='editors').order_by('cast__position')
return self._manual(qs, 'cast__manual')
def producers(self):
qs = self.people.filter(cast__role='producers').order_by('cast__position')
return self._manual(qs, 'cast__manual')
def cinematographers(self):
qs = self.people.filter(cast__role='cinematographers').order_by('cast__position')
return self._manual(qs, 'cast__manual')
def cast(self):
cast = []
qs = Cast.objects.filter(movie=self, role='cast').order_by('position')
qs = self._manual(qs)
for c in qs:
cast.append((c.person.name, c.character))
return tuple(cast)
def alternative_titles(self):
return self._manual(self.alternative_titles_all)
def genres(self):
return self._manual(self.genres_all)
def keywords(self):
return self._manual(self.keywords_all)
def countries(self):
return self._manual(self.countries_all, 'moviecountry__manual')
def languages(self):
return self._manual(self.languages_all, 'movielanguage__manual')
def trivia(self):
return self._manual(self.trivia_all)
def locations(self):
return self._manual(self.locations_all)
def connections(self):
return self._manual(self.connections_all)
def connections_json(self):
connections = {}
for connection in self.connections():
if connection.relation not in connections:
connections[connection.relation] = []
connections[connection.relation].append(connection.object.movieId)
return connections
def reviews(self):
q = self.reviews_all.filter(manual=True)
if q.count() > 0:
return q
whitelist = ReviewWhitelist.objects.all()
q = Q(id=-1)
for w in whitelist:
q = q | Q(url__contains=w.url)
return self.reviews_all.filter(q).filter(manual=False)
rights_level = models.IntegerField(default=-1)
#FIXME: use data.0xdb.org
tpb_id = models.CharField(max_length=128, blank=True)
kg_id = models.CharField(max_length=128, blank=True)
open_subtitle_id = models.IntegerField(null=True, blank=True)
wikipedia_url = models.TextField(blank=True)
#what of this is still required? #what of this is still required?
still_pos = models.IntegerField(null=True, blank=True) still_pos = models.IntegerField(null=True, blank=True)
poster = models.TextField(blank=True) poster = models.TextField(blank=True)
@ -110,11 +186,16 @@ class Movie(models.Model):
scene_height = models.IntegerField(null=True, blank=True) scene_height = models.IntegerField(null=True, blank=True)
def __unicode__(self): def __unicode__(self):
return u'%s (%s)' % (self.title, self.year) return u'%s (%s)' % (self.get('title'), self.get('year'))
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.imdbId: if not self.oxdb:
mid = self.imdbId oxdb = MovieOxdb()
oxdb.save()
self.oxdb = oxdb
if self.imdb:
mid = self.imdb.imdbId
else: else:
mid = self.oxdbId mid = self.oxdbId
self.movieId = mid self.movieId = mid
@ -133,33 +214,41 @@ class Movie(models.Model):
'countries': 'country', 'countries': 'country',
'directors': 'director', 'directors': 'director',
'genres': 'genres', 'languages': 'language',
'keywords': 'keywords', 'genres': 'genre',
'keywords': 'keyword',
'cast': 'cast', 'cast': 'cast',
'series_title': 'series_title', 'series_title': 'series_title',
'episode_title': 'episode_title', 'episode_title': 'episode_title',
'season': 'season', 'season': 'season',
'episode': 'episode', 'episode': 'episode',
'filtered_reviews': 'reviews', 'reviews': 'reviews',
'trivia': 'trivia', 'trivia': 'trivia',
'rating': 'rating',
'votes': 'votes',
'alternative_titles': 'alternative_titles', 'alternative_titles': 'alternative_titles',
'connections': 'connections_json' 'connections_json': 'connections'
} }
def json(self, fields=None): def json(self, fields=None):
movie = {} movie = {}
for key in self._public_fields: for key in self._public_fields:
pub_key = self._public_fields.get(key, key) pub_key = self._public_fields.get(key, key)
if not fields or pub_key in fields: if not fields or pub_key in fields:
value = getattr(self, key) if hasattr(self, key):
if key in ('directors', 'writers', 'filtered_reviews'): value = getattr(self, key)
else:
value = self.get(key)
if key in ('directors', 'writers', 'reviews',
'countries', 'languages', 'keywords', 'genres', 'trivia', 'alternative_titles'):
movie[pub_key] = tuple([v.json() for v in value()]) movie[pub_key] = tuple([v.json() for v in value()])
elif key in ('countries', 'keywords', 'genres', 'trivia', 'alternative_titles'): elif callable(value):
movie[pub_key] = tuple([v.json() for v in value.all()]) movie[pub_key] = value()
else: else:
movie[pub_key] = value movie[pub_key] = value
for f in fields: if fields:
if f.endswith('.length') and f[:-7] in ('cast', 'genre', 'trivia'): for f in fields:
movie[f] = getattr(self.sort.all()[0], f[:-7]) if f.endswith('.length') and f[:-7] in ('cast', 'genre', 'trivia'):
movie[f] = getattr(self.sort.all()[0], f[:-7])
return movie return movie
def fields(self): def fields(self):
@ -181,7 +270,7 @@ class Movie(models.Model):
byMovieId = classmethod(byMovieId) byMovieId = classmethod(byMovieId)
def byImdbId(self, imdbId): def byImdbId(self, imdbId):
return self.objects.get(imdbId=imdbId) return self.objects.get(imdb__imdbId=imdbId)
byImdbId = classmethod(byImdbId) byImdbId = classmethod(byImdbId)
def byOxdbId(self, oxdbId): def byOxdbId(self, oxdbId):
@ -190,43 +279,44 @@ class Movie(models.Model):
def oxid(self): def oxid(self):
directors = ','.join([d.name for d in self.directors()]) directors = ','.join([d.name for d in self.directors()])
return utils.oxid(self.title, directors, self.year, return utils.oxid(self.get('title', ''), directors, self.get('year', ''),
self.series_title, self.episode_title, self.season, self.episode) self.get('series_title', ''), self.get('episode_title', ''),
self.get('season', ''), self.get('episode', ''))
def updateFind(self): def updateFind(self):
if self.find.count() == 0: try:
f = MovieFind() f = self.find
f.movie = self except MovieFind.DoesNotExist:
else: f = MovieFind(movie=self)
f = self.find.all()[0]
f.title = self.title + ' '.join([t.title for t in self.alternative_titles.all()]) f.title = self.get('title') + ' '.join([t.title for t in self.alternative_titles()])
f.director = ' '.join([i.name for i in self.directors()]) f.director = ' '.join([i.name for i in self.directors()])
f.country = ' '.join([i.name for i in self.countries.all()]) f.country = ' '.join([i.name for i in self.countries()])
f.year = self.year f.year = self.get('year', '')
f.language = ' '.join([i.name for i in self.languages.all()]) f.language = ' '.join([i.name for i in self.languages()])
f.writer = ' '.join([i.name for i in self.writers()]) f.writer = ' '.join([i.name for i in self.writers()])
f.producer = ' '.join([i.name for i in self.producers()]) f.producer = ' '.join([i.name for i in self.producers()])
f.editor = ' '.join([i.name for i in self.editors()]) f.editor = ' '.join([i.name for i in self.editors()])
f.cinematographer = ' '.join([i.name for i in self.cinematographers()]) f.cinematographer = ' '.join([i.name for i in self.cinematographers()])
f.cast = ' '.join(['%s %s' % i for i in self.cast()]) f.cast = ' '.join(['%s %s' % i for i in self.cast()])
f.genre = ' '.join([i.name for i in self.genres.all()]) f.genre = ' '.join([i.name for i in self.genres()])
f.keywords = ' '.join([i.name for i in self.keywords.all()]) f.keywords = ' '.join([i.name for i in self.keywords()])
f.summary = self.plot + self.plot_outline f.summary = self.get('plot', '') + self.get('plot_outline', '')
f.trivia = ' '.join([i.trivia for i in self.trivia.all()]) f.trivia = ' '.join([i.trivia for i in self.trivia()])
f.location = ' '.join([i.name for i in self.locations.all()]) f.location = ' '.join([i.name for i in self.locations()])
f.filename = self.filename #FIXME: collate filenames
f.all = ' '.join([f.title, f.director, f.country, f.year, f.language, #f.filename = self.filename
f.all = ' '.join(filter(None, [f.title, f.director, f.country, f.year, f.language,
f.writer, f.producer, f.editor, f.cinematographer, f.writer, f.producer, f.editor, f.cinematographer,
f.cast, f.genre, f.keywords, f.summary, f.trivia, f.cast, f.genre, f.keywords, f.summary, f.trivia,
f.location, f.filename]) f.location, f.filename]))
f.save() f.save()
def updateSort(self): def updateSort(self):
if self.sort.count() == 0: try:
s = MovieSort() s = self.sort
s.movie = self except MovieSort.DoesNotExist:
else: s = MovieSort(movie=self)
s = self.sort.all()[0]
def sortName(value): def sortName(value):
sort_value = '~' sort_value = '~'
@ -239,7 +329,7 @@ class Movie(models.Model):
return sort_value return sort_value
#title #title
title = canonicalTitle(self.title) title = canonicalTitle(self.get('title'))
title = re.sub(u'[\'!¿¡,\.;\-"\:\*\[\]]', '', title) title = re.sub(u'[\'!¿¡,\.;\-"\:\*\[\]]', '', title)
title = title.replace(u'Æ', 'Ae') title = title.replace(u'Æ', 'Ae')
#pad numbered titles #pad numbered titles
@ -253,8 +343,8 @@ class Movie(models.Model):
directors = ','.join([i.name for i in self.directors()]) directors = ','.join([i.name for i in self.directors()])
s.director = sortName(directors) s.director = sortName(directors)
s.country = ','.join([i.name for i in self.countries.all()]) s.country = ','.join([i.name for i in self.countries()])
s.year = self.year s.year = self.get('year', '')
names = ','.join([i.name for i in self.producers()]) names = ','.join([i.name for i in self.producers()])
s.producer = sortName(names) s.producer = sortName(names)
@ -265,16 +355,19 @@ class Movie(models.Model):
names = ','.join([i.name for i in self.cinematographers()]) names = ','.join([i.name for i in self.cinematographers()])
s.cinematographer = sortName(names) s.cinematographer = sortName(names)
s.country = ','.join([i.name for i in self.languages.all()]) s.language = ','.join([i.name for i in self.languages()])
s.runtime = self.runtime s.country = ','.join([i.name for i in self.countries()])
s.runtime = self.get('runtime', 0)
s.keywords = self.keywords.all().count() s.keywords = self.keywords().count()
s.genre = self.genres.all().count() s.genre = self.genres().count()
s.cast = len(self.cast()) s.cast = len(self.cast())
s.summary = len(self.plot.split()) s.summary = len(self.get('plot', '').split())
s.trivia = self.trivia.all().count() s.trivia = self.trivia().count()
s.connections = self.connections.all().count() s.connections = self.connections().count()
s.movieId = self.movieId s.movieId = self.movieId
s.rating = self.get('rating', -1)
s.votes = self.get('votes', -1)
# data from related subtitles # data from related subtitles
s.scenes = 0 #FIXME s.scenes = 0 #FIXME
@ -296,29 +389,29 @@ class MovieFind(models.Model):
""" """
used to search movies, all search values are in here used to search movies, all search values are in here
""" """
movie = models.ForeignKey('Movie', related_name='find', unique=True) movie = models.OneToOneField('Movie', related_name='find', primary_key=True)
all = models.TextField(blank=True) all = models.TextField(blank=True)
title = models.CharField(max_length=1000) title = models.CharField(max_length=1000)
director = models.TextField(blank=True) director = models.TextField(blank=True, default='')
country = models.TextField(blank=True) country = models.TextField(blank=True, default='')
year = models.CharField(max_length=4) year = models.CharField(max_length=4)
language = models.TextField(blank=True) language = models.TextField(blank=True, default='')
writer = models.TextField(blank=True) writer = models.TextField(blank=True, default='')
producer = models.TextField(blank=True) producer = models.TextField(blank=True, default='')
editor = models.TextField(blank=True) editor = models.TextField(blank=True, default='')
cinematographer = models.TextField(blank=True) cinematographer = models.TextField(blank=True, default='')
cast = models.TextField(blank=True) cast = models.TextField(blank=True, default='')
#person #person
genre = models.TextField(blank=True) genre = models.TextField(blank=True)
keywords = models.TextField(blank=True) keywords = models.TextField(blank=True)
summary = models.TextField(blank=True) summary = models.TextField(blank=True)
trivia = models.TextField(blank=True) trivia = models.TextField(blank=True)
locations = models.TextField(blank=True) locations = models.TextField(blank=True, default='')
#only for own files or as admin? #only for own files or as admin?
filename = models.TextField(blank=True) filename = models.TextField(blank=True, default='')
_private_fields = ('id', 'movie') _private_fields = ('id', 'movie')
_public_names = { _public_names = {
@ -338,7 +431,7 @@ class MovieSort(models.Model):
""" """
used to sort movies, all sort values are in here used to sort movies, all sort values are in here
""" """
movie = models.ForeignKey('Movie', related_name='sort', unique=True) movie = models.OneToOneField('Movie', related_name='sort', primary_key=True)
title = models.CharField(max_length=1000) title = models.CharField(max_length=1000)
director = models.TextField(blank=True) director = models.TextField(blank=True)
@ -360,6 +453,8 @@ class MovieSort(models.Model):
trivia = models.IntegerField(blank=True) trivia = models.IntegerField(blank=True)
connections = models.IntegerField(blank=True) connections = models.IntegerField(blank=True)
rating = models.FloatField(blank=True)
votes = models.IntegerField(blank=True)
scenes = models.IntegerField(blank=True) scenes = models.IntegerField(blank=True)
words = models.IntegerField(null=True, blank=True) words = models.IntegerField(null=True, blank=True)
wpm = models.IntegerField(null=True, blank=True) wpm = models.IntegerField(null=True, blank=True)
@ -391,9 +486,10 @@ class MovieSort(models.Model):
options = classmethod(options) options = classmethod(options)
class AlternativeTitle(models.Model): class AlternativeTitle(models.Model):
movie = models.ForeignKey(Movie, related_name='alternative_titles') movie = models.ForeignKey(Movie, related_name='alternative_titles_all')
title = models.TextField() title = models.TextField()
type = models.CharField(max_length=1000) type = models.CharField(max_length=1000)
manual = models.BooleanField(default=False)
class Meta: class Meta:
ordering = ('title', ) ordering = ('title', )
@ -453,6 +549,7 @@ class Cast(models.Model):
role = models.CharField(max_length=200) role = models.CharField(max_length=200)
character = models.CharField(max_length=200, blank=True) character = models.CharField(max_length=200, blank=True)
position = models.IntegerField() position = models.IntegerField()
manual = models.BooleanField(default=False)
class Meta: class Meta:
ordering = ('position', 'person__name_sort') ordering = ('position', 'person__name_sort')
@ -460,11 +557,12 @@ class Cast(models.Model):
def __unicode__(self): def __unicode__(self):
return "%s <> %s" % (self.person, self.movie) return "%s <> %s" % (self.person, self.movie)
def link(self, movie, person, role, character, position): def link(self, movie, person, role, character, position, manual=False):
q = self.objects.filter(movie=movie, person=person, role=role, character=character) q = self.objects.filter(movie=movie, person=person, role=role, character=character)
if q.count() > 0: if q.count() > 0:
link = q[0] link = q[0]
link.position = position link.position = position
link.manual = manual
link.save() link.save()
else: else:
link = self() link = self()
@ -473,6 +571,7 @@ class Cast(models.Model):
link.role=role link.role=role
link.character=character link.character=character
link.position = position link.position = position
link.manual = manual
link.save() link.save()
return link return link
link = classmethod(link) link = classmethod(link)
@ -482,7 +581,7 @@ class Cast(models.Model):
class Country(models.Model): class Country(models.Model):
name = models.CharField(max_length=200, unique=True) name = models.CharField(max_length=200, unique=True)
movies = models.ManyToManyField(Movie, related_name='countries', through='MovieCountry') movies = models.ManyToManyField(Movie, related_name='countries_all', through='MovieCountry')
class Meta: class Meta:
#!! adding this to ordering, breaks: #!! adding this to ordering, breaks:
@ -502,6 +601,7 @@ class MovieCountry(models.Model):
movie = models.ForeignKey(Movie) movie = models.ForeignKey(Movie)
country = models.ForeignKey(Country) country = models.ForeignKey(Country)
position = models.IntegerField() position = models.IntegerField()
manual = models.BooleanField(default=False)
class Meta: class Meta:
ordering = ('position', 'country') ordering = ('position', 'country')
@ -526,7 +626,7 @@ class MovieCountry(models.Model):
class Language(models.Model): class Language(models.Model):
name = models.CharField(max_length=200, unique=True) name = models.CharField(max_length=200, unique=True)
movies = models.ManyToManyField(Movie, related_name='languages', through="MovieLanguage") movies = models.ManyToManyField(Movie, related_name='languages_all', through="MovieLanguage")
class Meta: class Meta:
ordering = ('name', ) ordering = ('name', )
@ -542,6 +642,7 @@ class MovieLanguage(models.Model):
movie = models.ForeignKey(Movie) movie = models.ForeignKey(Movie)
language = models.ForeignKey(Language) language = models.ForeignKey(Language)
position = models.IntegerField() position = models.IntegerField()
manual = models.BooleanField(default=False)
class Meta: class Meta:
ordering = ('position', 'language') ordering = ('position', 'language')
@ -566,7 +667,8 @@ class MovieLanguage(models.Model):
class Keyword(models.Model): class Keyword(models.Model):
name = models.CharField(max_length=200, unique=True) name = models.CharField(max_length=200, unique=True)
movies = models.ManyToManyField(Movie, related_name='keywords') manual = models.BooleanField(default=False)
movies = models.ManyToManyField(Movie, related_name='keywords_all')
class Meta: class Meta:
ordering = ('name', ) ordering = ('name', )
@ -581,7 +683,8 @@ class Keyword(models.Model):
class Genre(models.Model): class Genre(models.Model):
name = models.CharField(max_length=200, unique=True) name = models.CharField(max_length=200, unique=True)
movies = models.ManyToManyField(Movie, related_name='genres') manual = models.BooleanField(default=False)
movies = models.ManyToManyField(Movie, related_name='genres_all')
class Meta: class Meta:
ordering = ('name', ) ordering = ('name', )
@ -596,7 +699,8 @@ class Genre(models.Model):
class Location(models.Model): class Location(models.Model):
name = models.CharField(max_length=200, unique=True) name = models.CharField(max_length=200, unique=True)
movies = models.ManyToManyField(Movie, related_name='locations') manual = models.BooleanField(default=False)
movies = models.ManyToManyField(Movie, related_name='locations_all')
#fixme: geo data #fixme: geo data
lat_sw = models.FloatField(default=0) lat_sw = models.FloatField(default=0)
@ -620,8 +724,9 @@ class Location(models.Model):
class Trivia(models.Model): class Trivia(models.Model):
trivia = models.TextField() trivia = models.TextField()
movie = models.ForeignKey(Movie, related_name='trivia') manual = models.BooleanField(default=False)
position = models.IntegerField() position = models.IntegerField()
movie = models.ForeignKey(Movie, related_name='trivia_all')
class Meta: class Meta:
ordering = ('position', ) ordering = ('position', )
@ -637,16 +742,17 @@ class Trivia(models.Model):
return trivia return trivia
class Connection(models.Model): class Connection(models.Model):
subject = models.ForeignKey(Movie, related_name='connections') subject = models.ForeignKey(Movie, related_name='connections_all')
relation = models.CharField(max_length=512) relation = models.CharField(max_length=512)
object = models.ForeignKey(Movie) object = models.ForeignKey(Movie)
manual = models.BooleanField(default=False)
def get_or_create(model, subject, relation, object, reverse=True): def get_or_create(model, subject, relation, object, reverse=True, manual=False):
q = model.objects.filter(subject=subject, relation=relation, object=object) q = model.objects.filter(subject=subject, relation=relation, object=object)
if q.count() > 0: if q.count() > 0:
o = q[0] o = q[0]
else: else:
o = model.objects.create(subject=subject, relation=relation, object=object) o = model.objects.create(subject=subject, relation=relation, object=object, manual=manual)
o.save() o.save()
if reverse: if reverse:
_map = { _map = {
@ -672,119 +778,11 @@ class Connection(models.Model):
def __unicode__(self): def __unicode__(self):
return '%s %s %s' % (self.subject, self.relation, self.object) return '%s %s %s' % (self.subject, self.relation, self.object)
class File(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
oshash = models.CharField(blank=True, unique=True, max_length=16)
sha1 = models.CharField(blank=True, unique=True, max_length=40)
md5 = models.CharField(blank=True, unique=True, max_length=32)
movie = models.ForeignKey('Movie', related_name="files", default=None)
computed_path = models.CharField(blank=True, max_length=2048)
size = models.IntegerField(default=-1)
duration = models.FloatField(default=-1)
video_codec = models.CharField(blank=True, max_length=256)
pixel_format = models.CharField(blank=True, max_length=256)
width = models.IntegerField(default=-1)
height = models.IntegerField(default=-1)
pixel_aspect_ratio = models.CharField(blank=True, max_length=256)
display_aspect_ratio = models.CharField(blank=True, max_length=256)
framerate = models.CharField(blank=True, max_length=256)
audio_codec = models.CharField(blank=True, max_length=256)
samplerate = models.IntegerField(default=-1)
channels = models.IntegerField(default=-1)
#computed values
bpp = models.FloatField(default=-1)
pixels = models.IntegerField(default=0)
part = models.IntegerField(default=0)
def get_or_create(model, oshash):
try:
f = model.objects.get(oshash=oshash)
except model.DoesNotExist:
f = model.objects.create(oshash=oshash)
f.save()
return f
get_or_create = classmethod(get_or_create)
def __unicode__(self):
return "%s (%s)" % (self.computed_path, self.oshash)
class Archive(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
name = models.CharField(max_length=255, unique=True)
users = models.ManyToManyField(User, related_name='archives')
class ArchiveFile(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
archive = models.ForeignKey(Archive)
movie_file = models.ForeignKey(File)
path = models.CharField(blank=True, max_length=2048)
objects = managers.ArchiveFileManager()
def update(self, data):
"""
only add, do not overwrite keys in movie_file
"""
for key in ('duration', 'video_codec', 'pixel_format', 'width', 'height',
'pixel_aspect_ratio', 'display_aspect_ratio', 'framerate',
'audio_codec', 'samplerate', 'channels', 'size', 'sha1', 'md5'):
if key in data and not getattr(self.movie_file, key):
setattr(self.movie_file, key, data[key])
self.path = data.get('path', '')
self.movie_file.save()
self.save()
def get_or_create(model, archive, oshash):
try:
f = model.objects.by_oshash(oshash=oshash)
except model.DoesNotExist:
f = model.objects.create()
f.movie_file = File.get_or_create(oshash)
f.archive = archive
f.save()
return f
get_or_create = classmethod(get_or_create)
def __unicode__(self):
return '%s (%s)' % (self.path, unicode(self.user))
class Subtitle(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User)
movie_file = models.ForeignKey(File)
language = models.CharField(max_length=16)
srt = models.TextField(blank=True)
def get_or_create(model, user, oshash, language):
q = model.objects.filter(movie_file__oshash=oshash, language=language, user=user)
if q.count() > 0:
s = q[0]
else:
movie_file = models.File.get_or_create(oshash=oshash)
s = model.objects.create(user=user, language=language, movie_file=movie_file)
s.save()
return s
get_or_create = classmethod(get_or_create)
def __unicode__(self):
return '%s.%s.srt' % (os.path.splitext(self.movie_file.computed_path)[0], self.language)
class Review(models.Model): class Review(models.Model):
movie = models.ForeignKey('Movie', related_name='reviews') movie = models.ForeignKey('Movie', related_name='reviews_all')
title = models.CharField(blank=True, max_length=2048) title = models.CharField(blank=True, max_length=2048)
url = models.CharField(blank=True, max_length=2048) url = models.CharField(blank=True, max_length=2048)
manual = models.BooleanField(default=False)
def __unicode__(self): def __unicode__(self):
return self.title return self.title
@ -843,3 +841,158 @@ class ListItem(models.Model):
def __unicode__(self): def __unicode__(self):
return u'%s in %s' % (unicode(self.movie), unicode(self.list)) return u'%s in %s' % (unicode(self.movie), unicode(self.list))
def stream_path(f, size):
name = "%s.%s" % (size, 'ogv')
url_hash = f.oshash
return os.path.join('stream', url_hash[:2], url_hash[2:4], url_hash[4:6], name)
class File(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
oshash = models.CharField(blank=True, unique=True, max_length=16)
sha1 = models.CharField(blank=True, unique=True, max_length=40)
md5 = models.CharField(blank=True, unique=True, max_length=32)
movie = models.ForeignKey(Movie, related_name="files", default=None)
computed_path = models.CharField(blank=True, max_length=2048)
size = models.IntegerField(default=-1)
duration = models.FloatField(default=-1)
is_video = models.BooleanField(default=False)
video_codec = models.CharField(blank=True, max_length=256)
pixel_format = models.CharField(blank=True, max_length=256)
width = models.IntegerField(default=-1)
height = models.IntegerField(default=-1)
pixel_aspect_ratio = models.CharField(blank=True, max_length=256)
display_aspect_ratio = models.CharField(blank=True, max_length=256)
framerate = models.CharField(blank=True, max_length=256)
audio_codec = models.CharField(blank=True, max_length=256)
samplerate = models.IntegerField(default=-1)
channels = models.IntegerField(default=-1)
#computed values
bpp = models.FloatField(default=-1)
pixels = models.IntegerField(default=0)
part = models.IntegerField(default=0)
#stream related fields
available = models.BooleanField(default=False)
stream128 = models.FileField(default=None, upload_to=lambda f, x: stream_path(f, '128'))
stream320 = models.FileField(default=None, upload_to=lambda f, x: stream_path(f, '320'))
stream640 = models.FileField(default=None, upload_to=lambda f, x: stream_path(f, '640'))
def save_chunk(self, chunk, name='video.ogv'):
if not self.available:
#FIXME: this should use stream128 or stream640 depending on configuration
video = getattr(self, 'stream128')
if not video:
video.save(name, ContentFile(chunk))
self.save()
else:
f = open(video.path, 'a')
f.write(chunk)
f.close()
return True
return False
objects = managers.FileManager()
def get_or_create(model, oshash):
try:
f = model.objects.get(oshash=oshash)
except model.DoesNotExist:
f = model.objects.create(oshash=oshash)
f.save()
return f
get_or_create = classmethod(get_or_create)
def __unicode__(self):
return "%s (%s)" % (self.computed_path, self.oshash)
class Subtitle(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User)
file = models.ForeignKey(File, related_name="subtitles")
language = models.CharField(max_length=16)
srt = models.TextField(blank=True)
def get_or_create(model, user, oshash, language):
q = model.objects.filter(file__oshash=oshash, language=language, user=user)
if q.count() > 0:
s = q[0]
else:
f = models.File.get_or_create(oshash=oshash)
s = model.objects.create(user=user, language=language, file=f)
s.save()
return s
get_or_create = classmethod(get_or_create)
def __unicode__(self):
return '%s.%s.srt' % (os.path.splitext(self.movie_file.computed_path)[0], self.language)
class Layer(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User)
movie = models.ForeignKey(Movie)
#seconds
time_in = models.FloatField(default=-1)
time_out = models.FloatField(default=-1)
type = models.CharField(blank=True, max_length=255)
value = models.TextField()
#location = models.ForeignKey('Location', default=None)
class Archive(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
name = models.CharField(max_length=255, unique=True)
public = models.BooleanField(default=False)
users = models.ManyToManyField(User, related_name='archives')
class ArchiveFile(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
archive = models.ForeignKey(Archive, related_name='files')
file = models.ForeignKey(File)
path = models.CharField(blank=True, max_length=2048)
objects = managers.ArchiveFileManager()
def update(self, data):
"""
only add, do not overwrite keys in file
"""
for key in ('duration', 'video_codec', 'pixel_format', 'width', 'height',
'pixel_aspect_ratio', 'display_aspect_ratio', 'framerate',
'audio_codec', 'samplerate', 'channels', 'size', 'sha1', 'md5'):
if key in data and not getattr(self.file, key):
setattr(self.file, key, data[key])
self.path = data.get('path', '')
self.file.save()
self.save()
def get_or_create(model, archive, oshash):
try:
f = model.objects.by_oshash(oshash=oshash)
except model.DoesNotExist:
f = model.objects.create()
f.file = File.get_or_create(oshash)
f.archive = archive
f.save()
return f
get_or_create = classmethod(get_or_create)
def __unicode__(self):
return '%s (%s)' % (self.path, unicode(self.user))

View file

@ -24,8 +24,9 @@ urlpatterns = patterns("oxdb.backend.views",
(r'^find', 'find'), (r'^find', 'find'),
(r'^files/find', 'find_files'), (r'^files/find', 'find_files'),
(r'^files/info', 'file_info'), (r'^files/info', 'file_info'),
(r'^files/(?P<archive>.+)/add', 'add_file'), (r'^archive/(?P<archive>.+)/add', 'add_file'),
(r'^files/(?P<archive>.+)/remove', 'remove_file'), (r'^archive/(?P<archive>.+)/remove', 'remove_file'),
(r'^file/parse', 'file_parse'),
(r'^subtitle/get', 'subtitles'), (r'^subtitle/get', 'subtitles'),
(r'^preferences', 'preferences'), (r'^preferences', 'preferences'),

View file

@ -22,3 +22,106 @@ def oxid(title, director, year='', seriesTitle='', episodeTitle='', season=0, ep
oxid += hashlib.sha1(oxid_value.encode('utf-8')).hexdigest()[:20] oxid += hashlib.sha1(oxid_value.encode('utf-8')).hexdigest()[:20]
return u"0x" + oxid return u"0x" + oxid
def oxdb_director(director):
director = os.path.basename(os.path.dirname(director))
if director.endswith('_'):
director = "%s." % director[:-1]
director = ", ".join([normalizeName(d) for d in director.split('; ')])
director = director.replace('Series', '')
director = director.replace('Unknown Director', '')
director = director.replace('Various Directors', '')
return director
def oxdb_title(_title, searchTitle = False):
'''
normalize filename to get movie title
'''
_title = os.path.basename(_title)
_title = _title.replace('... ', '_dot_dot_dot_')
_title = _title.replace('. ', '_dot__space_')
_title = _title.replace(' .', '_space__dot_')
title = _title.split('.')[0]
title = re.sub('([a-z0-9])_ ', '\\1: ', title)
se = re.compile('Season (\d+).Episode (\d+)').findall(_title)
if se:
se = "S%02dE%02d" % (int(se[0][0]), int(se[0][1]))
if 'Part' in _title.split('.')[-2] and 'Episode' not in _title.split('.')[-3]:
stitle = _title.split('.')[-3]
else:
stitle = _title.split('.')[-2]
if stitle.startswith('Episode '):
stitle = ''
if searchTitle:
title = '"%s" %s' % (title, stitle)
else:
title = '%s (%s) %s' % (title, se, stitle)
title = title.strip()
title = title.replace('_dot_dot_dot_', '... ')
title = title.replace('_dot__space_', '. ')
title = title.replace('_space__dot_', ' .')
return title
def oxdb_year(data):
return oxlib.findRe(data, '\.(\d{4})\.')
def oxdb_series_title(path):
seriesTitle = u''
if path.startswith('Series'):
seriesTitle = os.path.basename(os.path.dirname(path))
else:
t = oxdb_title(path)
if " (S" in t:
seriesTitle = t.split(" (S")[0]
return seriesTitle
def oxdb_episode_title(path):
episodeTitle = u''
ep = re.compile('.Episode \d+?\.(.*?)\.[a-zA-Z]').findall(path)
if ep:
episodeTitle = ep[0][0]
return episodeTitle
def oxdb_season_episode(path):
season = 0
episode = 0
path = os.path.basename(path)
se = re.compile('Season (\d+).Episode (\d+)').findall(path)
if se:
season = int(se[0][0])
episode = int(se[0][1])
else:
ep = re.compile('.Episode (\d+?)').findall(path)
if ep:
episode = int(ep[0][0])
if season == 0 and episode == 0:
se = re.compile('S(\d\d)E(\d\d)').findall(path)
if se:
season = int(se[0][0])
episode = int(se[0][1])
return (season, episode)
def oxdb_part(path):
part = 1
path = path.lower()
p = re.compile('part\s*?(\d+)\.').findall(path)
if p:
part = p[0]
else:
p = re.compile('cd\s*?(\d+)\.').findall(path)
if p:
part = p[0]
return part
def parsePath(path):
import oxweb.imdb
search_title = oxdb_title(path, True)
r = {}
r['title'] = oxdb_title(path)
r['director'] = oxdb_director(path)
r['episode_title'] = oxdb_episode_title(path)
r['season'], r['episode'] = oxdb_season_episode(path)
r['series'] = oxdb_series_title(path)
r['part'] = oxdb_part(path)
r['imdbId'] = oxweb.imdb.guess(search_title, r['director'])
return r

View file

@ -12,14 +12,14 @@ from django.shortcuts import render_to_response, get_object_or_404, get_list_or_
from django.template import RequestContext from django.template import RequestContext
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.utils import simplejson as json
from oxdb.utils.shortcuts import render_to_json_response from oxdb.utils.shortcuts import render_to_json_response
import models import models
import utils
from decorators import login_required_json
''' '''
field.length -> movie.sort.all()[0].field field.length -> movie.sort.all()[0].field
o=0&n=100 o=0&n=100
@ -28,7 +28,6 @@ a & b | c & d
query query
l=user:name or l=name l=user:name or l=name
q=year:1980,hello,country:usa q=year:1980,hello,country:usa
q=year:1980,hello,country:!usa q=year:1980,hello,country:!usa
@ -58,14 +57,25 @@ q=year:<1960,year:>1950,title:sex
s=director:asc,year:desc default: director:asc,year:desc s=director:asc,year:desc default: director:asc,year:desc
r=0:100 or r=100 or r=100: default: 0:100 r=0:100 or r=100 or r=100: default: 0:100
k=id,title,director,date,cast.length default: title,director,year,country p=id,title,director,date,cast.length default: title,director,year,country
q
List data backend spec:
url = //url for request
params = [] //additional params passed to url, i.e. query, or group
id=0133093 the url must understand the following requests:
number of items:
url?params&n=1
> {items: N}
items sorted by key range X to Y:
url?params&s=key:asc|desc&r=X:Y
> {items: [{k0:v0, k1:v1...}, {k0:v0, k1:v1...}]}
/json/find?l=all&s=date&f=all&q=&a=desc&p=id,title,director,date,cast.length Examples:
/json/find?l=all&s=title&f=all&q=&a=desc&p=id,title,director,date,cast.length
/json/find?o=0&n=100&l=all&s=date&f=all&q=&a=desc&p=id,title,director,date,cast.length /json/find?r=0:100&l=all&s=title&f=all&q=&a=desc&p=id,title,director,date,cast.length
{ {
movies=[ movies=[
{ {
@ -77,7 +87,7 @@ id=0133093
} }
#get sort order for all ids #get sort order for all ids
/json/find?o=0&n=1000&l=all&s=date&f=all&q=&a=desc&p=id /json/find?r=0:1000&l=all&s=title&f=all&q=&a=desc&p=id
{ {
movies=[ movies=[
{ {
@ -86,7 +96,7 @@ id=0133093
] ]
} }
/json/find?l=all&s=date&f=all&q=&a=desc /json/find?l=all&s=title&f=all&q=&a=desc
{ {
movies: 1234, movies: 1234,
files: 2345, files: 2345,
@ -96,7 +106,7 @@ id=0133093
} }
/json/find?o=0&n=100&l=all&s=[name, items]&f=all&q=&a=desc&g=country /json/find?r=0:100&l=all&s=[name, items]&f=all&q=&a=desc&g=country
{ {
groups = [ {name:"USA", movies: 123}, {name:"UK", movies: 1234} ] groups = [ {name:"USA", movies: 123}, {name:"UK", movies: 1234} ]
} }
@ -106,13 +116,13 @@ id=0133093
#auto compleat in find box #auto compleat in find box
''' '''
def order_query(qs, s, prefix='sort__'): def order_query(qs, s, prefix='sort__'):
order_by = [] order_by = []
for e in s.split(','): for e in s.split(','):
o = e.split(':') o = e.split(':')
if len(o) == 1: o.append('asc') if len(o) == 1: o.append('asc')
order = '%s%s' % (prefix, o[0]) order = {'id': 'movieId'}.get(o[0], o[0])
order = '%s%s' % (prefix, order)
if o[1] == 'desc': if o[1] == 'desc':
order = '-%s' % order order = '-%s' % order
order_by.append(order) order_by.append(order)
@ -129,9 +139,9 @@ def parse_query(request):
def parse_dict(s): def parse_dict(s):
d = s.split(",") d = s.split(",")
return [i.strip() for i in d] return [i.strip() for i in d]
_dicts = ['k', ] _dicts = ['p', ]
_ints = ['o', 'n'] _ints = ['n', ]
for key in ('s', 'k', 'g', 'l'): for key in ('s', 'p', 'g', 'l', 'n'):
if key in get: if key in get:
if key in _ints: if key in _ints:
query[key] = int(get[key]) query[key] = int(get[key])
@ -147,25 +157,31 @@ def parse_query(request):
if r[1] == '': r[0] = -1 if r[1] == '': r[0] = -1
query['i'] = int(r[0]) query['i'] = int(r[0])
query['o'] = int(r[1]) query['o'] = int(r[1])
#group by only allows sorting by name or number of itmes
return query return query
def find(request): def find(request):
query = parse_query(request) query = parse_query(request)
response = {} response = {}
if 'k' in query: if 'p' in query:
response['movies'] = [] response['items'] = []
qs = order_query(query['q'], query['s']) qs = order_query(query['q'], query['s'])
qs = qs[query['i']:query['o']] if 'n' in query:
p = Paginator(qs, 100) response = {'items': qs.count()}
for i in p.page_range: else:
page = p.page(i) qs = qs[query['i']:query['o']]
for m in page.object_list: p = Paginator(qs, 100)
response['movies'].append(m.json(query['k'])) for i in p.page_range:
page = p.page(i)
for m in page.object_list:
response['items'].append(m.json(query['p']))
elif 'g' in query: elif 'g' in query:
if query['s'].split(':')[0] not in ('name', 'items'):
query['s'] = 'name'
#FIXME: also filter lists here #FIXME: also filter lists here
response['groups'] = [] response['items'] = []
name = 'name' name = 'name'
movies = 'movies' items = 'movies'
movie_qs = query['q'] movie_qs = query['q']
_objects = { _objects = {
'country': models.Country.objects, 'country': models.Country.objects,
@ -176,18 +192,29 @@ def find(request):
if query['g'] in _objects: if query['g'] in _objects:
qs = _objects[query['g']].filter(movies__id__in=movie_qs).values('name').annotate(movies=Count('movies')) qs = _objects[query['g']].filter(movies__id__in=movie_qs).values('name').annotate(movies=Count('movies'))
elif query['g'] == "year": elif query['g'] == "year":
qs = movie_qs.values('year').annotate(movies=Count('id')) qs = movie_qs.values('imdb__year').annotate(movies=Count('id'))
name='year' name='imdb__year'
qs = order_query(qs, query['s'], '') if 'n' in query:
qs = qs[query['i']:query['o']] response['items'] = qs.count()
for i in qs: else:
group = {'name': i[name], 'movies': i[movies]} #replace normalized items/name sort with actual db value
response['groups'].append(group) order_by = query['s'].split(":")
if len(order_by) == 1:
order_by.append('desc')
if order_by[0] == 'name':
order_by = "%s:%s" % (name, order_by[1])
else:
order_by = "%s:%s" % (items, order_by[1])
qs = order_query(qs, order_by, '')
qs = qs[query['i']:query['o']]
for i in qs:
group = {'title': i[name], 'items': i[items]}
response['items'].append(group)
else: else:
#FIXME: also filter lists here #FIXME: also filter lists here
movies = models.Movie.objects.all() movies = models.Movie.objects.filter(available=True)
files = models.MovieFile.objects.all() files = models.File.objects.all()
response['movies'] = movies.count() response['items'] = movies.count()
response['files'] = files.count() response['files'] = files.count()
r = files.aggregate(Count('size'), Count('pixels'), Count('duration')) r = files.aggregate(Count('size'), Count('pixels'), Count('duration'))
response['pixels'] = r['pixels__count'] response['pixels'] = r['pixels__count']
@ -260,7 +287,7 @@ GET list
} }
} }
''' '''
@login_required @login_required_json
def list_files(request): def list_files(request):
response['files'] = {} response['files'] = {}
qs = models.UserFile.filter(user=request.user) qs = models.UserFile.filter(user=request.user)
@ -284,7 +311,7 @@ def find_files(request):
''' '''
POST add POST add
> { > file: {
"duration": 5.266667, "duration": 5.266667,
"video_codec": "mpeg1", "video_codec": "mpeg1",
"pixel_format": "yuv420p", "pixel_format": "yuv420p",
@ -303,12 +330,14 @@ POST add
"md5":.. "md5":..
} }
''' '''
@login_required #@login_required_json
def add_file(request, archive): def add_file(request, archive):
oshash = request.POST['oshash'] print request.POST
info = json.loads(request.POST['file'])
oshash = info['oshash']
archive = models.Archive.objects.get(name=archive) archive = models.Archive.objects.get(name=archive)
if archive.users.filter(user=request.user).count() == 1: if archive.users.filter(user=request.user).count() == 1:
user_file = models.ArchiveFiles.get_or_create(request.user, oshash) user_file = models.ArchiveFile.get_or_create(archive, oshash)
user_file.update(request.POST) user_file.update(request.POST)
response = {'status': 200} response = {'status': 200}
else: else:
@ -318,7 +347,7 @@ def add_file(request, archive):
''' '''
POST remove?oshash= POST remove?oshash=
''' '''
@login_required @login_required_json
def remove_file(request, archive): def remove_file(request, archive):
oshash = request.POST['oshash'] oshash = request.POST['oshash']
archive = models.Archive.objects.get(name=archive) archive = models.Archive.objects.get(name=archive)
@ -326,11 +355,15 @@ def remove_file(request, archive):
response = {'status': 200} response = {'status': 200}
return render_to_json_response(response) return render_to_json_response(response)
def file_parse(request):
response = utils.parsePath(request.POST['path'])
return render_to_json_response(response)
''' '''
POST preferences/get?key= POST preferences/get?key=
POST preferences/set?key=&value POST preferences/set?key=&value
''' '''
@login_required @login_required_json
def preferences(request): def preferences(request):
oshash = request.POST['oshash'] oshash = request.POST['oshash']
return '' return ''

View file

@ -43,6 +43,7 @@ USE_I18N = True
# Example: "/home/media/media.lawrence.com/" # Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = join(PROJECT_ROOT, 'media') MEDIA_ROOT = join(PROJECT_ROOT, 'media')
STATIC_ROOT = join(PROJECT_ROOT, 'static') STATIC_ROOT = join(PROJECT_ROOT, 'static')
TESTS_ROOT = join(PROJECT_ROOT, 'tests')
# URL that handles the media served from MEDIA_ROOT. Make sure to use a # URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases). # trailing slash if there is a path component (optional in other cases).
@ -80,9 +81,9 @@ INSTALLED_APPS = (
'django.contrib.sites', 'django.contrib.sites',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.humanize', 'django.contrib.humanize',
'south', # 'south',
'oxdb.backend', 'backend',
) )
#overwrite default settings with local settings #overwrite default settings with local settings

37
static/css/ui.css Normal file
View file

@ -0,0 +1,37 @@
body {
background: rgb(16, 16, 16);
}
div {
-moz-user-select: none;
-webkit-user-select: none;
}
input#find {
position: absolute;
top: 4px;
right: 24px;
width: 240px;
}
img#loading {
position: absolute;
top: 4px;
right: 4px;
width: 16px;
height: 16px;
display: hidden;
}
#sideBrowserPlayer {
text-align: center;
}
#statusBar {
-moz-box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.75);
-webkit-box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.75);
}
.OxCell.OxColumnTitle {
//font-weight: bold;
}

1127
static/js/ui.js Normal file

File diff suppressed because it is too large Load diff

BIN
static/png/frame.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
static/png/timeline.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

17
templates/index.html Normal file
View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title>0xdb.org</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="text/javascript" src="/static/js/jquery/jquery.js"></script>
<script type="text/javascript" src="/static/oxjs/js/ox.js"></script>
<script type="text/javascript" src="/static/oxjs/js/ox.ui.js"></script>
<script type="text/javascript" src="/static/oxjs/js/ox.iso.js"></script>
<script type="text/javascript" src="/static/oxjs/js/ox.unicode.js"></script>
<script type="text/javascript" src="/static/js/ui.js"></script>
<link rel="stylesheet" type="text/css" href="/static/oxjs/css/ox.css"/>
<link rel="stylesheet" type="text/css" href="/static/css/ui.css"/>
</head>
<body>
</body>
</html>

View file

@ -10,6 +10,7 @@ admin.autodiscover()
urlpatterns = patterns('', urlpatterns = patterns('',
# Example: # Example:
(r'^json/', include('backend.urls')), (r'^json/', include('backend.urls')),
(r'^$', 'app.views.index'),
# Uncomment the admin/doc line below and add 'django.contrib.admindocs' # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
# to INSTALLED_APPS to enable admin documentation: # to INSTALLED_APPS to enable admin documentation:
@ -25,6 +26,8 @@ if settings.DEBUG:
{'document_root': settings.MEDIA_ROOT}), {'document_root': settings.MEDIA_ROOT}),
(r'^static/(?P<path>.*)$', 'django.views.static.serve', (r'^static/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.STATIC_ROOT}), {'document_root': settings.STATIC_ROOT}),
(r'^tests/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.TESTS_ROOT}),
) )

0
utils/__init__.py Normal file
View file

15
utils/shortcuts.py Normal file
View file

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from django.http import HttpResponse
from django.utils import simplejson
from django.conf import settings
def render_to_json_response(dictionary, content_type="text/json"):
indent=None
if settings.DEBUG:
content_type = "text/javascript"
indent = 2
return HttpResponse(simplejson.dumps(dictionary, indent=indent), content_type=content_type)