From: Radek Czajka Date: Thu, 28 Feb 2019 09:27:12 +0000 (+0100) Subject: Reorganize code. X-Git-Url: https://git.mdrn.pl/audio.git/commitdiff_plain/eee35e00bf0d0eb3d2a9f08f72c7052962fecbf6 Reorganize code. --- diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 diff --git a/apps/archive/__init__.py b/apps/archive/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/archive/admin.py b/apps/archive/admin.py deleted file mode 100644 index d0804be..0000000 --- a/apps/archive/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -from archive.models import Project, Audiobook -from django.contrib import admin - -admin.site.register(Project) -admin.site.register(Audiobook) diff --git a/apps/archive/constants.py b/apps/archive/constants.py deleted file mode 100644 index 17f4e78..0000000 --- a/apps/archive/constants.py +++ /dev/null @@ -1,14 +0,0 @@ -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')), - ] diff --git a/apps/archive/cover.png b/apps/archive/cover.png deleted file mode 100644 index 1435a03..0000000 Binary files a/apps/archive/cover.png and /dev/null differ diff --git a/apps/archive/forms.py b/apps/archive/forms.py deleted file mode 100644 index 81568c6..0000000 --- a/apps/archive/forms.py +++ /dev/null @@ -1,42 +0,0 @@ -from datetime import datetime -import os -import os.path - -from django import forms -from django.utils.translation import ugettext_lazy as _ -import mutagen -from django.utils.encoding import force_bytes - -from archive.models import Audiobook -from archive.settings import FILES_PATH, NEW_PATH -from archive.utils import ExistingFile, sha1_file - -class AudiobookForm(forms.ModelForm): - class Meta: - model = Audiobook - - def save(self, commit=True, path=None): - """ Performs normal save, with given file as an source audiobook. - - `path' is relative to NEW_PATH. - """ - m = super(AudiobookForm, self).save(commit=False) - m.modified = datetime.now() - - if path: - # adding a new audiobook - if not os.path.isdir(FILES_PATH): - os.makedirs(FILES_PATH) - # save the file in model - - abs_path = os.path.join(NEW_PATH, path) - m.source_file.save( - path, - ExistingFile(abs_path)) - -# f = open(force_bytes(m.source_file.path)) -# m.source_sha1 = sha1_file(f) -# f.close() - - if commit: - m.save() diff --git a/apps/archive/locale/pl/LC_MESSAGES/django.mo b/apps/archive/locale/pl/LC_MESSAGES/django.mo deleted file mode 100644 index e1135bf..0000000 Binary files a/apps/archive/locale/pl/LC_MESSAGES/django.mo and /dev/null differ diff --git a/apps/archive/locale/pl/LC_MESSAGES/django.po b/apps/archive/locale/pl/LC_MESSAGES/django.po deleted file mode 100644 index 317ab76..0000000 --- a/apps/archive/locale/pl/LC_MESSAGES/django.po +++ /dev/null @@ -1,250 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) 2011 Fundacja Nowoczesna Polska -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-03-02 09:36+0100\n" -"PO-Revision-Date: 2012-07-11 15:52+0100\n" -"Last-Translator: Radek Czajka \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " -"|| n%100>=20) ? 1 : 2)\n" - -#: constants.py:10 -msgid "Waiting" -msgstr "W kolejce" - -#: constants.py:11 -msgid "Encoding" -msgstr "Konwersja" - -#: constants.py:12 -msgid "Tagging" -msgstr "Opisywanie" - -#: constants.py:13 -msgid "Sending" -msgstr "Wysyłanie" - -#: models.py:23 models.py:49 -msgid "project" -msgstr "projekt" - -#: models.py:24 -msgid "projects" -msgstr "projekty" - -#: models.py:37 -msgid "source file" -msgstr "plik źródłowy" - -#: models.py:40 -msgid "title" -msgstr "tytuł" - -#: models.py:41 -msgid "part name" -msgstr "nazwa części" - -#: models.py:41 -msgid "eg. chapter in a novel" -msgstr "np. rozdział w powieści" - -#: models.py:43 -msgid "index" -msgstr "numer" - -#: models.py:44 -msgid "parts count" -msgstr "liczba części" - -#: models.py:45 -msgid "artist" -msgstr "lektor" - -#: models.py:46 -msgid "conductor" -msgstr "reżyser" - -#: models.py:47 -msgid "encoded by" -msgstr "przyg. techn." - -#: models.py:48 -msgid "date" -msgstr "data" - -#: models.py:50 -msgid "book url" -msgstr "URL książki" - -#: models.py:51 -msgid "translator" -msgstr "tłumacz" - -#: models.py:71 -msgid "audiobook" -msgstr "audiobook" - -#: models.py:72 -msgid "audiobooks" -msgstr "audiobooki" - -#: templates/base.html:7 -msgid "Audiobook repository" -msgstr "Repozytorium audiobooków" - -#: templates/archive/base.html:5 -msgid "New" -msgstr "Nowe" - -#: templates/archive/base.html:6 -msgid "Unpublished" -msgstr "Nie opublikowane" - -#: templates/archive/base.html:7 templates/archive/file_managed.html:11 -msgid "Publishing" -msgstr "Publikacja" - -#: templates/archive/base.html:8 -msgid "Published" -msgstr "Opublikowane" - -#: templates/archive/base.html:9 -msgid "Archive" -msgstr "Archiwum" - -#: templates/archive/base.html:11 -msgid "Projects" -msgstr "Projekty" - -#: templates/archive/base.html:14 -msgid "Logout" -msgstr "Wyloguj" - -#: templates/archive/base.html:16 templates/registration/login.html:9 -msgid "Login" -msgstr "Zaloguj" - -#: templates/archive/base.html:19 -msgid "Administration" -msgstr "Administracja" - -#: templates/archive/file_managed.html:15 -msgid "Publishing pending" -msgstr "Czeka na publikację" - -#: templates/archive/file_managed.html:19 -msgid "Cancel publishing" -msgstr "Anuluj publikację" - -#: templates/archive/file_managed.html:51 -msgid "Publish" -msgstr "Opublikuj" - -#: templates/archive/file_managed.html:57 -msgid "Convert without publishing" -msgstr "Konwertuj bez publikacji" - -#: templates/archive/file_managed.html:67 -msgid "MP3 file" -msgstr "Plik MP3" - -#: templates/archive/file_managed.html:68 -msgid "Download MP3 file." -msgstr "Pobierz plik MP3." - -#: templates/archive/file_managed.html:70 -#: templates/archive/file_managed.html:86 -msgid "Published:" -msgstr "Opublikowano:" - -#: templates/archive/file_managed.html:75 -#: templates/archive/file_managed.html:91 -msgid "Not published yet." -msgstr "Nie opublikowane." - -#: templates/archive/file_managed.html:78 -msgid "MP3 file hasn't been generated yet." -msgstr "Plik MP3 nie został jeszcze wygenerowany." - -#: templates/archive/file_managed.html:83 -msgid "Ogg Vorbis file" -msgstr "Plik Ogg Vorbis" - -#: templates/archive/file_managed.html:84 -msgid "Download Ogg Vorbis file." -msgstr "Pobierz plik Ogg Vorbis." - -#: templates/archive/file_managed.html:94 -msgid "Ogg Vorbis file hasn't been generated yet." -msgstr "Plik Ogg Vorbis nie został jeszcze wygenerowany." - -#: templates/archive/file_managed.html:105 -msgid "Update tags" -msgstr "Uaktualnij tagi" - -#: templates/archive/file_managed.html:117 templates/archive/file_new.html:19 -msgid "Commit" -msgstr "Zatwierdź" - -#: templates/archive/file_managed.html:128 -msgid "Are you sure you want to move this audiobook to archive?" -msgstr "Czy na pewno chcesz przenieść ten plik to archiwum?" - -#: templates/archive/file_managed.html:130 -msgid "Remove to archive" -msgstr "Usuń do archiwum" - -#: templates/archive/file_new.html:8 -msgid "Move to archive" -msgstr "Przenieś do archiwum" - -#: templates/archive/file_unmanaged.html:6 -msgid "File with same name already exists!" -msgstr "Plik o tej nazwie już istnieje!" - -#: templates/archive/file_unmanaged.html:28 -msgid "Move to new files" -msgstr "Przenieś do nowych plików" - -#: templates/archive/list_new.html:6 -msgid "New audiobooks" -msgstr "Nowe audiobooki" - -#: templates/archive/list_new.html:11 -msgid "Put source audiobooks in:" -msgstr "Umieść nowe audiobooki w:" - -#: templates/archive/list_published.html:6 -msgid "Published audiobooks" -msgstr "Opublikowane audiobooki" - -#: templates/archive/list_publishing.html:6 -msgid "Audiobooks being published" -msgstr "Aktualnie publikowane audiobooki" - -#: templates/archive/list_unmanaged.html:5 -msgid "Unmanaged archive" -msgstr "Audiobooki archiwalne" - -#: templates/archive/list_unpublished.html:6 -msgid "Unpublished audiobooks" -msgstr "Nie opublikowane audiobooki" - -#~ msgid "arranger" -#~ msgstr "aranżer" - -#~ msgid "Audiobook marked for publishing with tags:" -#~ msgstr "Audiobook zaznaczony do publikacji z tagami:" - -#~ msgid "Publishing already in progress." -#~ msgstr "Publikowanie rozpoczęte." diff --git a/apps/archive/migrations/0001_initial.py b/apps/archive/migrations/0001_initial.py deleted file mode 100644 index ac5a2c9..0000000 --- a/apps/archive/migrations/0001_initial.py +++ /dev/null @@ -1,80 +0,0 @@ -# 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): - - # Adding model 'Project' - db.create_table('archive_project', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128, db_index=True)), - ('sponsors', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), - )) - db.send_create_signal('archive', ['Project']) - - # Adding model 'Audiobook' - db.create_table('archive_audiobook', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('source_file', self.gf('django.db.models.fields.files.FileField')(max_length=100)), - ('title', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('artist', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('conductor', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('encoded_by', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('date', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['archive.Project'])), - ('url', self.gf('django.db.models.fields.URLField')(max_length=255)), - ('modified', self.gf('django.db.models.fields.DateTimeField')(null=True)), - ('published_tags', self.gf('jsonfield.fields.JSONField')(null=True)), - ('mp3_file', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True)), - ('ogg_file', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True)), - ('publishing_tags', self.gf('jsonfield.fields.JSONField')(null=True)), - ('publish_wait', self.gf('django.db.models.fields.DateTimeField')(null=True)), - ('publishing', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('published', self.gf('django.db.models.fields.DateTimeField')(null=True)), - )) - db.send_create_signal('archive', ['Audiobook']) - - - def backwards(self, orm): - - # Deleting model 'Project' - db.delete_table('archive_project') - - # Deleting model 'Audiobook' - db.delete_table('archive_audiobook') - - - 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'}), - 'ogg_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}), - 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archive.Project']"}), - 'publish_wait': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'published': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), - 'published_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}), - 'publishing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'publishing_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}), - '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'] diff --git a/apps/archive/migrations/0002_auto__del_field_audiobook_publishing_tags__del_field_audiobook_publish.py b/apps/archive/migrations/0002_auto__del_field_audiobook_publishing_tags__del_field_audiobook_publish.py deleted file mode 100644 index 03cb130..0000000 --- a/apps/archive/migrations/0002_auto__del_field_audiobook_publishing_tags__del_field_audiobook_publish.py +++ /dev/null @@ -1,139 +0,0 @@ -# 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'] diff --git a/apps/archive/migrations/0003_auto__add_field_audiobook_source_sha1__add_field_audiobook_translator.py b/apps/archive/migrations/0003_auto__add_field_audiobook_source_sha1__add_field_audiobook_translator.py deleted file mode 100644 index 9711b72..0000000 --- a/apps/archive/migrations/0003_auto__add_field_audiobook_source_sha1__add_field_audiobook_translator.py +++ /dev/null @@ -1,84 +0,0 @@ -# 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'] diff --git a/apps/archive/migrations/0004_auto__chg_field_audiobook_source_file.py b/apps/archive/migrations/0004_auto__chg_field_audiobook_source_file.py deleted file mode 100644 index 95d630e..0000000 --- a/apps/archive/migrations/0004_auto__chg_field_audiobook_source_file.py +++ /dev/null @@ -1,57 +0,0 @@ -# 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): - - # Changing field 'Audiobook.source_file' - db.alter_column('archive_audiobook', 'source_file', self.gf('django.db.models.fields.files.FileField')(max_length=255)) - - - def backwards(self, orm): - - # Changing field 'Audiobook.source_file' - db.alter_column('archive_audiobook', 'source_file', self.gf('django.db.models.fields.files.FileField')(max_length=100)) - - - 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': '255'}), - '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'] diff --git a/apps/archive/migrations/0005_auto__add_field_audiobook_part_name__add_field_audiobook_index__add_fi.py b/apps/archive/migrations/0005_auto__add_field_audiobook_part_name__add_field_audiobook_index__add_fi.py deleted file mode 100644 index c163971..0000000 --- a/apps/archive/migrations/0005_auto__add_field_audiobook_part_name__add_field_audiobook_index__add_fi.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding field 'Audiobook.part_name' - db.add_column(u'archive_audiobook', 'part_name', - self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), - keep_default=False) - - # Adding field 'Audiobook.index' - db.add_column(u'archive_audiobook', 'index', - self.gf('django.db.models.fields.IntegerField')(default=0), - keep_default=False) - - # Adding field 'Audiobook.parts_count' - db.add_column(u'archive_audiobook', 'parts_count', - self.gf('django.db.models.fields.IntegerField')(default=1), - keep_default=False) - - - def backwards(self, orm): - # Deleting field 'Audiobook.part_name' - db.delete_column(u'archive_audiobook', 'part_name') - - # Deleting field 'Audiobook.index' - db.delete_column(u'archive_audiobook', 'index') - - # Deleting field 'Audiobook.parts_count' - db.delete_column(u'archive_audiobook', 'parts_count') - - - models = { - u'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'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'index': ('django.db.models.fields.IntegerField', [], {'default': '0'}), - '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'}), - 'part_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), - 'parts_count': ('django.db.models.fields.IntegerField', [], {'default': '1'}), - 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['archive.Project']"}), - 'source_file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}), - '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'}) - }, - u'archive.project': { - 'Meta': {'ordering': "('name',)", 'object_name': 'Project'}, - u'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'] \ No newline at end of file diff --git a/apps/archive/migrations/__init__.py b/apps/archive/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/archive/models.py b/apps/archive/models.py deleted file mode 100644 index c75a874..0000000 --- a/apps/archive/models.py +++ /dev/null @@ -1,139 +0,0 @@ -# -*- coding: utf-8 -*- -import os.path - -from django.db import models -from time import sleep -from jsonfield.fields import JSONField -from django.utils.encoding import force_bytes -from django.utils.translation import ugettext_lazy as _ -from archive.constants import status -from archive.settings import FILES_SAVE_PATH, ADVERT, LICENSE, ORGANIZATION, PROJECT -from archive.utils import OverwriteStorage, sha1_file - -# Create your models here. - - -class Project(models.Model): - """ an audiobook project, needed for specyfing sponsors """ - - name = models.CharField(max_length=128, unique=True, db_index=True, verbose_name="Nazwa") - sponsors = models.TextField(blank=True, null=True, verbose_name="Sponsorzy") - - class Meta: - verbose_name = _("project") - verbose_name_plural = _("projects") - ordering = ("name",) - - def __unicode__(self): - return self.name - - -def source_upload_to(intance, filename): - return os.path.join(FILES_SAVE_PATH, filename) # FIXME: what about really long file names? - - -class Audiobook(models.Model): - source_file = models.FileField(upload_to=source_upload_to, max_length=255, - verbose_name=_('source file'), editable=False) - source_sha1 = models.CharField(max_length=40, editable=False) - - title = models.CharField(max_length=255, verbose_name=_('title')) - part_name = models.CharField(max_length=255, verbose_name=_('part name'), help_text=_('eg. chapter in a novel'), - default='', blank=True) - index = models.IntegerField(verbose_name=_('index'), default=0) - parts_count = models.IntegerField(verbose_name=_('parts count'), default=1) - artist = models.CharField(max_length=255, verbose_name=_('artist')) - conductor = models.CharField(max_length=255, verbose_name=_('conductor')) - encoded_by = models.CharField(max_length=255, verbose_name=_('encoded by')) - 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) - - # 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', storage=OverwriteStorage(), 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', storage=OverwriteStorage(), editable=False) - ogg_published_tags = JSONField(null=True, editable=False) - ogg_published = models.DateTimeField(null=True, editable=False) - - - class Meta: - verbose_name = _("audiobook") - verbose_name_plural = _("audiobooks") - ordering = ("title",) - - def __unicode__(self): - return self.title - - def published(self): - return self.mp3_published and self.ogg_published - - def get_source_sha1(self): - source_sha1 = self.source_sha1 - if self.pk: - source_sha1 = type(self).objects.get(pk=self.pk).source_sha1 - while source_sha1 == 'wait': - sleep(10) - if not source_sha1: - self.source_sha1 = 'wait' - if self.pk: - type(self).objects.filter(pk=self.pk).update(source_sha1='wait') - try: - f = open(force_bytes(self.source_file.path)) - source_sha1 = sha1_file(f) - self.source_sha1 = source_sha1 - if self.pk: - type(self).objects.filter(pk=self.pk).update(source_sha1=source_sha1) - except: - self.source_sha1 = '' - if self.pk: - type(self).objects.filter(pk=self.pk).update(source_sha1='') - return None - return source_sha1 - - - 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) - - tags = { - '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.get_source_sha1(), - 'project': self.project.name, - 'funded_by': self.project.sponsors, - } - if self.source_sha1 and self.source_sha1 != 'wait': - tags['flac_sha1'] = self.source_sha1 - return tags - diff --git a/apps/archive/settings.py b/apps/archive/settings.py deleted file mode 100644 index 476f6c7..0000000 --- a/apps/archive/settings.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- - -import os.path -from django.conf import settings - -# this is where the end user puts new files -try: - NEW_PATH = settings.ARCHIVE_NEW_PATH -except AttributeError: - NEW_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT, - "archive/new")) - -# here the application keeps its managed files -try: - FILES_PATH = settings.ARCHIVE_FILES_PATH -except AttributeError: - FILES_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT, - "archive/files")) - -if FILES_PATH.startswith(settings.MEDIA_ROOT): - FILES_SAVE_PATH = FILES_PATH[len(settings.MEDIA_ROOT):].lstrip('/') - - -# here the app keeps the unmanaged (archive) files -try: - UNMANAGED_PATH = settings.ARCHIVE_UNMANAGED_PATH -except AttributeError: - UNMANAGED_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT, - "archive/unmanaged")) - - -# here the app keeps the resulting (published) files -try: - FINAL_PATH = settings.ARCHIVE_FINAL_PATH -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_PASSWORD = settings.ARCHIVE_UPLOAD_PASSWORD -except AttributeError: - UPLOAD_PASSWORD = None - -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') - diff --git a/apps/archive/static/style.css b/apps/archive/static/style.css deleted file mode 100755 index 434b87b..0000000 --- a/apps/archive/static/style.css +++ /dev/null @@ -1,67 +0,0 @@ -body { - margin: 0; - font-family: verdana, sans-serif; - font-size: 12px; -} - -a { - color: #bf6000; - text-decoration: none; -} - -.clr { - clear: both; -} - - -#repo-zones-nav { - padding: 5px 5px 0 10px; - background: #ffdfbf; - border-bottom: 1px solid #ff8000; -} - -#repo-zones-nav a, #repo-zones-nav span { - display: block; - float: left; - padding: 5px 20px 5px 20px; - margin-bottom: -1px; - border-width: 1px; - border-style: solid; - border-color: rgba(0,0,0,0); -} - -#repo-zones-nav .active { - background: white; - border-color: #ff8000 #ff8000 white #ff8000; -} - -#content { - padding: 10px; -} - - -.errorlist { - margin: 0; -} - -.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; -} - - -.list-published-tag, .list-publishing-tag { - font-size: .8em; -} -.list-publishing-tag { - color: #aaa; -} diff --git a/apps/archive/tasks.py b/apps/archive/tasks.py deleted file mode 100644 index a46b79c..0000000 --- a/apps/archive/tasks.py +++ /dev/null @@ -1,214 +0,0 @@ -from datetime import datetime -import errno -import mimetypes -import os -import os.path -import pipes -import stat -import subprocess -from tempfile import NamedTemporaryFile -from time import sleep - -#from celery.decorators import task -from celery.task import Task -from django.db.models import F -from fabric import api -from fabric.network import disconnect_all -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_PASSWORD, UPLOAD_PATH, UPLOAD_CMD, UPLOAD_SUDO) -from archive.utils import ExistingFile - -api.env.host_string = UPLOAD_HOST -api.env.user = UPLOAD_USER -api.env.password = UPLOAD_PASSWORD - -class AudioFormatTask(Task): - abstract = True - - class RemoteOperationError(BaseException): - pass - - @classmethod - def set_status(cls, aid, status): - Audiobook.objects.filter(pk=aid).update( - **{'%s_status' % cls.ext: status}) - - @staticmethod - def encode(in_path, out_path): - raise NotImplemented - - @classmethod - def set_tags(cls, audiobook, file_name): - tags = getattr(audiobook, "%s_tags" % cls.ext)['tags'] - if not tags.get('flac_sha1'): - tags['flac_sha1'] = audiobook.get_source_sha1() - audio = File(file_name) - for k, v in tags.items(): - audio[k] = v - audio.save() - - @classmethod - def save(cls, audiobook, file_name): - field = "%s_file" % cls.ext - getattr(audiobook, field).save( - "%d.%s" % (audiobook.pk, cls.ext), - ExistingFile(file_name), - save=False - ) - os.chmod(getattr(audiobook, field).path, stat.S_IREAD|stat.S_IWRITE|stat.S_IRGRP|stat.S_IROTH) - Audiobook.objects.filter(pk=audiobook.pk).update( - **{field: getattr(audiobook, field)}) - - @classmethod - def published(cls, aid): - kwargs = { - "%s_published_tags" % cls.ext: F("%s_tags" % cls.ext), - "%s_tags" % cls.ext: None, - "%s_published" % cls.ext: datetime.now(), - '%s_status' % cls.ext: None, - } - Audiobook.objects.filter(pk=aid).update(**kwargs) - - @classmethod - def put(cls, audiobook, path): - tags = getattr(audiobook, "%s_tags" % cls.ext) - prefix, slug = tags['url'].rstrip('/').rsplit('/', 1) - name = tags['name'] - command = UPLOAD_CMD + (u' %s %s %s %s %s %s > output.txt' % ( - pipes.quote(os.path.join(UPLOAD_PATH, os.path.basename(path))), - pipes.quote(slug), - pipes.quote(name), - pipes.quote(audiobook.part_name), - audiobook.index, - audiobook.parts_count, - )).encode('utf-8') - try: - api.put(path, UPLOAD_PATH) - if UPLOAD_SUDO: - api.sudo(command, user=UPLOAD_SUDO, shell=False) - else: - api.run(command) - disconnect_all() - except SystemExit, e: - raise cls.RemoteOperationError - - def run(self, aid, publish=True): - aid = int(aid) - audiobook = Audiobook.objects.get(id=aid) - self.set_status(aid, 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='%d-' % aid, suffix='.%s' % self.ext, dir=BUILD_PATH) - out_file.close() - self.encode(audiobook.source_file.path, out_file.name) - self.set_status(aid, status.TAGGING) - self.set_tags(audiobook, out_file.name) - self.set_status(aid, status.SENDING) - - if publish: - self.put(audiobook, out_file.name) - self.published(aid) - else: - self.set_status(aid, None) - - self.save(audiobook, out_file.name) - - def on_failure(self, exc, task_id, args, kwargs, einfo): - aid = (args[0], kwargs.get('aid'))[0] - self.set_status(aid, None) - - -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_priv(tag, text, what=u''): - return tag(owner='wolnelektury.pl?%s' % what, data=text.encode('utf-8')) - - 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_priv, id3.PRIV, 'flac_sha1'), - 'project': (id3_priv, id3.PRIV, 'project'), - 'funded_by': (id3_priv, id3.PRIV, 'funded_by'), - } - - @staticmethod - def encode(in_path, out_path): - # 44.1kHz 64kbps mono MP3 - subprocess.check_call(['ffmpeg', - '-i', in_path.encode('utf-8'), - '-ar', '44100', - '-ab', '64k', - '-ac', '1', - '-y', - '-acodec', 'libmp3lame', - out_path.encode('utf-8') - ]) - - @classmethod - def set_tags(cls, audiobook, file_name): - mp3_tags = audiobook.mp3_tags['tags'] - if not mp3_tags.get('flac_sha1'): - mp3_tags['flac_sha1'] = audiobook.get_source_sha1() - audio = id3.ID3(file_name) - for k, v in mp3_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(['ffmpeg', - '-i', in_path.encode('utf-8'), - '-ar', '44100', - '-ab', '64k', - '-ac', '1', - '-y', - '-acodec', 'libvorbis', - out_path.encode('utf-8') - ]) diff --git a/apps/archive/templates/404.html b/apps/archive/templates/404.html deleted file mode 100644 index 467dacd..0000000 --- a/apps/archive/templates/404.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "archive/base.html" %} -{% block content %} -

