merge
This commit is contained in:
commit
01dc04a9fb
18 changed files with 295 additions and 631 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
17
pandora/item/data_api.py
Normal 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': {}}
|
|
@ -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,46 +94,27 @@ 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:
|
||||||
|
info['imdbId'] = itemId
|
||||||
|
item = get_item(info)
|
||||||
|
return item
|
||||||
|
|
||||||
|
try:
|
||||||
|
item = Item.objects.get(itemId=info.get('oxdbId'))
|
||||||
except Item.DoesNotExist:
|
except Item.DoesNotExist:
|
||||||
item = Item()
|
item = Item()
|
||||||
item.data = {
|
item.user = user
|
||||||
'title': info['title'],
|
item.data = item_data
|
||||||
'director': info['director'],
|
item.itemId = info.get('oxdbId', item.oxdb_id())
|
||||||
'year': info.get('year', '')
|
|
||||||
}
|
|
||||||
item.itemId = info['oxdbId']
|
|
||||||
|
|
||||||
for key in ('episode_title', 'series_title', 'season', 'episode'):
|
|
||||||
if key in info and info[key]:
|
|
||||||
item.data[key] = info[key]
|
|
||||||
try:
|
try:
|
||||||
existing_item = Item.objects.get(oxdbId=item.oxdb_id())
|
existing_item = Item.objects.get(oxdbId=item.oxdb_id())
|
||||||
item = existing_item
|
item = existing_item
|
||||||
except Item.DoesNotExist:
|
except Item.DoesNotExist:
|
||||||
|
item.oxdbId = item.oxdb_id()
|
||||||
item.save()
|
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'])
|
||||||
|
@ -111,14 +122,11 @@ def get_item(info, user=None, async=False):
|
||||||
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',
|
|
||||||
'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 '<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()
|
self.save()
|
||||||
|
self.make_poster(True)
|
||||||
|
|
||||||
def expand_connections(self):
|
def expand_connections(self):
|
||||||
c = self.get('connections')
|
c = self.get('connections')
|
||||||
|
@ -293,8 +255,15 @@ class Item(models.Model):
|
||||||
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:
|
||||||
|
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
|
self.oxdbId = oxdbId
|
||||||
|
|
||||||
#id changed, what about existing item with new id?
|
#id changed, what about existing item with new id?
|
||||||
|
@ -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()
|
||||||
|
if save:
|
||||||
other.save()
|
other.save()
|
||||||
#FIXME: update poster, stills and streams after this
|
#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', []),
|
||||||
|
str(self.get('year', '')),
|
||||||
self.get('season', ''), self.get('episode', ''),
|
self.get('season', ''), self.get('episode', ''),
|
||||||
self.get('episode_title', ''), self.get('episode_director', []), self.get('episode_year', ''))
|
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()
|
|
||||||
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:
|
else:
|
||||||
files = self.files.all()
|
break
|
||||||
files = files.filter(is_video=True, instances__extra=False, instances__gt=0).order_by('part')
|
self.files.filter(id__in=wanted).update(wanted=True)
|
||||||
folders = list(set([f.folder for f in files]))
|
self.files.exclude(id__in=wanted).update(wanted=False)
|
||||||
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
|
|
||||||
|
|
||||||
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.rendered = False
|
||||||
self.save()
|
self.save()
|
||||||
self.update_timeline()
|
self.update_timeline()
|
||||||
else:
|
break
|
||||||
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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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''
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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''
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue