--i https://py.mdrn.pl:8443/simple
+-i https://py.mdrn.pl/simple
-Django==3.1.8
-django-cas-ng==4.1.1
-django-bootstrap4==2.2.0
+Django==3.2.16
+django-cas-ng==4.3.0
+django-bootstrap4==22.2
django-pglocks==1.0.4
-fnp-django-pagination==2.2.4
+fnp-django-pagination==2.2.5
+# ?
celery[redis]==4.4.2
-psycopg2-binary==2.8.5
-mutagen==1.45.1
-requests==2.24.0
-requests-toolbelt==0.9.1
-requests-oauthlib==1.3.0
-PyYAML==5.4.1
-Pillow==8.2.0
-librarian==1.9
+psycopg2-binary==2.9.5
+mutagen==1.46.0
+requests==2.28.1
+requests-toolbelt==0.10.1
+requests-oauthlib==1.3.1
+PyYAML==6.0
+Pillow==9.2.0
+librarian==2.4.8
py3-aeneas==1.1.0
-from django.conf.urls import url
+from django.urls import path
from . import views
urlpatterns = [
- url(r'^oauth/$', views.oauth, name='apiclient_oauth'),
- url(r'^oauth_callback/$', views.oauth_callback, name='apiclient_oauth_callback'),
- url(r'^oauth2/$', views.oauth2, name='apiclient_oauth2'),
- url(r'^oauth2_redirect/$', views.oauth2_redirect, name='apiclient_oauth2_redirect'),
+ path('oauth/', views.oauth, name='apiclient_oauth'),
+ path('auth_callback/', views.oauth_callback, name='apiclient_oauth_callback'),
+ path('oauth2/', views.oauth2, name='apiclient_oauth2'),
+ path('oauth2_redirect/', views.oauth2_redirect, name='apiclient_oauth2_redirect'),
]
-from archive.models import Project, Audiobook, License
+from archive.models import Project, Audiobook, License, Config
from django.contrib import admin
admin.site.register(Project)
class AudiobookAdmin(admin.ModelAdmin):
list_display = ["title", "slug", "index", "part_name", "duration", "license", "youtube_volume"]
- list_filter = ["license"]
+ list_filter = [
+ "license",
+ "project",
+ ("mp3_published", admin.EmptyFieldListFilter),
+ ("youtube_published", admin.EmptyFieldListFilter),
+ ]
search_fields = ["title", "slug", "part_name", "youtube_volume"]
list_editable = ["youtube_volume"]
readonly_fields = ['duration']
admin.site.register(Audiobook, AudiobookAdmin)
admin.site.register(License)
+admin.site.register(Config)
class AudiobookForm(forms.ModelForm):
class Meta:
model = Audiobook
- exclude = []
+ exclude = [
+ 'youtube_id', 'youtube_queued'
+ ]
def save(self, commit=True, path=None):
""" Performs normal save, with given file as an source audiobook.
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-06-06 00:25+0200\n"
-"PO-Revision-Date: 2020-06-06 00:30+0200\n"
+"POT-Creation-Date: 2022-10-25 13:56+0200\n"
+"PO-Revision-Date: 2022-10-25 13:57+0200\n"
"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
"Language-Team: \n"
"Language: pl\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"
-"X-Generator: Poedit 2.2.4\n"
+"X-Generator: Poedit 3.0.1\n"
-#: constants.py:18
-msgid "Waiting"
-msgstr "Czeka"
-
-#: constants.py:19
+#: archive/constants.py:18
msgid "Queued"
msgstr "W kolejce"
-#: constants.py:20
+#: archive/constants.py:19
+msgid "Waiting"
+msgstr "Czeka"
+
+#: archive/constants.py:20
msgid "Encoding"
msgstr "Konwersja"
-#: constants.py:21
+#: archive/constants.py:21
msgid "Tagging"
msgstr "Opisywanie"
-#: constants.py:22
+#: archive/constants.py:22
msgid "Converting audio"
msgstr "Konwersja audio"
-#: constants.py:23
+#: archive/constants.py:23
msgid "Converting video"
msgstr "Konwersja wideo"
-#: constants.py:24
+#: archive/constants.py:24
msgid "Assembling audio"
msgstr "Kompletowanie audio"
-#: constants.py:25
+#: archive/constants.py:25
msgid "Assembling video"
msgstr "Kompletowanie wideo"
-#: constants.py:26
+#: archive/constants.py:26
msgid "Joining audio and video"
msgstr "Łączenie audio i wideo"
-#: constants.py:27
+#: archive/constants.py:27
msgid "Sending"
msgstr "Wysyłanie"
-#: constants.py:28
+#: archive/constants.py:28
msgid "Setting thumbnail"
msgstr "Wysyłanie miniatury"
-#: models.py:31 models.py:82
+#: archive/models.py:44 archive/models.py:122
msgid "project"
msgstr "projekt"
-#: models.py:32
+#: archive/models.py:45
msgid "projects"
msgstr "projekty"
-#: models.py:63
+#: archive/models.py:76
+msgid "Configuration"
+msgstr "Konfiguracja"
+
+#: archive/models.py:77
+msgid "Configurations"
+msgstr "Konfiguracje"
+
+#: archive/models.py:102
msgid "source file"
msgstr "plik źródłowy"
-#: models.py:66
+#: archive/models.py:106
msgid "title"
msgstr "tytuł"
-#: models.py:67
+#: archive/models.py:107
msgid "part name"
msgstr "nazwa części"
-#: models.py:67
+#: archive/models.py:107
msgid "eg. chapter in a novel"
msgstr "np. rozdział w powieści"
-#: models.py:69
+#: archive/models.py:109
msgid "index"
msgstr "numer"
-#: models.py:69
+#: archive/models.py:109
msgid "Ordering of parts of a book."
msgstr "Kolejność części w książce"
-#: models.py:71
+#: archive/models.py:111
msgid "Volume name for YouTube"
msgstr "Nazwa woluminu na YouTube"
-#: models.py:75
+#: archive/models.py:115
msgid ""
"If set, audiobooks with the save value will be published as single YouTube "
"video."
"Jeśli ustawione, audiobooki z tą samą wartością będą opublikowane jako jeden "
"film na YouTube."
-#: models.py:78
+#: archive/models.py:118
msgid "artist"
msgstr "lektor"
-#: models.py:79
+#: archive/models.py:119
msgid "conductor"
msgstr "reżyser"
-#: models.py:80
+#: archive/models.py:120
msgid "encoded by"
msgstr "przyg. techn."
-#: models.py:81
+#: archive/models.py:121
msgid "date"
msgstr "data"
-#: models.py:83
+#: archive/models.py:123
msgid "WL catalogue slug of the book."
msgstr "Slug książki z katalogu WL."
-#: models.py:84
+#: archive/models.py:124
msgid "translator"
msgstr "tłumacz"
-#: models.py:86
+#: archive/models.py:126
msgid "license"
msgstr "licencja"
-#: models.py:112
+#: archive/models.py:127
+msgid "secondary license"
+msgstr "druga licencja"
+
+#: archive/models.py:144
msgid "audiobook"
msgstr "audiobook"
-#: models.py:113
+#: archive/models.py:145
msgid "audiobooks"
msgstr "audiobooki"
-#: templates/archive/audiobook_list.html:10 templates/archive/base.html:6
+#: archive/templates/archive/audiobook_list.html:11
+#: archive/templates/archive/base.html:6
msgid "Audiobooks"
msgstr "Audiobooki"
-#: templates/archive/audiobook_list.html:28 templates/archive/book.html:19
+#: archive/templates/archive/audiobook_list.html:37
+#: archive/templates/archive/book.html:19
msgid "Title"
msgstr "tytuł"
-#: templates/archive/audiobook_list.html:29
+#: archive/templates/archive/audiobook_list.html:38
msgid "YouTube volume"
msgstr "wolumin YouTube"
-#: templates/archive/base.html:7 templates/archive/file_managed.html:19
+#: archive/templates/archive/base.html:7
msgid "Publishing"
msgstr "Publikacja"
-#: templates/archive/base.html:8
+#: archive/templates/archive/base.html:8
msgid "New"
msgstr "Nowe"
-#: templates/archive/base.html:9
+#: archive/templates/archive/base.html:9
msgid "Archive"
msgstr "Archiwum"
-#: templates/archive/base.html:14
+#: archive/templates/archive/base.html:14
msgid "Projects"
msgstr "Projekty"
-#: templates/archive/base.html:17
+#: archive/templates/archive/base.html:17
msgid "Logout"
msgstr "Wyloguj"
-#: templates/archive/base.html:19 templates/registration/login.html:9
+#: archive/templates/archive/base.html:19
+#: archive/templates/registration/login.html:9
msgid "Login"
msgstr "Zaloguj"
-#: templates/archive/base.html:22
+#: archive/templates/archive/base.html:22
msgid "Administration"
msgstr "Administracja"
-#: templates/archive/book.html:18
+#: archive/templates/archive/book.html:18
msgid "Index"
msgstr "numer"
-#: templates/archive/file_managed.html:23
+#: archive/templates/archive/file_managed.html:29
+#: archive/templates/archive/file_new.html:29
+msgid "Commit"
+msgstr "Zatwierdź"
+
+#: archive/templates/archive/file_managed.html:57
msgid "Publishing pending"
msgstr "Czeka na publikację"
-#: templates/archive/file_managed.html:26
+#: archive/templates/archive/file_managed.html:60
msgid "Cancel publishing"
msgstr "Anuluj publikację"
-#: templates/archive/file_managed.html:65
-msgid "Publish"
-msgstr "Opublikuj"
-
-#: templates/archive/file_managed.html:74
-msgid "Convert without publishing"
-msgstr "Konwertuj bez publikacji"
-
-#: templates/archive/file_managed.html:82
-msgid "Preview YouTube metadata"
-msgstr "Podgląd metadanych dla YouTube"
-
-#: templates/archive/file_managed.html:87
-msgid "Publish on YouTube"
-msgstr "Opublikuj na YouTube"
-
-#: templates/archive/file_managed.html:94
-msgid "Update YouTube metadata"
-msgstr "Aktualizuj metadane na YouTube"
-
-#: templates/archive/file_managed.html:111
-msgid "MP3 file"
-msgstr "Plik MP3"
-
-#: templates/archive/file_managed.html:115
+#: archive/templates/archive/file_managed.html:81
msgid "Download MP3 file."
msgstr "Pobierz plik MP3."
-#: templates/archive/file_managed.html:117
-#: templates/archive/file_managed.html:140
-#: templates/archive/file_managed.html:167
+#: archive/templates/archive/file_managed.html:83
+#: archive/templates/archive/file_managed.html:94
+#: archive/templates/archive/file_managed.html:192
msgid "Published:"
msgstr "Opublikowano:"
-#: templates/archive/file_managed.html:122
-#: templates/archive/file_managed.html:145
-#: templates/archive/file_managed.html:172
+#: archive/templates/archive/file_managed.html:85
+#: archive/templates/archive/file_managed.html:96
msgid "Not published yet."
msgstr "Nie opublikowane."
-#: templates/archive/file_managed.html:125
+#: archive/templates/archive/file_managed.html:88
msgid "MP3 file hasn't been generated yet."
msgstr "Plik MP3 nie został jeszcze wygenerowany."
-#: templates/archive/file_managed.html:134
-msgid "Ogg Vorbis file"
-msgstr "Plik Ogg Vorbis"
-
-#: templates/archive/file_managed.html:138
+#: archive/templates/archive/file_managed.html:92
msgid "Download Ogg Vorbis file."
msgstr "Pobierz plik Ogg Vorbis."
-#: templates/archive/file_managed.html:148
+#: archive/templates/archive/file_managed.html:99
msgid "Ogg Vorbis file hasn't been generated yet."
msgstr "Plik Ogg Vorbis nie został jeszcze wygenerowany."
-#: templates/archive/file_managed.html:157
-msgid "YouTube"
-msgstr "YouTube"
+#: archive/templates/archive/file_managed.html:105
+msgid "Publish"
+msgstr "Opublikuj"
+
+#: archive/templates/archive/file_managed.html:114
+msgid "Convert without publishing"
+msgstr "Konwertuj bez publikacji"
+
+#: archive/templates/archive/file_managed.html:160
+msgid "Preview YouTube metadata"
+msgstr "Podgląd metadanych dla YouTube"
+
+#: archive/templates/archive/file_managed.html:169
+msgid "Update YouTube thumbnail"
+msgstr "Aktualizuj metadane na YouTube"
+
+#: archive/templates/archive/file_managed.html:175
+msgid "Update YouTube metadata"
+msgstr "Aktualizuj metadane na YouTube"
-#: templates/archive/file_managed.html:163
+#: archive/templates/archive/file_managed.html:180
+msgid "Publish on YouTube"
+msgstr "Opublikuj na YouTube"
+
+#: archive/templates/archive/file_managed.html:187
msgid "See on YouTube"
msgstr "Zobacz na YouTube"
-#: templates/archive/file_managed.html:175
-msgid "YouTube file hasn't been generated yet."
-msgstr "Plik dla YouTube nie został jeszcze wygenerowany."
-
-#: templates/archive/file_managed.html:194
+#: archive/templates/archive/file_managed.html:226
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:196
+#: archive/templates/archive/file_managed.html:234
msgid "Remove to archive"
msgstr "Usuń do archiwum"
-#: templates/archive/file_managed.html:206
-msgid "Update tags"
-msgstr "Uaktualnij tagi"
-
-#: templates/archive/file_managed.html:213 templates/archive/file_new.html:29
-msgid "Commit"
-msgstr "Zatwierdź"
-
-#: templates/archive/file_new.html:17
+#: archive/templates/archive/file_new.html:17
msgid "Move to archive"
msgstr "Przenieś do archiwum"
-#: templates/archive/file_unmanaged.html:11
+#: archive/templates/archive/file_unmanaged.html:11
msgid "File with same name already exists!"
msgstr "Plik o tej nazwie już istnieje!"
-#: templates/archive/file_unmanaged.html:27
+#: archive/templates/archive/file_unmanaged.html:27
msgid "Move to new files"
msgstr "Przenieś do nowych plików"
-#: templates/archive/list_new.html:10
+#: archive/templates/archive/list_new.html:10
msgid "New audiobooks"
msgstr "Nowe audiobooki"
-#: templates/archive/list_new.html:15
+#: archive/templates/archive/list_new.html:15
msgid "Put source audiobooks in:"
msgstr "Umieść nowe audiobooki w:"
-#: templates/archive/list_publishing.html:9
+#: archive/templates/archive/list_publishing.html:9
msgid "Audiobooks being published"
msgstr "Aktualnie publikowane audiobooki"
-#: templates/archive/list_unmanaged.html:9
+#: archive/templates/archive/list_unmanaged.html:9
msgid "Unmanaged archive"
msgstr "Audiobooki archiwalne"
-#: templates/archive/status.html:7
+#: archive/templates/archive/status.html:7
msgid "Published at"
msgstr "Opublikowane"
-#: templates/archive/status.html:8
+#: archive/templates/archive/status.html:8
msgid "OK"
msgstr "OK"
-#: templates/base.html:12
+#: archive/templates/base.html:12
msgid "Audiobook repository"
msgstr "Repozytorium audiobooków"
-#: templates/pagination/pagination.html:3
+#: archive/templates/pagination/pagination.html:3
msgid "Pagination"
msgstr "Paginacja"
-#: templates/pagination/pagination.html:8
-#: templates/pagination/pagination.html:14
+#: archive/templates/pagination/pagination.html:8
+#: archive/templates/pagination/pagination.html:14
msgid "previous"
msgstr "poprzednia"
-#: templates/pagination/pagination.html:40
-#: templates/pagination/pagination.html:46
+#: archive/templates/pagination/pagination.html:40
+#: archive/templates/pagination/pagination.html:46
msgid "next"
msgstr "następna"
-#: views.py:264
+#: archive/views.py:258
msgid "There is more than one part, but index is not set."
msgstr "Utwór ma więcej niż jedną część, ale indeks części nie jest ustawiony."
-#: views.py:266
+#: archive/views.py:260
#, python-format
msgid "Part indexes are not 1..%(parts_count)d."
msgstr "Indeksy części utworu nie tworzą zakresu 1..%(parts_count)d."
+#~ msgid "MP3 file"
+#~ msgstr "Plik MP3"
+
+#~ msgid "Ogg Vorbis file"
+#~ msgstr "Plik Ogg Vorbis"
+
+#~ msgid "YouTube"
+#~ msgstr "YouTube"
+
+#~ msgid "YouTube file hasn't been generated yet."
+#~ msgstr "Plik dla YouTube nie został jeszcze wygenerowany."
+
+#~ msgid "Update tags"
+#~ msgstr "Uaktualnij tagi"
+
#~ msgid "parts count"
#~ msgstr "liczba części"
--- /dev/null
+# Generated by Django 3.1.2 on 2021-06-25 16:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('archive', '0022_auto_20210316_1406'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='project',
+ name='info_flac',
+ field=models.FileField(blank=True, upload_to='archive/info_flac'),
+ ),
+ ]
--- /dev/null
+# Generated by Django 3.1.14 on 2021-12-22 16:04
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('archive', '0023_project_info_flac'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Config',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255)),
+ ('intro_flac', models.FileField(blank=True, upload_to='config/intro_flac')),
+ ('intro_min_seconds', models.IntegerField()),
+ ('outro_flac', models.FileField(blank=True, upload_to='config/outro_flac')),
+ ('outro_min_seconds', models.IntegerField()),
+ ],
+ options={
+ 'verbose_name': 'Configuration',
+ 'verbose_name_plural': 'Configurations',
+ },
+ ),
+ migrations.AddField(
+ model_name='project',
+ name='config',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='archive.config'),
+ ),
+ ]
--- /dev/null
+# Generated by Django 3.1.14 on 2021-12-22 16:00
+
+from django.db import migrations
+
+
+def create_config(apps, schema_editor):
+ Config = apps.get_model('archive', 'Config')
+ Project = apps.get_model('archive', 'Project')
+ c = Config.objects.create(name='Wolne Lektury', intro_min_seconds=0, outro_min_seconds=0)
+ Project.objects.all().update(config=c)
+
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('archive', '0024_auto_20211222_1604'),
+ ]
+
+ operations = [
+ migrations.RunPython(
+ create_config,
+ migrations.RunPython.noop
+ )
+ ]
--- /dev/null
+# Generated by Django 3.1.14 on 2021-12-22 16:05
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('archive', '0025_create_config'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='project',
+ name='config',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='archive.config'),
+ ),
+ ]
--- /dev/null
+# Generated by Django 3.1.14 on 2022-10-25 13:36
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('archive', '0026_auto_20211222_1605'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='audiobook',
+ name='mp3_published_tags',
+ ),
+ migrations.RemoveField(
+ model_name='audiobook',
+ name='mp3_tags',
+ ),
+ migrations.RemoveField(
+ model_name='audiobook',
+ name='mp3_task',
+ ),
+ migrations.RemoveField(
+ model_name='audiobook',
+ name='ogg_published_tags',
+ ),
+ migrations.RemoveField(
+ model_name='audiobook',
+ name='ogg_tags',
+ ),
+ migrations.RemoveField(
+ model_name='audiobook',
+ name='ogg_task',
+ ),
+ migrations.RemoveField(
+ model_name='audiobook',
+ name='youtube_published_tags',
+ ),
+ migrations.RemoveField(
+ model_name='audiobook',
+ name='youtube_tags',
+ ),
+ migrations.RemoveField(
+ model_name='audiobook',
+ name='youtube_task',
+ ),
+ migrations.AddField(
+ model_name='audiobook',
+ name='license_secondary',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='secondary', to='archive.license', verbose_name='license'),
+ ),
+ ]
--- /dev/null
+# Generated by Django 3.1.14 on 2022-10-25 13:41
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('archive', '0027_auto_20221025_1336'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='project',
+ name='can_sell',
+ field=models.BooleanField(default=True, verbose_name='Do sprzedaży'),
+ ),
+ migrations.AddField(
+ model_name='project',
+ name='private_notes',
+ field=models.TextField(blank=True, verbose_name='Prywatne notatki'),
+ ),
+ migrations.AddField(
+ model_name='project',
+ name='required_license',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='archive.license', verbose_name='Wymagana licencja'),
+ ),
+ ]
--- /dev/null
+# Generated by Django 3.1.14 on 2022-10-25 13:57
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('archive', '0028_auto_20221025_1341'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='audiobook',
+ name='license_secondary',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='secondary', to='archive.license', verbose_name='secondary license'),
+ ),
+ ]
import io
import json
+from os import unlink
import os.path
from urllib.parse import urljoin
from archive.constants import status
from archive.settings import FILES_SAVE_PATH, ADVERT, ORGANIZATION, PROJECT
from archive.utils import OverwriteStorage, sha1_file
+from youtube.utils import concat_audio, standardize_audio
class License(models.Model):
name = models.CharField(max_length=128, unique=True, db_index=True, verbose_name="Nazwa")
sponsors = models.TextField(blank=True, null=True, verbose_name="Sponsorzy")
description = models.TextField(blank=True, verbose_name="Opis")
+ private_notes = models.TextField(blank=True, verbose_name="Prywatne notatki")
+ config = models.ForeignKey('Config', models.PROTECT)
+ can_sell = models.BooleanField(default=True, verbose_name="Do sprzedaży")
+ required_license = models.ForeignKey('License', models.PROTECT, blank=True, null=True, verbose_name='Wymagana licencja')
youtube = models.ForeignKey('youtube.YouTube', models.PROTECT)
icon = models.FileField(upload_to='archive/project', blank=True, null=True)
+ info_flac = models.FileField(upload_to='archive/info_flac', blank=True)
class Meta:
verbose_name = _("project")
)
+class Config(models.Model):
+ name = models.CharField(max_length=255)
+ intro_flac = models.FileField(upload_to='config/intro_flac', blank=True)
+ intro_min_seconds = models.IntegerField()
+ outro_flac = models.FileField(upload_to='config/outro_flac', blank=True)
+ outro_min_seconds = models.IntegerField()
+
+ class Meta:
+ verbose_name = _("Configuration")
+ verbose_name_plural = _("Configurations")
+
+ def __str__(self):
+ return self.name
+
+ def prepare_audio(self, audiobook):
+ total_duration = audiobook.total_duration
+ files = []
+ if self.intro_flac and total_duration > self.intro_min_seconds and audiobook.is_first:
+ files.append(standardize_audio(self.intro_flac.path))
+ files.append(standardize_audio(audiobook.source_file.path))
+ if self.outro_flac and total_duration > self.outro_min_seconds and audiobook.is_last:
+ files.append(standardize_audio(self.outro_flac.path))
+ output = concat_audio(files)
+ for d in files:
+ unlink(d)
+ return output
+
+
def source_upload_to(intance, filename):
return os.path.join(FILES_SAVE_PATH, filename) # FIXME: what about really long file names?
translator = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('translator'))
modified = models.DateTimeField(null=True, editable=False)
license = models.ForeignKey(License, models.PROTECT, null=True, blank=True, verbose_name=_('license'))
+ license_secondary = models.ForeignKey(License, models.PROTECT, null=True, blank=True, verbose_name=_('secondary license'), related_name='secondary')
# 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 = models.TextField(null=True, editable=False)
mp3_file = models.FileField(null=True, upload_to='archive/final', storage=OverwriteStorage(), editable=False)
- mp3_published_tags = models.TextField(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 = models.TextField(null=True, editable=False)
ogg_file = models.FileField(null=True, upload_to='archive/final', storage=OverwriteStorage(), editable=False)
- ogg_published_tags = models.TextField(null=True, editable=False)
ogg_published = models.DateTimeField(null=True, editable=False)
youtube_status = models.SmallIntegerField(null=True, editable=False, choices=status.choices)
- youtube_task = models.CharField(max_length=64, null=True, editable=False)
- youtube_tags = models.TextField(null=True, editable=False)
- youtube_published_tags = models.TextField(null=True, editable=False)
youtube_published = models.DateTimeField(null=True, editable=False)
youtube_id = models.CharField(max_length=255, blank=True, default='')
youtube_queued = models.DateTimeField(null=True, blank=True)
def parts_count(self):
return type(self).objects.filter(slug=self.slug).count()
+ @property
+ def total_duration(self):
+ return type(self).objects.filter(slug=self.slug).aggregate(s=models.Sum('duration'))['s']
+
+ @property
+ def is_first(self):
+ return not type(self).objects.filter(slug=self.slug, index__lte=self.index).exclude(pk=self.pk).exists()
+
+ @property
+ def is_last(self):
+ return not type(self).objects.filter(slug=self.slug, index__gte=self.index).exclude(pk=self.pk).exists()
+
@property
def youtube_volume_count(self):
total = 0
return (
not self.youtube_volume
or not type(self)
- .objects.filter(youtube_volume=self.youtube_volume, index__lt=self.index)
+ .objects.filter(slug=self.slug, youtube_volume=self.youtube_volume, index__lt=self.index)
.exists()
)
self.youtube_queued = now()
self.save(update_fields=['youtube_status', 'youtube_queued'])
- def get_mp3_tags(self): return json.loads(self.mp3_tags) if self.mp3_tags else None
- def get_ogg_tags(self): return json.loads(self.ogg_tags) if self.ogg_tags else None
- def get_mp3_published_tags(self): return json.loads(self.mp3_published_tags) if self.mp3_published_tags else None
- def get_ogg_published_tags_tags(self): return json.loads(self.ogg_published_tags) if self.ogg_published_tags else None
- def set_mp3_tags(self, tags): self.mp3_tags = json.dumps(tags)
- def set_ogg_tags(self, tags): self.ogg_tags = json.dumps(tags)
-
def published(self):
return self.mp3_published and self.ogg_published
+ def publish(self, user, publish=True):
+ from . import tasks
+ self.mp3_status = self.ogg_status = status.WAITING
+ self.save(update_fields=['mp3_status', 'ogg_status'])
+ tasks.Mp3Task.delay(user.id, self.pk, publish=publish).task_id
+ tasks.OggTask.delay(user.id, self.pk, publish=publish).task_id
+
def get_source_sha1(self):
assert self.pk or self.source_sha1
if not self.source_sha1:
if self.project.sponsors:
tags['funded_by'] = self.project.sponsors
- if self.source_sha1:
- tags['flac_sha1'] = self.source_sha1
+ tags['flac_sha1'] = self.get_source_sha1()
+
return tags
+ def prepare_audio(self):
+ return self.project.config.prepare_audio(self)
+
@cached_property
def book(self):
if self.slug:
if xml_url is None:
return None
- return WLDocument(
- etree.parse(
- io.BytesIO(
- requests.get(xml_url).content
- )
- ,parser = parser
- )
- )
+ return WLDocument(url=xml_url)
+
+ @property
+ def cover(self):
+ from librarian.cover import LogoWLCover
+ return LogoWLCover(self.document.meta).output_file.get_bytes()
raise NotImplemented
@classmethod
- def set_tags(cls, audiobook, file_name):
- tags = getattr(audiobook, "get_%s_tags" % cls.prefix)()['tags']
- if not tags.get('flac_sha1'):
- tags['flac_sha1'] = audiobook.get_source_sha1()
+ def set_tags(cls, tags, file_name):
audio = File(file_name)
for k, v in tags.items():
audio[k] = v
**{field: getattr(audiobook, field)})
@classmethod
- def published(cls, aid):
+ def published(cls, aid, tags):
kwargs = {
- "%s_published_tags" % cls.prefix: F("%s_tags" % cls.prefix),
- "%s_tags" % cls.prefix: None,
"%s_published" % cls.prefix: datetime.now(),
'%s_status' % cls.prefix: None,
}
@classmethod
def put(cls, user, audiobook, path):
- tags = getattr(audiobook, "get_%s_tags" % cls.prefix)()
data = {
- 'book': tags['url'],
+ 'book': audiobook.url,
'type': cls.ext,
- 'name': tags['name'],
+ 'name': audiobook.title, ##### IS IT USED?
'part_name': audiobook.part_name,
'part_index': audiobook.index,
'parts_count': audiobook.parts_count,
out_file.close()
self.encode(self.get_source_file_paths(audiobook), out_file.name)
self.set_status(aid, status.TAGGING)
- self.set_tags(audiobook, out_file.name)
+
+ tags = audiobook.new_publish_tags()
+ self.set_tags(tags, out_file.name)
self.set_status(aid, status.SENDING)
if publish:
self.put(user, audiobook, out_file.name)
- self.published(aid)
+ self.published(aid, tags)
else:
self.set_status(aid, None)
self.save(audiobook, out_file.name)
def get_source_file_paths(self, audiobook):
- return [audiobook.source_file.path]
+ return [audiobook.prepare_audio()]
def on_failure(self, exc, task_id, args, kwargs, einfo):
aid = (args[0], kwargs.get('aid'))[0]
])
@classmethod
- def set_tags(cls, audiobook, file_name):
- mp3_tags = audiobook.get_mp3_tags()['tags']
- if not mp3_tags.get('flac_sha1'):
- mp3_tags['flac_sha1'] = audiobook.get_source_sha1()
+ def set_tags(cls, tags, file_name):
audio = id3.ID3(file_name)
- for k, v in mp3_tags.items():
+ for k, v in tags.items():
factory_tuple = cls.TAG_MAP[k]
factory, tagtype = factory_tuple[:2]
audio.add(factory(tagtype, v, *factory_tuple[2:]))
<th>{% trans "Title" %}</th>
<th>MP3</th>
<th>Ogg</th>
- <th colspan="3">YouTube</th>
+ <th colspan="5">YouTube</th>
</tr>
</thead>
<tbody>
<em class="text-warning" title="ddd">
({{ audiobook }})
</em>
- {% endif %}
+ {% endif %}
</a>
</td>
<td>{% status audiobook "mp3" %}</td>
{{ audiobook.youtube_volume_index }}/{{ volumes }}
</td>
<td>
- {{ audiobook.youtube_volume }}
+ <form method="POST" action="{% url 'book_youtube_volume' audiobook.id %}">
+ {% csrf_token %}
+ <input name='volume' value="{{ audiobook.youtube_volume }}" class="form-control">
+ </form>
+ </td>
+ <td>
+ {% if audiobook.youtube_volume %}
+ {{ audiobook.total|duration }}
+ {% else %}
+ <small class="text-secondary">{{ audiobook.subtotal|duration }}</small>
+ {% endif %}
+ </td>
+ <td>
+ {{ audiobook.duration|duration }}
</td>
<td>
{% status audiobook "youtube" %}
</td>
<td>
<span class="text-secondary">
- {{ audiobook.youtube_volume }}
+ <form method="POST" action="{% url 'book_youtube_volume' audiobook.id %}">
+ {% csrf_token %}
+ <input name='volume' value="{{ audiobook.youtube_volume }}" class="form-control">
+ </form>
</span>
</td>
+ <td>
+ <small class="text-secondary">{{ audiobook.subtotal|duration }}</small>
+ </td>
+ <td class="text-secondary">
+ {{ audiobook.duration|duration }}
+ </td>
<td>
</td>
{% endifchanged %}
<div class="row mt-4">
- <div class="col-xl-7">
+ <div class="col-xl-12">
<div class="card mt-4">
<div class="card-header">
<h2>{% if audiobook.slug %}<a href="{% url 'book' audiobook.slug %}" %}">{% endif %}{{ audiobook.title }}{% if audiobook.slug %}</a>{% endif %}</h2>
<div class="card-body">
<form method='post' action='.'>
{% csrf_token %}
- {% bootstrap_form form %}
+ {% bootstrap_form form layout="horizontal" %}
<input class="btn btn-primary" type="submit" value='{% trans "Commit" %}' />
</form>
-
</div>
</div>
</div>
- <div class="col-xl-5">
- {% if audiobook.mp3_status or audiobook.ogg_status or audiobook.youtube_status %}
- <h2>{% trans "Publishing pending" %}</h2>
- <form method="post" action="{% url 'cancel_publishing' audiobook.id %}">
- {% csrf_token %}
- <input class='btn btn-danger' type="submit" value="{% trans "Cancel publishing" %}" />
- </form>
- {% endif %}
+
+</div>
+
+
+
+
+
+
+
+
+<div class="row">
+
+
+ <div class="col-xl-8">
<div class="card mt-4">
<h3>MP3, Ogg</h3>
</div>
<div class="card-body">
+ {% if audiobook.mp3_status or audiobook.ogg_status or audiobook.youtube_status %}
+ <h2>{% trans "Publishing pending" %}</h2>
+ <form method="post" action="{% url 'cancel_publishing' audiobook.id %}">
+ {% csrf_token %}
+ <input class='btn btn-danger' type="submit" value="{% trans "Cancel publishing" %}" />
+ </form>
+ {% endif %}
+
+
{% if audiobook.mp3_status %}
<hr/>
<h2>MP3</h2>
- {% tags_table audiobook.get_mp3_tags.tags %}
-
<p>Status: <b>{{ audiobook.get_mp3_status_display }}</b></p>
{% endif %}
<hr/>
<h2>Ogg Vorbis</h2>
- {% tags_table audiobook.get_ogg_tags.tags %}
-
<p>Status: <b>{{ audiobook.get_ogg_status_display }}</b></p>
{% endif %}
<p><a href="{% url 'download' audiobook.id 'mp3' %}">{% trans "Download MP3 file." %}</a></p>
{% if audiobook.mp3_published %}
<p>{% trans "Published:" %} {{ audiobook.mp3_published }}</a></p>
- {% if audiobook.get_mp3_published_tags.tags %}
- {% tags_table audiobook.get_mp3_published_tags.tags %}
- {% endif %}
{% else %}
<p>{% trans "Not published yet." %}</p>
{% endif %}
<p><a href="{% url 'download' audiobook.id 'ogg' %}">{% trans "Download Ogg Vorbis file." %}</a></p>
{% if audiobook.ogg_published %}
<p>{% trans "Published:" %} {{ audiobook.ogg_published }}</a></p>
- {% if audiobook.get_ogg_published_tags.tags %}
- {% tags_table audiobook.get_ogg_published_tags.tags %}
- {% endif %}
{% else %}
<p>{% trans "Not published yet." %}</p>
{% endif %}
</div>
</div>
+
+
<div class="card mt-4">
<div class="card-header">
- <h3>YouTube</h3>
+ <h3>Metadane dla MP3, Ogg</h3>
</div>
<div class="card-body">
- {% if audiobook.youtube_status %}
- <hr/>
- <h2>YouTube</h2>
+ <table class='table'>
+ tagi, które zostałyby zapisane do pliku, gdyby go teraz opublikować
+ {% tags_table audiobook.new_publish_tags 0 %}
+ </table>
+ </div>
+ </div>
- <p>Status: <b>{{ audiobook.get_youtube_status_display }}</b></p>
- {% endif %}
+ </div>
- {% if audiobook.youtube_id %}
- {% if audiobook.youtube_id %}
- <p>
- <a href="https://youtu.be/{{ audiobook.youtube_id }}" target="_blank" title="{% trans "See on YouTube" %}">
- <img src="https://i.ytimg.com/vi/{{ audiobook.youtube_id }}/hq720.jpg" style="width: 100%">
- </a>
- </p>
- {% endif %}
- {% if audiobook.youtube_published %}
- <p>{% trans "Published:" %} {{ audiobook.youtube_published }}</a></p>
- {% if audiobook.get_youtube_published_tags.tags %}
- {% tags_table audiobook.get_youtube_published_tags.tags %}
- {% endif %}
- {% else %}
- <p>{% trans "Not published yet." %}</p>
+ <div class="col-xl-4">
+ <div class="card mt-4">
+ <div class="card-header">
+ <h3>
+ YouTube
+ {% if audiobook.youtube_status %}
+ <span class="badge badge-pill badge-primary">{{ audiobook.get_youtube_status_display }}</span>
{% endif %}
- {% else %}
- <p>{% trans "YouTube file hasn't been generated yet." %}</p>
+ </h3>
+ </div>
+ <div class="card-body">
+ <img src="{% url 'youtube_thumbnail' audiobook.id %}" style="width:100%">
+ <strong>{{ youtube_title }}</strong><br><br>
+ {{ youtube_description|linebreaksbr }}
+
+
+
+ {% if audiobook.is_youtube_publishable %}
+ <br>
+ <p>
+ <a href="{% url 'youtube_preview' audiobook.id %}">
+ {% trans "Preview YouTube metadata" %}
+ </a>
+ </p>
+
{% endif %}
{% if audiobook.youtube_id %}
<input class="btn btn-secondary" type="submit" value="{% trans "Update YouTube metadata" %}">
</form>
{% endif %}
+ <form method="post" action="{% url 'youtube_publish' audiobook.id %}">
+ {% csrf_token %}
+ <input class="btn btn-primary" type="submit" value="{% trans "Publish on YouTube" %}" />
+ </form>
- {% if audiobook.is_youtube_publishable %}
- <br>
+ </div>
+ <div class="card-footer">
+ {% if audiobook.youtube_id %}
<p>
- <a href="{% url 'youtube_preview' audiobook.id %}">
- {% trans "Preview YouTube metadata" %}
+ <a href="https://youtu.be/{{ audiobook.youtube_id }}" target="_blank" title="{% trans "See on YouTube" %}">
+ <img src="https://i.ytimg.com/vi/{{ audiobook.youtube_id }}/hq720.jpg" style="width: 100%">
</a>
</p>
- <form method="post" action="{% url 'youtube_publish' audiobook.id %}">
- {% csrf_token %}
- <input class="btn btn-primary" type="submit" value="{% trans "Publish on YouTube" %}" />
- </form>
{% endif %}
+ <p>{% trans "Published:" %} {{ audiobook.youtube_published }}</a></p>
</div>
</div>
</div>
+
</div>
-<div class="row">
- <div class="col-xl-6">
-
- <div class="card mt-4">
- <div class="card-header">
- <h3>Metadane dla MP3, Ogg</h3>
- </div>
- <div class="card-body">
- <table class='table'>
- {% tags_table audiobook.new_publish_tags 0 %}
- </table>
- </div>
- </div>
-
- </div>
- <div class="col-xl-6">
- <div class="card mt-4">
- <div class="card-header">
- <h3>Metadane dla YouTube</h3>
- </div>
- <div class="card-body">
- <img src="{% url 'youtube_thumbnail' audiobook.id %}" style="width:100%">
- <strong>{{ youtube_title }}</strong><br><br>
- {{ youtube_description|linebreaksbr }}
- </div>
- </div>
- </div>
-
-</div>
<div class="card mt-4 mb-4">
<div class="card-header">
<h2>Plik źródłowy</h2>
+ (informacje fizycznie ze źródłowego pliku)
</div>
<div class="card-body">
- <p>Last modified: {{ audiobook.modified }}</p>
- <p>Plik źródłowy: <a href='{{ audiobook.source_file.url }}'>{{ path }}</a>
- (sha1: <tt>{{ audiobook.source_sha1 }}</tt>).
- </p>
- {% multiple_tags_table tags %}
-
- <form method="post" action="{% url 'remove_to_archive' audiobook.id %}"
- onsubmit='return confirm("{% trans "Are you sure you want to move this audiobook to archive?" %}")'>
- {% csrf_token %}
- <input class="btn btn-danger" type="submit" value="{% trans "Remove to archive" %}" />
- </form>
+ <div class="row">
+ <div class="col-lg-8">
+
+ <p>Last modified: {{ audiobook.modified }}</p>
+ <p>Plik źródłowy: <a href='{{ audiobook.source_file.url }}'>{{ path }}</a>
+ (sha1: <tt>{{ audiobook.source_sha1 }}</tt>).
+ </p>
+ {% multiple_tags_table tags %}
+ </div>
+
+ <div class="col-xl-4">
+ <div class="card">
+ <div class="card-body">
+ <form method="post" action="{% url 'remove_to_archive' audiobook.id %}"
+ onsubmit='return confirm("{% trans "Are you sure you want to move this audiobook to archive?" %}")'>
+ {% csrf_token %}
+
+ <p>
+ Jeśli ten plik nie jest potrzebny, możesz usunąć informacje o nim z systemu,
+ zachowując go tylko w postaci samego źródłowego pliku.
+ </p>
+
+ <input class="btn btn-danger" type="submit" value="{% trans "Remove to archive" %}" />
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
</div>
</div>
+
{% endblock %}
<table class="table">
{% for file in objects %}
<tr>
- <td><a href='{% url "file" file.id %}'>{{ file }}</a></td>
+ <td>
+ <a href='{% url "file" file.id %}'>
+ {{ file }}
+ {% if file.youtube_status == k.0 and file.youtube_volume %}
+ <br>
+ <small>
+ ({{ file.youtube_volume_index }}/{{ file.youtube_volume_count }})
+ {{ file.youtube_volume }}
+ </small>
+ {% endif %}
+ </a>
+ </td>
<td class="text-warning">
{% if file.mp3_status == k.0 %}MP3{% endif %}
</td>
"format": format,
"link": link,
}
+
+
+
+@register.filter
+def duration(s):
+ try:
+ h = int(s / 3600)
+ except:
+ return s
+ s %= 3600
+ m = int(s / 60)
+ s %= 60
+ return f'{h}:{m:02d}:{s:02.1f}'
-from django.conf.urls import url
from django.urls import path
from django.views.generic import RedirectView
from . import views
urlpatterns = [
path("", views.AudiobookList.as_view(), name="list_managed"),
- url(r'^new/$', views.list_new, name="list_new"),
- url(r'^new/(.+)/$', views.file_new, name="file_new"),
- url(r'^move_to_archive/(.+)/$', views.move_to_archive, name="move_to_archive"),
- url(r'^publishing/$', views.list_publishing, name="list_publishing"),
+ path('new/', views.list_new, name="list_new"),
+ path('new/<path:filename>/', views.file_new, name="file_new"),
+ path('move_to_archive/<path:filename>/', views.move_to_archive, name="move_to_archive"),
+ path('publishing/', views.list_publishing, name="list_publishing"),
path('book/<slug:slug>/', views.BookView.as_view(), name="book"),
- url(r'^file/(\d+)/$', views.file_managed, name="file"),
- url(r'^publish/(\d+)/$', views.publish, name="publish"),
- url(r'^convert/(\d+)/$', views.publish, {'publish': False}, name="convert"),
- url(r'^download/(\d+)/$', views.download, name="download"),
- url(r'^download/(\d+)\.(mp3|ogg|mkv)$', views.download, name="download"),
- url(r'^cancel/(\d+)/$', views.cancel_publishing, name="cancel_publishing"),
- url(r'^remove_to_archive/(\d+)/$', views.remove_to_archive, name="remove_to_archive"),
- url(r'^unmanaged/$', views.list_unmanaged, name="list_unmanaged"),
- url(r'^unmanaged/(.+)/$', views.file_unmanaged, name="file_unmanaged"),
- url(r'^move_to_new/(.+)/$', views.move_to_new, name="move_to_new"),
+ path('book-youtube-volume/<int:aid>/', views.book_youtube_volume, name="book_youtube_volume"),
+ path('file/<int:id>/', views.file_managed, name="file"),
+ path('publish/<int:aid>/', views.publish, name="publish"),
+ path('convert/<int:aid>/', views.publish, {'publish': False}, name="convert"),
+ path('download/<int:aid>/', views.download, name="download"),
+ path('download/<int:aid>.<slug:which>', views.download, name="download"),
+ path('cancel/<int:aid>/', views.cancel_publishing, name="cancel_publishing"),
+ path('remove_to_archive/<int:aid>/', views.remove_to_archive, name="remove_to_archive"),
+ path('unmanaged/', views.list_unmanaged, name="list_unmanaged"),
+ path('unmanaged/<path:filename>/', views.file_unmanaged, name="file_unmanaged"),
+ path('move_to_new/<path:filename>/', views.move_to_new, name="move_to_new"),
]
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)
+ old_path = os.path.join(settings.NEW_PATH, filename)
+ new_path = os.path.join(settings.UNMANAGED_PATH, filename)
new_dir = os.path.split(new_path)[0]
if not os.path.isdir(new_dir):
os.makedirs(new_dir)
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)
+ old_path = os.path.join(settings.UNMANAGED_PATH, filename)
+ new_path = os.path.join(settings.NEW_PATH, filename)
new_dir = os.path.split(new_path)[0]
if not os.path.isdir(new_dir):
os.makedirs(new_dir)
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.set_mp3_tags(tags)
- audiobook.set_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(request.user.id, aid, publish).task_id
- audiobook.ogg_task = tasks.OggTask.delay(request.user.id, aid, publish).task_id
- audiobook.save()
-
+ audiobook.publish(request.user, publish=publish)
return redirect(file_managed, aid)
def file_unmanaged(request, filename):
- tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename.encode('utf-8')))
+ tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename))
if not tags:
tags = {}
template_name = 'archive/book.html'
def get_queryset(self):
- return models.Audiobook.objects.filter(slug=self.kwargs["slug"]).order_by(
+ qs = models.Audiobook.objects.filter(slug=self.kwargs["slug"]).order_by(
"index"
)
+ last_vol = None
+ last_vol_sub = None
+ for b in qs:
+ if last_vol is None or last_vol.youtube_volume_index != b.youtube_volume_index:
+ last_vol = b
+ b.total = 0
+ if last_vol_sub is None or b.youtube_volume:
+ last_vol_sub = last_vol
+ last_vol_sub.total_for_sub = 0
+ last_vol.total += b.duration
+ last_vol_sub.total_for_sub += b.duration
+ b.subtotal = last_vol_sub.total_for_sub
+ return list(qs)
+
+
+@permission_required('archive.change_audiobook')
+def book_youtube_volume(request, aid):
+ audiobook = get_object_or_404(models.Audiobook, id=aid)
+ slug = audiobook.slug
+ cur_vol = audiobook.youtube_volume
+ new_vol = request.POST.get('volume', '')
+
+ audiobook.youtube_volume = new_vol
+ audiobook.save()
+
+ for a in models.Audiobook.objects.filter(slug=slug, youtube_volume=cur_vol, index__gt=audiobook.index).order_by('index'):
+ if a.youtube_volume != cur_vol:
+ break
+ a.youtube_volume = new_vol
+ a.save()
+
+ return redirect('book', audiobook.slug)
+
}
}
+DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
+
+
# 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.
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-04-01 14:18+0200\n"
-"PO-Revision-Date: 2020-04-01 14:18+0200\n"
+"POT-Creation-Date: 2022-10-25 13:56+0200\n"
+"PO-Revision-Date: 2022-10-25 13:57+0200\n"
+"Last-Translator: \n"
+"Language-Team: \n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n"
-"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n"
-"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
-"Last-Translator: \n"
-"Language-Team: \n"
-"X-Generator: Poedit 2.2.4\n"
+"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && "
+"(n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && "
+"n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
+"X-Generator: Poedit 3.0.1\n"
+
+#: youtube/models.py:37
+msgid "public"
+msgstr "publiczny"
+
+#: youtube/models.py:38
+msgid "unlisted"
+msgstr "niepubliczny"
+
+#: youtube/models.py:39
+msgid "private"
+msgstr "prywatny"
-#: models.py:23
+#: youtube/models.py:44
msgid "YouTube configuration"
msgstr "konfiguracja YouTube"
-#: models.py:24
+#: youtube/models.py:45
msgid "YouTube configurations"
msgstr "konfiguracje YouTube"
.exclude(youtube_queued=None)
.order_by("youtube_queued")[: options["limit"]]
):
- audiobook.youtube_task = tasks.YouTubeTask.delay(
+ tasks.YouTubeTask.delay(
None, audiobook.id, True
).task_id
audiobook.youtube_status = status.WAITING
- audiobook.save(update_fields=["youtube_task", "youtube_status"])
+ audiobook.save(update_fields=["youtube_status"])
ThumbnailTemplate = apps.get_model('youtube', 'ThumbnailTemplate')
order = 1
for youtube in YouTube.objects.all():
- src_def = yaml.load(youtube.thumbnail_definition)
+ src_def = yaml.safe_load(youtube.thumbnail_definition)
version_lists = [
box['versions']
for box in src_def['boxes']
def encode(self, in_paths, out_path):
self.audiobook.project.youtube.prepare_file(in_paths, out_path)
- def set_tags(self, audiobook, filename):
+ def set_tags(self, tags, filename):
pass
@classmethod
def get_source_file_paths(self, audiobook):
if not audiobook.youtube_volume:
- return [audiobook.source_file.path]
- return [
- a.source_file.path
- for a in type(audiobook)
- .objects.filter(
- slug=audiobook.slug, youtube_volume=audiobook.youtube_volume
- )
- .order_by("index")
- ]
+ paths = [audiobook.source_file.path]
+ else:
+ paths = [
+ a.source_file.path
+ for a in type(audiobook)
+ .objects.filter(
+ slug=audiobook.slug, youtube_volume=audiobook.youtube_volume
+ )
+ .order_by("index")
+ ]
+ if audiobook.project.info_flac:
+ paths.append(audiobook.project.info_flac.path)
+ return paths
def create_thumbnail(background_path, defn, context, get_font_path):
img = Image.open(background_path)
- d = yaml.load(defn)
+ d = yaml.safe_load(defn)
for boxdef in d['boxes']:
if not draw_box_with_scaling(img, boxdef, context, get_font_path):
raise ValueError()
-from django.conf.urls import url
from django.urls import path
from . import views
urlpatterns = [
- url(r'^publish/(\d+)/$', views.publish, name="youtube_publish"),
+ path('publish/<int:aid>/', views.publish, name="youtube_publish"),
path('book/<slug:slug>/publish/', views.book_publish, name="youtube_book_publish"),
path('thumbnail/<int:aid>/', views.thumbnail, name='youtube_thumbnail'),
path('thumbnail/<int:aid>/<int:thumbnail_id>/', views.thumbnail, name='youtube_thumbnail'),
+import hashlib
import os
import shutil
import subprocess
output_path = tmp.name
if cache_key:
- cache_path = FILE_CACHE + cache_key.replace('/', '__')
+ cache_path = cache_key.replace('/', '__')
+ if len(cache_path) > 200:
+ parts = cache_path.rsplit('.', 1)
+ limit = 200 - 9
+ if len(parts) > 1:
+ limit -= len(parts[1]) + 1
+ cache_path = parts[0][:limit] + '.' + hashlib.sha1(cache_key.encode('utf-8')).hexdigest()[:8]
+ if len(parts) > 1:
+ cache_path += '.' + parts[1]
+ cache_path = FILE_CACHE + cache_path
if cache_key and os.path.exists(cache_path):
link_or_copy(cache_path, output_path)