Nie znaleziono strony.

- -

Nie znaleziono żądanej strony.

-{% endblock %} diff --git a/apps/archive/templates/500.html b/apps/archive/templates/500.html deleted file mode 100644 index b46280d..0000000 --- a/apps/archive/templates/500.html +++ /dev/null @@ -1 +0,0 @@ -Błąd serwera. diff --git a/apps/archive/templates/archive/base.html b/apps/archive/templates/archive/base.html deleted file mode 100644 index 8ec34e9..0000000 --- a/apps/archive/templates/archive/base.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block repo-zones-nav %} - {% trans "New" %} - {% trans "Unpublished" %} - {% trans "Publishing" %} - {% trans "Published" %} - {% trans "Archive" %} - {% if user.is_staff %} - {% trans "Projects" %} - {% endif %} - {% if user.is_authenticated %} - {% trans "Logout" %} - {% else %} - {% trans "Login" %} - {% endif %} - {% if user.is_staff %} - {% trans "Administration" %} - {% endif %} - {% if user.is_authenticated %} - {{ user }} - {% endif %} -
-{% endblock %} diff --git a/apps/archive/templates/archive/file_managed.html b/apps/archive/templates/archive/file_managed.html deleted file mode 100755 index 8917243..0000000 --- a/apps/archive/templates/archive/file_managed.html +++ /dev/null @@ -1,135 +0,0 @@ -{% extends "archive/base.html" %} -{% load i18n %} -{% load tags %} - -{% block content %} - -

