From 45798810a912f5886b47b38817d97812a30635c2 Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Sat, 15 Oct 2011 17:21:41 +0200 Subject: [PATCH] - rework file/upload handling, no more extras - cleanup imdb data, get from external data service - rename filesview --- pandora/archive/admin.py | 6 +- pandora/archive/forms.py | 2 +- pandora/archive/models.py | 105 ++--- pandora/archive/tasks.py | 49 +-- pandora/archive/views.py | 46 ++- pandora/item/data_api.py | 17 + pandora/item/models.py | 387 +++++++----------- pandora/item/timelines.py | 14 +- pandora/item/utils.py | 173 -------- pandora/item/views.py | 25 +- pandora/person/models.py | 2 + pandora/settings.py | 25 +- pandora/title/models.py | 4 + pandora/user/views.py | 2 +- .../ui/{Ox.FilesView.js => filesView.js} | 44 +- static/js/pandora/ui/infoView.js | 21 +- static/js/pandora/ui/item.js | 2 +- static/json/pandora.json | 2 +- 18 files changed, 295 insertions(+), 631 deletions(-) create mode 100644 pandora/item/data_api.py rename static/js/pandora/ui/{Ox.FilesView.js => filesView.js} (92%) diff --git a/pandora/archive/admin.py b/pandora/archive/admin.py index 2a0fa6fb..93c8c3f1 100644 --- a/pandora/archive/admin.py +++ b/pandora/archive/admin.py @@ -8,8 +8,8 @@ import models class FileAdmin(admin.ModelAdmin): - search_fields = ['name', 'folder','oshash', 'video_codec'] - list_display = ['available', 'wanted', 'active', '__unicode__', 'itemId'] + search_fields = ['path','oshash', 'video_codec'] + list_display = ['available', 'wanted', 'selected', '__unicode__', 'itemId'] list_display_links = ('__unicode__', ) def itemId(self, obj): @@ -21,7 +21,7 @@ admin.site.register(models.File, FileAdmin) class InstanceAdmin(admin.ModelAdmin): - search_fields = ['name', 'folder', 'volume__name', 'file__oshash'] + search_fields = ['path', 'volume__name', 'file__oshash'] form = InstanceAdminForm admin.site.register(models.Instance, InstanceAdmin) diff --git a/pandora/archive/forms.py b/pandora/archive/forms.py index 9b602a3c..04221754 100644 --- a/pandora/archive/forms.py +++ b/pandora/archive/forms.py @@ -25,7 +25,7 @@ class FileAdminForm(forms.ModelForm): class InstanceAdminForm(forms.ModelForm): - file = ForeignKeyByLetter(models.File, field_name='name') + file = ForeignKeyByLetter(models.File, field_name='path') class Meta: model = models.Instance diff --git a/pandora/archive/models.py b/pandora/archive/models.py index 9c03e554..0d92e60f 100644 --- a/pandora/archive/models.py +++ b/pandora/archive/models.py @@ -6,10 +6,10 @@ import os.path import re import time +from django.conf import settings +from django.contrib.auth.models import User from django.db import models 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 ox.django import fields @@ -17,7 +17,6 @@ import ox import chardet from item import utils -from person.models import get_name_sort import extract @@ -26,19 +25,17 @@ class File(models.Model): created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) - active = models.BooleanField(default=False) auto = models.BooleanField(default=True) oshash = models.CharField(max_length=16, unique=True) item = models.ForeignKey("item.Item", related_name='files') - name = models.CharField(max_length=2048, default="") # canoncial path/file - folder = models.CharField(max_length=2048, default="") # canoncial path/file - sort_name = models.CharField(max_length=2048, default="") # sort name + path = models.CharField(max_length=2048, default="") # canoncial path/file + sort_path = models.CharField(max_length=2048, default="") # sort name type = models.CharField(default="", max_length=255) 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) season = models.IntegerField(default=-1) @@ -65,22 +62,22 @@ class File(models.Model): #This is true if derivative is available or subtitles where uploaded available = models.BooleanField(default = False) - wanted = models.BooleanField(default = False) + selected = models.BooleanField(default = False) uploading = models.BooleanField(default = False) + wanted = models.BooleanField(default = False) is_audio = models.BooleanField(default=False) is_video = models.BooleanField(default=False) is_subtitle = models.BooleanField(default=False) def __unicode__(self): - return self.name + return self.path def set_state(self): - self.name = self.get_name() - self.folder = self.get_folder() - self.sort_name = utils.sort_string(ox.get_sort_title(self.name)) + self.path = self.create_path() + self.sort_path= utils.sort_string(self.path) - 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') \ and self.info: for key in ('duration', 'size'): @@ -99,8 +96,8 @@ class File(models.Model): self.display_aspect_ratio = "%s:%s" % (self.width, self.height) self.is_video = True self.is_audio = False - if self.name.endswith('.jpg') or \ - self.name.endswith('.png') or \ + if self.path.endswith('.jpg') or \ + self.path.endswith('.png') or \ self.duration == 0.04: self.is_video = False else: @@ -126,11 +123,11 @@ class File(models.Model): self.pixels = int(self.width * self.height * float(utils.parse_decimal(self.framerate)) * self.duration) else: - self.is_video = os.path.splitext(self.name)[-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_subtitle = os.path.splitext(self.name)[-1] in ('.srt', ) + self.is_video = os.path.splitext(self.path)[-1] in ('.avi', '.mkv', '.dv', '.ogv', '.mpeg', '.mov', '.webm') + self.is_audio = os.path.splitext(self.path)[-1] in ('.mp3', '.wav', '.ogg', '.flac', '.oga') + 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_audio = False self.is_video = False @@ -138,7 +135,8 @@ class File(models.Model): self.is_subtitle = False 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() if self.type not in ('audio', 'video'): @@ -156,9 +154,9 @@ class File(models.Model): #upload and data handling 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 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, 'video_codec': self.video_codec, 'audio_codec': self.audio_codec, - 'name': self.name, + 'path': self.path, 'size': self.size, #'info': self.info, 'users': list(set([u.username for u in User.objects.filter(volumes__files__in=self.instances.all())])), 'instances': [i.json() for i in self.instances.all()], - 'folder': self.get_folder(), 'type': self.get_type(), 'part': self.get_part() } @@ -295,17 +292,17 @@ class File(models.Model): def get_part(self): #FIXME: this breaks for sub/idx/srt - if os.path.splitext(self.name)[-1] in ('.sub', '.idx', '.srt'): - name = os.path.splitext(self.name)[0] + if os.path.splitext(self.path)[-1] in ('.sub', '.idx', '.srt'): + name = os.path.splitext(self.path)[0] if self.language: name = name[-(len(self.language)+1)] 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: return qs[0].part - if self.active: + if self.selected: 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: return files.index(self) + 1 return None @@ -315,7 +312,7 @@ class File(models.Model): return 'video' if self.is_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 'unknown' @@ -325,30 +322,10 @@ class File(models.Model): return self.instances.all()[0] return None - def get_folder(self): + def create_path(self): instance = self.get_instance() if instance: - return instance.folder - 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 + return instance.path if self.item: name = self.item.get('title', 'Untitled') name = re.sub(r'[:\\/]', '_', name) @@ -357,12 +334,6 @@ class File(models.Model): ext = '.unknown' 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): f = kwargs['instance'] #FIXME: delete streams here @@ -394,7 +365,7 @@ class Volume(models.Model): class Instance(models.Model): class Meta: - unique_together = ("name", "folder", "volume") + unique_together = ("path", "volume") created = models.DateTimeField(auto_now_add=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) mtime = models.IntegerField(default=lambda: int(time.time()), editable=False) - name = models.CharField(max_length=2048) - folder = models.CharField(max_length=2048) - extra = models.BooleanField(default=False) + path = models.CharField(max_length=2048) + ignore = models.BooleanField(default=False) file = models.ForeignKey(File, related_name='instances') volume = models.ForeignKey(Volume, related_name='files') 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 def itemId(self): @@ -421,14 +391,13 @@ class Instance(models.Model): return { 'user': self.volume.user.username, 'volume': self.volume.name, - 'folder': self.folder, - 'name': self.name + 'path': self.path } def frame_path(frame, name): ext = os.path.splitext(name)[-1] name = "%s%s" % (frame.position, ext) - return frame.file.path(name) + return frame.file.get_path(name) class Frame(models.Model): @@ -486,7 +455,7 @@ class Stream(models.Model): return u"%s/%s" % (self.file, self.name()) def path(self, name=''): - return self.file.path(name) + return self.file.get_path(name) def extract_derivatives(self): config = settings.CONFIG['video'] diff --git a/pandora/archive/tasks.py b/pandora/archive/tasks.py index e9cb879b..f6b53cb1 100644 --- a/pandora/archive/tasks.py +++ b/pandora/archive/tasks.py @@ -1,29 +1,19 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 -import os - from celery.decorators import task +import ox -from item.utils import parse_path from item.models import get_item from django.conf import settings import models -_INSTANCE_KEYS = ('mtime', 'name', 'folder') +_INSTANCE_KEYS = ('mtime', 'path') -def get_or_create_item(volume, f, user): - in_same_folder = models.Instance.objects.filter(folder=f['folder'], volume=volume) - if in_same_folder.count() > 0: - 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_item(volume, info, user): + item_info = ox.parse_movie_info(info['path']) + return get_item(item_info, user) def get_or_create_file(volume, f, user, item=None): try: @@ -31,7 +21,7 @@ def get_or_create_file(volume, f, user, item=None): except models.File.DoesNotExist: file = models.File() file.oshash = f['oshash'] - file.name = f['name'] + file.path = f['path'] if item: file.item = item else: @@ -50,13 +40,11 @@ def update_or_create_instance(volume, f): setattr(instance, key, f[key]) updated=True if updated: - if instance.name.lower().startswith('extras/') or \ - instance.name.lower().startswith('versions/'): - instance.extra = True + instance.ignore = False instance.save() instance.file.save() 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: #same path, other oshash, keep path/item mapping, remove instance 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) for key in _INSTANCE_KEYS: setattr(instance, key, f[key]) - if instance.name.lower().startswith('extras/') or \ - instance.name.lower().startswith('versions/'): - instance.extra = True instance.save() instance.file.save() 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) all_files = [] for f in files: - folder = f['path'].split('/') - name = folder.pop() - if folder and folder[-1].lower() in ('extras', 'versions', 'dvds'): - name = '/'.join([folder.pop(), name]) - f['folder'] = '/'.join(folder) - f['name'] = name - all_files.append(f['oshash']) - update_or_create_instance(volume, f) - + #ignore extras etc, + #imdb stlye is L/Last, First/Title (Year)/Title.. 4 + #otherwise T/Title (Year)/Title... 3 + folder_depth = settings.USE_IMDB and 4 or 3 + if len(f['path'].split('/')) == folder_depth: + all_files.append(f['oshash']) + update_or_create_instance(volume, f) + #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() @task(queue="encoding") diff --git a/pandora/archive/views.py b/pandora/archive/views.py index 4d0ce26a..f8ac0b08 100644 --- a/pandora/archive/views.py +++ b/pandora/archive/views.py @@ -9,6 +9,7 @@ from django.shortcuts import get_object_or_404, redirect from django.conf import settings from django.db.models import Count, Sum +import ox from ox.utils import json from ox.django.decorators import login_required_json from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response @@ -105,7 +106,7 @@ def update(request): file__wanted=True)] response['data']['file'] = [f.file.oshash for f in files.filter(file__is_subtitle=True, file__available=False, - name__endswith='.srt')] + path__endswith='.srt')] return render_to_json_response(response) actions.register(update, cache=False) @@ -319,24 +320,8 @@ def lookup_file(request, oshash): def _order_query(qs, sort, prefix=''): order_by = [] if len(sort) == 1: - sort.append({'operator': '+', 'key': 'sort_name'}) + sort.append({'operator': '+', 'key': 'path'}) 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: operator = e['operator'] @@ -346,7 +331,7 @@ def _order_query(qs, sort, prefix=''): 'id': 'item__itemId', 'users': 'instances__volume__user__username', 'resolution': 'width', - 'name': 'sort_name' + 'path': 'sort_path' }.get(e['key'], e['key']) #if operator=='-' and '%s_desc'%key in models.ItemSort.descending_fields: # key = '%s_desc' % key @@ -400,10 +385,10 @@ Groups keys: array of keys to return group: group elements by, country, genre, director... - possible values for keys: name, items + possible values for keys: path, items with keys - items contains list of {'name': string, 'items': int}: + items contains list of {'path': string, 'items': int}: return {'status': {'code': int, 'text': string}, 'data': {items: array}} @@ -465,7 +450,7 @@ Positions elif 'range' in data: 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: response['data']['items'] = qs.count() elif 'positions' in query: @@ -481,6 +466,7 @@ Positions response['data']['items'] = [] qs = models.File.objects.filter(item__in=query['qs']) qs = _order_query(qs, query['sort']) + qs = qs.select_related() keys = query['keys'] qs = qs[query['range'][0]:query['range'][1]] response['data']['items'] = [f.json(keys) for f in qs] @@ -492,3 +478,19 @@ Positions 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) diff --git a/pandora/item/data_api.py b/pandora/item/data_api.py new file mode 100644 index 00000000..1e28bdf0 --- /dev/null +++ b/pandora/item/data_api.py @@ -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': {}} diff --git a/pandora/item/models.py b/pandora/item/models.py index 63d0dc6e..ff60fd8c 100644 --- a/pandora/item/models.py +++ b/pandora/item/models.py @@ -15,7 +15,6 @@ from urllib import quote from django.db import models from django.db.models import Count, Q, Sum from django.core.files.base import ContentFile -from django.utils import simplejson as json from django.conf import settings from django.contrib.auth.models import User, Group from django.db.models.signals import pre_delete @@ -30,6 +29,7 @@ import managers import utils import tasks from .timelines import join_timelines +from data_api import external_data from archive import extract from annotation.models import Annotation, Layer @@ -39,11 +39,45 @@ from person.models import get_name_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): ''' 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 'imdbId' in info and info['imdbId']: try: @@ -51,11 +85,7 @@ def get_item(info, user=None, async=False): except Item.DoesNotExist: item = Item(itemId=info['imdbId']) if 'title' in info and 'director' in info: - item.external_data = { - 'title': info['title'], - 'director': info['director'], - 'year': info.get('year', '') - } + item.external_data = item_data item.user = user item.oxdbId = item.itemId item.save() @@ -64,61 +94,39 @@ def get_item(info, user=None, async=False): else: item.update_external() else: - q = Item.objects.all() - for key in ('title', 'director', 'year'): - 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: + itemId = get_id(info) + if itemId: try: - item = Item.objects.get(itemId=info['oxdbId']) + item = Item.objects.get(itemId=itemId) except Item.DoesNotExist: - item = Item() - item.data = { - 'title': info['title'], - 'director': info['director'], - 'year': info.get('year', '') - } - item.itemId = info['oxdbId'] + info['imdbId'] = itemId + item = get_item(info) + return item - for key in ('episode_title', 'series_title', 'season', 'episode'): - if key in info and info[key]: - item.data[key] = info[key] - try: - existing_item = Item.objects.get(oxdbId=item.oxdb_id()) - item = existing_item - except Item.DoesNotExist: - item.save() + try: + item = Item.objects.get(itemId=info.get('oxdbId')) + except Item.DoesNotExist: + item = Item() + item.user = user + item.data = item_data + item.itemId = info.get('oxdbId', item.oxdb_id()) + try: + existing_item = Item.objects.get(oxdbId=item.oxdb_id()) + item = existing_item + except Item.DoesNotExist: + item.oxdbId = item.oxdb_id() + item.save() else: qs = Item.objects.filter(find__key='title', find__value=info['title']) if qs.count() == 1: item = qs[0] else: item = Item() - item.data = { - 'title': info['title'] - } + item.data = item_data item.user = user item.save() return item - class Item(models.Model): created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) @@ -202,59 +210,13 @@ class Item(models.Model): self.data[key] = data[key] 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): - if len(self.itemId) == 7: - data = ox.web.imdb.Imdb(self.itemId) - #FIXME: all this should be in ox.web.imdb.Imdb - for key in ('directors', 'writers', 'editors', 'producers', - 'cinematographers', 'languages', 'genres', 'keywords', - 'episode_directors'): - 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 '%s' % ( - quote(m.group(2).encode('utf-8')), m.group(2) - ) - t = re.sub('(.*?)', fix_names, t) - def fix_titles(m): - return '%s' % ( - quote(m.group(2).encode('utf-8')), m.group(2) - ) - t = re.sub('(.*?)', 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() + if settings.DATA_SERVICE and not self.itemId.startswith('0x'): + response = external_data('getData', {'id': self.itemId}) + if response['status']['code'] == 200: + self.external_data = response['data'] + self.save() + self.make_poster(True) def expand_connections(self): c = self.get('connections') @@ -292,10 +254,17 @@ class Item(models.Model): super(Item, self).save(*args, **kwargs) if not settings.USE_IMDB: self.itemId = ox.to26(self.id) - + + #this does not work if another item without imdbid has the same metadata oxdbId = self.oxdb_id() 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? 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() 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 ''' @@ -350,15 +319,15 @@ class Item(models.Model): f.item = other f.save() self.delete() - other.save() - #FIXME: update poster, stills and streams after this + if save: + other.save() + #FIXME: update poster, stills and streams after this def get_posters(self): url = self.prefered_poster_url() + external_posters = self.external_data.get('posters', {}) + services = external_posters.keys() index = [] - services = [p['service'] - for p in self.poster_urls.values("service") - .annotate(Count("id")).order_by()] for service in settings.POSTER_PRECEDENCE: if service in services: index.append(service) @@ -369,7 +338,6 @@ class Item(models.Model): index.append(settings.URL) posters = [] - poster = self.path('siteposter.jpg') poster = os.path.abspath(os.path.join(settings.MEDIA_ROOT, poster)) if os.path.exists(poster): @@ -382,18 +350,12 @@ class Item(models.Model): 'index': index.index(settings.URL) }) - got = {} - for p in self.poster_urls.all().order_by('-height'): - if p.service not in got: - got[p.service] = 1 - posters.append({ - 'url': p.url, - 'width': p.width, - 'height': p.height, - 'source': p.service, - 'selected': p.url == url, - 'index': index.index(p.service) - }) + for service in external_posters: + p = external_posters[service][0] + p['source'] = service + p['selected'] = p['url'] == url + p['index'] = index.index(service) + posters.append(p) posters.sort(key=lambda a: a['index']) return posters @@ -452,10 +414,6 @@ class Item(models.Model): if 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): i['cast'] = [i['cast']] if 'cast' in i and isinstance(i['cast'][0], list): @@ -497,15 +455,16 @@ class Item(models.Model): return info return i - def oxdb_id(self): if not settings.USE_IMDB: return self.itemId if not self.get('title') and not self.get('director'): return None - return utils.oxdb_id(self.get('title', ''), self.get('director', []), str(self.get('year', '')), - self.get('season', ''), self.get('episode', ''), - self.get('episode_title', ''), self.get('episode_director', []), self.get('episode_year', '')) + return ox.get_oxid(self.get('title', ''), self.get('director', []), + str(self.get('year', '')), + self.get('season', ''), self.get('episode', ''), + self.get('episodeTitle', ''), + self.get('episodeDirector', []), self.get('episodeYear', '')) ''' Search related functions @@ -528,10 +487,10 @@ class Item(models.Model): i = key['id'] if i == 'title': save(i, u'\n'.join([self.get('title', 'Untitled'), - self.get('original_title', '')])) + self.get('originalTitle', '')])) elif i == 'filename': 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': qs = Annotation.objects.filter(layer__name=i, item=self).order_by('start') 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.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: s.duration = sum([v.duration for v in videos]) v = videos[0] @@ -715,7 +674,7 @@ class Item(models.Model): current_values = [current_values] else: current_values = [] - ot = self.get('original_title') + ot = self.get('originalTitle') if ot: current_values.append(ot) #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()] 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): - users = self.users_with_files() - if users.filter(is_superuser=True).count()>0: - files = self.files.filter(instances__volume__user__is_superuser=True) - users = User.objects.filter(volumes__files__file__in=files, - is_superuser=True).distinct() - elif users.filter(is_staff=True).count()>0: - files = self.files.filter(instances__volume__user__is_staff=True) - users = User.objects.filter(volumes__files__file__in=files, - 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) + wanted = [] + for s in self.sets(): + if s.filter(selected=False).count() != 0: + wanted += [i.id for i in s] + else: + break + self.files.filter(id__in=wanted).update(wanted=True) + self.files.exclude(id__in=wanted).update(wanted=False) def update_selected(self): - files = archive.models.File.objects.filter(item=self, - streams__available=True, - streams__source=None) - if files.count() == 0: - return - - def get_level(users): - if users.filter(is_superuser=True).count() > 0: level = 0 - 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() - + for s in self.sets(): + if s.filter(Q(is_video=True)|Q(is_audio=True)).filter(available=False).count() == 0: + if s.filter(selected=False).count() > 0: + s.update(selected=True, wanted=False) + self.rendered = False + self.save() + self.update_timeline() + break def make_torrent(self): base = self.path('torrent') base = os.path.abspath(os.path.join(settings.MEDIA_ROOT, base)) if os.path.exists(base): shutil.rmtree(base) - os.makedirs(base) + ox.makedirs(base) base = self.path('torrent/%s' % self.get('title')) base = os.path.abspath(os.path.join(settings.MEDIA_ROOT, base)) @@ -893,7 +820,7 @@ class Item(models.Model): def streams(self): 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): streams = self.streams() @@ -911,32 +838,6 @@ class Item(models.Model): self.rendered = streams != [] 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): if self.poster: path = self.poster.path @@ -948,15 +849,14 @@ class Item(models.Model): os.unlink(f) def prefered_poster_url(self): - self.update_poster_urls() + external_posters = self.external_data.get('posters', {}) service = self.poster_source - if service and service != settings.URL: - for u in self.poster_urls.filter(service=service).order_by('-height'): - return u.url + if service and service != settings.URL and service in external_posters: + return external_posters[service][0]['url'] if not service: for service in settings.POSTER_PRECEDENCE: - for u in self.poster_urls.filter(service=service).order_by('-height'): - return u.url + if service in external_posters: + return external_posters[service][0]['url'] return None def make_timeline(self): @@ -984,7 +884,7 @@ class Item(models.Model): poster = os.path.abspath(os.path.join(settings.MEDIA_ROOT, poster)) 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'])) cmd = [settings.ITEM_POSTER, @@ -1018,7 +918,7 @@ class Item(models.Model): def poster_frames(self): frames = [] 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(): frames.append({ 'position': offset + ff.position, @@ -1052,7 +952,7 @@ class Item(models.Model): frame = self.get_poster_frame_path() icon = self.path('icon.jpg') self.icon.name = icon - timeline = '%s.64.png' % self.timeline_prefix + timeline = '%s64p.png' % self.timeline_prefix cmd = [settings.ITEM_ICON, '-i', self.icon.path ] @@ -1074,8 +974,8 @@ class Item(models.Model): Annotation.objects.filter(layer=layer,item=self).delete() offset = 0 language = '' - languages = [f.language for f in self.files.filter(active=True, is_subtitle=True, - available=True)] + subtitles = self.files.filter(selected=True, is_subtitle=True, available=True) + languages = [f.language for f in subtitles] if languages: if 'en' in languages: language = 'en' @@ -1083,10 +983,18 @@ class Item(models.Model): language = '' else: language = languages[0] - for f in self.files.filter(active=True, is_subtitle=True, - available=True, language=language).order_by('part'): - user = f.instances.all()[0].volume.user - for data in f.srt(offset): + + #loop over all videos + for f in self.files.filter(Q(is_audio=True)|Q(is_video=True)) \ + .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( item=f.item, layer=layer, @@ -1096,14 +1004,7 @@ class Item(models.Model): user=user ) annotation.save() - duration = self.files.filter(Q(is_audio=True)|Q(is_video=True)) \ - .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 + offset += f.duration self.update_find() def delete_item(sender, **kwargs): diff --git a/pandora/item/timelines.py b/pandora/item/timelines.py index 32fd4acb..fa4e7049 100644 --- a/pandora/item/timelines.py +++ b/pandora/item/timelines.py @@ -8,7 +8,7 @@ from glob import glob import Image 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]) width = f.size[0] f = Image.open(files[-1]) @@ -22,7 +22,7 @@ def loadTimeline(timeline_prefix, height=64): return timeline def makeTiles(timeline_prefix, height=16, width=3600): - files = glob('%s.64.*.png' % timeline_prefix) + files = glob('%s64p*.png' % timeline_prefix) fps = 25 part_step = 60 output_width = width @@ -43,14 +43,14 @@ def makeTiles(timeline_prefix, height=16, width=3600): i = 0 while pos < 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 i += 1 def makeTimelineOverview(timeline_prefix, width, inpoint=0, outpoint=0, duration=-1, height=16): input_scale = 25 - timeline_file = '%s.%s.png' % (timeline_prefix, height) + timeline_file = '%s%sp.png' % (timeline_prefix, height) if outpoint > 0: timeline_file = '%s.overview.%s.%d-%d.png' % (timeline_prefix, height, inpoint, outpoint) @@ -76,7 +76,7 @@ def join_timelines(timelines, prefix): tiles = [] 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)) @@ -87,7 +87,7 @@ def join_timelines(timelines, prefix): timeline.paste(tile, (pos, 0, pos+tile.size[0], height)) pos += tile.size[0] 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) i += 1 if pos > width: @@ -95,7 +95,7 @@ def join_timelines(timelines, prefix): timeline.paste(t, (0, 0, t.size[0], height)) pos -= width 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) makeTiles(prefix, 16, 3600) diff --git a/pandora/item/utils.py b/pandora/item/utils.py index cd8db007..6c89914a 100644 --- a/pandora/item/utils.py +++ b/pandora/item/utils.py @@ -1,19 +1,10 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 # from decimal import Decimal -import os import re -import hashlib 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): string = string.replace(':', '/') @@ -29,170 +20,6 @@ def plural_key(term): }.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): string = string.replace(u'Þ', 'Th') #pad numbered titles diff --git a/pandora/item/views.py b/pandora/item/views.py index d753e3eb..10d27363 100644 --- a/pandora/item/views.py +++ b/pandora/item/views.py @@ -419,22 +419,6 @@ actions.register(remove, cache=False) ''' 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 @@ -667,7 +651,7 @@ def timeline(request, id, size, position): item = get_object_or_404(models.Item, itemId=id) if not item.access(request.user): 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') @@ -675,7 +659,7 @@ def timeline_overview(request, id, size): item = get_object_or_404(models.Item, itemId=id) if not item.access(request.user): 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') def torrent(request, id, filename=None): @@ -708,6 +692,11 @@ def video(request, id, resolution, format, index=None): index = int(index) - 1 else: 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, resolution=resolution, format=format).order_by('file__part') if index > streams.count(): diff --git a/pandora/person/models.py b/pandora/person/models.py index 06bc0c90..ab696f54 100644 --- a/pandora/person/models.py +++ b/pandora/person/models.py @@ -18,6 +18,8 @@ def get_name_sort(name): name = unicodedata.normalize('NFKD', name).strip() if name: person, created = Person.objects.get_or_create(name=name) + if created: + person.save() sortname = unicodedata.normalize('NFKD', person.sortname) else: sortname = u'' diff --git a/pandora/settings.py b/pandora/settings.py index 084e48b3..10cdd5e1 100644 --- a/pandora/settings.py +++ b/pandora/settings.py @@ -172,20 +172,10 @@ SITE_CONFIG = join(PROJECT_ROOT, '0xdb.jsonc') 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 -POSTER_SERVICES = [] +DATA_SERVICE = '' POSTER_PRECEDENCE = ( + 'piratecinema.org', 'local', 'criterion.com', 'wikipedia.org', @@ -196,19 +186,8 @@ POSTER_PRECEDENCE = ( 'other' ) -DEFAULT_LISTS = [ - {"name": "Favorites"}, - {"name": "1960s", "query": { - "conditions": [{"key": "year", "value": "196", "operator": "^"}], - "operator": ""} - } -] #0xdb.org 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 ITEM_POSTER = join('scripts', 'oxdb_poster') diff --git a/pandora/title/models.py b/pandora/title/models.py index b5d73d88..7eade25f 100644 --- a/pandora/title/models.py +++ b/pandora/title/models.py @@ -13,9 +13,13 @@ from item import utils import managers def get_title_sort(title): + if isinstance(title, str): + title = unicode(title) title = unicodedata.normalize('NFKD', title).strip() if title: title, created = Title.objects.get_or_create(title=title) + if created: + title.save() sorttitle = unicodedata.normalize('NFKD', title.sorttitle) else: sorttitle = u'' diff --git a/pandora/user/views.py b/pandora/user/views.py index f76e2218..524e5e1b 100644 --- a/pandora/user/views.py +++ b/pandora/user/views.py @@ -169,7 +169,7 @@ def signup(request): user.is_staff = first_user user.save() #create default user lists: - for l in settings.DEFAULT_LISTS: + for l in settings.CONFIG['personalLists']: list = models.List(name=l['name'], user=user) for key in ('query', 'public', 'featured'): if key in l: diff --git a/static/js/pandora/ui/Ox.FilesView.js b/static/js/pandora/ui/filesView.js similarity index 92% rename from static/js/pandora/ui/Ox.FilesView.js rename to static/js/pandora/ui/filesView.js index 92d29480..9c933202 100644 --- a/static/js/pandora/ui/Ox.FilesView.js +++ b/static/js/pandora/ui/filesView.js @@ -1,6 +1,6 @@ // vim: et:ts=4:sw=4:sts=4:ft=javascript -Ox.FilesView = function(options, self) { +pandora.ui.filesView = function(options, self) { var self = self || {}, that = Ox.Element({}, self) @@ -55,19 +55,11 @@ Ox.FilesView = function(options, self) { }, { align: 'left', - id: 'folder', + id: 'path', operator: '+', - title: 'Folder', + title: 'Path', visible: true, - width: 180 - }, - { - align: 'left', - id: 'name', - operator: '+', - title: 'Name', - visible: true, - width: 360 + width: 560 }, { align: 'left', @@ -147,7 +139,7 @@ Ox.FilesView = function(options, self) { }), callback); }, scrollbarVisible: true, - sort: [{key: 'name', operator: '+'}] + sort: [{key: 'path', operator: '+'}] }) .bindEvent({ open: openFiles, @@ -174,19 +166,11 @@ Ox.FilesView = function(options, self) { }, { align: 'left', - id: 'folder', + id: 'path', operator: '+', - title: 'Folder', + title: 'Path', visible: true, - width: 180 - }, - { - align: 'left', - id: 'name', - operator: '+', - title: 'Name', - visible: true, - width: 360 + width: 560 }, ], columnsMovable: true, @@ -352,11 +336,19 @@ Ox.FilesView = function(options, self) { }); 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) { - //Ox.print('........', JSON.stringify(self.$filesList.value(data.ids[0], 'instances'))) self.selected = data.ids; self.$instancesList.options({ items: data.ids.length == 1 diff --git a/static/js/pandora/ui/infoView.js b/static/js/pandora/ui/infoView.js index f4401cca..f65c762b 100644 --- a/static/js/pandora/ui/infoView.js +++ b/static/js/pandora/ui/infoView.js @@ -146,8 +146,8 @@ pandora.ui.infoView = function(data) { }) .html( data.title + ( - data.original_title && data.original_title != data.title - ? ' ' + formatLight('(' + data.original_title + ')') : '' + data.originalTitle && data.originalTitle != data.title + ? ' ' + formatLight('(' + data.originalTitle + ')') : '' ) ) .appendTo($text); @@ -182,11 +182,10 @@ pandora.ui.infoView = function(data) { $div.html(html.join('; ')); } - // fixme: should be camelCase! - data.alternative_titles && $('
') + data.alternativeTitles && $('
') .css(css) .html( - formatKey('Alternative Titles') + data.alternative_titles.map(function(value) { + formatKey('Alternative Titles') + data.alternativeTitles.map(function(value) { return value[0] + (value[1] ? ' ' + formatLight('(' + value[1] + ')') : ''); }).join(', ') @@ -225,9 +224,9 @@ pandora.ui.infoView = function(data) { .css(css) .appendTo($text); html = []; - ['genre', 'keyword'].forEach(function(key) { + ['genre', 'keywords'].forEach(function(key) { data[key] && html.push( - formatKey(key == 'keyword' ? 'keywords' : key) + formatKey(key) + formatValue(data[key], key) ); }); @@ -272,19 +271,19 @@ pandora.ui.infoView = function(data) { .appendTo($text); }); - data.filming_locations && $('
') + data.filmingLocations && $('
') .css(css) .html( - formatKey('Filming Locations') + data.filming_locations.map(function(location) { + formatKey('Filming Locations') + data.filmingLocations.map(function(location) { return '' + location + '' }).join(', ') ) .appendTo($text); - data.releasedate && $('
') + data.releaseDate && $('
') .css(css) .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); diff --git a/static/js/pandora/ui/item.js b/static/js/pandora/ui/item.js index c3ae2412..ad7feba3 100644 --- a/static/js/pandora/ui/item.js +++ b/static/js/pandora/ui/item.js @@ -293,7 +293,7 @@ pandora.ui.item = function() { } else if (pandora.user.ui.itemView == 'files') { pandora.$ui.contentPanel.replaceElement(1, - pandora.$ui.item = Ox.FilesView({ + pandora.$ui.item = pandora.ui.filesView({ id: result.data.id }) ); diff --git a/static/json/pandora.json b/static/json/pandora.json index 1314d77a..4389f04e 100644 --- a/static/json/pandora.json +++ b/static/json/pandora.json @@ -4,7 +4,7 @@ "js/pandora/URL.js", "js/pandora/autovalidate.js", "js/pandora/utils.js", - "js/pandora/ui/Ox.FilesView.js", + "js/pandora/ui/filesView.js", "js/pandora/ui/account.js", "js/pandora/ui/appPanel.js", "js/pandora/ui/backButton.js",