- rework file/upload handling, no more extras

- cleanup imdb data, get from external data service
- rename filesview
This commit is contained in:
j 2011-10-15 17:21:41 +02:00
parent 3bff9d1fb9
commit 45798810a9
18 changed files with 295 additions and 631 deletions

View file

@ -8,8 +8,8 @@ import models
class FileAdmin(admin.ModelAdmin): class FileAdmin(admin.ModelAdmin):
search_fields = ['name', 'folder','oshash', 'video_codec'] search_fields = ['path','oshash', 'video_codec']
list_display = ['available', 'wanted', 'active', '__unicode__', 'itemId'] list_display = ['available', 'wanted', 'selected', '__unicode__', 'itemId']
list_display_links = ('__unicode__', ) list_display_links = ('__unicode__', )
def itemId(self, obj): def itemId(self, obj):
@ -21,7 +21,7 @@ admin.site.register(models.File, FileAdmin)
class InstanceAdmin(admin.ModelAdmin): class InstanceAdmin(admin.ModelAdmin):
search_fields = ['name', 'folder', 'volume__name', 'file__oshash'] search_fields = ['path', 'volume__name', 'file__oshash']
form = InstanceAdminForm form = InstanceAdminForm
admin.site.register(models.Instance, InstanceAdmin) admin.site.register(models.Instance, InstanceAdmin)

View file

@ -25,7 +25,7 @@ class FileAdminForm(forms.ModelForm):
class InstanceAdminForm(forms.ModelForm): class InstanceAdminForm(forms.ModelForm):
file = ForeignKeyByLetter(models.File, field_name='name') file = ForeignKeyByLetter(models.File, field_name='path')
class Meta: class Meta:
model = models.Instance model = models.Instance

View file

@ -6,10 +6,10 @@ import os.path
import re import re
import time import time
from django.conf import settings
from django.contrib.auth.models import User
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.conf import settings
from django.db.models.signals import pre_delete from django.db.models.signals import pre_delete
from ox.django import fields from ox.django import fields
@ -17,7 +17,6 @@ import ox
import chardet import chardet
from item import utils from item import utils
from person.models import get_name_sort
import extract import extract
@ -26,19 +25,17 @@ class File(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)
active = models.BooleanField(default=False)
auto = models.BooleanField(default=True) auto = models.BooleanField(default=True)
oshash = models.CharField(max_length=16, unique=True) oshash = models.CharField(max_length=16, unique=True)
item = models.ForeignKey("item.Item", related_name='files') item = models.ForeignKey("item.Item", related_name='files')
name = models.CharField(max_length=2048, default="") # canoncial path/file path = models.CharField(max_length=2048, default="") # canoncial path/file
folder = models.CharField(max_length=2048, default="") # canoncial path/file sort_path = models.CharField(max_length=2048, default="") # sort name
sort_name = models.CharField(max_length=2048, default="") # sort name
type = models.CharField(default="", max_length=255) type = models.CharField(default="", max_length=255)
part = models.IntegerField(null=True) part = models.IntegerField(null=True)
version = models.CharField(default="", max_length=255) # sort path/file name version = models.CharField(default="", max_length=255)
language = models.CharField(default="", max_length=8) language = models.CharField(default="", max_length=8)
season = models.IntegerField(default=-1) season = models.IntegerField(default=-1)
@ -65,22 +62,22 @@ class File(models.Model):
#This is true if derivative is available or subtitles where uploaded #This is true if derivative is available or subtitles where uploaded
available = models.BooleanField(default = False) available = models.BooleanField(default = False)
wanted = models.BooleanField(default = False) selected = models.BooleanField(default = False)
uploading = models.BooleanField(default = False) uploading = models.BooleanField(default = False)
wanted = models.BooleanField(default = False)
is_audio = models.BooleanField(default=False) is_audio = models.BooleanField(default=False)
is_video = models.BooleanField(default=False) is_video = models.BooleanField(default=False)
is_subtitle = models.BooleanField(default=False) is_subtitle = models.BooleanField(default=False)
def __unicode__(self): def __unicode__(self):
return self.name return self.path
def set_state(self): def set_state(self):
self.name = self.get_name() self.path = self.create_path()
self.folder = self.get_folder() self.sort_path= utils.sort_string(self.path)
self.sort_name = utils.sort_string(ox.get_sort_title(self.name))
if not os.path.splitext(self.name)[-1] in ( if not os.path.splitext(self.path)[-1] in (
'.srt', '.rar', '.sub', '.idx', '.txt', '.jpg', '.png', '.nfo') \ '.srt', '.rar', '.sub', '.idx', '.txt', '.jpg', '.png', '.nfo') \
and self.info: and self.info:
for key in ('duration', 'size'): for key in ('duration', 'size'):
@ -99,8 +96,8 @@ class File(models.Model):
self.display_aspect_ratio = "%s:%s" % (self.width, self.height) self.display_aspect_ratio = "%s:%s" % (self.width, self.height)
self.is_video = True self.is_video = True
self.is_audio = False self.is_audio = False
if self.name.endswith('.jpg') or \ if self.path.endswith('.jpg') or \
self.name.endswith('.png') or \ self.path.endswith('.png') or \
self.duration == 0.04: self.duration == 0.04:
self.is_video = False self.is_video = False
else: else:
@ -126,11 +123,11 @@ class File(models.Model):
self.pixels = int(self.width * self.height * float(utils.parse_decimal(self.framerate)) * self.duration) self.pixels = int(self.width * self.height * float(utils.parse_decimal(self.framerate)) * self.duration)
else: else:
self.is_video = os.path.splitext(self.name)[-1] in ('.avi', '.mkv', '.dv', '.ogv', '.mpeg', '.mov', '.webm') self.is_video = os.path.splitext(self.path)[-1] in ('.avi', '.mkv', '.dv', '.ogv', '.mpeg', '.mov', '.webm')
self.is_audio = os.path.splitext(self.name)[-1] in ('.mp3', '.wav', '.ogg', '.flac', '.oga') self.is_audio = os.path.splitext(self.path)[-1] in ('.mp3', '.wav', '.ogg', '.flac', '.oga')
self.is_subtitle = os.path.splitext(self.name)[-1] in ('.srt', ) self.is_subtitle = os.path.splitext(self.path)[-1] in ('.srt', )
if self.name.endswith('.srt'): if self.path.endswith('.srt'):
self.is_subtitle = True self.is_subtitle = True
self.is_audio = False self.is_audio = False
self.is_video = False self.is_video = False
@ -138,7 +135,8 @@ class File(models.Model):
self.is_subtitle = False self.is_subtitle = False
self.type = self.get_type() self.type = self.get_type()
self.language = self.get_language() info = ox.parse_movie_path(self.path)
self.language = info['language']
self.part = self.get_part() self.part = self.get_part()
if self.type not in ('audio', 'video'): if self.type not in ('audio', 'video'):
@ -156,9 +154,9 @@ class File(models.Model):
#upload and data handling #upload and data handling
data = models.FileField(null=True, blank=True, data = models.FileField(null=True, blank=True,
upload_to=lambda f, x: f.path('data.bin')) upload_to=lambda f, x: f.get_path('data.bin'))
def path(self, name): def get_path(self, name):
h = self.oshash h = self.oshash
return os.path.join('files', h[:2], h[2:4], h[4:6], h[6:], name) return os.path.join('files', h[:2], h[2:4], h[4:6], h[6:], name)
@ -277,13 +275,12 @@ class File(models.Model):
'samplerate': self.samplerate, 'samplerate': self.samplerate,
'video_codec': self.video_codec, 'video_codec': self.video_codec,
'audio_codec': self.audio_codec, 'audio_codec': self.audio_codec,
'name': self.name, 'path': self.path,
'size': self.size, 'size': self.size,
#'info': self.info, #'info': self.info,
'users': list(set([u.username 'users': list(set([u.username
for u in User.objects.filter(volumes__files__in=self.instances.all())])), for u in User.objects.filter(volumes__files__in=self.instances.all())])),
'instances': [i.json() for i in self.instances.all()], 'instances': [i.json() for i in self.instances.all()],
'folder': self.get_folder(),
'type': self.get_type(), 'type': self.get_type(),
'part': self.get_part() 'part': self.get_part()
} }
@ -295,17 +292,17 @@ class File(models.Model):
def get_part(self): def get_part(self):
#FIXME: this breaks for sub/idx/srt #FIXME: this breaks for sub/idx/srt
if os.path.splitext(self.name)[-1] in ('.sub', '.idx', '.srt'): if os.path.splitext(self.path)[-1] in ('.sub', '.idx', '.srt'):
name = os.path.splitext(self.name)[0] name = os.path.splitext(self.path)[0]
if self.language: if self.language:
name = name[-(len(self.language)+1)] name = name[-(len(self.language)+1)]
qs = self.item.files.filter(Q(is_video=True)|Q(is_audio=True), qs = self.item.files.filter(Q(is_video=True)|Q(is_audio=True),
active=True, name__startswith=name) selected=True, path__startswith=name)
if qs.count()>0: if qs.count()>0:
return qs[0].part return qs[0].part
if self.active: if self.selected:
files = list(self.item.files.filter(type=self.type, language=self.language, files = list(self.item.files.filter(type=self.type, language=self.language,
active=self.active).order_by('sort_name')) selected=self.selected).order_by('sort_path'))
if self in files: if self in files:
return files.index(self) + 1 return files.index(self) + 1
return None return None
@ -315,7 +312,7 @@ class File(models.Model):
return 'video' return 'video'
if self.is_audio: if self.is_audio:
return 'audio' return 'audio'
if self.is_subtitle or os.path.splitext(self.name)[-1] in ('.sub', '.idx'): if self.is_subtitle or os.path.splitext(self.path)[-1] in ('.sub', '.idx'):
return 'subtitle' return 'subtitle'
return 'unknown' return 'unknown'
@ -325,30 +322,10 @@ class File(models.Model):
return self.instances.all()[0] return self.instances.all()[0]
return None return None
def get_folder(self): def create_path(self):
instance = self.get_instance() instance = self.get_instance()
if instance: if instance:
return instance.folder return instance.path
name = os.path.splitext(self.get_name())[0]
name = name.replace('. ', '||').split('.')[0].replace('||', '. ')
if self.item:
if settings.USE_IMDB:
director = self.item.get('director', ['Unknown Director'])
director = map(get_name_sort, director)
director = u'; '.join(director)
director = re.sub(r'[:\\/]', '_', director)
name = os.path.join(director, name)
year = self.item.get('year')
if year:
name += u' (%s)' % year
name = os.path.join(name[0].upper(), name)
return name
return u''
def get_name(self):
instance = self.get_instance()
if instance:
return instance.name
if self.item: if self.item:
name = self.item.get('title', 'Untitled') name = self.item.get('title', 'Untitled')
name = re.sub(r'[:\\/]', '_', name) name = re.sub(r'[:\\/]', '_', name)
@ -357,12 +334,6 @@ class File(models.Model):
ext = '.unknown' ext = '.unknown'
return name + ext return name + ext
def get_language(self):
language = self.name.split('.')
if len(language) >= 3 and len(language[-2]) == 2:
return language[-2]
return ''
def delete_file(sender, **kwargs): def delete_file(sender, **kwargs):
f = kwargs['instance'] f = kwargs['instance']
#FIXME: delete streams here #FIXME: delete streams here
@ -394,7 +365,7 @@ class Volume(models.Model):
class Instance(models.Model): class Instance(models.Model):
class Meta: class Meta:
unique_together = ("name", "folder", "volume") unique_together = ("path", "volume")
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)
@ -403,15 +374,14 @@ class Instance(models.Model):
ctime = models.IntegerField(default=lambda: int(time.time()), editable=False) ctime = models.IntegerField(default=lambda: int(time.time()), editable=False)
mtime = models.IntegerField(default=lambda: int(time.time()), editable=False) mtime = models.IntegerField(default=lambda: int(time.time()), editable=False)
name = models.CharField(max_length=2048) path = models.CharField(max_length=2048)
folder = models.CharField(max_length=2048) ignore = models.BooleanField(default=False)
extra = models.BooleanField(default=False)
file = models.ForeignKey(File, related_name='instances') file = models.ForeignKey(File, related_name='instances')
volume = models.ForeignKey(Volume, related_name='files') volume = models.ForeignKey(Volume, related_name='files')
def __unicode__(self): def __unicode__(self):
return u"%s's %s <%s>"% (self.volume.user, self.name, self.file.oshash) return u"%s's %s <%s>"% (self.volume.user, self.path, self.file.oshash)
@property @property
def itemId(self): def itemId(self):
@ -421,14 +391,13 @@ class Instance(models.Model):
return { return {
'user': self.volume.user.username, 'user': self.volume.user.username,
'volume': self.volume.name, 'volume': self.volume.name,
'folder': self.folder, 'path': self.path
'name': self.name
} }
def frame_path(frame, name): def frame_path(frame, name):
ext = os.path.splitext(name)[-1] ext = os.path.splitext(name)[-1]
name = "%s%s" % (frame.position, ext) name = "%s%s" % (frame.position, ext)
return frame.file.path(name) return frame.file.get_path(name)
class Frame(models.Model): class Frame(models.Model):
@ -486,7 +455,7 @@ class Stream(models.Model):
return u"%s/%s" % (self.file, self.name()) return u"%s/%s" % (self.file, self.name())
def path(self, name=''): def path(self, name=''):
return self.file.path(name) return self.file.get_path(name)
def extract_derivatives(self): def extract_derivatives(self):
config = settings.CONFIG['video'] config = settings.CONFIG['video']