Plik źródłowy: {{ path }} -(sha1: {{ audiobook.source_sha1 }}). -

- -

{% trans "Publishing" %}

- -{% if audiobook.mp3_status or audiobook.ogg_status %} - -

{% trans "Publishing pending" %}

- -
- {% csrf_token %} - -
- - -{% if audiobook.mp3_status %} -
-

MP3

- - {% tags_table audiobook.mp3_tags.tags %} - -

Status: {{ audiobook.get_mp3_status_display }}

-{% endif %} - -{% if audiobook.ogg_status %} -
-

Ogg Vorbis

- - {% tags_table audiobook.ogg_tags.tags %} - -

Status: {{ audiobook.get_ogg_status_display }}

-{% endif %} - - - - -{% else %} - - {% tags_table audiobook.new_publish_tags 0 %} - -
- -
- {% csrf_token %} - -
- - {% if not audiobook.mp3_published or not audiobook.ogg_published %} -
- {% csrf_token %} - -
- {% endif %} - -
-{% endif %} - -
-{% if audiobook.mp3_file %} -

{% trans "MP3 file" %}

-

{% trans "Download MP3 file." %}

- {% if audiobook.mp3_published %} -

{% trans "Published:" %} {{ audiobook.mp3_published }}

- {% if audiobook.mp3_published_tags.tags %} - {% tags_table audiobook.mp3_published_tags.tags %} - {% endif %} - {% else %} -

{% trans "Not published yet." %}

- {% endif %} -{% else %} -

{% trans "MP3 file hasn't been generated yet." %}

-{% endif %} - -
-{% if audiobook.ogg_file %} -

{% trans "Ogg Vorbis file" %}

-

{% trans "Download Ogg Vorbis file." %}

- {% if audiobook.ogg_published %} -

{% trans "Published:" %} {{ audiobook.ogg_published }}

- {% if audiobook.ogg_published_tags.tags %} - {% tags_table audiobook.ogg_published_tags.tags %} - {% endif %} - {% else %} -

{% trans "Not published yet." %}

- {% endif %} -{% else %} -

{% trans "Ogg Vorbis file hasn't been generated yet." %}

-{% endif %} - - - - -
- - - - -

{% trans "Update tags" %}

- -Last modified: {{ audiobook.modified }} - -{% multiple_tags_table tags %} - - - -
- {% csrf_token %} - - {{ form.as_table }} - -
-
- - - -
- - - -
- {% csrf_token %} - -
- - - -{% endblock %} diff --git a/apps/archive/templates/archive/file_new.html b/apps/archive/templates/archive/file_new.html deleted file mode 100644 index acc161d..0000000 --- a/apps/archive/templates/archive/file_new.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "archive/base.html" %} -{% load i18n tags %} - -{% block content %} - -
- {% csrf_token %} - -
- - -{% multiple_tags_table tags %} - - -
- {% csrf_token %} - - {{ form.as_table }} - -
-
- -{% endblock %} diff --git a/apps/archive/templates/archive/file_unmanaged.html b/apps/archive/templates/archive/file_unmanaged.html deleted file mode 100755 index 89f79ba..0000000 --- a/apps/archive/templates/archive/file_unmanaged.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "archive/base.html" %} -{% load i18n %} - -{% block messages %} - {% if err_exists %} -

{% trans "File with same name already exists!" %}

- {% endif %} -{% endblock %} - - -{% block content %} - - -
    - {% for t, v in tags.items %} -
  • {{t}} -
      - {% for x in v %} -
    • {{x}}
    • - {% endfor %} -
  • - {% endfor %} -
- - -
- {% csrf_token %} - -
- -{% endblock %} diff --git a/apps/archive/templates/archive/list.html b/apps/archive/templates/archive/list.html deleted file mode 100644 index bcd85de..0000000 --- a/apps/archive/templates/archive/list.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "archive/base.html" %} -{% load i18n %} - - -{% block content %} - -
-

{% block file-list-title %}{% endblock %}

-
{% block file-list-info %}{% endblock %}
- {% block file-list-wrapper %} -
    {% block file-list %}{% endblock %}
