--- /dev/null
+from django.utils.translation import ugettext_lazy as _
+
+class status:
+ WAITING = 1
+ ENCODING = 2
+ TAGGING = 3
+ SENDING = 4
+
+ choices = [
+ (WAITING, _('Waiting')),
+ (ENCODING, _('Encoding')),
+ (TAGGING, _('Tagging')),
+ (SENDING, _('Sending')),
+ ]
+from datetime import datetime
import os
import os.path
-from datetime import datetime
from django import forms
from django.utils.translation import ugettext_lazy as _
from archive.models import Audiobook
from archive.settings import FILES_PATH
-from archive.utils import ExistingFile
+from archive.utils import ExistingFile, sha1_file
class AudiobookForm(forms.ModelForm):
class Meta:
# save the file in model
m.source_file.save(
- os.path.join(FILES_PATH, os.path.basename(path)),
+ os.path.basename(path),
ExistingFile(path))
+ f = open(m.source_file.path)
+ m.source_sha1 = sha1_file(f)
+ f.close()
+
if commit:
m.save()
-
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Deleting field 'Audiobook.publishing_tags'
+ db.delete_column('archive_audiobook', 'publishing_tags')
+
+ # Deleting field 'Audiobook.published_tags'
+ db.delete_column('archive_audiobook', 'published_tags')
+
+ # Deleting field 'Audiobook.publish_wait'
+ db.delete_column('archive_audiobook', 'publish_wait')
+
+ # Deleting field 'Audiobook.published'
+ db.delete_column('archive_audiobook', 'published')
+
+ # Deleting field 'Audiobook.publishing'
+ db.delete_column('archive_audiobook', 'publishing')
+
+ # Adding field 'Audiobook.mp3_status'
+ db.add_column('archive_audiobook', 'mp3_status', self.gf('django.db.models.fields.SmallIntegerField')(null=True), keep_default=False)
+
+ # Adding field 'Audiobook.mp3_task'
+ db.add_column('archive_audiobook', 'mp3_task', self.gf('django.db.models.fields.CharField')(max_length=64, null=True), keep_default=False)
+
+ # Adding field 'Audiobook.mp3_tags'
+ db.add_column('archive_audiobook', 'mp3_tags', self.gf('jsonfield.fields.JSONField')(null=True), keep_default=False)
+
+ # Adding field 'Audiobook.mp3_published_tags'
+ db.add_column('archive_audiobook', 'mp3_published_tags', self.gf('jsonfield.fields.JSONField')(null=True), keep_default=False)
+
+ # Adding field 'Audiobook.mp3_published'
+ db.add_column('archive_audiobook', 'mp3_published', self.gf('django.db.models.fields.DateTimeField')(null=True), keep_default=False)
+
+ # Adding field 'Audiobook.ogg_status'
+ db.add_column('archive_audiobook', 'ogg_status', self.gf('django.db.models.fields.SmallIntegerField')(null=True), keep_default=False)
+
+ # Adding field 'Audiobook.ogg_task'
+ db.add_column('archive_audiobook', 'ogg_task', self.gf('django.db.models.fields.CharField')(max_length=64, null=True), keep_default=False)
+
+ # Adding field 'Audiobook.ogg_tags'
+ db.add_column('archive_audiobook', 'ogg_tags', self.gf('jsonfield.fields.JSONField')(null=True), keep_default=False)
+
+ # Adding field 'Audiobook.ogg_published_tags'
+ db.add_column('archive_audiobook', 'ogg_published_tags', self.gf('jsonfield.fields.JSONField')(null=True), keep_default=False)
+
+ # Adding field 'Audiobook.ogg_published'
+ db.add_column('archive_audiobook', 'ogg_published', self.gf('django.db.models.fields.DateTimeField')(null=True), keep_default=False)
+
+
+ def backwards(self, orm):
+
+ # Adding field 'Audiobook.publishing_tags'
+ db.add_column('archive_audiobook', 'publishing_tags', self.gf('jsonfield.fields.JSONField')(null=True), keep_default=False)
+
+ # Adding field 'Audiobook.published_tags'
+ db.add_column('archive_audiobook', 'published_tags', self.gf('jsonfield.fields.JSONField')(null=True), keep_default=False)
+
+ # Adding field 'Audiobook.publish_wait'
+ db.add_column('archive_audiobook', 'publish_wait', self.gf('django.db.models.fields.DateTimeField')(null=True), keep_default=False)
+
+ # Adding field 'Audiobook.published'
+ db.add_column('archive_audiobook', 'published', self.gf('django.db.models.fields.DateTimeField')(null=True), keep_default=False)
+
+ # Adding field 'Audiobook.publishing'
+ db.add_column('archive_audiobook', 'publishing', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
+
+ # Deleting field 'Audiobook.mp3_status'
+ db.delete_column('archive_audiobook', 'mp3_status')
+
+ # Deleting field 'Audiobook.mp3_task'
+ db.delete_column('archive_audiobook', 'mp3_task')
+
+ # Deleting field 'Audiobook.mp3_tags'
+ db.delete_column('archive_audiobook', 'mp3_tags')
+
+ # Deleting field 'Audiobook.mp3_published_tags'
+ db.delete_column('archive_audiobook', 'mp3_published_tags')
+
+ # Deleting field 'Audiobook.mp3_published'
+ db.delete_column('archive_audiobook', 'mp3_published')
+
+ # Deleting field 'Audiobook.ogg_status'
+ db.delete_column('archive_audiobook', 'ogg_status')
+
+ # Deleting field 'Audiobook.ogg_task'
+ db.delete_column('archive_audiobook', 'ogg_task')
+
+ # Deleting field 'Audiobook.ogg_tags'
+ db.delete_column('archive_audiobook', 'ogg_tags')
+
+ # Deleting field 'Audiobook.ogg_published_tags'
+ db.delete_column('archive_audiobook', 'ogg_published_tags')
+
+ # Deleting field 'Audiobook.ogg_published'
+ db.delete_column('archive_audiobook', 'ogg_published')
+
+
+ models = {
+ 'archive.audiobook': {
+ 'Meta': {'ordering': "('title',)", 'object_name': 'Audiobook'},
+ 'artist': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'conductor': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'date': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'encoded_by': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'mp3_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+ 'mp3_published': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'mp3_published_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}),
+ 'mp3_status': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True'}),
+ 'mp3_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}),
+ 'mp3_task': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+ 'ogg_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+ 'ogg_published': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'ogg_published_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}),
+ 'ogg_status': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True'}),
+ 'ogg_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}),
+ 'ogg_task': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archive.Project']"}),
+ 'source_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '255'})
+ },
+ 'archive.project': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Project'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
+ 'sponsors': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['archive']
--- /dev/null
+# encoding: utf-8
+import datetime
+from hashlib import sha1
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+def sha1_file(f):
+ sha = sha1()
+ for piece in iter(lambda: f.read(1024*1024), ''):
+ sha.update(piece)
+ return sha.hexdigest()
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding field 'Audiobook.source_sha1'
+ db.add_column('archive_audiobook', 'source_sha1', self.gf('django.db.models.fields.CharField')(default='', max_length=40), keep_default=False)
+
+ # Adding field 'Audiobook.translator'
+ db.add_column('archive_audiobook', 'translator', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False)
+
+ if not db.dry_run:
+ for obj in orm.Audiobook.objects.all():
+ try:
+ f = open(obj.source_file.path)
+ except ValueError, e:
+ print "Audiobook has no source file"
+ pass
+ else:
+ obj.source_sha1 = sha1_file(f)
+ f.close()
+ obj.save()
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'Audiobook.source_sha1'
+ db.delete_column('archive_audiobook', 'source_sha1')
+
+ # Deleting field 'Audiobook.translator'
+ db.delete_column('archive_audiobook', 'translator')
+
+
+ models = {
+ 'archive.audiobook': {
+ 'Meta': {'ordering': "('title',)", 'object_name': 'Audiobook'},
+ 'artist': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'conductor': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'date': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'encoded_by': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'mp3_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+ 'mp3_published': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'mp3_published_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}),
+ 'mp3_status': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True'}),
+ 'mp3_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}),
+ 'mp3_task': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+ 'ogg_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+ 'ogg_published': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'ogg_published_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}),
+ 'ogg_status': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True'}),
+ 'ogg_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}),
+ 'ogg_task': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archive.Project']"}),
+ 'source_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'translator': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '255'})
+ },
+ 'archive.project': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Project'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
+ 'sponsors': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['archive']
+# -*- coding: utf-8 -*-
+
from django.db import models
from jsonfield.fields import JSONField
from django.utils.translation import ugettext_lazy as _
-from archive.settings import FILES_PATH
+from archive.constants import status
+from archive.settings import FILES_PATH, ADVERT, LICENSE, ORGANIZATION, PROJECT
# Create your models here.
class Audiobook(models.Model):
- source_file = models.FileField(upload_to=FILES_PATH, verbose_name=_('source file'), editable=False)
+ source_file = models.FileField(upload_to='archive/files', verbose_name=_('source file'), editable=False)
+ source_sha1 = models.CharField(max_length=40, editable=False)
title = models.CharField(max_length=255, verbose_name=_('title'))
artist = models.CharField(max_length=255, verbose_name=_('artist'))
date = models.CharField(max_length=255, verbose_name=_('date'))
project = models.ForeignKey(Project, verbose_name=_('project'))
url = models.URLField(max_length=255, verbose_name=_('book url'))
+ translator = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('translator'))
modified = models.DateTimeField(null=True, editable=False)
- published_tags = JSONField(null=True, editable=False)
+ # publishing process
+ mp3_status = models.SmallIntegerField(null=True, editable=False, choices=status.choices)
+ mp3_task = models.CharField(max_length=64, null=True, editable=False)
+ mp3_tags = JSONField(null=True, editable=False)
mp3_file = models.FileField(null=True, upload_to='archive/final', editable=False)
+ mp3_published_tags = JSONField(null=True, editable=False)
+ mp3_published = models.DateTimeField(null=True, editable=False)
+
+ ogg_status = models.SmallIntegerField(null=True, editable=False, choices=status.choices)
+ ogg_task = models.CharField(max_length=64, null=True, editable=False)
+ ogg_tags = JSONField(null=True, editable=False)
ogg_file = models.FileField(null=True, upload_to='archive/final', editable=False)
- publishing_tags = JSONField(null=True, editable=False)
+ ogg_published_tags = JSONField(null=True, editable=False)
+ ogg_published = models.DateTimeField(null=True, editable=False)
- publish_wait = models.DateTimeField(null=True, editable=False) # somebody hit "publish"
- publishing = models.BooleanField(default=False, editable=False)
- published = models.DateTimeField(null=True, editable=False)
class Meta:
verbose_name = _("audiobook")
def __unicode__(self):
return self.title
+ def published(self):
+ return self.mp3_published and self.ogg_published
+
def new_publish_tags(self):
+ title = self.title
+ if self.translator:
+ title += u' (tłum. %s)' % self.translator
+
+ copyright = u"%s %s. Licensed to the public under %s verify at %s" % (
+ self.date, ORGANIZATION, LICENSE, self.url)
+
+ comment = u"Audiobook nagrany w ramach projektu %s%s.\n%s" % (
+ self.project.name,
+ u" finansowanego przez %s" % self.project.sponsors if self.project.sponsors else "",
+ ADVERT)
+
return {
- 'title': self.title,
- 'copyright': 'Fundacja Nowoczesna Polska',
+ 'album': PROJECT,
+ 'albumartist': ORGANIZATION,
+ 'artist': self.artist,
+ 'comment': comment,
+ 'conductor': self.conductor,
+ 'contact': self.url,
+ 'copyright': copyright,
+ 'date': self.date,
+ 'genre': u'Speech',
+ 'language': u'pol',
+ 'license': LICENSE,
+ 'organization': ORGANIZATION,
+ 'title': title,
+ 'flac_sha1': self.source_sha1,
}
+# -*- coding: utf-8 -*-
+
import os.path
from django.conf import settings
except AttributeError:
FINAL_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT,
"archive/final"))
+
+
+# here the app keeps temporary build files
+try:
+ BUILD_PATH = settings.ARCHIVE_BUILD_PATH
+except AttributeError:
+ BUILD_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT,
+ "archive/build"))
+
+# upload conf
+try:
+ UPLOAD_HOST = settings.ARCHIVE_UPLOAD_HOST
+except AttributeError:
+ UPLOAD_HOST = 'wolnelektury.pl'
+
+try:
+ UPLOAD_USER = settings.ARCHIVE_UPLOAD_USER
+except AttributeError:
+ UPLOAD_USER = 'username'
+
+try:
+ UPLOAD_PATH = settings.ARCHIVE_UPLOAD_PATH
+except AttributeError:
+ UPLOAD_PATH = ''
+
+try:
+ UPLOAD_CMD = settings.ARCHIVE_UPLOAD_CMD
+except AttributeError:
+ UPLOAD_CMD = '/path/to/manage.py savemedia'
+
+try:
+ UPLOAD_SUDO = settings.ARCHIVE_UPLOAD_SUDO
+except AttributeError:
+ UPLOAD_SUDO = None
+
+
+try:
+ PROJECT = settings.ARCHIVE_PROJECT
+except AttributeError:
+ PROJECT = u'Wolne Lektury'
+
+try:
+ LICENSE = settings.ARCHIVE_LICENSE
+except AttributeError:
+ LICENSE = u'http://creativecommons.org/licenses/by-sa/3.0/deed.pl'
+
+try:
+ ORGANIZATION = settings.ARCHIVE_ORGANIZATION
+except AttributeError:
+ ORGANIZATION = u'Fundacja Nowoczesna Polska'
+
+try:
+ ADVERT = settings.ARCHIVE_ADVERT
+except AttributeError:
+ ADVERT = u"""
+Przekaż 1% podatku na rozwój Wolnych Lektur:
+Fundacja Nowoczesna Polska
+KRS 0000070056
+http://nowoczesnapolska.org.pl/wesprzyj_nas/
+"""
+
+try:
+ COVER_IMAGE = settings.ARCHIVE_COVER_IMAGE
+except AttributeError:
+ COVER_IMAGE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cover.png')
+
.errorlist li {
color: red;
}
+
+
+.tags, .multiple_tags {
+ border-collapse: collapse;
+}
+
+.tags td, .tags th, .multiple_tags td, .multiple_tags th {
+ border: 1px solid #ddd;
+ padding: 3px;
+}
--- /dev/null
+from datetime import datetime
+import errno
+import mimetypes
+import os
+import os.path
+import pipes
+import subprocess
+from tempfile import NamedTemporaryFile
+from time import sleep
+
+#from celery.decorators import task
+from celery.task import Task
+from fabric import api
+from mutagen import File
+from mutagen import id3
+
+import mutagen
+
+from archive.constants import status
+from archive.models import Audiobook
+from archive.settings import (BUILD_PATH, COVER_IMAGE,
+ UPLOAD_HOST, UPLOAD_USER, UPLOAD_PATH, UPLOAD_CMD, UPLOAD_SUDO)
+from archive.utils import ExistingFile
+
+api.env.host_string = UPLOAD_HOST
+api.env.user = UPLOAD_USER
+
+class AudioFormatTask(Task):
+ abstract = True
+
+ @classmethod
+ def set_status(cls, audiobook, status):
+ setattr(audiobook, '%s_status' % cls.ext, status)
+ audiobook.save()
+
+ @staticmethod
+ def encode(in_path, out_path):
+ pass
+
+ @classmethod
+ def set_tags(cls, audiobook, file_name):
+ audio = File(file_name)
+ for k, v in getattr(audiobook, "%s_tags" % cls.ext)['tags'].items():
+ audio[k] = v
+ audio.save()
+
+ @classmethod
+ def save(cls, audiobook, file_name):
+ getattr(audiobook, "%s_file" % cls.ext).save(
+ "%d.%s" % (audiobook.pk, cls.ext),
+ ExistingFile(file_name)
+ )
+
+ @classmethod
+ def published(cls, audiobook):
+ setattr(audiobook, "%s_published_tags" % cls.ext,
+ getattr(audiobook, "%s_tags" % cls.ext))
+ setattr(audiobook, "%s_tags" % cls.ext, None)
+ setattr(audiobook, "%s_published" % cls.ext, datetime.now())
+ cls.set_status(audiobook, None)
+
+ @classmethod
+ def put(cls, audiobook):
+ tags = getattr(audiobook, "%s_tags" % cls.ext)
+ prefix, slug = tags['url'].rstrip('/').rsplit('/', 1)
+ name = tags['name']
+ path = getattr(audiobook, "%s_file" % cls.ext).path
+ api.put(path, UPLOAD_PATH)
+ command = UPLOAD_CMD + (u' %s %s %s > output.txt' % (
+ pipes.quote(os.path.join(UPLOAD_PATH, os.path.basename(path))),
+ pipes.quote(slug),
+ pipes.quote(name)
+ )).encode('utf-8')
+ print command
+ if UPLOAD_SUDO:
+ api.sudo(command, user=UPLOAD_SUDO, shell=False)
+ else:
+ api.run(command)
+
+ def run(self, aid):
+ audiobook = Audiobook.objects.get(id=aid)
+ self.set_status(audiobook, status.ENCODING)
+
+ try:
+ os.makedirs(BUILD_PATH)
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ pass
+ else:
+ raise
+
+ out_file = NamedTemporaryFile(delete=False, prefix='audiobook-', suffix='.%s' % self.ext, dir=BUILD_PATH)
+ out_file.close()
+ self.encode(audiobook.source_file.path, out_file.name)
+ self.set_status(audiobook, status.TAGGING)
+ self.set_tags(audiobook, out_file.name)
+ self.save(audiobook, out_file.name)
+ self.set_status(audiobook, status.SENDING)
+
+ #self.put(audiobook)
+
+ self.published(audiobook)
+ audiobook.save()
+
+
+class Mp3Task(AudioFormatTask):
+ ext = 'mp3'
+
+ # these shouldn't be staticmethods
+ def id3_text(tag, text):
+ return tag(encoding=1, text=text)
+ def id3_url(tag, text):
+ return tag(url=text)
+ def id3_comment(tag, text, lang=u'pol'):
+ return tag(encoding=1, lang=lang, desc=u'', text=text)
+ def id3_sha1(tag, text, what=u''):
+ return tag(owner='http://wolnelektury.pl?%s' % what, data=text)
+
+ TAG_MAP = {
+ 'album': (id3_text, id3.TALB),
+ 'albumartist': (id3_text, id3.TPE2),
+ 'artist': (id3_text, id3.TPE1),
+ 'conductor': (id3_text, id3.TPE3),
+ 'copyright': (id3_text, id3.TCOP),
+ 'date': (id3_text, id3.TDRC),
+ 'genre': (id3_text, id3.TCON),
+ 'language': (id3_text, id3.TLAN),
+ 'organization': (id3_text, id3.TPUB),
+ 'title': (id3_text, id3.TIT2),
+ 'comment': (id3_comment, id3.COMM, 'pol'),
+ 'contact': (id3_url, id3.WOAF),
+ 'license': (id3_url, id3.WCOP),
+ 'flac_sha1': (id3_sha1, id3.PRIV, 'flac_sha1'),
+ }
+
+ @staticmethod
+ def encode(in_path, out_path):
+ # 44.1kHz 64kbps mono MP3
+ subprocess.check_call(['ffmpeg',
+ '-i', in_path,
+ '-ar', '44100',
+ '-ab', '64k',
+ '-ac', '1',
+ '-y',
+ out_path
+ ])
+
+ @classmethod
+ def set_tags(cls, audiobook, file_name):
+ audio = id3.ID3(file_name)
+ for k, v in audiobook.mp3_tags['tags'].items():
+ factory_tuple = cls.TAG_MAP[k]
+ factory, tagtype = factory_tuple[:2]
+ audio.add(factory(tagtype, v, *factory_tuple[2:]))
+
+ if COVER_IMAGE:
+ mime = mimetypes.guess_type(COVER_IMAGE)
+ f = open(COVER_IMAGE)
+ audio.add(id3.APIC(encoding=0, mime=mime, type=3, desc=u'', data=f.read()))
+ f.close()
+
+ audio.save()
+
+
+class OggTask(AudioFormatTask):
+ ext = 'ogg'
+
+ @staticmethod
+ def encode(in_path, out_path):
+ # 44.1kHz 64kbps mono Ogg Vorbis
+ subprocess.check_call(['oggenc',
+ in_path,
+ '--discard-comments',
+ '--resample', '44100',
+ '--downmix',
+ '-b', '64',
+ '-o', out_path
+ ])
+{% extends "base.html" %}
{% load i18n %}
-<!DOCTYPE html>
-<html>
-<head>
- <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
- <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}style.css" />
- <title>{% trans "Audiobook repository" %}</title>
-</head>
-<body>
-<div id="repo-zones-nav">
+{% block repo-zones-nav %}
<a {% if division = "new" %}class="active" {% endif %}href="{% url list_new %}">{% trans "New" %}</a>
<a {% if division = "unpublished" %}class="active" {% endif %}href="{% url list_unpublished %}">{% trans "Unpublished" %}</a>
<a {% if division = "published" %}class="active" {% endif %}href="{% url list_published %}">{% trans "Published" %}</a>
<a {% if division = "unmanaged" %}class="active" {% endif %}href="{% url list_unmanaged %}">{% trans "Archive" %}</a>
+ <a href="{% url logout %}" style='float: right;'>{% trans "Logout" %}</a>
<div class='clr' ></div>
-</div>
-
-<div id="messages">
-{% block messages %}{% endblock %}
-</div>
-
-<div id="content">
-{% block content %}{% endblock %}
-</div>
-
-</body>
-</html>
+{% endblock %}
{% extends "archive/base.html" %}
{% load i18n %}
+{% load tags %}
{% block content %}
<h2>{% trans "Publishing" %}</h2>
-{% if audiobook.publish_wait %}
- <p>{% trans "Audiobook marked for publishing with tags:" %}</p>
+{% if audiobook.mp3_status or audiobook.ogg_status %}
- <table>
- {% for t, v in audiobook.publishing_tags.items %}
- <tr><th>{{ t }}</th><td>{{ v }}</td></tr>
- {% endfor %}
- </table>
+<h2>{% trans "Publishing pending" %}</h2>
- {% if audiobook.publishing %}
- <p>{% trans "Publishing already in progress." %}</p>
- {% else %}
- <form method="post" action="{% url cancel_publishing audiobook.id %}">
- {% csrf_token %}
- <input type="submit" value="{% trans "Cancel publishing" %}" />
- </form>
- {% endif %}
-{% else %}
- {% if audiobook.published %}
- Here be currently published version, for comparison.
- {% endif %}
+<form method="post" action="{% url cancel_publishing audiobook.id %}">
+ {% csrf_token %}
+ <input type="submit" value="{% trans "Cancel publishing" %}" />
+</form>
+
+
+{% if audiobook.mp3_status %}
+ <hr/>
+ <h2>MP3</h2>
+
+ {% tags_table audiobook.mp3_tags.tags %}
+
+ <p>Status: <b>{{ audiobook.get_mp3_status_display }}</b></p>
+{% endif %}
+
+{% if audiobook.ogg_status %}
+ <hr/>
+ <h2>Ogg Vorbis</h2>
+
+ {% tags_table audiobook.ogg_tags.tags %}
+ <p>Status: <b>{{ audiobook.get_ogg_status_display }}</b></p>
+{% endif %}
+
+
+
+
+{% else %}
<form method="post" action="{% url publish audiobook.id %}">
- <table>
- {% for k, v in audiobook.new_publish_tags.items %}
- <tr><th>{{ k }}</th><td>{{ v }}</td></tr>
- {% endfor %}
+ <table class='tags'>
+ {% tags_table audiobook.new_publish_tags 0 %}
{% csrf_token %}
<tr><th></th><td><input type="submit" value="{% trans "Publish" %}" /></td></tr>
</form>
{% endif %}
+<hr/>
+{% if audiobook.mp3_published %}
+ <h2>Published MP3</h2>
+ <a href="{{ audiobook.mp3_file.url }}">{{ audiobook.mp3_published }}</a>
+ {% tags_table audiobook.mp3_published_tags.tags %}
+{% else %}<p>MP3 file hasn't been published yet.</p>
+{% endif %}
+
+<hr/>
+{% if audiobook.ogg_published %}
+ <h2>Published Ogg Vorbis</h2>
+ <a href="{{ audiobook.ogg_file.url }}">{{ audiobook.ogg_published }}</a>
+ {% tags_table audiobook.ogg_published_tags.tags %}
+{% else %}Ogg Vorbis file hasn't been published yet.
+{% endif %}
+
<h2>{% trans "Update tags" %}</h2>
+Last modified: {{ audiobook.modified }}
-<table class="file_tags">
- {% for t, v in tags.items %}
- <tr><th>{{t}}</th><td>
- {% for x in v %}
- {{x}}<br />
- {% endfor %}
- </td></tr>
- {% endfor %}
-</table>
+{% multiple_tags_table tags %}
{% block file-list %}
{% for file in objects %}
<li>
- <a href='{% url file file %}'>{{ file }}</a></form>
+ <a href='{% url file file.id %}'>{{ file }}</a></form>
</li>
{% endfor %}
{% endblock %}
-<table>
+{% if table %}<table class='multiple_tags'>{% endif %}
{% for t, v in tags.items %}
- <tr><th>{{t}}</th><td>
+ <tr><th>{{ t }}</th><td>
{% for x in v %}
- <div>{{x}}</div>
+ <div>{{ x|linebreaksbr }}</div>
{% endfor %}
</td></tr>
{% endfor %}
-</table>
+{% if table %}</table>{% endif %}
--- /dev/null
+{% if table %}<table class='tags'>{% endif %}
+ {% for t, v in tags.items %}
+ <tr><th>{{ t }}</th><td>
+ {{ v|linebreaksbr }}
+ </td></tr>
+ {% endfor %}
+{% if table %}</table>{% endif %}
--- /dev/null
+{% load i18n %}
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}style.css" />
+ <title>{% trans "Audiobook repository" %}</title>
+</head>
+<body>
+
+<div id="repo-zones-nav">
+ {% block repo-zones-nav %} {% endblock %}
+</div>
+
+<div id="messages">
+{% block messages %}{% endblock %}
+</div>
+
+<div id="content">
+{% block content %}{% endblock %}
+</div>
+
+</body>
+</html>
--- /dev/null
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+<form method='post'>
+{% csrf_token %}
+{{ form.as_p }}
+<p><input type='submit' value='{% trans "Login" %}' /></p>
+</form>
+
+{% endblock %}
register = template.Library()
-@register.simple_tag
-def multiple_tags_table(tags):
- return template.loader.render_to_string(
- "archive/tags/multiple_tags_table.html",
- {"tags": tags}
- )
+@register.inclusion_tag('archive/tags/multiple_tags_table.html')
+def multiple_tags_table(tags, table=True):
+ return locals()
-#@register.simple_tag
-#def multiple_tags_table(tags):
-# return template.loader.render_to_string(
-# "archive/tags/multiple_tags_table.html",
-# {"tags": tags}
-# )
-
+@register.inclusion_tag('archive/tags/tags_table.html')
+def tags_table(tags, table=True):
+ return locals()
+from hashlib import sha1
from django.core.files.uploadedfile import UploadedFile
+
class ExistingFile(UploadedFile):
def __init__(self, path, *args, **kwargs):
def close(self):
pass
+
+
+def sha1_file(f):
+ sha = sha1()
+ for piece in iter(lambda: f.read(1024*1024), ''):
+ sha.update(piece)
+ return sha.hexdigest()
import os.path
from archive import settings
+from django.contrib.auth import logout
+from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
+from django.db.models import Q
from django.http import Http404
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
import mutagen
+from archive.constants import status
from archive import models
from archive.forms import AudiobookForm
+from archive import tasks
+@login_required
def list_new(request):
division = 'new'
return render(request, "archive/list_new.html", locals())
+@login_required
def file_new(request, filename):
division = 'new'
@require_POST
+@login_required
def move_to_archive(request, filename):
""" move a new file to the unmanaged files dir """
@require_POST
+@login_required
def move_to_new(request, filename):
""" move a unmanaged file to new files dir """
return redirect(list_unmanaged)
+
@require_POST
-def publish(request, id):
+@login_required
+def publish(request, aid):
""" mark file for publishing """
- audiobook = get_object_or_404(models.Audiobook, id=id)
- audiobook.publish_wait = datetime.now()
- audiobook.publishing_tags = audiobook.new_publish_tags()
+ audiobook = get_object_or_404(models.Audiobook, id=aid)
+ tags = {
+ 'name': audiobook.title,
+ 'url': audiobook.url,
+ 'tags': audiobook.new_publish_tags(),
+ }
+ audiobook.mp3_tags = tags
+ audiobook.ogg_tags = tags
+ audiobook.mp3_status = audiobook.ogg_status = status.WAITING
audiobook.save()
- return redirect(file_managed, id)
+ # isn't there a race here?
+ audiobook.mp3_task = tasks.Mp3Task.delay(aid).task_id
+ audiobook.ogg_task = tasks.OggTask.delay(aid).task_id
+ audiobook.save()
+
+ return redirect(file_managed, aid)
+
@require_POST
-def cancel_publishing(request, id):
+@login_required
+def cancel_publishing(request, aid):
""" cancel scheduled publishing """
- audiobook = get_object_or_404(models.Audiobook, id=id)
- if not audiobook.publishing:
- audiobook.publish_wait = None
- audiobook.publishing_tags = None
- audiobook.save()
- return redirect(file_managed, id)
+ audiobook = get_object_or_404(models.Audiobook, id=aid)
+ # TODO: cancel tasks
+ audiobook.mp3_status = None
+ audiobook.ogg_status = None
+ audiobook.save()
+ return redirect(file_managed, aid)
+@login_required
def list_unpublished(request):
division = 'unpublished'
- objects = models.Audiobook.objects.filter(published=None)
+ objects = models.Audiobook.objects.filter(Q(mp3_published=None) | Q(ogg_published=None))
return render(request, "archive/list_unpublished.html", locals())
+@login_required
+def list_published(request):
+ division = 'published'
+
+ objects = models.Audiobook.objects.exclude(Q(mp3_published=None) | Q(ogg_published=None))
+ return render(request, "archive/list_published.html", locals())
+
+@login_required
def file_managed(request, id):
audiobook = get_object_or_404(models.Audiobook, id=id)
- division = 'published' if audiobook.published else 'unpublished'
+
+ if request.POST:
+ form = AudiobookForm(request.POST, instance=audiobook)
+ if form.is_valid():
+ try:
+ form.save()
+ except IOError:
+ raise Http404
+
+ division = 'published' if audiobook.published() else 'unpublished'
# for tags update
tags = mutagen.File(audiobook.source_file.path)
return render(request, "archive/file_managed.html", locals())
-
-def list_published(request):
- division = 'published'
-
- objects = models.Audiobook.objects.exclude(published=None)
- return render(request, "archive/list_published.html", locals())
-
-
-
-
+@login_required
def list_unmanaged(request):
division = 'unmanaged'
return render(request, "archive/list_unmanaged.html", locals())
+@login_required
def file_unmanaged(request, filename):
division = 'unmanaged'
err_exists = request.GET.get('exists')
return render(request, "archive/file_unmanaged.html", locals())
+
+@login_required
+def logout_view(request):
+ logout(request)
+ return redirect(list_new)
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
-MEDIA_URL = ''
+MEDIA_URL = '/media/'
# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
+ 'djcelery',
+ 'djkombu',
+
'south',
'archive',
)
+
+import djcelery
+djcelery.setup_loader()
+
+BROKER_BACKEND = "djkombu.transport.DatabaseTransport"
+BROKER_HOST = "localhost"
+BROKER_PORT = 5672
+BROKER_USER = "guest"
+BROKER_PASSWORD = "guest"
+BROKER_VHOST = "/"
+
+
try:
from localsettings import *
except:
+from django.conf import settings
from django.conf.urls.defaults import patterns, include, url
# Uncomment the next two lines to enable the admin:
url(r'^archive/', include('archive.urls')),
url(r'^admin/', include(admin.site.urls)),
+ url(r'^accounts/login/', 'django.contrib.auth.views.login'),
+ url(r'^accounts/logout/', 'archive.views.logout_view', name='logout'),
+)
+
+if settings.DEBUG:
+ urlpatterns += patterns('',
+ (r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT, 'show_indexes':True}),
)
django-jsonfield
South>=0.7
+django-celery
+django-kombu
+
+paramiko>=1.7.7.1 # fabric dependency with http://code.fabfile.org/issues/show/214 fixed
+fabric
mutagen