View file

@ -1,29 +1,19 @@
# -*- 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 os
from celery.decorators import task from celery.decorators import task
import ox
from item.utils import parse_path
from item.models import get_item from item.models import get_item
from django.conf import settings from django.conf import settings
import models import models
_INSTANCE_KEYS = ('mtime', 'name', 'folder')
_INSTANCE_KEYS = ('mtime', 'path')
def get_or_create_item(volume, f, user): def get_or_create_item(volume, info, user):
in_same_folder = models.Instance.objects.filter(folder=f['folder'], volume=volume) item_info = ox.parse_movie_info(info['path'])
if in_same_folder.count() > 0: return get_item(item_info, user)
i = in_same_folder[0].file.item
else:
if settings.USE_FOLDER:
item_info = parse_path(f['folder'])
else:
item_info = parse_path(f['path'])
i = get_item(item_info, user)
return i
def get_or_create_file(volume, f, user, item=None): def get_or_create_file(volume, f, user, item=None):
try: try:
@ -31,7 +21,7 @@ def get_or_create_file(volume, f, user, item=None):
except models.File.DoesNotExist: except models.File.DoesNotExist:
file = models.File() file = models.File()
file.oshash = f['oshash'] file.oshash = f['oshash']
file.name = f['name'] file.path = f['path']
if item: if item:
file.item = item file.item = item
else: else:
@ -50,13 +40,11 @@ def update_or_create_instance(volume, f):
setattr(instance, key, f[key]) setattr(instance, key, f[key])
updated=True updated=True
if updated: if updated:
if instance.name.lower().startswith('extras/') or \ instance.ignore = False
instance.name.lower().startswith('versions/'):
instance.extra = True
instance.save() instance.save()
instance.file.save() instance.file.save()
else: else:
instance = models.Instance.objects.filter(name=f['name'], folder=f['folder'], volume=volume) instance = models.Instance.objects.filter(path=f['path'], volume=volume)
if instance.count()>0: if instance.count()>0:
#same path, other oshash, keep path/item mapping, remove instance #same path, other oshash, keep path/item mapping, remove instance
item = instance[0].file.item item = instance[0].file.item
@ -69,9 +57,6 @@ def update_or_create_instance(volume, f):
instance.file = get_or_create_file(volume, f, volume.user, item) instance.file = get_or_create_file(volume, f, volume.user, item)
for key in _INSTANCE_KEYS: for key in _INSTANCE_KEYS:
setattr(instance, key, f[key]) setattr(instance, key, f[key])
if instance.name.lower().startswith('extras/') or \
instance.name.lower().startswith('versions/'):
instance.extra = True
instance.save() instance.save()
instance.file.save() instance.file.save()
instance.file.item.update_wanted() instance.file.item.update_wanted()
@ -83,17 +68,15 @@ def update_files(user, volume, files):
volume, created = models.Volume.objects.get_or_create(user=user, name=volume) volume, created = models.Volume.objects.get_or_create(user=user, name=volume)
all_files = [] all_files = []
for f in files: for f in files:
folder = f['path'].split('/') #ignore extras etc,
name = folder.pop() #imdb stlye is L/Last, First/Title (Year)/Title.. 4
if folder and folder[-1].lower() in ('extras', 'versions', 'dvds'): #otherwise T/Title (Year)/Title... 3
name = '/'.join([folder.pop(), name]) folder_depth = settings.USE_IMDB and 4 or 3
f['folder'] = '/'.join(folder) if len(f['path'].split('/')) == folder_depth:
f['name'] = name all_files.append(f['oshash'])
all_files.append(f['oshash']) update_or_create_instance(volume, f)
update_or_create_instance(volume, f)
#remove deleted files #remove deleted files
#FIXME: can this have any bad consequences? i.e. on the selction of used item files.
models.Instance.objects.filter(volume=volume).exclude(file__oshash__in=all_files).delete() models.Instance.objects.filter(volume=volume).exclude(file__oshash__in=all_files).delete()
@task(queue="encoding") @task(queue="encoding")

View file

@ -9,6 +9,7 @@ from django.shortcuts import get_object_or_404, redirect
from django.conf import settings from django.conf import settings
from django.db.models import Count, Sum from django.db.models import Count, Sum
import ox
from ox.utils import json from ox.utils import json
from ox.django.decorators import login_required_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 ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response
@ -105,7 +106,7 @@ def update(request):
file__wanted=True)] file__wanted=True)]
response['data']['file'] = [f.file.oshash for f in files.filter(file__is_subtitle=True, response['data']['file'] = [f.file.oshash for f in files.filter(file__is_subtitle=True,
file__available=False, file__available=False,
name__endswith='.srt')] path__endswith='.srt')]
return render_to_json_response(response) return render_to_json_response(response)
actions.register(update, cache=False) actions.register(update, cache=False)
@ -319,24 +320,8 @@ def lookup_file(request, oshash):
def _order_query(qs, sort, prefix=''): def _order_query(qs, sort, prefix=''):
order_by = [] order_by = []
if len(sort) == 1: if len(sort) == 1:
sort.append({'operator': '+', 'key': 'sort_name'}) sort.append({'operator': '+', 'key': 'path'})
sort.append({'operator': '-', 'key': 'created'}) sort.append({'operator': '-', 'key': 'created'})
'''
if sort[0]['key'] == 'title':
sort.append({'operator': '-', 'key': 'year'})
sort.append({'operator': '+', 'key': 'director'})
elif sort[0]['key'] == 'director':
sort.append({'operator': '-', 'key': 'year'})
sort.append({'operator': '+', 'key': 'title'})
elif sort[0]['key'] == 'year':
sort.append({'operator': '+', 'key': 'director'})
sort.append({'operator': '+', 'key': 'title'})
elif not sort[0]['key'] in ('value', 'value_sort'):
sort.append({'operator': '+', 'key': 'director'})
sort.append({'operator': '-', 'key': 'year'})
sort.append({'operator': '+', 'key': 'title'})
'''
for e in sort: for e in sort:
operator = e['operator'] operator = e['operator']
@ -346,7 +331,7 @@ def _order_query(qs, sort, prefix=''):
'id': 'item__itemId', 'id': 'item__itemId',
'users': 'instances__volume__user__username', 'users': 'instances__volume__user__username',
'resolution': 'width', 'resolution': 'width',
'name': 'sort_name' 'path': 'sort_path'
}.get(e['key'], e['key']) }.get(e['key'], e['key'])
#if operator=='-' and '%s_desc'%key in models.ItemSort.descending_fields: #if operator=='-' and '%s_desc'%key in models.ItemSort.descending_fields:
# key = '%s_desc' % key # key = '%s_desc' % key
@ -400,10 +385,10 @@ Groups
keys: array of keys to return keys: array of keys to return
group: group elements by, country, genre, director... group: group elements by, country, genre, director...
possible values for keys: name, items possible values for keys: path, items
with keys with keys
items contains list of {'name': string, 'items': int}: items contains list of {'path': string, 'items': int}:
return {'status': {'code': int, 'text': string}, return {'status': {'code': int, 'text': string},
'data': {items: array}} 'data': {items: array}}
@ -465,7 +450,7 @@ Positions
elif 'range' in data: elif 'range' in data:
qs = qs[query['range'][0]:query['range'][1]] qs = qs[query['range'][0]:query['range'][1]]
response['data']['items'] = [{'name': i['value'], 'items': i[items]} for i in qs] response['data']['items'] = [{'path': i['value'], 'items': i[items]} for i in qs]
else: else:
response['data']['items'] = qs.count() response['data']['items'] = qs.count()
elif 'positions' in query: elif 'positions' in query:
@ -481,6 +466,7 @@ Positions
response['data']['items'] = [] response['data']['items'] = []
qs = models.File.objects.filter(item__in=query['qs']) qs = models.File.objects.filter(item__in=query['qs'])
qs = _order_query(qs, query['sort']) qs = _order_query(qs, query['sort'])
qs = qs.select_related()
keys = query['keys'] keys = query['keys']
qs = qs[query['range'][0]:query['range'][1]] qs = qs[query['range'][0]:query['range'][1]]
response['data']['items'] = [f.json(keys) for f in qs] response['data']['items'] = [f.json(keys) for f in qs]
@ -492,3 +478,19 @@ Positions
actions.register(findFiles) actions.register(findFiles)
def parsePath(request): #parse path and return info
'''
param data {
path: string
}
return {
status: {'code': int, 'text': string},
data: {
imdb: string
}
}
'''
path = json.loads(request.POST['data'])['path']
response = json_response(ox.parse_movie_path(path))
return render_to_json_response(response)
actions.register(parsePath)

17
pandora/item/data_api.py Normal file
View file

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import division, with_statement
import ox
from django.conf import settings
_DATA_SERVICE=None
def external_data(action, data):
global _DATA_SERVICE
try:
if not _DATA_SERVICE and settings.DATA_SERVICE:
_DATA_SERVICE = ox.API(settings.DATA_SERVICE)
return getattr(_DATA_SERVICE, action)(data)
except:
pass
return {'status': {'code': 500, 'text':'not available'}, 'data': {}}

View file

