pandora/pandora/archive/models.py

479 lines
16 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
2011-12-29 19:13:03 +00:00
from __future__ import division, with_statement
2011-01-01 11:44:42 +00:00
import os.path
import re
2010-08-10 22:01:41 +00:00
import time
2012-01-07 10:48:05 +00:00
import shutil
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
2011-06-02 08:19:15 +00:00
from django.db.models import Q
2011-04-18 18:50:31 +00:00
from django.db.models.signals import pre_delete
from ox.django import fields
import ox
2010-09-18 14:44:35 +00:00
import chardet
2010-11-08 16:34:25 +00:00
from item import utils
2011-06-27 13:39:35 +00:00
import extract
class File(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
2011-06-27 13:39:35 +00:00
auto = models.BooleanField(default=True)
2010-08-10 22:01:41 +00:00
oshash = models.CharField(max_length=16, unique=True)
2011-09-04 18:35:38 +00:00
item = models.ForeignKey("item.Item", related_name='files')
path = models.CharField(max_length=2048, default="") # canoncial path/file
sort_path = models.CharField(max_length=2048, default="") # sort name
2011-02-01 13:19:34 +00:00
type = models.CharField(default="", max_length=255)
part = models.IntegerField(null=True)
version = models.CharField(default="", max_length=255)
language = models.CharField(default="", max_length=8)
2010-08-10 22:01:41 +00:00
season = models.IntegerField(default=-1)
episode = models.IntegerField(default=-1)
2010-08-10 22:01:41 +00:00
size = models.BigIntegerField(default=0)
duration = models.FloatField(null=True)
info = fields.DictField(default={})
video_codec = models.CharField(max_length=255)
pixel_format = models.CharField(max_length=255)
display_aspect_ratio = models.CharField(max_length=255)
width = models.IntegerField(default = 0)
height = models.IntegerField(default = 0)
framerate = models.CharField(max_length=255)
audio_codec = models.CharField(max_length=255)
2010-08-10 22:01:41 +00:00
channels = models.IntegerField(default=0)
samplerate = models.IntegerField(default=0)
bits_per_pixel = models.FloatField(default=-1)
pixels = models.BigIntegerField(default=0)
2010-08-07 14:31:20 +00:00
#This is true if derivative is available or subtitles where uploaded
available = models.BooleanField(default = False)
selected = models.BooleanField(default = False)
2011-08-23 17:39:34 +00:00
uploading = models.BooleanField(default = False)
wanted = models.BooleanField(default = False)
2010-08-07 14:31:20 +00:00
2010-08-10 22:01:41 +00:00
is_audio = models.BooleanField(default=False)
is_video = models.BooleanField(default=False)
is_subtitle = models.BooleanField(default=False)
2010-08-07 14:31:20 +00:00
def __unicode__(self):
return self.path
2011-06-27 13:39:35 +00:00
def set_state(self):
self.path = self.create_path()
if not self.path.split('.')[-1] in (
'srt', 'rar', 'sub', 'idx', 'txt', 'jpg', 'png', 'nfo'
) and self.info:
for key in ('duration', 'size'):
setattr(self, key, self.info.get(key, 0))
2011-01-28 08:48:38 +00:00
if 'video' in self.info and self.info['video'] and \
'width' in self.info['video'][0]:
2011-01-22 10:14:30 +00:00
video = self.info['video'][0]
self.video_codec = video['codec']
self.width = video['width']
self.height = video['height']
self.framerate = video['framerate']
if 'display_aspect_ratio' in video:
self.display_aspect_ratio = video['display_aspect_ratio']
else:
self.display_aspect_ratio = "%s:%s" % (self.width, self.height)
self.is_video = True
self.is_audio = False
if self.path.endswith('.jpg') or \
self.path.endswith('.png') or \
2012-01-02 03:50:54 +00:00
self.path.endswith('.txt') or \
self.video_codec == 'ansi' or \
2011-01-22 10:14:30 +00:00
self.duration == 0.04:
2010-12-27 05:07:15 +00:00
self.is_video = False
2012-01-02 03:50:54 +00:00
self.video_codec = ''
else:
self.is_video = False
2011-02-23 11:51:32 +00:00
self.display_aspect_ratio = "4:3"
self.width = '320'
self.height = '240'
2011-08-22 07:05:14 +00:00
if 'audio' in self.info and self.info['audio'] and self.duration > 0:
2011-01-22 10:14:30 +00:00
audio = self.info['audio'][0]
self.audio_codec = audio['codec']
self.samplerate = audio.get('samplerate', 0)
self.channels = audio.get('channels', 0)
if not self.is_video:
self.is_audio = True
else:
self.is_audio = False
2011-08-22 07:05:14 +00:00
self.audio_codec = ''
self.sampleate = 0
self.channels = 0
if self.framerate:
2010-12-22 15:17:38 +00:00
self.pixels = int(self.width * self.height * float(utils.parse_decimal(self.framerate)) * self.duration)
2011-02-01 13:19:34 +00:00
else:
self.is_video = self.path.split('.')[-1].lower() in ox.movie.EXTENSIONS['video']
self.is_audio = self.path.split('.')[-1].lower() in ox.movie.EXTENSIONS['audio']
self.is_audio = self.path.split('.')[-1].lower() == 'srt'
2011-02-01 13:19:34 +00:00
if self.path.endswith('.srt'):
self.is_subtitle = True
2011-08-26 14:32:00 +00:00
self.is_audio = False
self.is_video = False
2011-06-02 08:19:15 +00:00
else:
self.is_subtitle = False
2011-02-01 13:19:34 +00:00
self.type = self.get_type()
2011-10-20 19:32:31 +00:00
if self.instances.count()>0:
info = ox.movie.parse_path(self.path)
self.language = info['language'] or ''
self.part = self.get_part()
2011-02-02 07:51:59 +00:00
2011-02-01 13:19:34 +00:00
if self.type not in ('audio', 'video'):
self.duration = None
2011-06-27 13:39:35 +00:00
def save(self, *args, **kwargs):
if self.auto:
self.set_state()
2012-09-08 11:41:11 +00:00
if self.duration <= 0:
self.duration = sum([s.info.get('duration',0) for s in self.streams.filter(source=None)])
self.sort_path = utils.sort_string(self.path)
2011-08-26 15:43:31 +00:00
if self.is_subtitle:
self.available = self.data and True or False
else:
2011-08-26 15:52:08 +00:00
self.available = not self.uploading and \
self.streams.filter(source=None, available=True).count() > 0
super(File, self).save(*args, **kwargs)
2010-09-03 13:28:44 +00:00
#upload and data handling
2011-01-22 10:14:30 +00:00
data = models.FileField(null=True, blank=True,
upload_to=lambda f, x: f.get_path('data.bin'))
2010-12-04 01:09:10 +00:00
def get_path(self, name):
2010-12-04 01:09:10 +00:00
h = self.oshash
return os.path.join('files', h[:2], h[2:4], h[4:6], h[6:], name)
2010-09-03 13:28:44 +00:00
2010-08-24 17:16:33 +00:00
def contents(self):
2010-09-17 21:06:01 +00:00
if self.data != None:
self.data.seek(0)
2010-09-03 13:28:44 +00:00
return self.data.read()
2010-08-24 17:16:33 +00:00
return None
def srt(self, offset=0):
2012-03-06 22:04:29 +00:00
srt = ox.srt.load(self.data.path)
2012-01-02 16:45:01 +00:00
#subtitles should not overlap
for i in range(1, len(srt)):
if srt[i-1]['out'] > srt[i]['in']:
srt[i-1]['out'] = srt[i]['in']
2010-09-18 14:44:35 +00:00
return srt
2010-09-03 13:28:44 +00:00
def editable(self, user):
p = user.get_profile()
return p.get_level() in ('admin', 'staff') or \
self.instances.filter(volume__user=user).count() > 0 or \
self.item.user == user
2010-09-03 13:28:44 +00:00
def save_chunk(self, chunk, chunk_id=-1, done=False):
2010-09-08 17:14:01 +00:00
if not self.available:
2011-09-06 12:06:59 +00:00
config = settings.CONFIG['video']
stream, created = Stream.objects.get_or_create(
file=self,
2011-08-19 15:37:37 +00:00
resolution=config['resolutions'][0],
format=config['formats'][0])
if created:
2011-12-30 09:21:43 +00:00
stream.video.name = stream.path(stream.name())
2011-12-30 06:41:57 +00:00
ox.makedirs(os.path.dirname(stream.video.path))
2011-12-29 19:13:03 +00:00
with open(stream.video.path, 'w') as f:
f.write(chunk.read())
2011-12-30 08:50:52 +00:00
stream.save()
2010-09-03 13:28:44 +00:00
else:
2011-12-29 19:13:03 +00:00
with open(stream.video.path, 'a') as f:
#FIXME: should check that chunk_id/offset is right
f.write(chunk.read())
if done:
stream.available = True
stream.save()
2010-09-03 13:28:44 +00:00
return True
return False
2011-01-19 12:06:03 +00:00
def json(self, keys=None, user=None):
2011-02-01 13:19:34 +00:00
resolution = (self.width, self.height)
if resolution == (0, 0):
resolution = None
duration = self.duration
2011-10-18 20:06:01 +00:00
if self.type != 'video':
2011-02-01 13:19:34 +00:00
duration = None
2011-01-19 12:06:03 +00:00
data = {
2011-10-18 20:06:01 +00:00
'audioCodec': self.audio_codec,
2011-01-19 12:06:03 +00:00
'available': self.available,
2011-02-01 13:19:34 +00:00
'duration': duration,
2011-01-19 12:06:03 +00:00
'framerate': self.framerate,
2011-06-27 13:39:35 +00:00
'id': self.oshash,
2011-10-18 20:06:01 +00:00
'instances': [i.json() for i in self.instances.all()],
'part': self.part,
'path': self.path,
2011-10-18 20:06:01 +00:00
'resolution': resolution,
'samplerate': self.samplerate,
'selected': self.selected,
2011-01-19 12:06:03 +00:00
'size': self.size,
2011-10-18 20:06:01 +00:00
'type': self.type,
'videoCodec': self.video_codec,
'wanted': self.wanted,
2011-01-19 12:06:03 +00:00
}
data['users'] = list(set([i['user'] for i in data['instances']]))
2011-01-19 12:06:03 +00:00
if keys:
for k in data.keys():
if k not in keys:
del data[k]
return data
2011-01-01 11:44:42 +00:00
2011-02-01 13:19:34 +00:00
def get_part(self):
if os.path.splitext(self.path)[-1] in ('.sub', '.idx', '.srt'):
name = os.path.splitext(self.path)[0]
2011-06-02 08:19:15 +00:00
if self.language:
name = name[-(len(self.language)+1)]
qs = self.item.files.filter(Q(is_video=True)|Q(is_audio=True),
selected=True, path__startswith=name)
2011-06-02 08:19:15 +00:00
if qs.count()>0:
2011-10-18 20:30:32 +00:00
return qs[0].get_part()
if self.selected:
2011-06-01 16:55:30 +00:00
files = list(self.item.files.filter(type=self.type, language=self.language,
selected=self.selected).order_by('sort_path'))
if self in files:
return files.index(self) + 1
return None
2011-02-01 13:19:34 +00:00
def get_type(self):
if self.is_video:
return 'video'
if self.is_audio:
return 'audio'
if self.is_subtitle or os.path.splitext(self.path)[-1] in ('.sub', '.idx'):
2011-02-01 13:19:34 +00:00
return 'subtitle'
return 'unknown'
def get_instance(self):
2011-04-08 08:20:59 +00:00
#FIXME: what about other instances?
if self.instances.all().count() > 0:
return self.instances.all()[0]
return None
def create_path(self):
2011-04-12 19:22:03 +00:00
instance = self.get_instance()
if instance:
2012-09-07 22:30:56 +00:00
return ox.movie.parse_path(instance.path)['normalizedPath']
return self.path
2011-02-02 07:51:59 +00:00
def all_paths(self):
return [self.path] + [i.path for i in self.instances.all()]
2012-01-07 10:48:05 +00:00
def delete_frames(self):
frames = os.path.join(settings.MEDIA_ROOT, self.get_path('frames'))
if os.path.exists(frames):
shutil.rmtree(frames)
2011-04-18 18:50:31 +00:00
def delete_file(sender, **kwargs):
f = kwargs['instance']
#FIXME: delete streams here
2011-04-18 18:50:31 +00:00
if f.data:
f.data.delete()
pre_delete.connect(delete_file, sender=File)
2010-08-10 22:01:41 +00:00
class Volume(models.Model):
2011-01-01 11:44:42 +00:00
2010-08-10 22:01:41 +00:00
class Meta:
unique_together = ("user", "name")
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User, related_name='volumes')
name = models.CharField(max_length=1024)
def __unicode__(self):
return u"%s's %s"% (self.user, self.name)
def json(self):
return {
'name': self.name,
'path': 'unknown',
'items': self.files.count()
}
2010-11-08 17:43:59 +00:00
class Instance(models.Model):
2011-01-01 11:44:42 +00:00
2010-08-10 22:01:41 +00:00
class Meta:
unique_together = ("path", "volume")
2010-08-10 22:01:41 +00:00
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
2010-08-07 14:31:20 +00:00
2010-08-10 22:01:41 +00:00
atime = 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)
path = models.CharField(max_length=2048)
ignore = models.BooleanField(default=False)
file = models.ForeignKey(File, related_name='instances')
2010-08-10 22:01:41 +00:00
volume = models.ForeignKey(Volume, related_name='files')
def __unicode__(self):
return u"%s's %s <%s>"% (self.volume.user, self.path, self.file.oshash)
@property
2010-09-23 16:01:48 +00:00
def itemId(self):
return File.objects.get(oshash=self.oshash).itemId
2011-02-01 13:19:34 +00:00
def json(self):
return {
2011-10-18 20:06:01 +00:00
'ignore': self.ignore,
'path': self.path,
2011-02-01 13:19:34 +00:00
'user': self.volume.user.username,
'volume': self.volume.name,
}
2011-01-01 11:44:42 +00:00
2010-09-08 17:14:01 +00:00
def frame_path(frame, name):
2010-08-24 17:16:33 +00:00
ext = os.path.splitext(name)[-1]
2010-09-08 17:14:01 +00:00
name = "%s%s" % (frame.position, ext)
return frame.file.get_path(name)
2010-08-07 14:31:20 +00:00
2011-01-01 11:44:42 +00:00
class Frame(models.Model):
2011-01-01 11:44:42 +00:00
2010-08-10 22:01:41 +00:00
class Meta:
unique_together = ("file", "position")
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
file = models.ForeignKey(File, related_name="frames")
position = models.FloatField()
2010-08-07 14:31:20 +00:00
frame = models.ImageField(default=None, null=True, upload_to=frame_path)
2010-11-06 16:14:40 +00:00
'''
def save(self, *args, **kwargs):
name = "%d.jpg" % self.position
if file.name != name:
#FIXME: frame path should be renamed on save to match current position
super(Frame, self).save(*args, **kwargs)
'''
def __unicode__(self):
2011-01-22 10:14:30 +00:00
return u'%s/%s' % (self.file, self.position)
2011-04-18 18:50:31 +00:00
def delete_frame(sender, **kwargs):
f = kwargs['instance']
if f.frame:
f.frame.delete()
pre_delete.connect(delete_frame, sender=Frame)
class Stream(models.Model):
class Meta:
unique_together = ("file", "resolution", "format")
file = models.ForeignKey(File, related_name='streams')
resolution = models.IntegerField(default=96)
format = models.CharField(max_length=255, default='webm')
2011-08-18 18:57:56 +00:00
video = models.FileField(default=None, blank=True, upload_to=lambda f, x: f.path(x))
source = models.ForeignKey('Stream', related_name='derivatives', default=None, null=True)
available = models.BooleanField(default=False)
2012-01-20 18:15:54 +00:00
oshash = models.CharField(max_length=16, null=True, db_index=True)
info = fields.DictField(default={})
2011-08-18 19:37:12 +00:00
duration = models.FloatField(default=0)
aspect_ratio = models.FloatField(default=0)
2011-10-21 18:02:36 +00:00
cuts = fields.TupleField(default=[])
color = fields.TupleField(default=[])
2012-05-17 09:38:59 +00:00
volume = models.FloatField(default=0)
2011-10-21 18:02:36 +00:00
@property
def timeline_prefix(self):
2012-05-17 09:38:59 +00:00
return os.path.join(settings.MEDIA_ROOT, self.path())
return os.path.join(settings.MEDIA_ROOT, self.path(), 'timeline')
def name(self):
return u"%sp.%s" % (self.resolution, self.format)
def __unicode__(self):
return u"%s/%s" % (self.file, self.name())
def path(self, name=''):
return self.file.get_path(name)
def extract_derivatives(self, rebuild=False):
2011-09-06 12:06:59 +00:00
config = settings.CONFIG['video']
2011-08-19 15:37:37 +00:00
for resolution in config['resolutions']:
for f in config['formats']:
derivative, created = Stream.objects.get_or_create(file=self.file,
resolution=resolution, format=f)
2011-08-18 20:35:43 +00:00
if created:
derivative.source = self
2011-08-21 08:57:53 +00:00
derivative.save()
2011-08-18 20:35:43 +00:00
name = derivative.name()
derivative.video.name = os.path.join(os.path.dirname(self.video.name), name)
derivative.encode()
derivative.save()
2012-05-29 22:26:31 +00:00
elif rebuild or not derivative.available:
derivative.encode()
return True
def encode(self):
if self.source:
video = self.source.video.path
target = self.video.path
info = ox.avinfo(video)
if extract.stream(video, target, self.name(), info):
self.available = True
else:
self.available = False
self.save()
def make_timeline(self):
if self.available and not self.source:
extract.timeline(self.video.path, self.timeline_prefix)
2011-10-21 18:02:36 +00:00
self.cuts = tuple(extract.cuts(self.timeline_prefix))
self.color = tuple(extract.average_color(self.timeline_prefix))
self.volume = extract.average_volume(self.timeline_prefix)
2011-10-21 18:05:39 +00:00
self.save()
def save(self, *args, **kwargs):
if self.video and not self.info:
self.info = ox.avinfo(self.video.path)
2012-01-20 18:15:54 +00:00
self.oshash = self.info.get('oshash')
2011-08-18 19:37:12 +00:00
self.duration = self.info.get('duration', 0)
if 'video' in self.info and self.info['video']:
self.aspect_ratio = self.info['video'][0]['width'] / self.info['video'][0]['height']
else:
self.aspect_ratio = 128/80
super(Stream, self).save(*args, **kwargs)
2011-08-21 08:57:53 +00:00
if self.available and not self.file.available:
self.file.save()
2011-08-18 19:37:12 +00:00
def json(self):
return {
'duration': self.duration,
2011-10-28 23:27:44 +00:00
'aspectratio': self.aspect_ratio,
2011-08-18 19:37:12 +00:00
}
def delete_stream(sender, **kwargs):
f = kwargs['instance']
if f.video:
f.video.delete()
pre_delete.connect(delete_stream, sender=Stream)