- {% endblock file-list-wrapper %} -
- -{% endblock %} diff --git a/apps/archive/templates/archive/list_new.html b/apps/archive/templates/archive/list_new.html deleted file mode 100644 index fabf446..0000000 --- a/apps/archive/templates/archive/list_new.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "archive/list.html" %} -{% load i18n %} - - -{% block file-list-title %} - {% trans "New audiobooks" %} -{% endblock %} - - -{% block file-list-info %} - {% trans "Put source audiobooks in:" %} - {{ path }} -{% endblock %} - - -{% block file-list %} - {% for file in objects %} -
  • - {{ file }} -
  • - {% endfor %} -{% endblock %} diff --git a/apps/archive/templates/archive/list_published.html b/apps/archive/templates/archive/list_published.html deleted file mode 100755 index 5e61ba1..0000000 --- a/apps/archive/templates/archive/list_published.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "archive/list.html" %} -{% load i18n %} - - -{% block file-list-title %} - {% trans "Published audiobooks" %} -{% endblock %} - - -{% block file-list-info %} -{% endblock %} - - -{% block file-list %} - {% for file in objects %} -
  • - {{ file }} -
  • - {% endfor %} -{% endblock %} diff --git a/apps/archive/templates/archive/list_publishing.html b/apps/archive/templates/archive/list_publishing.html deleted file mode 100755 index 6bcf9e0..0000000 --- a/apps/archive/templates/archive/list_publishing.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "archive/list.html" %} -{% load i18n %} - - -{% block file-list-title %} - {% trans "Audiobooks being published" %} -{% endblock %} - - - -{% block file-list-wrapper %} - {% for k, objects in status_objects %} -

    {{ k.1 }}

    -
      - {% for file in objects %} -
    • - {{ file }} - ({% if file.mp3_status = k.0 %}MP3{% if file.ogg_status = k.0 %}, {% endif %}{% endif %}{% if file.ogg_status = k.0 %}Ogg{% endif %}) -
    • - {% endfor %} -
    - {% endfor %} -{% endblock %} diff --git a/apps/archive/templates/archive/list_unmanaged.html b/apps/archive/templates/archive/list_unmanaged.html deleted file mode 100755 index fd48c33..0000000 --- a/apps/archive/templates/archive/list_unmanaged.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "archive/list.html" %} -{% load i18n %} - -{% block file-list-title %} - {% trans "Unmanaged archive" %} -{% endblock %} - - -{% block file-list-info %} -{% endblock %} - - -{% block file-list %} - {% for file in objects %} -
  • - {{ file }} -
  • - {% endfor %} -{% endblock %} diff --git a/apps/archive/templates/archive/list_unpublished.html b/apps/archive/templates/archive/list_unpublished.html deleted file mode 100755 index b901ef0..0000000 --- a/apps/archive/templates/archive/list_unpublished.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "archive/list.html" %} -{% load i18n %} - - -{% block file-list-title %} - {% trans "Unpublished audiobooks" %} -{% endblock %} - - -{% block file-list-info %} -{% endblock %} - - -{% block file-list %} - {% for file in objects %} -
  • - {{ file }} - - {% if file.mp3_published %} - MP3 - {% else %}{% if file.mp3_status %} - MP3 - {% endif %}{% endif %} - - {% if file.ogg_published %} - Ogg - {% else %}{% if file.ogg_status %} - Ogg - {% endif %}{% endif %} -
  • - {% endfor %} -{% endblock %} diff --git a/apps/archive/templates/archive/tags/multiple_tags_table.html b/apps/archive/templates/archive/tags/multiple_tags_table.html deleted file mode 100755 index 4609278..0000000 --- a/apps/archive/templates/archive/tags/multiple_tags_table.html +++ /dev/null @@ -1,9 +0,0 @@ -{% if table %}{% endif %} - {% for t, v in tags.items %} - - {% endfor %} -{% if table %}
    {{ t }} - {% for x in v %} -
    {{ x|linebreaksbr }}
    - {% endfor %} -
    {% endif %} diff --git a/apps/archive/templates/archive/tags/tags_table.html b/apps/archive/templates/archive/tags/tags_table.html deleted file mode 100755 index 70b9d48..0000000 --- a/apps/archive/templates/archive/tags/tags_table.html +++ /dev/null @@ -1,7 +0,0 @@ -{% if table %}{% endif %} - {% for t, v in tags.items %} - - {% endfor %} -{% if table %}
    {{ t }} - {{ v|linebreaksbr }} -
    {% endif %} diff --git a/apps/archive/templates/base.html b/apps/archive/templates/base.html deleted file mode 100755 index 5e0053f..0000000 --- a/apps/archive/templates/base.html +++ /dev/null @@ -1,24 +0,0 @@ -{% load i18n %} - - - - - - {% trans "Audiobook repository" %} - - - -
    - {% block repo-zones-nav %} {% endblock %} -
    - -
    -{% block messages %}{% endblock %} -
    - -
    -{% block content %}{% endblock %} -
    - - - diff --git a/apps/archive/templates/registration/login.html b/apps/archive/templates/registration/login.html deleted file mode 100755 index 115cdf1..0000000 --- a/apps/archive/templates/registration/login.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block content %} - -
    -{% csrf_token %} -{{ form.as_p }} -

    -
    - -{% endblock %} diff --git a/apps/archive/templatetags/__init__.py b/apps/archive/templatetags/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/apps/archive/templatetags/tags.py b/apps/archive/templatetags/tags.py deleted file mode 100755 index 8e208a6..0000000 --- a/apps/archive/templatetags/tags.py +++ /dev/null @@ -1,21 +0,0 @@ -from django import template - -register = template.Library() - -@register.inclusion_tag('archive/tags/multiple_tags_table.html') -def multiple_tags_table(tags, table=True): - new_tags = {} - if tags: - for k, v in tags.items(): - if isinstance(v, list): - new_tags[k] = v - else: - new_tags[k] = [v] - return {"tags": new_tags, "table": table} - - -@register.inclusion_tag('archive/tags/tags_table.html') -def tags_table(tags, table=True): - if tags is None: - tags = {} - return locals() diff --git a/apps/archive/tests.py b/apps/archive/tests.py deleted file mode 100644 index 501deb7..0000000 --- a/apps/archive/tests.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -This file demonstrates writing tests using the unittest module. These will pass -when you run "manage.py test". - -Replace this with more appropriate tests for your application. -""" - -from django.test import TestCase - - -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.assertEqual(1 + 1, 2) diff --git a/apps/archive/urls.py b/apps/archive/urls.py deleted file mode 100644 index efaeeea..0000000 --- a/apps/archive/urls.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.conf.urls import patterns, include, url -from django.views.generic import RedirectView - -urlpatterns = patterns('', - url(r'^$', RedirectView.as_view(url='new/')), - - url(r'^new/$', 'archive.views.list_new', name="list_new"), - url(r'^new/(.+)/$', 'archive.views.file_new', name="file_new"), - url(r'^move_to_archive/(.+)/$', 'archive.views.move_to_archive', name="move_to_archive"), - - url(r'^unpublished/$', 'archive.views.list_unpublished', name="list_unpublished"), - url(r'^publishing/$', 'archive.views.list_publishing', name="list_publishing"), - url(r'^published/$', 'archive.views.list_published', name="list_published"), - url(r'^file/(\d+)/$', 'archive.views.file_managed', name="file"), - url(r'^publish/(\d+)/$', 'archive.views.publish', name="publish"), - url(r'^convert/(\d+)/$', 'archive.views.publish', {'publish': False}, name="convert"), - url(r'^download/(\d+)/$', 'archive.views.download', name="download"), - url(r'^download/(\d+)\.(mp3|ogg)$', 'archive.views.download', name="download"), - url(r'^cancel/(\d+)/$', 'archive.views.cancel_publishing', name="cancel_publishing"), - url(r'^remove_to_archive/(\d+)/$', 'archive.views.remove_to_archive', name="remove_to_archive"), - - url(r'^unmanaged/$', 'archive.views.list_unmanaged', name="list_unmanaged"), - url(r'^unmanaged/(.+)/$', 'archive.views.file_unmanaged', name="file_unmanaged"), - url(r'^move_to_new/(.+)/$', 'archive.views.move_to_new', name="move_to_new"), -) diff --git a/apps/archive/utils.py b/apps/archive/utils.py deleted file mode 100644 index 3e89a8b..0000000 --- a/apps/archive/utils.py +++ /dev/null @@ -1,44 +0,0 @@ -from hashlib import sha1 -import os -import os.path -from django.core.files.storage import FileSystemStorage -from django.core.files.uploadedfile import UploadedFile - - -class ExistingFile(UploadedFile): - - def __init__(self, path, *args, **kwargs): - self.path = path - return super(ExistingFile, self).__init__(*args, **kwargs) - - def temporary_file_path(self): - return self.path - - def close(self): - pass - - -class OverwriteStorage(FileSystemStorage): - - def _save(self, name, content): - if self.exists(name): - self.delete(name) - return super(OverwriteStorage, self)._save(name, content) - - def get_available_name(self, name): - return name - - -def sha1_file(f): - sha = sha1() - for piece in iter(lambda: f.read(1024*1024), ''): - sha.update(piece) - return sha.hexdigest() - - -def all_files(root_path): - root_len = len(root_path) - for path, dirs, files in os.walk(root_path): - for fname in files: - yield os.path.join(path, fname)[root_len:].lstrip('/') - diff --git a/apps/archive/views.py b/apps/archive/views.py deleted file mode 100644 index b5850a0..0000000 --- a/apps/archive/views.py +++ /dev/null @@ -1,279 +0,0 @@ -# Create your views here. - -from datetime import datetime -import os -import os.path -from urllib import quote - -from archive import settings -from django.contrib.auth import logout -from django.contrib.auth.decorators import permission_required -from django.core.urlresolvers import reverse -from django.db.models import Q, Max -from django.http import Http404, HttpResponse -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 -from archive.utils import all_files - - -def list_new(request): - division = 'new' - - path = settings.NEW_PATH - objects = sorted(all_files(path)) - return render(request, "archive/list_new.html", locals()) - - -@permission_required('archive.change_audiobook') -def file_new(request, filename): - division = 'new' - - filepath = filename.encode('utf-8') - root_filepath = os.path.join(settings.NEW_PATH, filename.encode('utf-8')) - if request.POST: - form = AudiobookForm(request.POST) - if form.is_valid(): - try: - form.save(path=filepath) - except IOError: - raise Http404 - return redirect(list_new) - - try: - tags = mutagen.File(root_filepath) - except IOError: - raise Http404 - d = {} - if tags: - for tag in tags: - value = tags[tag] - if isinstance(value, list): - d[tag] = value[0] - else: - d[tag] = value - if tag == 'project': - try: - d[tag] = models.Project.objects.get(name=d[tag]).pk - except models.Project.DoesNotExist: - d[tag] = None - - if not request.POST: - form = AudiobookForm(d) - return render(request, "archive/file_new.html", locals()) - - -@require_POST -@permission_required('archive.change_audiobook') -def move_to_archive(request, filename): - """ move a new file to the unmanaged files dir """ - - filename_str = filename.encode('utf-8') - old_path = os.path.join(settings.NEW_PATH, filename_str) - new_path = os.path.join(settings.UNMANAGED_PATH, filename_str) - new_dir = os.path.split(new_path)[0] - if not os.path.isdir(new_dir): - os.makedirs(new_dir) - - if not os.path.isfile(old_path): - raise Http404 - - try: - os.link(old_path, new_path) - os.unlink(old_path) - except OSError: - # destination file exists, don't overwrite it - # TODO: this should probably be more informative - return redirect(file_new, filename) - - return redirect(list_new) - - -@require_POST -@permission_required('archive.change_audiobook') -def remove_to_archive(request, aid): - """ move a managed file to the unmanaged files dir """ - - audiobook = get_object_or_404(models.Audiobook, id=aid) - old_path = audiobook.source_file.path - new_path = os.path.join(settings.UNMANAGED_PATH, - str(audiobook.source_file)[len(settings.FILES_SAVE_PATH):].lstrip('/')) - new_dir = os.path.split(new_path)[0] - if not os.path.isdir(new_dir): - os.makedirs(new_dir) - - if not os.path.isfile(old_path): - raise Http404 - - success = False - try_new_path = new_path - try_number = 0 - while not success: - try: - os.link(old_path, try_new_path) - except OSError: - # destination file exists, don't overwrite it - try_number += 1 - parts = new_path.rsplit('.', 1) - parts[0] += '_%d' % try_number - try_new_path = ".".join(parts) - else: - os.unlink(old_path) - audiobook.delete() - success = True - - return redirect(list_unmanaged) - -@require_POST -@permission_required('archive.change_audiobook') -def move_to_new(request, filename): - """ move a unmanaged file to new files dir """ - - filename_str = filename.encode('utf-8') - old_path = os.path.join(settings.UNMANAGED_PATH, filename_str) - new_path = os.path.join(settings.NEW_PATH, filename_str) - new_dir = os.path.split(new_path)[0] - if not os.path.isdir(new_dir): - os.makedirs(new_dir) - - if not os.path.isfile(old_path): - raise Http404 - - try: - os.link(old_path, new_path) - os.unlink(old_path) - except OSError: - # destination file exists, don't overwrite it - # TODO: this should probably be more informative - return redirect(reverse(file_unmanaged, args=[filename]) + "?exists=1") - - return redirect(list_unmanaged) - - -@require_POST -@permission_required('archive.change_audiobook') -def publish(request, aid, publish=True): - """ mark file for publishing """ - 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() - # isn't there a race here? - audiobook.mp3_task = tasks.Mp3Task.delay(aid, publish).task_id - audiobook.ogg_task = tasks.OggTask.delay(aid, publish).task_id - audiobook.save() - - return redirect(file_managed, aid) - - -@require_POST -@permission_required('archive.change_audiobook') -def cancel_publishing(request, aid): - """ cancel scheduled publishing """ - 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) - - -def download(request, aid, which="source"): - if which not in ("source", "mp3", "ogg"): - raise Http404 - audiobook = get_object_or_404(models.Audiobook, id=aid) - file_ = getattr(audiobook, "%s_file" % which) - if not file_: - raise Http404 - ext = file_.path.rsplit('.', 1)[-1] - response = HttpResponse(mimetype='application/force-download') - - response['Content-Disposition'] = "attachment; filename*=UTF-8''%s.%s" % ( - quote(audiobook.title.encode('utf-8'), safe=''), ext) - response['X-Sendfile'] = file_.path.encode('utf-8') - return response - - -def list_unpublished(request): - division = 'unpublished' - - objects = models.Audiobook.objects.filter(Q(mp3_published=None) | Q(ogg_published=None)) - return render(request, "archive/list_unpublished.html", locals()) - - -def list_publishing(request): - division = 'publishing' - - objects = models.Audiobook.objects.exclude(mp3_status=None, ogg_status=None) - objects_by_status = {} - for o in objects: - if o.mp3_status: - k = o.mp3_status, o.get_mp3_status_display() - objects_by_status.setdefault(k, []).append(o) - if o.ogg_status and o.ogg_status != o.mp3_status: - k = o.ogg_status, o.get_ogg_status_display() - objects_by_status.setdefault(k, []).append(o) - status_objects = sorted(objects_by_status.items(), reverse=True) - - return render(request, "archive/list_publishing.html", locals()) - - -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()) - - -@permission_required('archive.change_audiobook') -def file_managed(request, id): - audiobook = get_object_or_404(models.Audiobook, id=id) - - 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' - path = audiobook.source_file.path[len(settings.FILES_PATH):].lstrip('/') - - # for tags update - tags = mutagen.File(audiobook.source_file.path.encode('utf-8')) - if not tags: - tags = {} - form = AudiobookForm(instance=audiobook) - - return render(request, "archive/file_managed.html", locals()) - - -def list_unmanaged(request): - division = 'unmanaged' - - objects = sorted(all_files(settings.UNMANAGED_PATH)) - return render(request, "archive/list_unmanaged.html", locals()) - - -def file_unmanaged(request, filename): - division = 'unmanaged' - - tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename.encode('utf-8'))) - if not tags: - tags = {} - - err_exists = request.GET.get('exists') - return render(request, "archive/file_unmanaged.html", locals()) diff --git a/audiobooks/__init__.py b/audiobooks/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/audiobooks/manage.py b/audiobooks/manage.py deleted file mode 100755 index 3e4eedc..0000000 --- a/audiobooks/manage.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -from django.core.management import execute_manager -import imp -try: - imp.find_module('settings') # Assumed to be in the same directory. -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) - sys.exit(1) - -import settings - -if __name__ == "__main__": - execute_manager(settings) diff --git a/audiobooks/settings.py b/audiobooks/settings.py deleted file mode 100644 index 3f92da0..0000000 --- a/audiobooks/settings.py +++ /dev/null @@ -1,198 +0,0 @@ -# Django settings for audiobooks project. -# -*- coding: utf-8 -*- - -import os -import sys -PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) -sys.path.insert(0, os.path.join(PROJECT_ROOT, "../apps")) - - - -DEBUG = False -TEMPLATE_DEBUG = DEBUG - -ADMINS = ( - # ('Your Name', 'your_email@example.com'), -) - -MANAGERS = ADMINS - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': os.path.join(PROJECT_ROOT, 'dev.sqlite'), # Or path to database file if using sqlite3. - 'USER': '', # Not used with sqlite3. - 'PASSWORD': '', # Not used with sqlite3. - 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. - 'PORT': '', # Set to empty string for default. Not used with sqlite3. - } -} - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# On Unix systems, a value of None will cause Django to use the same -# timezone as the operating system. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = 'Europe/Warsaw' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'pl' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# If you set this to False, Django will not format dates, numbers and -# calendars according to the current locale -USE_L10N = True - -# Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/home/media/media.lawrence.com/media/" -MEDIA_ROOT = os.path.join(PROJECT_ROOT, '../media') - -# 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/' - -# Absolute path to the directory static files should be collected to. -# Don't put anything in this directory yourself; store your static files -# in apps' "static/" subdirectories and in STATICFILES_DIRS. -# Example: "/home/media/media.lawrence.com/static/" -STATIC_ROOT = '' - -# URL prefix for static files. -# Example: "http://media.lawrence.com/static/" -STATIC_URL = '/static/' - -# URL prefix for admin static files -- CSS, JavaScript and images. -# Make sure to use a trailing slash. -# Examples: "http://foo.com/static/admin/", "/static/admin/". -ADMIN_MEDIA_PREFIX = '/static/admin/' - -# Additional locations of static files -STATICFILES_DIRS = ( - # Put strings here, like "/home/html/static" or "C:/www/django/static". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. -) - -# List of finder classes that know how to find static files in -# various locations. -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -# 'django.contrib.staticfiles.finders.DefaultStorageFinder', -) - -# Make this unique, and don't share it with anybody. -SECRET_KEY = '8ehk^-+pr(o)k6lh_gl9+ks6gkd9u#fka7fv%ikpk(c%llqa6%' - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -# 'django.template.loaders.eggs.Loader', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django_cas.middleware.CASMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', -) - -AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', - 'django_cas.backends.CASBackend', -) - -ROOT_URLCONF = 'audiobooks.urls' - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - # Uncomment the next line to enable the admin: - 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', - - 'djcelery', - 'djkombu', - - 'south', - 'archive', -) - -# A sample logging configuration. The only tangible logging -# performed by this configuration is to send an email to -# the site admins on every HTTP 500 error. -# See http://docs.djangoproject.com/en/dev/topics/logging for -# more details on how to customize your logging configuration. -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' - } - }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' - } - }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, - }, - } -} - -#http://logowanie.nowoczesnapolska.org.pl/cas/' -CAS_SERVER_URL = "http://logowanie.nowoczesnapolska.org.pl/cas/" -CAS_VERSION = "1" - - -EMAIL_SUBJECT_PREFIX = '[Audio] ' -SERVER_EMAIL = 'no-reply@audio.wolnelektury.pl' -EMAIL_HOST = 'localhost' -EMAIL_PORT = 25 - -import djcelery -djcelery.setup_loader() - -CELERY_SEND_TASK_ERROR_EMAILS = True -BROKER_BACKEND = "djkombu.transport.DatabaseTransport" -BROKER_HOST = "localhost" -BROKER_PORT = 5672 -BROKER_USER = "guest" -BROKER_PASSWORD = "guest" -BROKER_VHOST = "/" - - -try: - from localsettings import * -except: - pass diff --git a/audiobooks/urls.py b/audiobooks/urls.py deleted file mode 100644 index f3113c8..0000000 --- a/audiobooks/urls.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.conf import settings -from django.conf.urls import patterns, include, url -from django.views.generic import RedirectView - -# Uncomment the next two lines to enable the admin: -from django.contrib import admin -admin.autodiscover() - -urlpatterns = patterns('', - url(r'^$', RedirectView.as_view(url='archive/')), - url(r'^archive/', include('archive.urls')), - - url(r'^admin/', include(admin.site.urls)), - url(r'^accounts/login/$', 'django_cas.views.login', name='login'), - url(r'^accounts/logout/$', 'django_cas.views.logout', name='logout'), -) - -if settings.DEBUG: - urlpatterns += patterns('', - (r'^media/(?P.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT, 'show_indexes':True}), -) diff --git a/audiobooks/wsgi.py b/audiobooks/wsgi.py deleted file mode 100644 index 5e0ddaa..0000000 --- a/audiobooks/wsgi.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -WSGI config for edumed project. - -This module contains the WSGI application used by Django's development server -and any production WSGI deployments. It should expose a module-level variable -named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover -this application via the ``WSGI_APPLICATION`` setting. - -Usually you will have the standard Django WSGI application here, but it also -might make sense to replace the whole Django WSGI application with a custom one -that later delegates to the Django one. For example, you could introduce WSGI -middleware here, or combine a Django application with an application of another -framework. - -""" -import os - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "audiobooks.settings") - -# This application object is used by any WSGI server configured to use this -# file. This includes Django's development server, if the WSGI_APPLICATION -# setting points here. -from django.core.wsgi import get_wsgi_application -application = get_wsgi_application() - -# Apply WSGI middleware here. -# from helloworld.wsgi import HelloWorldApplication -# application = HelloWorldApplication(application) diff --git a/src/archive/__init__.py b/src/archive/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/archive/admin.py b/src/archive/admin.py new file mode 100644 index 0000000..d0804be --- /dev/null +++ b/src/archive/admin.py @@ -0,0 +1,5 @@ +from archive.models import Project, Audiobook +from django.contrib import admin + +admin.site.register(Project) +admin.site.register(Audiobook) diff --git a/src/archive/constants.py b/src/archive/constants.py new file mode 100644 index 0000000..17f4e78 --- /dev/null +++ b/src/archive/constants.py @@ -0,0 +1,14 @@ +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')), + ] diff --git a/src/archive/cover.png b/src/archive/cover.png new file mode 100644 index 0000000..1435a03 Binary files /dev/null and b/src/archive/cover.png differ diff --git a/src/archive/forms.py b/src/archive/forms.py new file mode 100644 index 0000000..81568c6 --- /dev/null +++ b/src/archive/forms.py @@ -0,0 +1,42 @@ +from datetime import datetime +import os +import os.path + +from django import forms +from django.utils.translation import ugettext_lazy as _ +import mutagen +from django.utils.encoding import force_bytes + +from archive.models import Audiobook +from archive.settings import FILES_PATH, NEW_PATH +from archive.utils import ExistingFile, sha1_file + +class AudiobookForm(forms.ModelForm): + class Meta: + model = Audiobook + + def save(self, commit=True, path=None): + """ Performs normal save, with given file as an source audiobook. + + `path' is relative to NEW_PATH. + """ + m = super(AudiobookForm, self).save(commit=False) + m.modified = datetime.now() + + if path: + # adding a new audiobook + if not os.path.isdir(FILES_PATH): + os.makedirs(FILES_PATH) + # save the file in model + + abs_path = os.path.join(NEW_PATH, path) + m.source_file.save( + path, + ExistingFile(abs_path)) + +# f = open(force_bytes(m.source_file.path)) +# m.source_sha1 = sha1_file(f) +# f.close() + + if commit: + m.save() diff --git a/src/archive/locale/pl/LC_MESSAGES/django.mo b/src/archive/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 0000000..e1135bf Binary files /dev/null and b/src/archive/locale/pl/LC_MESSAGES/django.mo differ diff --git a/src/archive/locale/pl/LC_MESSAGES/django.po b/src/archive/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 0000000..317ab76 --- /dev/null +++ b/src/archive/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,250 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2011 Fundacja Nowoczesna Polska +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-02 09:36+0100\n" +"PO-Revision-Date: 2012-07-11 15:52+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2)\n" + +#: constants.py:10 +msgid "Waiting" +msgstr "W kolejce" + +#: constants.py:11 +msgid "Encoding" +msgstr "Konwersja" + +#: constants.py:12 +msgid "Tagging" +msgstr "Opisywanie" + +#: constants.py:13 +msgid "Sending" +msgstr "Wysyłanie" + +#: models.py:23 models.py:49 +msgid "project" +msgstr "projekt" + +#: models.py:24 +msgid "projects" +msgstr "projekty" + +#: models.py:37 +msgid "source file" +msgstr "plik źródłowy" + +#: models.py:40 +msgid "title" +msgstr "tytuł" + +#: models.py:41 +msgid "part name" +msgstr "nazwa części" + +#: models.py:41 +msgid "eg. chapter in a novel" +msgstr "np. rozdział w powieści" + +#: models.py:43 +msgid "index" +msgstr "numer" + +#: models.py:44 +msgid "parts count" +msgstr "liczba części" + +#: models.py:45 +msgid "artist" +msgstr "lektor" + +#: models.py:46 +msgid "conductor" +msgstr "reżyser" + +#: models.py:47 +msgid "encoded by" +msgstr "przyg. techn." + +#: models.py:48 +msgid "date" +msgstr "data" + +#: models.py:50 +msgid "book url" +msgstr "URL książki" + +#: models.py:51 +msgid "translator" +msgstr "tłumacz" + +#: models.py:71 +msgid "audiobook" +msgstr "audiobook" + +#: models.py:72 +msgid "audiobooks" +msgstr "audiobooki" + +#: templates/base.html:7 +msgid "Audiobook repository" +msgstr "Repozytorium audiobooków" + +#: templates/archive/base.html:5 +msgid "New" +msgstr "Nowe" + +#: templates/archive/base.html:6 +msgid "Unpublished" +msgstr "Nie opublikowane" + +#: templates/archive/base.html:7 templates/archive/file_managed.html:11 +msgid "Publishing" +msgstr "Publikacja" + +#: templates/archive/base.html:8 +msgid "Published" +msgstr "Opublikowane" + +#: templates/archive/base.html:9 +msgid "Archive" +msgstr "Archiwum" + +#: templates/archive/base.html:11 +msgid "Projects" +msgstr "Projekty" + +#: templates/archive/base.html:14 +msgid "Logout" +msgstr "Wyloguj" + +#: templates/archive/base.html:16 templates/registration/login.html:9 +msgid "Login" +msgstr "Zaloguj" + +#: templates/archive/base.html:19 +msgid "Administration" +msgstr "Administracja" + +#: templates/archive/file_managed.html:15 +msgid "Publishing pending" +msgstr "Czeka na publikację" + +#: templates/archive/file_managed.html:19 +msgid "Cancel publishing" +msgstr "Anuluj publikację" + +#: templates/archive/file_managed.html:51 +msgid "Publish" +msgstr "Opublikuj" + +#: templates/archive/file_managed.html:57 +msgid "Convert without publishing" +msgstr "Konwertuj bez publikacji" + +#: templates/archive/file_managed.html:67 +msgid "MP3 file" +msgstr "Plik MP3" + +#: templates/archive/file_managed.html:68 +msgid "Download MP3 file." +msgstr "Pobierz plik MP3." + +#: templates/archive/file_managed.html:70 +#: templates/archive/file_managed.html:86 +msgid "Published:" +msgstr "Opublikowano:" + +#: templates/archive/file_managed.html:75 +#: templates/archive/file_managed.html:91 +msgid "Not published yet." +msgstr "Nie opublikowane." + +#: templates/archive/file_managed.html:78 +msgid "MP3 file hasn't been generated yet." +msgstr "Plik MP3 nie został jeszcze wygenerowany." + +#: templates/archive/file_managed.html:83 +msgid "Ogg Vorbis file" +msgstr "Plik Ogg Vorbis" + +#: templates/archive/file_managed.html:84 +msgid "Download Ogg Vorbis file." +msgstr "Pobierz plik Ogg Vorbis." + +#: templates/archive/file_managed.html:94 +msgid "Ogg Vorbis file hasn't been generated yet." +msgstr "Plik Ogg Vorbis nie został jeszcze wygenerowany." + +#: templates/archive/file_managed.html:105 +msgid "Update tags" +msgstr "Uaktualnij tagi" + +#: templates/archive/file_managed.html:117 templates/archive/file_new.html:19 +msgid "Commit" +msgstr "Zatwierdź" + +#: templates/archive/file_managed.html:128 +msgid "Are you sure you want to move this audiobook to archive?" +msgstr "Czy na pewno chcesz przenieść ten plik to archiwum?" + +#: templates/archive/file_managed.html:130 +msgid "Remove to archive" +msgstr "Usuń do archiwum" + +#: templates/archive/file_new.html:8 +msgid "Move to archive" +msgstr "Przenieś do archiwum" + +#: templates/archive/file_unmanaged.html:6 +msgid "File with same name already exists!" +msgstr "Plik o tej nazwie już istnieje!" + +#: templates/archive/file_unmanaged.html:28 +msgid "Move to new files" +msgstr "Przenieś do nowych plików" + +#: templates/archive/list_new.html:6 +msgid "New audiobooks" +msgstr "Nowe audiobooki" + +#: templates/archive/list_new.html:11 +msgid "Put source audiobooks in:" +msgstr "Umieść nowe audiobooki w:" + +#: templates/archive/list_published.html:6 +msgid "Published audiobooks" +msgstr "Opublikowane audiobooki" + +#: templates/archive/list_publishing.html:6 +msgid "Audiobooks being published" +msgstr "Aktualnie publikowane audiobooki" + +#: templates/archive/list_unmanaged.html:5 +msgid "Unmanaged archive" +msgstr "Audiobooki archiwalne" + +#: templates/archive/list_unpublished.html:6 +msgid "Unpublished audiobooks" +msgstr "Nie opublikowane audiobooki" + +#~ msgid "arranger" +#~ msgstr "aranżer" + +#~ msgid "Audiobook marked for publishing with tags:" +#~ msgstr "Audiobook zaznaczony do publikacji z tagami:" + +#~ msgid "Publishing already in progress." +#~ msgstr "Publikowanie rozpoczęte." diff --git a/src/archive/migrations/0001_initial.py b/src/archive/migrations/0001_initial.py new file mode 100644 index 0000000..ac5a2c9 --- /dev/null +++ b/src/archive/migrations/0001_initial.py @@ -0,0 +1,80 @@ +# 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): + + # Adding model 'Project' + db.create_table('archive_project', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128, db_index=True)), + ('sponsors', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + )) + db.send_create_signal('archive', ['Project']) + + # Adding model 'Audiobook' + db.create_table('archive_audiobook', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('source_file', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('artist', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('conductor', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('encoded_by', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('date', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['archive.Project'])), + ('url', self.gf('django.db.models.fields.URLField')(max_length=255)), + ('modified', self.gf('django.db.models.fields.DateTimeField')(null=True)), + ('published_tags', self.gf('jsonfield.fields.JSONField')(null=True)), + ('mp3_file', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True)), + ('ogg_file', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True)), + ('publishing_tags', self.gf('jsonfield.fields.JSONField')(null=True)), + ('publish_wait', self.gf('django.db.models.fields.DateTimeField')(null=True)), + ('publishing', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('published', self.gf('django.db.models.fields.DateTimeField')(null=True)), + )) + db.send_create_signal('archive', ['Audiobook']) + + + def backwards(self, orm): + + # Deleting model 'Project' + db.delete_table('archive_project') + + # Deleting model 'Audiobook' + db.delete_table('archive_audiobook') + + + 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'}), + 'ogg_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archive.Project']"}), + 'publish_wait': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'published': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'published_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}), + 'publishing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'publishing_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}), + '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'] diff --git a/src/archive/migrations/0002_auto__del_field_audiobook_publishing_tags__del_field_audiobook_publish.py b/src/archive/migrations/0002_auto__del_field_audiobook_publishing_tags__del_field_audiobook_publish.py new file mode 100644 index 0000000..03cb130 --- /dev/null +++ b/src/archive/migrations/0002_auto__del_field_audiobook_publishing_tags__del_field_audiobook_publish.py @@ -0,0 +1,139 @@ +# 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'] diff --git a/src/archive/migrations/0003_auto__add_field_audiobook_source_sha1__add_field_audiobook_translator.py b/src/archive/migrations/0003_auto__add_field_audiobook_source_sha1__add_field_audiobook_translator.py new file mode 100644 index 0000000..9711b72 --- /dev/null +++ b/src/archive/migrations/0003_auto__add_field_audiobook_source_sha1__add_field_audiobook_translator.py @@ -0,0 +1,84 @@ +# 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'] diff --git a/src/archive/migrations/0004_auto__chg_field_audiobook_source_file.py b/src/archive/migrations/0004_auto__chg_field_audiobook_source_file.py new file mode 100644 index 0000000..95d630e --- /dev/null +++ b/src/archive/migrations/0004_auto__chg_field_audiobook_source_file.py @@ -0,0 +1,57 @@ +# 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): + + # Changing field 'Audiobook.source_file' + db.alter_column('archive_audiobook', 'source_file', self.gf('django.db.models.fields.files.FileField')(max_length=255)) + + + def backwards(self, orm): + + # Changing field 'Audiobook.source_file' + db.alter_column('archive_audiobook', 'source_file', self.gf('django.db.models.fields.files.FileField')(max_length=100)) + + + 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': '255'}), + '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'] diff --git a/src/archive/migrations/0005_auto__add_field_audiobook_part_name__add_field_audiobook_index__add_fi.py b/src/archive/migrations/0005_auto__add_field_audiobook_part_name__add_field_audiobook_index__add_fi.py new file mode 100644 index 0000000..c163971 --- /dev/null +++ b/src/archive/migrations/0005_auto__add_field_audiobook_part_name__add_field_audiobook_index__add_fi.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Audiobook.part_name' + db.add_column(u'archive_audiobook', 'part_name', + self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), + keep_default=False) + + # Adding field 'Audiobook.index' + db.add_column(u'archive_audiobook', 'index', + self.gf('django.db.models.fields.IntegerField')(default=0), + keep_default=False) + + # Adding field 'Audiobook.parts_count' + db.add_column(u'archive_audiobook', 'parts_count', + self.gf('django.db.models.fields.IntegerField')(default=1), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Audiobook.part_name' + db.delete_column(u'archive_audiobook', 'part_name') + + # Deleting field 'Audiobook.index' + db.delete_column(u'archive_audiobook', 'index') + + # Deleting field 'Audiobook.parts_count' + db.delete_column(u'archive_audiobook', 'parts_count') + + + models = { + u'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'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'index': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + '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'}), + 'part_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), + 'parts_count': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['archive.Project']"}), + 'source_file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}), + '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'}) + }, + u'archive.project': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Project'}, + u'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'] \ No newline at end of file diff --git a/src/archive/migrations/__init__.py b/src/archive/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/archive/models.py b/src/archive/models.py new file mode 100644 index 0000000..c75a874 --- /dev/null +++ b/src/archive/models.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +import os.path + +from django.db import models +from time import sleep +from jsonfield.fields import JSONField +from django.utils.encoding import force_bytes +from django.utils.translation import ugettext_lazy as _ +from archive.constants import status +from archive.settings import FILES_SAVE_PATH, ADVERT, LICENSE, ORGANIZATION, PROJECT +from archive.utils import OverwriteStorage, sha1_file + +# Create your models here. + + +class Project(models.Model): + """ an audiobook project, needed for specyfing sponsors """ + + name = models.CharField(max_length=128, unique=True, db_index=True, verbose_name="Nazwa") + sponsors = models.TextField(blank=True, null=True, verbose_name="Sponsorzy") + + class Meta: + verbose_name = _("project") + verbose_name_plural = _("projects") + ordering = ("name",) + + def __unicode__(self): + return self.name + + +def source_upload_to(intance, filename): + return os.path.join(FILES_SAVE_PATH, filename) # FIXME: what about really long file names? + + +class Audiobook(models.Model): + source_file = models.FileField(upload_to=source_upload_to, max_length=255, + verbose_name=_('source file'), editable=False) + source_sha1 = models.CharField(max_length=40, editable=False) + + title = models.CharField(max_length=255, verbose_name=_('title')) + part_name = models.CharField(max_length=255, verbose_name=_('part name'), help_text=_('eg. chapter in a novel'), + default='', blank=True) + index = models.IntegerField(verbose_name=_('index'), default=0) + parts_count = models.IntegerField(verbose_name=_('parts count'), default=1) + artist = models.CharField(max_length=255, verbose_name=_('artist')) + conductor = models.CharField(max_length=255, verbose_name=_('conductor')) + encoded_by = models.CharField(max_length=255, verbose_name=_('encoded by')) + 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) + + # 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', storage=OverwriteStorage(), 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', storage=OverwriteStorage(), editable=False) + ogg_published_tags = JSONField(null=True, editable=False) + ogg_published = models.DateTimeField(null=True, editable=False) + + + class Meta: + verbose_name = _("audiobook") + verbose_name_plural = _("audiobooks") + ordering = ("title",) + + def __unicode__(self): + return self.title + + def published(self): + return self.mp3_published and self.ogg_published + + def get_source_sha1(self): + source_sha1 = self.source_sha1 + if self.pk: + source_sha1 = type(self).objects.get(pk=self.pk).source_sha1 + while source_sha1 == 'wait': + sleep(10) + if not source_sha1: + self.source_sha1 = 'wait' + if self.pk: + type(self).objects.filter(pk=self.pk).update(source_sha1='wait') + try: + f = open(force_bytes(self.source_file.path)) + source_sha1 = sha1_file(f) + self.source_sha1 = source_sha1 + if self.pk: + type(self).objects.filter(pk=self.pk).update(source_sha1=source_sha1) + except: + self.source_sha1 = '' + if self.pk: + type(self).objects.filter(pk=self.pk).update(source_sha1='') + return None + return source_sha1 + + + 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) + + tags = { + '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.get_source_sha1(), + 'project': self.project.name, + 'funded_by': self.project.sponsors, + } + if self.source_sha1 and self.source_sha1 != 'wait': + tags['flac_sha1'] = self.source_sha1 + return tags + diff --git a/src/archive/settings.py b/src/archive/settings.py new file mode 100644 index 0000000..476f6c7 --- /dev/null +++ b/src/archive/settings.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +import os.path +from django.conf import settings + +# this is where the end user puts new files +try: + NEW_PATH = settings.ARCHIVE_NEW_PATH +except AttributeError: + NEW_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT, + "archive/new")) + +# here the application keeps its managed files +try: + FILES_PATH = settings.ARCHIVE_FILES_PATH +except AttributeError: + FILES_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT, + "archive/files")) + +if FILES_PATH.startswith(settings.MEDIA_ROOT): + FILES_SAVE_PATH = FILES_PATH[len(settings.MEDIA_ROOT):].lstrip('/') + + +# here the app keeps the unmanaged (archive) files +try: + UNMANAGED_PATH = settings.ARCHIVE_UNMANAGED_PATH +except AttributeError: + UNMANAGED_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT, + "archive/unmanaged")) + + +# here the app keeps the resulting (published) files +try: + FINAL_PATH = settings.ARCHIVE_FINAL_PATH +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_PASSWORD = settings.ARCHIVE_UPLOAD_PASSWORD +except AttributeError: + UPLOAD_PASSWORD = None + +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') + diff --git a/src/archive/static/style.css b/src/archive/static/style.css new file mode 100755 index 0000000..434b87b --- /dev/null +++ b/src/archive/static/style.css @@ -0,0 +1,67 @@ +body { + margin: 0; + font-family: verdana, sans-serif; + font-size: 12px; +} + +a { + color: #bf6000; + text-decoration: none; +} + +.clr { + clear: both; +} + + +#repo-zones-nav { + padding: 5px 5px 0 10px; + background: #ffdfbf; + border-bottom: 1px solid #ff8000; +} + +#repo-zones-nav a, #repo-zones-nav span { + display: block; + float: left; + padding: 5px 20px 5px 20px; + margin-bottom: -1px; + border-width: 1px; + border-style: solid; + border-color: rgba(0,0,0,0); +} + +#repo-zones-nav .active { + background: white; + border-color: #ff8000 #ff8000 white #ff8000; +} + +#content { + padding: 10px; +} + + +.errorlist { + margin: 0; +} + +.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; +} + + +.list-published-tag, .list-publishing-tag { + font-size: .8em; +} +.list-publishing-tag { + color: #aaa; +} diff --git a/src/archive/tasks.py b/src/archive/tasks.py new file mode 100644 index 0000000..a46b79c --- /dev/null +++ b/src/archive/tasks.py @@ -0,0 +1,214 @@ +from datetime import datetime +import errno +import mimetypes +import os +import os.path +import pipes +import stat +import subprocess +from tempfile import NamedTemporaryFile +from time import sleep + +#from celery.decorators import task +from celery.task import Task +from django.db.models import F +from fabric import api +from fabric.network import disconnect_all +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_PASSWORD, UPLOAD_PATH, UPLOAD_CMD, UPLOAD_SUDO) +from archive.utils import ExistingFile + +api.env.host_string = UPLOAD_HOST +api.env.user = UPLOAD_USER +api.env.password = UPLOAD_PASSWORD + +class AudioFormatTask(Task): + abstract = True + + class RemoteOperationError(BaseException): + pass + + @classmethod + def set_status(cls, aid, status): + Audiobook.objects.filter(pk=aid).update( + **{'%s_status' % cls.ext: status}) + + @staticmethod + def encode(in_path, out_path): + raise NotImplemented + + @classmethod + def set_tags(cls, audiobook, file_name): + tags = getattr(audiobook, "%s_tags" % cls.ext)['tags'] + if not tags.get('flac_sha1'): + tags['flac_sha1'] = audiobook.get_source_sha1() + audio = File(file_name) + for k, v in tags.items(): + audio[k] = v + audio.save() + + @classmethod + def save(cls, audiobook, file_name): + field = "%s_file" % cls.ext + getattr(audiobook, field).save( + "%d.%s" % (audiobook.pk, cls.ext), + ExistingFile(file_name), + save=False + ) + os.chmod(getattr(audiobook, field).path, stat.S_IREAD|stat.S_IWRITE|stat.S_IRGRP|stat.S_IROTH) + Audiobook.objects.filter(pk=audiobook.pk).update( + **{field: getattr(audiobook, field)}) + + @classmethod + def published(cls, aid): + kwargs = { + "%s_published_tags" % cls.ext: F("%s_tags" % cls.ext), + "%s_tags" % cls.ext: None, + "%s_published" % cls.ext: datetime.now(), + '%s_status' % cls.ext: None, + } + Audiobook.objects.filter(pk=aid).update(**kwargs) + + @classmethod + def put(cls, audiobook, path): + tags = getattr(audiobook, "%s_tags" % cls.ext) + prefix, slug = tags['url'].rstrip('/').rsplit('/', 1) + name = tags['name'] + command = UPLOAD_CMD + (u' %s %s %s %s %s %s > output.txt' % ( + pipes.quote(os.path.join(UPLOAD_PATH, os.path.basename(path))), + pipes.quote(slug), + pipes.quote(name), + pipes.quote(audiobook.part_name), + audiobook.index, + audiobook.parts_count, + )).encode('utf-8') + try: + api.put(path, UPLOAD_PATH) + if UPLOAD_SUDO: + api.sudo(command, user=UPLOAD_SUDO, shell=False) + else: + api.run(command) + disconnect_all() + except SystemExit, e: + raise cls.RemoteOperationError + + def run(self, aid, publish=True): + aid = int(aid) + audiobook = Audiobook.objects.get(id=aid) + self.set_status(aid, 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='%d-' % aid, suffix='.%s' % self.ext, dir=BUILD_PATH) + out_file.close() + self.encode(audiobook.source_file.path, out_file.name) + self.set_status(aid, status.TAGGING) + self.set_tags(audiobook, out_file.name) + self.set_status(aid, status.SENDING) + + if publish: + self.put(audiobook, out_file.name) + self.published(aid) + else: + self.set_status(aid, None) + + self.save(audiobook, out_file.name) + + def on_failure(self, exc, task_id, args, kwargs, einfo): + aid = (args[0], kwargs.get('aid'))[0] + self.set_status(aid, None) + + +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_priv(tag, text, what=u''): + return tag(owner='wolnelektury.pl?%s' % what, data=text.encode('utf-8')) + + 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_priv, id3.PRIV, 'flac_sha1'), + 'project': (id3_priv, id3.PRIV, 'project'), + 'funded_by': (id3_priv, id3.PRIV, 'funded_by'), + } + + @staticmethod + def encode(in_path, out_path): + # 44.1kHz 64kbps mono MP3 + subprocess.check_call(['ffmpeg', + '-i', in_path.encode('utf-8'), + '-ar', '44100', + '-ab', '64k', + '-ac', '1', + '-y', + '-acodec', 'libmp3lame', + out_path.encode('utf-8') + ]) + + @classmethod + def set_tags(cls, audiobook, file_name): + mp3_tags = audiobook.mp3_tags['tags'] + if not mp3_tags.get('flac_sha1'): + mp3_tags['flac_sha1'] = audiobook.get_source_sha1() + audio = id3.ID3(file_name) + for k, v in mp3_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(['ffmpeg', + '-i', in_path.encode('utf-8'), + '-ar', '44100', + '-ab', '64k', + '-ac', '1', + '-y', + '-acodec', 'libvorbis', + out_path.encode('utf-8') + ]) diff --git a/src/archive/templates/404.html b/src/archive/templates/404.html new file mode 100644 index 0000000..467dacd --- /dev/null +++ b/src/archive/templates/404.html @@ -0,0 +1,6 @@ +{% extends "archive/base.html" %} +{% block content %} +

    Nie znaleziono strony.

    + +

    Nie znaleziono żądanej strony.

    +{% endblock %} diff --git a/src/archive/templates/500.html b/src/archive/templates/500.html new file mode 100644 index 0000000..b46280d --- /dev/null +++ b/src/archive/templates/500.html @@ -0,0 +1 @@ +Błąd serwera. diff --git a/src/archive/templates/archive/base.html b/src/archive/templates/archive/base.html new file mode 100644 index 0000000..8ec34e9 --- /dev/null +++ b/src/archive/templates/archive/base.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block repo-zones-nav %} + {% trans "New" %} + {% trans "Unpublished" %} + {% trans "Publishing" %} + {% trans "Published" %} + {% trans "Archive" %} + {% if user.is_staff %} + {% trans "Projects" %} + {% endif %} + {% if user.is_authenticated %} + {% trans "Logout" %} + {% else %} + {% trans "Login" %} + {% endif %} + {% if user.is_staff %} + {% trans "Administration" %} + {% endif %} + {% if user.is_authenticated %} + {{ user }} + {% endif %} +
    +{% endblock %} diff --git a/src/archive/templates/archive/file_managed.html b/src/archive/templates/archive/file_managed.html new file mode 100755 index 0000000..8917243 --- /dev/null +++ b/src/archive/templates/archive/file_managed.html @@ -0,0 +1,135 @@ +{% extends "archive/base.html" %} +{% load i18n %} +{% load tags %} + +{% block content %} + +

    Plik źródłowy: {{ path }} +(sha1: {{ audiobook.source_sha1 }}). +

    + +

    {% trans "Publishing" %}

    + +{% if audiobook.mp3_status or audiobook.ogg_status %} + +

    {% trans "Publishing pending" %}

    + +
    + {% csrf_token %} + +
    + + +{% if audiobook.mp3_status %} +
    +

    MP3

    + + {% tags_table audiobook.mp3_tags.tags %} + +

    Status: {{ audiobook.get_mp3_status_display }}

    +{% endif %} + +{% if audiobook.ogg_status %} +
    +

    Ogg Vorbis

    + + {% tags_table audiobook.ogg_tags.tags %} + +

    Status: {{ audiobook.get_ogg_status_display }}

    +{% endif %} + + + + +{% else %} + + {% tags_table audiobook.new_publish_tags 0 %} + +
    + +
    + {% csrf_token %} + +
    + + {% if not audiobook.mp3_published or not audiobook.ogg_published %} +
    + {% csrf_token %} + +
    + {% endif %} + +
    +{% endif %} + +
    +{% if audiobook.mp3_file %} +

    {% trans "MP3 file" %}

    +

    {% trans "Download MP3 file." %}

    + {% if audiobook.mp3_published %} +

    {% trans "Published:" %} {{ audiobook.mp3_published }}

    + {% if audiobook.mp3_published_tags.tags %} + {% tags_table audiobook.mp3_published_tags.tags %} + {% endif %} + {% else %} +

    {% trans "Not published yet." %}

    + {% endif %} +{% else %} +

    {% trans "MP3 file hasn't been generated yet." %}

    +{% endif %} + +
    +{% if audiobook.ogg_file %} +

    {% trans "Ogg Vorbis file" %}

    +

    {% trans "Download Ogg Vorbis file." %}

    + {% if audiobook.ogg_published %} +

    {% trans "Published:" %} {{ audiobook.ogg_published }}

    + {% if audiobook.ogg_published_tags.tags %} + {% tags_table audiobook.ogg_published_tags.tags %} + {% endif %} + {% else %} +

    {% trans "Not published yet." %}

    + {% endif %} +{% else %} +

    {% trans "Ogg Vorbis file hasn't been generated yet." %}

    +{% endif %} + + + + +
    + + + + +

    {% trans "Update tags" %}

    + +Last modified: {{ audiobook.modified }} + +{% multiple_tags_table tags %} + + + +
    + {% csrf_token %} + + {{ form.as_table }} + +
    +
    + + + +
    + + + +
    + {% csrf_token %} + +
    + + + +{% endblock %} diff --git a/src/archive/templates/archive/file_new.html b/src/archive/templates/archive/file_new.html new file mode 100644 index 0000000..acc161d --- /dev/null +++ b/src/archive/templates/archive/file_new.html @@ -0,0 +1,23 @@ +{% extends "archive/base.html" %} +{% load i18n tags %} + +{% block content %} + +
    + {% csrf_token %} + +
    + + +{% multiple_tags_table tags %} + + +
    + {% csrf_token %} + + {{ form.as_table }} + +
    +
    + +{% endblock %} diff --git a/src/archive/templates/archive/file_unmanaged.html b/src/archive/templates/archive/file_unmanaged.html new file mode 100755 index 0000000..89f79ba --- /dev/null +++ b/src/archive/templates/archive/file_unmanaged.html @@ -0,0 +1,31 @@ +{% extends "archive/base.html" %} +{% load i18n %} + +{% block messages %} + {% if err_exists %} +

    {% trans "File with same name already exists!" %}

    + {% endif %} +{% endblock %} + + +{% block content %} + + +
      + {% for t, v in tags.items %} +
    • {{t}} +
        + {% for x in v %} +
      • {{x}}
      • + {% endfor %} +
    • + {% endfor %} +
    + + +
    + {% csrf_token %} + +
    + +{% endblock %} diff --git a/src/archive/templates/archive/list.html b/src/archive/templates/archive/list.html new file mode 100644 index 0000000..bcd85de --- /dev/null +++ b/src/archive/templates/archive/list.html @@ -0,0 +1,15 @@ +{% extends "archive/base.html" %} +{% load i18n %} + + +{% block content %} + +
    +

    {% block file-list-title %}{% endblock %}

    +
    {% block file-list-info %}{% endblock %}
    + {% block file-list-wrapper %} +
      {% block file-list %}{% endblock %}
    + {% endblock file-list-wrapper %} +
    + +{% endblock %} diff --git a/src/archive/templates/archive/list_new.html b/src/archive/templates/archive/list_new.html new file mode 100644 index 0000000..fabf446 --- /dev/null +++ b/src/archive/templates/archive/list_new.html @@ -0,0 +1,22 @@ +{% extends "archive/list.html" %} +{% load i18n %} + + +{% block file-list-title %} + {% trans "New audiobooks" %} +{% endblock %} + + +{% block file-list-info %} + {% trans "Put source audiobooks in:" %} + {{ path }} +{% endblock %} + + +{% block file-list %} + {% for file in objects %} +
  • + {{ file }} +
  • + {% endfor %} +{% endblock %} diff --git a/src/archive/templates/archive/list_published.html b/src/archive/templates/archive/list_published.html new file mode 100755 index 0000000..5e61ba1 --- /dev/null +++ b/src/archive/templates/archive/list_published.html @@ -0,0 +1,20 @@ +{% extends "archive/list.html" %} +{% load i18n %} + + +{% block file-list-title %} + {% trans "Published audiobooks" %} +{% endblock %} + + +{% block file-list-info %} +{% endblock %} + + +{% block file-list %} + {% for file in objects %} +
  • + {{ file }} +
  • + {% endfor %} +{% endblock %} diff --git a/src/archive/templates/archive/list_publishing.html b/src/archive/templates/archive/list_publishing.html new file mode 100755 index 0000000..6bcf9e0 --- /dev/null +++ b/src/archive/templates/archive/list_publishing.html @@ -0,0 +1,23 @@ +{% extends "archive/list.html" %} +{% load i18n %} + + +{% block file-list-title %} + {% trans "Audiobooks being published" %} +{% endblock %} + + + +{% block file-list-wrapper %} + {% for k, objects in status_objects %} +

    {{ k.1 }}

    +
      + {% for file in objects %} +
    • + {{ file }} + ({% if file.mp3_status = k.0 %}MP3{% if file.ogg_status = k.0 %}, {% endif %}{% endif %}{% if file.ogg_status = k.0 %}Ogg{% endif %}) +
    • + {% endfor %} +
    + {% endfor %} +{% endblock %} diff --git a/src/archive/templates/archive/list_unmanaged.html b/src/archive/templates/archive/list_unmanaged.html new file mode 100755 index 0000000..fd48c33 --- /dev/null +++ b/src/archive/templates/archive/list_unmanaged.html @@ -0,0 +1,19 @@ +{% extends "archive/list.html" %} +{% load i18n %} + +{% block file-list-title %} + {% trans "Unmanaged archive" %} +{% endblock %} + + +{% block file-list-info %} +{% endblock %} + + +{% block file-list %} + {% for file in objects %} +
  • + {{ file }} +
  • + {% endfor %} +{% endblock %} diff --git a/src/archive/templates/archive/list_unpublished.html b/src/archive/templates/archive/list_unpublished.html new file mode 100755 index 0000000..b901ef0 --- /dev/null +++ b/src/archive/templates/archive/list_unpublished.html @@ -0,0 +1,32 @@ +{% extends "archive/list.html" %} +{% load i18n %} + + +{% block file-list-title %} + {% trans "Unpublished audiobooks" %} +{% endblock %} + + +{% block file-list-info %} +{% endblock %} + + +{% block file-list %} + {% for file in objects %} +
  • + {{ file }} + + {% if file.mp3_published %} + MP3 + {% else %}{% if file.mp3_status %} + MP3 + {% endif %}{% endif %} + + {% if file.ogg_published %} + Ogg + {% else %}{% if file.ogg_status %} + Ogg + {% endif %}{% endif %} +
  • + {% endfor %} +{% endblock %} diff --git a/src/archive/templates/archive/tags/multiple_tags_table.html b/src/archive/templates/archive/tags/multiple_tags_table.html new file mode 100755 index 0000000..4609278 --- /dev/null +++ b/src/archive/templates/archive/tags/multiple_tags_table.html @@ -0,0 +1,9 @@ +{% if table %}{% endif %} + {% for t, v in tags.items %} + + {% endfor %} +{% if table %}
    {{ t }} + {% for x in v %} +
    {{ x|linebreaksbr }}
    + {% endfor %} +
    {% endif %} diff --git a/src/archive/templates/archive/tags/tags_table.html b/src/archive/templates/archive/tags/tags_table.html new file mode 100755 index 0000000..70b9d48 --- /dev/null +++ b/src/archive/templates/archive/tags/tags_table.html @@ -0,0 +1,7 @@ +{% if table %}{% endif %} + {% for t, v in tags.items %} + + {% endfor %} +{% if table %}
    {{ t }} + {{ v|linebreaksbr }} +
    {% endif %} diff --git a/src/archive/templates/base.html b/src/archive/templates/base.html new file mode 100755 index 0000000..5e0053f --- /dev/null +++ b/src/archive/templates/base.html @@ -0,0 +1,24 @@ +{% load i18n %} + + + + + + {% trans "Audiobook repository" %} + + + +
    + {% block repo-zones-nav %} {% endblock %} +
    + +
    +{% block messages %}{% endblock %} +
    + +
    +{% block content %}{% endblock %} +
    + + + diff --git a/src/archive/templates/registration/login.html b/src/archive/templates/registration/login.html new file mode 100755 index 0000000..115cdf1 --- /dev/null +++ b/src/archive/templates/registration/login.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} + +
    +{% csrf_token %} +{{ form.as_p }} +

    +
    + +{% endblock %} diff --git a/src/archive/templatetags/__init__.py b/src/archive/templatetags/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/src/archive/templatetags/tags.py b/src/archive/templatetags/tags.py new file mode 100755 index 0000000..8e208a6 --- /dev/null +++ b/src/archive/templatetags/tags.py @@ -0,0 +1,21 @@ +from django import template + +register = template.Library() + +@register.inclusion_tag('archive/tags/multiple_tags_table.html') +def multiple_tags_table(tags, table=True): + new_tags = {} + if tags: + for k, v in tags.items(): + if isinstance(v, list): + new_tags[k] = v + else: + new_tags[k] = [v] + return {"tags": new_tags, "table": table} + + +@register.inclusion_tag('archive/tags/tags_table.html') +def tags_table(tags, table=True): + if tags is None: + tags = {} + return locals() diff --git a/src/archive/urls.py b/src/archive/urls.py new file mode 100644 index 0000000..efaeeea --- /dev/null +++ b/src/archive/urls.py @@ -0,0 +1,25 @@ +from django.conf.urls import patterns, include, url +from django.views.generic import RedirectView + +urlpatterns = patterns('', + url(r'^$', RedirectView.as_view(url='new/')), + + url(r'^new/$', 'archive.views.list_new', name="list_new"), + url(r'^new/(.+)/$', 'archive.views.file_new', name="file_new"), + url(r'^move_to_archive/(.+)/$', 'archive.views.move_to_archive', name="move_to_archive"), + + url(r'^unpublished/$', 'archive.views.list_unpublished', name="list_unpublished"), + url(r'^publishing/$', 'archive.views.list_publishing', name="list_publishing"), + url(r'^published/$', 'archive.views.list_published', name="list_published"), + url(r'^file/(\d+)/$', 'archive.views.file_managed', name="file"), + url(r'^publish/(\d+)/$', 'archive.views.publish', name="publish"), + url(r'^convert/(\d+)/$', 'archive.views.publish', {'publish': False}, name="convert"), + url(r'^download/(\d+)/$', 'archive.views.download', name="download"), + url(r'^download/(\d+)\.(mp3|ogg)$', 'archive.views.download', name="download"), + url(r'^cancel/(\d+)/$', 'archive.views.cancel_publishing', name="cancel_publishing"), + url(r'^remove_to_archive/(\d+)/$', 'archive.views.remove_to_archive', name="remove_to_archive"), + + url(r'^unmanaged/$', 'archive.views.list_unmanaged', name="list_unmanaged"), + url(r'^unmanaged/(.+)/$', 'archive.views.file_unmanaged', name="file_unmanaged"), + url(r'^move_to_new/(.+)/$', 'archive.views.move_to_new', name="move_to_new"), +) diff --git a/src/archive/utils.py b/src/archive/utils.py new file mode 100644 index 0000000..3e89a8b --- /dev/null +++ b/src/archive/utils.py @@ -0,0 +1,44 @@ +from hashlib import sha1 +import os +import os.path +from django.core.files.storage import FileSystemStorage +from django.core.files.uploadedfile import UploadedFile + + +class ExistingFile(UploadedFile): + + def __init__(self, path, *args, **kwargs): + self.path = path + return super(ExistingFile, self).__init__(*args, **kwargs) + + def temporary_file_path(self): + return self.path + + def close(self): + pass + + +class OverwriteStorage(FileSystemStorage): + + def _save(self, name, content): + if self.exists(name): + self.delete(name) + return super(OverwriteStorage, self)._save(name, content) + + def get_available_name(self, name): + return name + + +def sha1_file(f): + sha = sha1() + for piece in iter(lambda: f.read(1024*1024), ''): + sha.update(piece) + return sha.hexdigest() + + +def all_files(root_path): + root_len = len(root_path) + for path, dirs, files in os.walk(root_path): + for fname in files: + yield os.path.join(path, fname)[root_len:].lstrip('/') + diff --git a/src/archive/views.py b/src/archive/views.py new file mode 100644 index 0000000..b5850a0 --- /dev/null +++ b/src/archive/views.py @@ -0,0 +1,279 @@ +# Create your views here. + +from datetime import datetime +import os +import os.path +from urllib import quote + +from archive import settings +from django.contrib.auth import logout +from django.contrib.auth.decorators import permission_required +from django.core.urlresolvers import reverse +from django.db.models import Q, Max +from django.http import Http404, HttpResponse +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 +from archive.utils import all_files + + +def list_new(request): + division = 'new' + + path = settings.NEW_PATH + objects = sorted(all_files(path)) + return render(request, "archive/list_new.html", locals()) + + +@permission_required('archive.change_audiobook') +def file_new(request, filename): + division = 'new' + + filepath = filename.encode('utf-8') + root_filepath = os.path.join(settings.NEW_PATH, filename.encode('utf-8')) + if request.POST: + form = AudiobookForm(request.POST) + if form.is_valid(): + try: + form.save(path=filepath) + except IOError: + raise Http404 + return redirect(list_new) + + try: + tags = mutagen.File(root_filepath) + except IOError: + raise Http404 + d = {} + if tags: + for tag in tags: + value = tags[tag] + if isinstance(value, list): + d[tag] = value[0] + else: + d[tag] = value + if tag == 'project': + try: + d[tag] = models.Project.objects.get(name=d[tag]).pk + except models.Project.DoesNotExist: + d[tag] = None + + if not request.POST: + form = AudiobookForm(d) + return render(request, "archive/file_new.html", locals()) + + +@require_POST +@permission_required('archive.change_audiobook') +def move_to_archive(request, filename): + """ move a new file to the unmanaged files dir """ + + filename_str = filename.encode('utf-8') + old_path = os.path.join(settings.NEW_PATH, filename_str) + new_path = os.path.join(settings.UNMANAGED_PATH, filename_str) + new_dir = os.path.split(new_path)[0] + if not os.path.isdir(new_dir): + os.makedirs(new_dir) + + if not os.path.isfile(old_path): + raise Http404 + + try: + os.link(old_path, new_path) + os.unlink(old_path) + except OSError: + # destination file exists, don't overwrite it + # TODO: this should probably be more informative + return redirect(file_new, filename) + + return redirect(list_new) + + +@require_POST +@permission_required('archive.change_audiobook') +def remove_to_archive(request, aid): + """ move a managed file to the unmanaged files dir """ + + audiobook = get_object_or_404(models.Audiobook, id=aid) + old_path = audiobook.source_file.path + new_path = os.path.join(settings.UNMANAGED_PATH, + str(audiobook.source_file)[len(settings.FILES_SAVE_PATH):].lstrip('/')) + new_dir = os.path.split(new_path)[0] + if not os.path.isdir(new_dir): + os.makedirs(new_dir) + + if not os.path.isfile(old_path): + raise Http404 + + success = False + try_new_path = new_path + try_number = 0 + while not success: + try: + os.link(old_path, try_new_path) + except OSError: + # destination file exists, don't overwrite it + try_number += 1 + parts = new_path.rsplit('.', 1) + parts[0] += '_%d' % try_number + try_new_path = ".".join(parts) + else: + os.unlink(old_path) + audiobook.delete() + success = True + + return redirect(list_unmanaged) + +@require_POST +@permission_required('archive.change_audiobook') +def move_to_new(request, filename): + """ move a unmanaged file to new files dir """ + + filename_str = filename.encode('utf-8') + old_path = os.path.join(settings.UNMANAGED_PATH, filename_str) + new_path = os.path.join(settings.NEW_PATH, filename_str) + new_dir = os.path.split(new_path)[0] + if not os.path.isdir(new_dir): + os.makedirs(new_dir) + + if not os.path.isfile(old_path): + raise Http404 + + try: + os.link(old_path, new_path) + os.unlink(old_path) + except OSError: + # destination file exists, don't overwrite it + # TODO: this should probably be more informative + return redirect(reverse(file_unmanaged, args=[filename]) + "?exists=1") + + return redirect(list_unmanaged) + + +@require_POST +@permission_required('archive.change_audiobook') +def publish(request, aid, publish=True): + """ mark file for publishing """ + 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() + # isn't there a race here? + audiobook.mp3_task = tasks.Mp3Task.delay(aid, publish).task_id + audiobook.ogg_task = tasks.OggTask.delay(aid, publish).task_id + audiobook.save() + + return redirect(file_managed, aid) + + +@require_POST +@permission_required('archive.change_audiobook') +def cancel_publishing(request, aid): + """ cancel scheduled publishing """ + 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) + + +def download(request, aid, which="source"): + if which not in ("source", "mp3", "ogg"): + raise Http404 + audiobook = get_object_or_404(models.Audiobook, id=aid) + file_ = getattr(audiobook, "%s_file" % which) + if not file_: + raise Http404 + ext = file_.path.rsplit('.', 1)[-1] + response = HttpResponse(mimetype='application/force-download') + + response['Content-Disposition'] = "attachment; filename*=UTF-8''%s.%s" % ( + quote(audiobook.title.encode('utf-8'), safe=''), ext) + response['X-Sendfile'] = file_.path.encode('utf-8') + return response + + +def list_unpublished(request): + division = 'unpublished' + + objects = models.Audiobook.objects.filter(Q(mp3_published=None) | Q(ogg_published=None)) + return render(request, "archive/list_unpublished.html", locals()) + + +def list_publishing(request): + division = 'publishing' + + objects = models.Audiobook.objects.exclude(mp3_status=None, ogg_status=None) + objects_by_status = {} + for o in objects: + if o.mp3_status: + k = o.mp3_status, o.get_mp3_status_display() + objects_by_status.setdefault(k, []).append(o) + if o.ogg_status and o.ogg_status != o.mp3_status: + k = o.ogg_status, o.get_ogg_status_display() + objects_by_status.setdefault(k, []).append(o) + status_objects = sorted(objects_by_status.items(), reverse=True) + + return render(request, "archive/list_publishing.html", locals()) + + +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()) + + +@permission_required('archive.change_audiobook') +def file_managed(request, id): + audiobook = get_object_or_404(models.Audiobook, id=id) + + 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' + path = audiobook.source_file.path[len(settings.FILES_PATH):].lstrip('/') + + # for tags update + tags = mutagen.File(audiobook.source_file.path.encode('utf-8')) + if not tags: + tags = {} + form = AudiobookForm(instance=audiobook) + + return render(request, "archive/file_managed.html", locals()) + + +def list_unmanaged(request): + division = 'unmanaged' + + objects = sorted(all_files(settings.UNMANAGED_PATH)) + return render(request, "archive/list_unmanaged.html", locals()) + + +def file_unmanaged(request, filename): + division = 'unmanaged' + + tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename.encode('utf-8'))) + if not tags: + tags = {} + + err_exists = request.GET.get('exists') + return render(request, "archive/file_unmanaged.html", locals()) diff --git a/src/audiobooks/__init__.py b/src/audiobooks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/audiobooks/settings.py b/src/audiobooks/settings.py new file mode 100644 index 0000000..6ec4fe7 --- /dev/null +++ b/src/audiobooks/settings.py @@ -0,0 +1,196 @@ +# Django settings for audiobooks project. +# -*- coding: utf-8 -*- + +import os +PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + + + +DEBUG = False +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@example.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': os.path.join(PROJECT_ROOT, 'dev.sqlite'), # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'Europe/Warsaw' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'pl' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale +USE_L10N = True + +# Absolute filesystem path to the directory that will hold user-uploaded files. +# Example: "/home/media/media.lawrence.com/media/" +MEDIA_ROOT = os.path.join(PROJECT_ROOT, '../media/') + +# 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/' + +# Absolute path to the directory static files should be collected to. +# Don't put anything in this directory yourself; store your static files +# in apps' "static/" subdirectories and in STATICFILES_DIRS. +# Example: "/home/media/media.lawrence.com/static/" +STATIC_ROOT = '' + +# URL prefix for static files. +# Example: "http://media.lawrence.com/static/" +STATIC_URL = '/static/' + +# URL prefix for admin static files -- CSS, JavaScript and images. +# Make sure to use a trailing slash. +# Examples: "http://foo.com/static/admin/", "/static/admin/". +ADMIN_MEDIA_PREFIX = '/static/admin/' + +# Additional locations of static files +STATICFILES_DIRS = ( + # Put strings here, like "/home/html/static" or "C:/www/django/static". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +# List of finder classes that know how to find static files in +# various locations. +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', +# 'django.contrib.staticfiles.finders.DefaultStorageFinder', +) + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '8ehk^-+pr(o)k6lh_gl9+ks6gkd9u#fka7fv%ikpk(c%llqa6%' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django_cas.middleware.CASMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', +) + +AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.ModelBackend', + 'django_cas.backends.CASBackend', +) + +ROOT_URLCONF = 'audiobooks.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + # Uncomment the next line to enable the admin: + 'django.contrib.admin', + # Uncomment the next line to enable admin documentation: + # 'django.contrib.admindocs', + + 'djcelery', + 'djkombu', + + 'south', + 'archive', +) + +# A sample logging configuration. The only tangible logging +# performed by this configuration is to send an email to +# the site admins on every HTTP 500 error. +# See http://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + } + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True, + }, + } +} + +#http://logowanie.nowoczesnapolska.org.pl/cas/' +CAS_SERVER_URL = "http://logowanie.nowoczesnapolska.org.pl/cas/" +CAS_VERSION = "1" + + +EMAIL_SUBJECT_PREFIX = '[Audio] ' +SERVER_EMAIL = 'no-reply@audio.wolnelektury.pl' +EMAIL_HOST = 'localhost' +EMAIL_PORT = 25 + +import djcelery +djcelery.setup_loader() + +CELERY_SEND_TASK_ERROR_EMAILS = True +BROKER_BACKEND = "djkombu.transport.DatabaseTransport" +BROKER_HOST = "localhost" +BROKER_PORT = 5672 +BROKER_USER = "guest" +BROKER_PASSWORD = "guest" +BROKER_VHOST = "/" + + +try: + from localsettings import * +except: + pass diff --git a/src/audiobooks/urls.py b/src/audiobooks/urls.py new file mode 100644 index 0000000..f3113c8 --- /dev/null +++ b/src/audiobooks/urls.py @@ -0,0 +1,21 @@ +from django.conf import settings +from django.conf.urls import patterns, include, url +from django.views.generic import RedirectView + +# Uncomment the next two lines to enable the admin: +from django.contrib import admin +admin.autodiscover() + +urlpatterns = patterns('', + url(r'^$', RedirectView.as_view(url='archive/')), + url(r'^archive/', include('archive.urls')), + + url(r'^admin/', include(admin.site.urls)), + url(r'^accounts/login/$', 'django_cas.views.login', name='login'), + url(r'^accounts/logout/$', 'django_cas.views.logout', name='logout'), +) + +if settings.DEBUG: + urlpatterns += patterns('', + (r'^media/(?P.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT, 'show_indexes':True}), +) diff --git a/src/audiobooks/wsgi.py b/src/audiobooks/wsgi.py new file mode 100644 index 0000000..5e0ddaa --- /dev/null +++ b/src/audiobooks/wsgi.py @@ -0,0 +1,28 @@ +""" +WSGI config for edumed project. + +This module contains the WSGI application used by Django's development server +and any production WSGI deployments. It should expose a module-level variable +named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover +this application via the ``WSGI_APPLICATION`` setting. + +Usually you will have the standard Django WSGI application here, but it also +might make sense to replace the whole Django WSGI application with a custom one +that later delegates to the Django one. For example, you could introduce WSGI +middleware here, or combine a Django application with an application of another +framework. + +""" +import os + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "audiobooks.settings") + +# This application object is used by any WSGI server configured to use this +# file. This includes Django's development server, if the WSGI_APPLICATION +# setting points here. +from django.core.wsgi import get_wsgi_application +application = get_wsgi_application() + +# Apply WSGI middleware here. +# from helloworld.wsgi import HelloWorldApplication +# application = HelloWorldApplication(application) diff --git a/src/manage.py b/src/manage.py new file mode 100755 index 0000000..3281d70 --- /dev/null +++ b/src/manage.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +import imp +try: + imp.find_module('settings', ['audiobooks']) +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) + sys.exit(1) + +import audiobooks.settings + +if __name__ == "__main__": + execute_manager(audiobooks.settings)