@ -15,7 +15,6 @@ from urllib import quote
from django.db import models from django.db import models
from django.db.models import Count, Q, Sum from django.db.models import Count, Q, Sum
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.utils import simplejson as json
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.db.models.signals import pre_delete from django.db.models.signals import pre_delete
@ -30,6 +29,7 @@ import managers
import utils import utils
import tasks import tasks
from .timelines import join_timelines from .timelines import join_timelines
from data_api import external_data
from archive import extract from archive import extract
from annotation.models import Annotation, Layer from annotation.models import Annotation, Layer
@ -39,11 +39,45 @@ from person.models import get_name_sort
from title.models import get_title_sort from title.models import get_title_sort
def get_id(info):
q = Item.objects.all()
for key in ('title', 'director', 'year'):
# 'episodeTitle', 'episodeDirector', 'episodeYear', 'season', 'episode'):
if key in info and info[key]:
k = 'find__key'
v = 'find__value'
if key in Item.facet_keys + ['title']:
k = 'facets__key'
v = 'facets__value'
if isinstance(info[key], list):
for value in info[key]:
q = q.filter(**{k: key, v: value})
else:
q = q.filter(**{k:key, v:info[key]})
if q.count() == 1:
return q[0].itemId
if settings.DATA_SERVICE:
r = external_data('getId', info)
if r['status']['code'] == 200:
imdbId = r['data']['imdbId']
return imdbId
return None
def get_item(info, user=None, async=False): def get_item(info, user=None, async=False):
''' '''
info dict with: info dict with:
imdbId, title, director, episode_title, season, series imdbId, title, director, year,
season, episode, episodeTitle, episodeDirector, episodeYear
''' '''
item_data = {
'title': info['title'],
'director': info['director'],
'year': info.get('year', '')
}
for key in ('episodeTitle', 'episodeDirector', 'episodeYear',
'season', 'episode', 'seriesTitle'):
if key in info and info[key]:
item_data[key] = info[key]
if settings.USE_IMDB: if settings.USE_IMDB:
if 'imdbId' in info and info['imdbId']: if 'imdbId' in info and info['imdbId']:
try: try:
@ -51,11 +85,7 @@ def get_item(info, user=None, async=False):
except Item.DoesNotExist: except Item.DoesNotExist:
item = Item(itemId=info['imdbId']) item = Item(itemId=info['imdbId'])
if 'title' in info and 'director' in info: if 'title' in info and 'director' in info:
item.external_data = { item.external_data = item_data
'title': info['title'],
'director': info['director'],
'year': info.get('year', '')
}
item.user = user item.user = user
item.oxdbId = item.itemId item.oxdbId = item.itemId
item.save() item.save()
@ -64,61 +94,39 @@ def get_item(info, user=None, async=False):
else: else:
item.update_external() item.update_external()
else: else:
q = Item.objects.all() itemId = get_id(info)
for key in ('title', 'director', 'year'): if itemId:
if key in info and info[key]:
if isinstance(info[key], list):
q = q.filter(find__key=key, find__value='\n'.join(info[key]))
else:
q = q.filter(find__key=key, find__value=info[key])
if q.count() >= 1:
item = q[0]
elif not 'oxdbId' in info:
item = Item()
item.data = {
'title': info['title'],
'director': info['director'],
'year': info.get('year', '')
}
for key in ('episode_title', 'series_title', 'season', 'episode'):
if key in info and info[key]:
item.data[key] = info[key]
item.oxdbId = item.oxdb_id()
item.save()
else:
try: try:
item = Item.objects.get(itemId=info['oxdbId']) item = Item.objects.get(itemId=itemId)
except Item.DoesNotExist: except Item.DoesNotExist:
item = Item() info['imdbId'] = itemId
item.data = { item = get_item(info)
'title': info['title'], return item
'director': info['director'],
'year': info.get('year', '')
}
item.itemId = info['oxdbId']
for key in ('episode_title', 'series_title', 'season', 'episode'): try:
if key in info and info[key]: item = Item.objects.get(itemId=info.get('oxdbId'))
item.data[key] = info[key] except Item.DoesNotExist:
try: item = Item()
existing_item = Item.objects.get(oxdbId=item.oxdb_id()) item.user = user
item = existing_item item.data = item_data
except Item.DoesNotExist: item.itemId = info.get('oxdbId', item.oxdb_id())
item.save() try:
existing_item = Item.objects.get(oxdbId=item.oxdb_id())
item = existing_item
except Item.DoesNotExist:
item.oxdbId = item.oxdb_id()
item.save()
else: else:
qs = Item.objects.filter(find__key='title', find__value=info['title']) qs = Item.objects.filter(find__key='title', find__value=info['title'])
if qs.count() == 1: if qs.count() == 1:
item = qs[0] item = qs[0]
else: else:
item = Item() item = Item()
item.data = { item.data = item_data
'title': info['title']
}
item.user = user item.user = user
item.save() item.save()
return item return item
class Item(models.Model): class Item(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)
@ -202,59 +210,13 @@ class Item(models.Model):
self.data[key] = data[key] self.data[key] = data[key]
self.save() self.save()
def reviews(self):
reviews = self.get('reviews', [])
_reviews = []
for r in reviews:
for url in settings.REVIEW_WHITELIST:
if url in r[0]:
_reviews.append({
'source': settings.REVIEW_WHITELIST[url],
'url': r[0]
})
return _reviews
def update_external(self): def update_external(self):
if len(self.itemId) == 7: if settings.DATA_SERVICE and not self.itemId.startswith('0x'):
data = ox.web.imdb.Imdb(self.itemId) response = external_data('getData', {'id': self.itemId})
#FIXME: all this should be in ox.web.imdb.Imdb if response['status']['code'] == 200:
for key in ('directors', 'writers', 'editors', 'producers', self.external_data = response['data']
'cinematographers', 'languages', 'genres', 'keywords', self.save()
'episode_directors'): self.make_poster(True)
if key in data:
data[key[:-1]] = data.pop(key)
if 'countries' in data:
data['country'] = data.pop('countries')
if 'release date' in data:
data['releasedate'] = data.pop('release date')
if isinstance(data['releasedate'], list):
data['releasedate'] = min(data['releasedate'])
if 'plot' in data:
data['summary'] = data.pop('plot')
if 'cast' in data:
if isinstance(data['cast'][0], basestring):
data['cast'] = [data['cast']]
data['actor'] = [c[0] for c in data['cast']]
data['cast'] = map(lambda x: {'actor': x[0], 'character': x[1]}, data['cast'])
if 'trivia' in data:
def fix_links(t):
def fix_names(m):
return '<a href="/?find=name:%s">%s</a>' % (
quote(m.group(2).encode('utf-8')), m.group(2)
)
t = re.sub('<a href="(/name/.*?/)">(.*?)</a>', fix_names, t)
def fix_titles(m):
return '<a href="/?find=title:%s">%s</a>' % (
quote(m.group(2).encode('utf-8')), m.group(2)
)
t = re.sub('<a href="(/title/.*?/)">(.*?)</a>', fix_titles, t)
return t
data['trivia'] = [fix_links(t) for t in data['trivia']]
if 'aspectratio' in data:
data['aspectRatio'] = data.pop('aspectratio')
#filter reviews
self.external_data = data
self.save()
def expand_connections(self): def expand_connections(self):
c = self.get('connections') c = self.get('connections')
@ -292,10 +254,17 @@ class Item(models.Model):
super(Item, self).save(*args, **kwargs) super(Item, self).save(*args, **kwargs)
if not settings.USE_IMDB: if not settings.USE_IMDB:
self.itemId = ox.to26(self.id) self.itemId = ox.to26(self.id)
#this does not work if another item without imdbid has the same metadata
oxdbId = self.oxdb_id() oxdbId = self.oxdb_id()
if oxdbId: if oxdbId:
self.oxdbId = oxdbId if self.oxdbId != oxdbId:
q = Item.objects.filter(oxdbId=oxdbId).exclude(id=self.id)
if q.count() != 0:
self.oxdbId = None
q[0].merge_with(self, save=False)
else:
self.oxdbId = oxdbId
#id changed, what about existing item with new id? #id changed, what about existing item with new id?
if settings.USE_IMDB and len(self.itemId) != 7 and self.oxdbId != self.itemId: if settings.USE_IMDB and len(self.itemId) != 7 and self.oxdbId != self.itemId:
@ -333,7 +302,7 @@ class Item(models.Model):
self.delete_files() self.delete_files()
super(Item, self).delete(*args, **kwargs) super(Item, self).delete(*args, **kwargs)
def merge_with(self, other): def merge_with(self, other, save=True):
''' '''
move all related tables to other and delete self move all related tables to other and delete self
''' '''
@ -350,15 +319,15 @@ class Item(models.Model):
f.item = other f.item = other
f.save() f.save()
self.delete() self.delete()
other.save() if save:
#FIXME: update poster, stills and streams after this other.save()
#FIXME: update poster, stills and streams after this
def get_posters(self): def get_posters(self):
url = self.prefered_poster_url() url = self.prefered_poster_url()
external_posters = self.external_data.get('posters', {})
services = external_posters.keys()
index = [] index = []
services = [p['service']
for p in self.poster_urls.values("service")
.annotate(Count("id")).order_by()]
for service in settings.POSTER_PRECEDENCE: for service in settings.POSTER_PRECEDENCE:
if service in services: if service in services:
index.append(service) index.append(service)
@ -369,7 +338,6 @@ class Item(models.Model):
index.append(settings.URL) index.append(settings.URL)
posters = [] posters = []
poster = self.path('siteposter.jpg') poster = self.path('siteposter.jpg')
poster = os.path.abspath(os.path.join(settings.MEDIA_ROOT, poster)) poster = os.path.abspath(os.path.join(settings.MEDIA_ROOT, poster))
if os.path.exists(poster): if os.path.exists(poster):
@ -382,18 +350,12 @@ class Item(models.Model):
'index': index.index(settings.URL) 'index': index.index(settings.URL)
}) })
got = {} for service in external_posters:
for p in self.poster_urls.all().order_by('-height'): p = external_posters[service][0]
if p.service not in got: p['source'] = service
got[p.service] = 1 p['selected'] = p['url'] == url
posters.append({ p['index'] = index.index(service)
'url': p.url, posters.append(p)
'width': p.width,
'height': p.height,
'source': p.service,
'selected': p.url == url,
'index': index.index(p.service)
})
posters.sort(key=lambda a: a['index']) posters.sort(key=lambda a: a['index'])
return posters return posters
@ -452,10 +414,6 @@ class Item(models.Model):
if value: if value:
i[key] = value i[key] = value
if 'reviews' in i:
i['reviews'] = self.reviews()
if not i['reviews']:
del i['reviews']
if 'cast' in i and isinstance(i['cast'][0], basestring): if 'cast' in i and isinstance(i['cast'][0], basestring):
i['cast'] = [i['cast']] i['cast'] = [i['cast']]
if 'cast' in i and isinstance(i['cast'][0], list): if 'cast' in i and isinstance(i['cast'][0], list):
@ -497,15 +455,16 @@ class Item(models.Model):
return info return info
return i return i
def oxdb_id(self): def oxdb_id(self):
if not settings.USE_IMDB: if not settings.USE_IMDB:
return self.itemId return self.itemId
if not self.get('title') and not self.get('director'): if not self.get('title') and not self.get('director'):
return None return None
return utils.oxdb_id(self.get('title', ''), self.get('director', []), str(self.get('year', '')), return ox.get_oxid(self.get('title', ''), self.get('director', []),
self.get('season', ''), self.get('episode', ''), str(self.get('year', '')),
self.get('episode_title', ''), self.get('episode_director', []), self.get('episode_year', '')) self.get('season', ''), self.get('episode', ''),
self.get('episodeTitle', ''),
self.get('episodeDirector', []), self.get('episodeYear', ''))
''' '''
Search related functions Search related functions
@ -528,10 +487,10 @@ class Item(models.Model):
i = key['id'] i = key['id']
if i == 'title': if i == 'title':
save(i, u'\n'.join([self.get('title', 'Untitled'), save(i, u'\n'.join([self.get('title', 'Untitled'),
self.get('original_title', '')])) self.get('originalTitle', '')]))
elif i == 'filename': elif i == 'filename':
save(i, save(i,
'\n'.join([os.path.join(f.folder, f.name) for f in self.files.all()])) '\n'.join([f.path for f in self.files.all()]))
elif key['type'] == 'layer': elif key['type'] == 'layer':
qs = Annotation.objects.filter(layer__name=i, item=self).order_by('start') qs = Annotation.objects.filter(layer__name=i, item=self).order_by('start')
save(i, '\n'.join([l.value for l in qs])) save(i, '\n'.join([l.value for l in qs]))
@ -662,7 +621,7 @@ class Item(models.Model):
s.words = sum([len(a.value.split()) for a in self.annotations.all()]) s.words = sum([len(a.value.split()) for a in self.annotations.all()])
s.clips = 0 #FIXME: get clips from all layers or something s.clips = 0 #FIXME: get clips from all layers or something
videos = self.files.filter(active=True, is_video=True) videos = self.files.filter(selected=True, is_video=True)
if videos.count() > 0: if videos.count() > 0:
s.duration = sum([v.duration for v in videos]) s.duration = sum([v.duration for v in videos])
v = videos[0] v = videos[0]
@ -715,7 +674,7 @@ class Item(models.Model):
current_values = [current_values] current_values = [current_values]
else: else:
current_values = [] current_values = []
ot = self.get('original_title') ot = self.get('originalTitle')
if ot: if ot:
current_values.append(ot) current_values.append(ot)
#FIXME: is there a better way to build name collection? #FIXME: is there a better way to build name collection?
@ -777,75 +736,43 @@ class Item(models.Model):
return [f.json() for f in self.files.all()] return [f.json() for f in self.files.all()]
def users_with_files(self): def users_with_files(self):
return User.objects.filter(volumes__files__file__item=self).distinct() return User.objects.filter(
volumes__files__file__item=self
).order_by('-profile__level', 'date_joined').distinct()
def sets(self):
sets = []
for user in self.users_with_files():
files = self.files.filter(instances__volume__user=user, instances__ignore=False)
sets.append(files)
return sets
def update_wanted(self): def update_wanted(self):
users = self.users_with_files() wanted = []
if users.filter(is_superuser=True).count()>0: for s in self.sets():
files = self.files.filter(instances__volume__user__is_superuser=True) if s.filter(selected=False).count() != 0:
users = User.objects.filter(volumes__files__file__in=files, wanted += [i.id for i in s]
is_superuser=True).distinct() else:
elif users.filter(is_staff=True).count()>0: break
files = self.files.filter(instances__volume__user__is_staff=True) self.files.filter(id__in=wanted).update(wanted=True)
users = User.objects.filter(volumes__files__file__in=files, self.files.exclude(id__in=wanted).update(wanted=False)
is_staff=True).distinct()
else:
files = self.files.all()
files = files.filter(is_video=True, instances__extra=False, instances__gt=0).order_by('part')
folders = list(set([f.folder for f in files]))
if len(folders) > 1:
files = files.filter(folder=folders[0])
files.update(wanted=True)
self.files.exclude(id__in=files).update(wanted=False)
def update_selected(self): def update_selected(self):
files = archive.models.File.objects.filter(item=self, for s in self.sets():
streams__available=True, if s.filter(Q(is_video=True)|Q(is_audio=True)).filter(available=False).count() == 0:
streams__source=None) if s.filter(selected=False).count() > 0:
if files.count() == 0: s.update(selected=True, wanted=False)
return self.rendered = False
self.save()
def get_level(users): self.update_timeline()
if users.filter(is_superuser=True).count() > 0: level = 0 break
elif users.filter(is_staff=True).count() > 0: level = 1
else: level = 2
return level
current_users = User.objects.filter(volumes__files__file__in=self.files.filter(active=True)).distinct()
current_level = get_level(current_users)
users = User.objects.filter(volumes__files__file__in=files).distinct()
possible_level = get_level(users)
if possible_level < current_level:
files = self.files.filter(instances__volume__user__in=users).order_by('part')
#FIXME: this should be instance folders
folders = list(set([f.folder
for f in files.filter(is_video=True, instances__extra=False)]))
files = files.filter(folder__startswith=folders[0])
files.update(active=True)
self.rendered = False
self.save()
self.update_timeline()
else:
files = self.files.filter(instances__volume__user__in=current_users).order_by('part')
#FIXME: this should be instance folders
folders = list(set([f.folder
for f in files.filter(is_video=True, instances__extra=False)]))
files = files.filter(folder__startswith=folders[0])
if files.filter(active=False, is_video=True).count() > 0:
files.update(active=True)
self.rendered = False
self.save()
self.update_timeline()
def make_torrent(self): def make_torrent(self):
base = self.path('torrent') base = self.path('torrent')
base = os.path.abspath(os.path.join(settings.MEDIA_ROOT, base)) base = os.path.abspath(os.path.join(settings.MEDIA_ROOT, base))
if os.path.exists(base): if os.path.exists(base):
shutil.rmtree(base) shutil.rmtree(base)
os.makedirs(base) ox.makedirs(base)
base = self.path('torrent/%s' % self.get('title')) base = self.path('torrent/%s' % self.get('title'))
base = os.path.abspath(os.path.join(settings.MEDIA_ROOT, base)) base = os.path.abspath(os.path.join(settings.MEDIA_ROOT, base))
@ -893,7 +820,7 @@ class Item(models.Model):
def streams(self): def streams(self):
return archive.models.Stream.objects.filter(source=None, available=True, return archive.models.Stream.objects.filter(source=None, available=True,
file__item=self, file__is_video=True, file__active=True).order_by('file__part') file__item=self, file__is_video=True, file__selected=True).order_by('file__part')
def update_timeline(self, force=False): def update_timeline(self, force=False):
streams = self.streams() streams = self.streams()
@ -911,32 +838,6 @@ class Item(models.Model):
self.rendered = streams != [] self.rendered = streams != []
self.save() self.save()
'''
Poster related functions
'''
def update_poster_urls(self):
_current = {}
for s in settings.POSTER_SERVICES:
url = '%s?id=%s'%(s, self.itemId)
try:
data = json.loads(ox.net.readUrlUnicode(url))
except:
continue
for service in data:
if service not in _current:
_current[service] = []
for poster in data[service]:
_current[service].append(poster)
#FIXME: remove urls that are no longer listed
for service in _current:
for poster in _current[service]:
p, created = PosterUrl.objects.get_or_create(item=self, url=poster['url'], service=service)
if created:
p.width = poster['width']
p.height = poster['height']
p.save()
def delete_poster(self): def delete_poster(self):
if self.poster: if self.poster:
path = self.poster.path path = self.poster.path
@ -948,15 +849,14 @@ class Item(models.Model):
os.unlink(f) os.unlink(f)
def prefered_poster_url(self): def prefered_poster_url(self):
self.update_poster_urls() external_posters = self.external_data.get('posters', {})
service = self.poster_source service = self.poster_source
if service and service != settings.URL: if service and service != settings.URL and service in external_posters:
for u in self.poster_urls.filter(service=service).order_by('-height'): return external_posters[service][0]['url']
return u.url
if not service: if not service:
for service in settings.POSTER_PRECEDENCE: for service in settings.POSTER_PRECEDENCE:
for u in self.poster_urls.filter(service=service).order_by('-height'): if service in external_posters:
return u.url return external_posters[service][0]['url']
return None return None
def make_timeline(self): def make_timeline(self):
@ -984,7 +884,7 @@ class Item(models.Model):
poster = os.path.abspath(os.path.join(settings.MEDIA_ROOT, poster)) poster = os.path.abspath(os.path.join(settings.MEDIA_ROOT, poster))
frame = self.get_poster_frame_path() frame = self.get_poster_frame_path()
timeline = '%s.64.png' % self.timeline_prefix timeline = '%s64p.png' % self.timeline_prefix
director = u', '.join(self.get('director', ['Unknown Director'])) director = u', '.join(self.get('director', ['Unknown Director']))
cmd = [settings.ITEM_POSTER, cmd = [settings.ITEM_POSTER,
@ -1018,7 +918,7 @@ class Item(models.Model):
def poster_frames(self): def poster_frames(self):
frames = [] frames = []
offset = 0 offset = 0
for f in self.files.filter(active=True, is_video=True): for f in self.files.filter(selected=True, is_video=True):
for ff in f.frames.all(): for ff in f.frames.all():
frames.append({ frames.append({
'position': offset + ff.position, 'position': offset + ff.position,
@ -1052,7 +952,7 @@ class Item(models.Model):
frame = self.get_poster_frame_path() frame = self.get_poster_frame_path()
icon = self.path('icon.jpg') icon = self.path('icon.jpg')
self.icon.name = icon self.icon.name = icon
timeline = '%s.64.png' % self.timeline_prefix timeline = '%s64p.png' % self.timeline_prefix
cmd = [settings.ITEM_ICON, cmd = [settings.ITEM_ICON,
'-i', self.icon.path '-i', self.icon.path
] ]
@ -1074,8 +974,8 @@ class Item(models.Model):
Annotation.objects.filter(layer=layer,item=self).delete() Annotation.objects.filter(layer=layer,item=self).delete()
offset = 0 offset = 0
language = '' language = ''
languages = [f.language for f in self.files.filter(active=True, is_subtitle=True, subtitles = self.files.filter(selected=True, is_subtitle=True, available=True)
available=True)] languages = [f.language for f in subtitles]
if languages: if languages:
if 'en' in languages: if 'en' in languages:
language = 'en' language = 'en'
@ -1083,10 +983,18 @@ class Item(models.Model):
language = '' language = ''
else: else:
language = languages[0] language = languages[0]
for f in self.files.filter(active=True, is_subtitle=True,
available=True, language=language).order_by('part'): #loop over all videos
user = f.instances.all()[0].volume.user for f in self.files.filter(Q(is_audio=True)|Q(is_video=True)) \
for data in f.srt(offset): .filter(selected=True).order_by('part'):
prefix = os.path.splitext(f.path)[0]
#if there is a subtitle with the same prefix, import
q = subtitles.filter(path__startswith=prefix,
language=language)
if q.count() == 1:
s = q[0]
user = s.instances.all()[0].volume.user
for data in s.srt(offset):
annotation = Annotation( annotation = Annotation(
item=f.item, item=f.item,
layer=layer, layer=layer,
@ -1096,14 +1004,7 @@ class Item(models.Model):
user=user user=user
) )
annotation.save() annotation.save()
duration = self.files.filter(Q(is_audio=True)|Q(is_video=True)) \ offset += f.duration
.filter(active=True, available=True, part=f.part)
if duration:
duration = duration[0].duration
else:
Annotation.objects.filter(layer=layer,item=self).delete()
break
offset += duration
self.update_find() self.update_find()
def delete_item(sender, **kwargs): def delete_item(sender, **kwargs):

View file

@ -8,7 +8,7 @@ from glob import glob
import Image import Image
def loadTimeline(timeline_prefix, height=64): def loadTimeline(timeline_prefix, height=64):
files = sorted(glob('%s.%s.*.png' % (timeline_prefix, height))) files = sorted(glob('%s%sp*.png' % (timeline_prefix, height)))
f = Image.open(files[0]) f = Image.open(files[0])
width = f.size[0] width = f.size[0]
f = Image.open(files[-1]) f = Image.open(files[-1])
@ -22,7 +22,7 @@ def loadTimeline(timeline_prefix, height=64):
return timeline return timeline
def makeTiles(timeline_prefix, height=16, width=3600): def makeTiles(timeline_prefix, height=16, width=3600):
files = glob('%s.64.*.png' % timeline_prefix) files = glob('%s64p*.png' % timeline_prefix)
fps = 25 fps = 25
part_step = 60 part_step = 60
output_width = width output_width = width
@ -43,14 +43,14 @@ def makeTiles(timeline_prefix, height=16, width=3600):
i = 0 i = 0
while pos < timeline.size[0]: while pos < timeline.size[0]:
end = min(pos+output_width, timeline.size[0]) end = min(pos+output_width, timeline.size[0])
timeline.crop((pos, 0, end, timeline.size[1])).save('%s.%s.%04d.png' % (timeline_prefix, timeline.size[1], i)) timeline.crop((pos, 0, end, timeline.size[1])).save('%s%sp%04d.png' % (timeline_prefix, timeline.size[1], i))
pos += output_width pos += output_width
i += 1 i += 1
def makeTimelineOverview(timeline_prefix, width, inpoint=0, outpoint=0, duration=-1, height=16): def makeTimelineOverview(timeline_prefix, width, inpoint=0, outpoint=0, duration=-1, height=16):
input_scale = 25 input_scale = 25
timeline_file = '%s.%s.png' % (timeline_prefix, height) timeline_file = '%s%sp.png' % (timeline_prefix, height)
if outpoint > 0: if outpoint > 0:
timeline_file = '%s.overview.%s.%d-%d.png' % (timeline_prefix, height, inpoint, outpoint) timeline_file = '%s.overview.%s.%d-%d.png' % (timeline_prefix, height, inpoint, outpoint)
@ -76,7 +76,7 @@ def join_timelines(timelines, prefix):
tiles = [] tiles = []
for timeline in timelines: for timeline in timelines:
tiles += sorted(glob('%s.%s.*.png'%(timeline, height))) tiles += sorted(glob('%s%sp*.png'%(timeline, height)))
timeline = Image.new("RGB", (2 * width, height)) timeline = Image.new("RGB", (2 * width, height))
@ -87,7 +87,7 @@ def join_timelines(timelines, prefix):
timeline.paste(tile, (pos, 0, pos+tile.size[0], height)) timeline.paste(tile, (pos, 0, pos+tile.size[0], height))
pos += tile.size[0] pos += tile.size[0]
if pos >= width: if pos >= width:
timeline_name = '%s.%s.%04d.png' % (prefix, height, i) timeline_name = '%s%sp%04d.png' % (prefix, height, i)
timeline.crop((0, 0, width, height)).save(timeline_name) timeline.crop((0, 0, width, height)).save(timeline_name)
i += 1 i += 1
if pos > width: if pos > width:
@ -95,7 +95,7 @@ def join_timelines(timelines, prefix):
timeline.paste(t, (0, 0, t.size[0], height)) timeline.paste(t, (0, 0, t.size[0], height))
pos -= width pos -= width
if pos: if pos:
timeline_name = '%s.%s.%04d.png' % (prefix, height, i) timeline_name = '%s%sp%04d.png' % (prefix, height, i)
timeline.crop((0, 0, pos, height)).save(timeline_name) timeline.crop((0, 0, pos, height)).save(timeline_name)
makeTiles(prefix, 16, 3600) makeTiles(prefix, 16, 3600)

View file

@ -1,19 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
# #
from decimal import Decimal from decimal import Decimal
import os
import re import re
import hashlib
import unicodedata import unicodedata
from django.conf import settings
import ox
import ox.iso
from ox.normalize import normalizeName, normalizeTitle
import ox.web.imdb
def parse_decimal(string): def parse_decimal(string):
string = string.replace(':', '/') string = string.replace(':', '/')
@ -29,170 +20,6 @@ def plural_key(term):
}.get(term, term + 's') }.get(term, term + 's')
def oxid(title, director, year='', seriesTitle='', episodeTitle='', season=0, episode=0):
director = ', '.join(director)
oxid_value = u"\n".join([title, director, year])
oxid = hashlib.sha1(oxid_value.encode('utf-8')).hexdigest()
if seriesTitle:
oxid_value = u"\n".join([seriesTitle, "%02d" % season])
oxid = hashlib.sha1(oxid_value.encode('utf-8')).hexdigest()[:20]
oxid_value = u"\n".join(["%02d" % episode, episodeTitle, director, year])
oxid += hashlib.sha1(oxid_value.encode('utf-8')).hexdigest()[:20]
return u"0x" + oxid
def oxdb_id(title, director=[], year='', season='', episode='', episode_title='', episode_director=[], episode_year=''):
# new id function, will replace oxid()
def get_hash(string):
return hashlib.sha1(string.encode('utf-8')).hexdigest().upper()
director = ', '.join(director)
episode_director = ', '.join(episode_director)
if not episode:
oxdb_id = get_hash(director)[:8] + get_hash('\n'.join([title, str(year)]))[:8]
else:
oxdb_id = get_hash('\n'.join([director, title, str(year), str(season)]))[:8] + \
get_hash('\n'.join([str(episode), episode_director, episode_title, str(episode_year)]))[:8]
return u'0x' + oxdb_id
def parse_director(director):
director = os.path.basename(os.path.dirname(director))
if director.endswith('_'):
director = "%s." % director[:-1]
director = [normalizeName(d) for d in director.split('; ')]
def cleanup(director):
director = director.strip()
director = director.replace('Series', '')
director = director.replace('Unknown Director', '')
director = director.replace('Various Directors', '')
return director
director = filter(None, [cleanup(d) for d in director])
return director
def parse_title(_title, searchTitle = False):
'''
normalize filename to get item 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-Za-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_', ' .')
year = ox.findRe(title, '(\(\d{4}\))')
if year and title.endswith(year):
title = title[:-len(year)].strip()
title = normalizeTitle(title)
if searchTitle and year:
title = u"%s %s" % (title, year)
return title
def parse_series_title(path):
seriesTitle = u''
if path.startswith('Series'):
seriesTitle = os.path.basename(path)
else:
t = parse_title(path)
if " (S" in t:
seriesTitle = t.split(" (S")[0]
return seriesTitle
def parse_episode_title(path):
episodeTitle = u''
ep = re.compile('.Episode \d+?\.(.*?)\.[a-zA-Z]').findall(path)
if ep:
episodeTitle = ep[0]
return episodeTitle
def parse_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 parse_path(path):
'''
expects path in the form
L/Last, First/Title (YYYY)
M/McCarthy, Thomas/The Visitor (2007)
G/Godard, Jean-Luc/Histoire(s) du cinema_ Toutes les histoires (1988)
'''
r = {}
r['title'] = parse_title(path)
year = ox.findRe(path, '\((\d{4})\)')
if year:
r['year'] = year
if not settings.USE_IMDB:
return r
search_title = parse_title(path, True)
r['director'] = parse_director(path)
#FIXME: only include it its actually a series
r['episode_title'] = parse_episode_title(path)
r['season'], r['episode'] = parse_season_episode(path)
r['series_title'] = parse_series_title(path)
#FIXME: use oxdata/id/?title=title&director=director&year=year
#r['imdbId'] = ox.web.imdb.guess(search_title, ', '.join(r['director']), timeout=-1)
r['imdbId'] = ox.web.imdb.guess(search_title, timeout=-1)
r['oxdbId'] = oxdb_id(r['title'], r['director'], r.get('year', ''),
r.get('season', ''), r.get('episode', ''),
episode_title=r['episode_title'],
episode_director=[],
episode_year='')
return r
def sort_string(string): def sort_string(string):
string = string.replace(u'Þ', 'Th') string = string.replace(u'Þ', 'Th')
#pad numbered titles #pad numbered titles

View file

@ -419,22 +419,6 @@ actions.register(remove, cache=False)
''' '''
Poster API Poster API
''' '''
def parse(request): #parse path and return info
'''
param data {
path: string
}
return {
status: {'code': int, 'text': string},
data: {
imdb: string
}
}
'''
path = json.loads(request.POST['data'])['path']
response = json_response(utils.parse_path(path))
return render_to_json_response(response)
actions.register(parse)
def setPosterFrame(request): #parse path and return info def setPosterFrame(request): #parse path and return info
@ -667,7 +651,7 @@ def timeline(request, id, size, position):
item = get_object_or_404(models.Item, itemId=id) item = get_object_or_404(models.Item, itemId=id)
if not item.access(request.user): if not item.access(request.user):
return HttpResponseForbidden() return HttpResponseForbidden()
timeline = '%s.%s.%04d.png' %(item.timeline_prefix, size, int(position)) timeline = '%s%sp%04d.png' %(item.timeline_prefix, size, int(position))
return HttpFileResponse(timeline, content_type='image/png') return HttpFileResponse(timeline, content_type='image/png')
@ -675,7 +659,7 @@ def timeline_overview(request, id, size):
item = get_object_or_404(models.Item, itemId=id) item = get_object_or_404(models.Item, itemId=id)
if not item.access(request.user): if not item.access(request.user):
return HttpResponseForbidden() return HttpResponseForbidden()
timeline = '%s.%s.png' %(item.timeline_prefix, size) timeline = '%s%sp.png' %(item.timeline_prefix, size)
return HttpFileResponse(timeline, content_type='image/png') return HttpFileResponse(timeline, content_type='image/png')
def torrent(request, id, filename=None): def torrent(request, id, filename=None):
@ -708,6 +692,11 @@ def video(request, id, resolution, format, index=None):
index = int(index) - 1 index = int(index) - 1
else: else:
index = 0 index = 0
#streams = Stream.object.filter(file__item__itemId=item.itemId,
# file__selected=True, file__part=index,
# resolution=resolution, format=format)
#if streams.count() != 1:
# reise Http404
streams = Stream.objects.filter(file__item__itemId=item.itemId, streams = Stream.objects.filter(file__item__itemId=item.itemId,
resolution=resolution, format=format).order_by('file__part') resolution=resolution, format=format).order_by('file__part')
if index > streams.count(): if index > streams.count():

View file

@ -18,6 +18,8 @@ def get_name_sort(name):
name = unicodedata.normalize('NFKD', name).strip() name = unicodedata.normalize('NFKD', name).strip()
if name: if name:
person, created = Person.objects.get_or_create(name=name) person, created = Person.objects.get_or_create(name=name)
if created:
person.save()
sortname = unicodedata.normalize('NFKD', person.sortname) sortname = unicodedata.normalize('NFKD', person.sortname)
else: else:
sortname = u'' sortname = u''

View file

@ -172,20 +172,10 @@ SITE_CONFIG = join(PROJECT_ROOT, '0xdb.jsonc')
TRACKER_URL="http://url2torrent.net:6970/announce" TRACKER_URL="http://url2torrent.net:6970/announce"
#Movie related settings
REVIEW_WHITELIST = {
u'.filmcritic.com': u'Filmcritic',
u'metacritic.com': u'Metacritic',
u'nytimes.com': u'New York Times',
u'rottentomatoes.com': u'Rotten Tomatoes',
u'salon.com': u'Salon.com',
u'sensesofcinema.com': u'Senses of Cinema',
u'villagevoice.com': u'Village Voice'
}
#list of poster services, https://wiki.0x2620.org/wiki/pandora/posterservice DATA_SERVICE = ''
POSTER_SERVICES = []
POSTER_PRECEDENCE = ( POSTER_PRECEDENCE = (
'piratecinema.org',
'local', 'local',
'criterion.com', 'criterion.com',
'wikipedia.org', 'wikipedia.org',
@ -196,19 +186,8 @@ POSTER_PRECEDENCE = (
'other' 'other'
) )
DEFAULT_LISTS = [
{"name": "Favorites"},
{"name": "1960s", "query": {
"conditions": [{"key": "year", "value": "196", "operator": "^"}],
"operator": ""}
}
]
#0xdb.org #0xdb.org
USE_IMDB = True USE_IMDB = True
#this should idealy go away, one folder per item
USE_FOLDER = True
#POSTER_SERVICES=['http://data.0xdb.org/poster/']
#copy scripts and adjust to customize #copy scripts and adjust to customize
ITEM_POSTER = join('scripts', 'oxdb_poster') ITEM_POSTER = join('scripts', 'oxdb_poster')

View file

@ -13,9 +13,13 @@ from item import utils
import managers import managers
def get_title_sort(title): def get_title_sort(title):
if isinstance(title, str):
title = unicode(title)
title = unicodedata.normalize('NFKD', title).strip() title = unicodedata.normalize('NFKD', title).strip()
if title: if title:
title, created = Title.objects.get_or_create(title=title) title, created = Title.objects.get_or_create(title=title)
if created:
title.save()
sorttitle = unicodedata.normalize('NFKD', title.sorttitle) sorttitle = unicodedata.normalize('NFKD', title.sorttitle)
else: else:
sorttitle = u'' sorttitle = u''

View file

@ -169,7 +169,7 @@ def signup(request):
user.is_staff = first_user user.is_staff = first_user
user.save() user.save()
#create default user lists: #create default user lists:
for l in settings.DEFAULT_LISTS: for l in settings.CONFIG['personalLists']:
list = models.List(name=l['name'], user=user) list = models.List(name=l['name'], user=user)
for key in ('query', 'public', 'featured'): for key in ('query', 'public', 'featured'):
if key in l: if key in l:

View file

@ -1,6 +1,6 @@
// vim: et:ts=4:sw=4:sts=4:ft=javascript // vim: et:ts=4:sw=4:sts=4:ft=javascript
Ox.FilesView = function(options, self) { pandora.ui.filesView = function(options, self) {
var self = self || {}, var self = self || {},
that = Ox.Element({}, self) that = Ox.Element({}, self)
@ -55,19 +55,11 @@ Ox.FilesView = function(options, self) {
}, },
{ {
align: 'left', align: 'left',
id: 'folder', id: 'path',
operator: '+', operator: '+',
title: 'Folder', title: 'Path',
visible: true, visible: true,
width: 180 width: 560
},
{
align: 'left',
id: 'name',
operator: '+',
title: 'Name',
visible: true,
width: 360
}, },
{ {
align: 'left', align: 'left',
@ -147,7 +139,7 @@ Ox.FilesView = function(options, self) {
}), callback); }), callback);
}, },
scrollbarVisible: true, scrollbarVisible: true,
sort: [{key: 'name', operator: '+'}] sort: [{key: 'path', operator: '+'}]
}) })
.bindEvent({ .bindEvent({
open: openFiles, open: openFiles,
@ -174,19 +166,11 @@ Ox.FilesView = function(options, self) {
}, },
{ {
align: 'left', align: 'left',
id: 'folder', id: 'path',
operator: '+', operator: '+',
title: 'Folder', title: 'Path',
visible: true, visible: true,
width: 180 width: 560
},
{
align: 'left',
id: 'name',
operator: '+',
title: 'Name',
visible: true,
width: 360
}, },
], ],
columnsMovable: true, columnsMovable: true,
@ -352,11 +336,19 @@ Ox.FilesView = function(options, self) {
}); });
function openFiles(data) { function openFiles(data) {
//Ox.print('........', JSON.stringify(self.$filesList.value(data.ids[0], 'instances'))) data.ids.length == 1 && pandora.api.parsePath({
path: self.$filesList.value(data.ids[0], 'path')
}, function(result) {
['title', 'director', 'year'].forEach(function(key) {
if (result.data[key]) {
self['$' + key + 'Input'].options({value: result.data[key]});
}
});
updateForm();
});
} }
function selectFiles(data) { function selectFiles(data) {
//Ox.print('........', JSON.stringify(self.$filesList.value(data.ids[0], 'instances')))
self.selected = data.ids; self.selected = data.ids;
self.$instancesList.options({ self.$instancesList.options({
items: data.ids.length == 1 items: data.ids.length == 1

View file

@ -146,8 +146,8 @@ pandora.ui.infoView = function(data) {
}) })
.html( .html(
data.title + ( data.title + (
data.original_title && data.original_title != data.title data.originalTitle && data.originalTitle != data.title
? ' ' + formatLight('(' + data.original_title + ')') : '' ? ' ' + formatLight('(' + data.originalTitle + ')') : ''
) )
) )
.appendTo($text); .appendTo($text);
@ -182,11 +182,10 @@ pandora.ui.infoView = function(data) {
$div.html(html.join('; ')); $div.html(html.join('; '));
} }
// fixme: should be camelCase! data.alternativeTitles && $('<div>')
data.alternative_titles && $('<div>')
.css(css) .css(css)
.html( .html(
formatKey('Alternative Titles') + data.alternative_titles.map(function(value) { formatKey('Alternative Titles') + data.alternativeTitles.map(function(value) {
return value[0] + (value[1] ? ' ' return value[0] + (value[1] ? ' '
+ formatLight('(' + value[1] + ')') : ''); + formatLight('(' + value[1] + ')') : '');
}).join(', ') }).join(', ')
@ -225,9 +224,9 @@ pandora.ui.infoView = function(data) {
.css(css) .css(css)
.appendTo($text); .appendTo($text);
html = []; html = [];
['genre', 'keyword'].forEach(function(key) { ['genre', 'keywords'].forEach(function(key) {
data[key] && html.push( data[key] && html.push(
formatKey(key == 'keyword' ? 'keywords' : key) formatKey(key)
+ formatValue(data[key], key) + formatValue(data[key], key)
); );
}); });
@ -272,19 +271,19 @@ pandora.ui.infoView = function(data) {
.appendTo($text); .appendTo($text);
}); });
data.filming_locations && $('<div>') data.filmingLocations && $('<div>')
.css(css) .css(css)
.html( .html(
formatKey('Filming Locations') + data.filming_locations.map(function(location) { formatKey('Filming Locations') + data.filmingLocations.map(function(location) {
return '<a href="/map/@' + location + '">' + location + '</a>' return '<a href="/map/@' + location + '">' + location + '</a>'
}).join(', ') }).join(', ')
) )
.appendTo($text); .appendTo($text);
data.releasedate && $('<div>') data.releaseDate && $('<div>')
.css(css) .css(css)
.html( .html(
formatKey('Release Date') + Ox.formatDate(data.releasedate, '%A, %B %e, %Y') formatKey('Release Date') + Ox.formatDate(data.releaseDate, '%A, %B %e, %Y')
) )
.appendTo($text); .appendTo($text);

View file

@ -293,7 +293,7 @@ pandora.ui.item = function() {
} else if (pandora.user.ui.itemView == 'files') { } else if (pandora.user.ui.itemView == 'files') {
pandora.$ui.contentPanel.replaceElement(1, pandora.$ui.contentPanel.replaceElement(1,
pandora.$ui.item = Ox.FilesView({ pandora.$ui.item = pandora.ui.filesView({
id: result.data.id id: result.data.id
}) })
); );

View file

@ -4,7 +4,7 @@
"js/pandora/URL.js", "js/pandora/URL.js",
"js/pandora/autovalidate.js", "js/pandora/autovalidate.js",
"js/pandora/utils.js", "js/pandora/utils.js",
"js/pandora/ui/Ox.FilesView.js", "js/pandora/ui/filesView.js",
"js/pandora/ui/account.js", "js/pandora/ui/account.js",
"js/pandora/ui/appPanel.js", "js/pandora/ui/appPanel.js",
"js/pandora/ui/backButton.js", "js/pandora/ui/backButton.js",