2010-07-12 14:56:14 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# vi:si:et:sw=4:sts=4:ts=4
|
|
|
|
from datetime import datetime
|
|
|
|
import os.path
|
|
|
|
import random
|
|
|
|
import re
|
|
|
|
from decimal import Decimal
|
2010-08-10 22:01:41 +00:00
|
|
|
import time
|
2010-07-12 14:56:14 +00:00
|
|
|
|
|
|
|
from django.db import models
|
|
|
|
from django.db.models import Q
|
|
|
|
from django.contrib.auth.models import User
|
|
|
|
from django.core.files.base import ContentFile
|
|
|
|
from django.utils import simplejson as json
|
|
|
|
from django.conf import settings
|
|
|
|
|
|
|
|
from oxdjango import fields
|
|
|
|
import ox
|
|
|
|
from ox import stripTags
|
|
|
|
from ox.normalize import canonicalTitle, canonicalName
|
|
|
|
from firefogg import Firefogg
|
|
|
|
|
|
|
|
from backend import utils
|
|
|
|
from backend import extract
|
|
|
|
from pandora.backend.models import Movie
|
|
|
|
|
2010-08-24 17:16:33 +00:00
|
|
|
import extract
|
|
|
|
|
|
|
|
|
2010-07-12 14:56:14 +00:00
|
|
|
def parse_decimal(string):
|
|
|
|
string = string.replace(':', '/')
|
|
|
|
if '/' not in string:
|
|
|
|
string = '%s/1' % string
|
|
|
|
d = string.split('/')
|
|
|
|
return Decimal(d[0]) / Decimal(d[1])
|
|
|
|
|
|
|
|
class File(models.Model):
|
|
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
|
|
modified = models.DateTimeField(auto_now=True)
|
|
|
|
|
2010-08-07 14:31:20 +00:00
|
|
|
verified = models.BooleanField(default=False)
|
2010-07-12 14:56:14 +00:00
|
|
|
|
2010-08-10 22:01:41 +00:00
|
|
|
oshash = models.CharField(max_length=16, unique=True)
|
2010-07-12 14:56:14 +00:00
|
|
|
movie = models.ForeignKey(Movie, related_name='files')
|
|
|
|
|
|
|
|
name = models.CharField(max_length=2048, default="") # canoncial path/file
|
|
|
|
sort_name = models.CharField(max_length=2048, default="") # sort path/file name
|
|
|
|
|
|
|
|
part = models.CharField(default="", max_length=255)
|
|
|
|
version = models.CharField(default="", max_length=255) # sort path/file name
|
|
|
|
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-07-12 14:56:14 +00:00
|
|
|
|
2010-08-10 22:01:41 +00:00
|
|
|
size = models.BigIntegerField(default=0)
|
|
|
|
duration = models.IntegerField(default=0)
|
2010-07-12 14:56:14 +00:00
|
|
|
|
|
|
|
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)
|
2010-07-12 14:56:14 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2010-08-10 22:01:41 +00:00
|
|
|
is_audio = models.BooleanField(default=False)
|
|
|
|
is_video = models.BooleanField(default=False)
|
|
|
|
is_extra = models.BooleanField(default=False)
|
|
|
|
is_main = models.BooleanField(default=False)
|
|
|
|
is_subtitle = models.BooleanField(default=False)
|
|
|
|
is_version = models.BooleanField(default=False)
|
2010-08-07 14:31:20 +00:00
|
|
|
|
2010-07-12 14:56:14 +00:00
|
|
|
def __unicode__(self):
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
if self.name and not self.sort_name:
|
|
|
|
self.sort_name = canonicalTitle(self.name)
|
|
|
|
if self.info:
|
|
|
|
for key in ('duration', 'size'):
|
|
|
|
setattr(self, key, self.info.get(key, 0))
|
|
|
|
|
|
|
|
if 'video' in self.info and self.info['video']:
|
|
|
|
self.video_codec = self.info['video'][0]['codec']
|
|
|
|
self.width = self.info['video'][0]['width']
|
|
|
|
self.height = self.info['video'][0]['height']
|
|
|
|
self.framerate = self.info['video'][0]['framerate']
|
|
|
|
if 'display_aspect_ratio' in self.info['video'][0]:
|
|
|
|
self.display_aspect_ratio = self.info['video'][0]['display_aspect_ratio']
|
|
|
|
else:
|
|
|
|
self.display_aspect_ratio = "%s:%s" % (self.width, self.height)
|
|
|
|
self.is_video = True
|
|
|
|
self.is_audio = False
|
|
|
|
else:
|
|
|
|
self.is_video = False
|
|
|
|
if 'audio' in self.info and self.info['audio']:
|
|
|
|
self.audio_codec = self.info['audio'][0]['codec']
|
|
|
|
self.samplerate = self.info['audio'][0]['samplerate']
|
|
|
|
self.channels = self.info['audio'][0]['channels']
|
|
|
|
|
|
|
|
if not self.is_video:
|
|
|
|
self.is_audio = True
|
|
|
|
else:
|
|
|
|
self.is_audio = False
|
|
|
|
|
|
|
|
if self.framerate:
|
|
|
|
self.pixels = int(self.width * self.height * float(parse_decimal(self.framerate)) * self.duration)
|
|
|
|
|
|
|
|
if not self.is_audio and not self.is_video and self.name.endswith('.srt'):
|
|
|
|
self.is_subtitle = True
|
|
|
|
|
2010-08-10 22:01:41 +00:00
|
|
|
if self.name and self.name.startswith('Extras/'):
|
2010-07-12 14:56:14 +00:00
|
|
|
self.is_extra = True
|
|
|
|
self.is_main = False
|
|
|
|
else:
|
|
|
|
self.is_extra = False
|
|
|
|
self.is_main = True
|
|
|
|
|
|
|
|
super(File, self).save(*args, **kwargs)
|
|
|
|
|
|
|
|
def json(self):
|
|
|
|
r = {}
|
|
|
|
for k in self:
|
|
|
|
r[k] = unicode(self[k])
|
|
|
|
return r
|
|
|
|
|
2010-08-24 17:16:33 +00:00
|
|
|
def contents(self):
|
|
|
|
if self.contents_set.count() > 0:
|
|
|
|
return self.contents_set.all()[0].data
|
|
|
|
return None
|
|
|
|
|
2010-08-10 22:01:41 +00:00
|
|
|
class Volume(models.Model):
|
|
|
|
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)
|
|
|
|
|
2010-07-12 14:56:14 +00:00
|
|
|
class FileInstance(models.Model):
|
2010-08-10 22:01:41 +00:00
|
|
|
class Meta:
|
|
|
|
unique_together = ("name", "folder", "volume")
|
|
|
|
|
2010-07-12 14:56:14 +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)
|
2010-07-12 14:56:14 +00:00
|
|
|
|
2010-08-10 22:01:41 +00:00
|
|
|
name = models.CharField(max_length=2048)
|
2010-07-12 14:56:14 +00:00
|
|
|
folder = models.CharField(max_length=255)
|
|
|
|
|
|
|
|
file = models.ForeignKey(File, related_name='instances')
|
2010-08-10 22:01:41 +00:00
|
|
|
volume = models.ForeignKey(Volume, related_name='files')
|
2010-07-12 14:56:14 +00:00
|
|
|
|
|
|
|
def __unicode__(self):
|
2010-08-10 22:01:41 +00:00
|
|
|
return u"%s's %s <%s>"% (self.volume.user, self.name, self.file.oshash)
|
2010-07-12 14:56:14 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def movieId(self):
|
|
|
|
return File.objects.get(oshash=self.oshash).movieId
|
|
|
|
|
2010-08-07 14:31:20 +00:00
|
|
|
def frame_path(f, name):
|
2010-08-24 17:16:33 +00:00
|
|
|
ext = os.path.splitext(name)[-1]
|
|
|
|
name = "%s%s" % (f.position, ext)
|
2010-08-07 14:31:20 +00:00
|
|
|
h = f.file.oshash
|
|
|
|
return os.path.join('frame', h[:2], h[2:4], h[4:6], name)
|
|
|
|
|
2010-07-12 14:56:14 +00:00
|
|
|
class Frame(models.Model):
|
2010-08-10 22:01:41 +00:00
|
|
|
class Meta:
|
|
|
|
unique_together = ("file", "position")
|
2010-07-12 14:56:14 +00:00
|
|
|
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-07-12 14:56:14 +00:00
|
|
|
|
|
|
|
#FIXME: frame path should be renamed on save to match current position
|
|
|
|
|
|
|
|
def __unicode__(self):
|
2010-08-24 17:16:33 +00:00
|
|
|
return u'%s at %s' % (self.file, self.position)
|
2010-07-12 14:56:14 +00:00
|
|
|
|
2010-08-10 22:01:41 +00:00
|
|
|
def stream_path(f):
|
2010-08-13 17:17:23 +00:00
|
|
|
h = f.file.oshash
|
2010-08-10 22:01:41 +00:00
|
|
|
return os.path.join('stream', h[:2], h[2:4], h[4:6], h[6:], f.profile)
|
|
|
|
|
|
|
|
class Stream(models.Model):
|
|
|
|
class Meta:
|
|
|
|
unique_together = ("file", "profile")
|
|
|
|
|
|
|
|
file = models.ForeignKey(File, related_name='streams')
|
|
|
|
profile = models.CharField(max_length=255, default='96p.webm')
|
|
|
|
video = models.FileField(default=None, blank=True, upload_to=lambda f, x: stream_path(f))
|
2010-08-13 17:17:23 +00:00
|
|
|
source = models.ForeignKey('Stream', related_name='derivatives', default=None, null=True)
|
2010-08-10 22:01:41 +00:00
|
|
|
available = models.BooleanField(default=False)
|
|
|
|
|
2010-08-24 17:16:33 +00:00
|
|
|
def __unicode__(self):
|
|
|
|
return self.video
|
|
|
|
|
2010-08-10 22:01:41 +00:00
|
|
|
def extract_derivates(self):
|
2010-08-24 17:16:33 +00:00
|
|
|
if settings.VIDEO_H264:
|
|
|
|
profile = self.profile.replace('.webm', '.mp4')
|
|
|
|
if Stream.objects.filter(profile=profile, source=self).count() == 0:
|
|
|
|
derivate = Stream(file=self.file, source=self, profile=profile)
|
|
|
|
derivate.video.name = self.video.name.replace(self.profile, profile)
|
|
|
|
derivate.encode()
|
|
|
|
|
|
|
|
for p in settings.VIDEO_DERIVATIVES:
|
|
|
|
profile = p + '.webm'
|
|
|
|
target = self.video.path.replace(self.profile, profile)
|
|
|
|
if Stream.objects.filter(profile=profile, source=self).count() == 0:
|
|
|
|
derivate = Stream(file=self.file, source=self, profile=profile)
|
|
|
|
derivate.video.name = self.video.name.replace(self.profile, profile)
|
|
|
|
derivate.encode()
|
|
|
|
|
|
|
|
if settings.VIDEO_H264:
|
|
|
|
profile = p + '.mp4'
|
|
|
|
if Stream.objects.filter(profile=profile, source=self).count() == 0:
|
|
|
|
derivate = Stream(file=self.file, source=self, profile=profile)
|
|
|
|
derivate.video.name = self.video.name.replace(self.profile, profile)
|
|
|
|
derivate.encode()
|
2010-08-10 22:01:41 +00:00
|
|
|
return True
|
|
|
|
|
2010-08-24 17:16:33 +00:00
|
|
|
def encode(self):
|
|
|
|
if self.source:
|
|
|
|
video = self.source.video.path
|
|
|
|
target = self.video.path
|
|
|
|
profile = self.profile
|
|
|
|
info = self.file.info
|
|
|
|
if extract.stream(video, target, profile, info):
|
|
|
|
self.available=True
|
|
|
|
self.save()
|
|
|
|
|
2010-08-10 22:01:41 +00:00
|
|
|
def editable(self, user):
|
|
|
|
#FIXME: possibly needs user setting for stream
|
|
|
|
return True
|
|
|
|
|
|
|
|
def save_chunk(self, chunk, chunk_id=-1):
|
|
|
|
if not self.available:
|
|
|
|
if not self.video:
|
2010-08-13 17:17:23 +00:00
|
|
|
self.video.save(self.profile, chunk)
|
2010-08-10 22:01:41 +00:00
|
|
|
else:
|
2010-08-13 17:17:23 +00:00
|
|
|
f = open(self.video.path, 'a')
|
2010-08-10 22:01:41 +00:00
|
|
|
#FIXME: should check that chunk_id/offset is right
|
2010-08-13 17:17:23 +00:00
|
|
|
f.write(chunk.read())
|
2010-08-10 22:01:41 +00:00
|
|
|
f.close()
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2010-08-13 17:17:23 +00:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
if self.available and not self.file.available:
|
|
|
|
self.file.available = True
|
|
|
|
self.file.save()
|
|
|
|
super(Stream, self).save(*args, **kwargs)
|
|
|
|
|
2010-08-24 17:16:33 +00:00
|
|
|
class FileContents(models.Model):
|
|
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
|
|
modified = models.DateTimeField(auto_now=True)
|
|
|
|
file = models.ForeignKey(File, related_name="contents_set")
|
|
|
|
data = models.TextField(default=u'')
|
|
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
if self.data and not self.file.available:
|
|
|
|
self.file.available = True
|
|
|
|
self.file.save()
|
|
|
|
super(FileContents, self).save(*args, **kwargs)
|
2010-08-13 17:17:23 +00:00
|
|
|
|