Off by one error. master
authorRadek Czajka <rczajka@rczajka.pl>
Mon, 26 Jun 2023 09:38:35 +0000 (11:38 +0200)
committerRadek Czajka <rczajka@rczajka.pl>
Mon, 26 Jun 2023 09:47:22 +0000 (11:47 +0200)
30 files changed:
requirements.txt
src/apiclient/urls.py
src/archive/admin.py
src/archive/forms.py
src/archive/locale/pl/LC_MESSAGES/django.mo
src/archive/locale/pl/LC_MESSAGES/django.po
src/archive/migrations/0023_project_info_flac.py [new file with mode: 0644]
src/archive/migrations/0024_auto_20211222_1604.py [new file with mode: 0644]
src/archive/migrations/0025_create_config.py [new file with mode: 0644]
src/archive/migrations/0026_auto_20211222_1605.py [new file with mode: 0644]
src/archive/migrations/0027_auto_20221025_1336.py [new file with mode: 0644]
src/archive/migrations/0028_auto_20221025_1341.py [new file with mode: 0644]
src/archive/migrations/0029_auto_20221025_1357.py [new file with mode: 0644]
src/archive/models.py
src/archive/tasks.py
src/archive/templates/archive/book.html
src/archive/templates/archive/file_managed.html
src/archive/templates/archive/list_publishing.html
src/archive/templatetags/tags.py
src/archive/urls.py
src/archive/views.py
src/audiobooks/settings.py
src/youtube/locale/pl/LC_MESSAGES/django.mo
src/youtube/locale/pl/LC_MESSAGES/django.po
src/youtube/management/commands/youtube.py
src/youtube/migrations/0012_move_thumbnail_definitions.py
src/youtube/tasks.py
src/youtube/thumbnail.py
src/youtube/urls.py
src/youtube/utils.py

index 20e30d7..5c31d8c 100644 (file)
@@ -1,19 +1,20 @@
--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
index ad3f166..58dec53 100755 (executable)
@@ -1,10 +1,10 @@
-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'),
 ]
index 10f1a33..f9249af 100644 (file)
@@ -1,4 +1,4 @@
-from archive.models import Project, Audiobook, License
+from archive.models import Project, Audiobook, License, Config
 from django.contrib import admin
 
 admin.site.register(Project)
@@ -6,7 +6,12 @@ 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']
@@ -14,3 +19,4 @@ class AudiobookAdmin(admin.ModelAdmin):
 
 admin.site.register(Audiobook, AudiobookAdmin)
 admin.site.register(License)
+admin.site.register(Config)
index 3b68c28..eca8f1a 100644 (file)
@@ -15,7 +15,9 @@ from archive.utils import ExistingFile, sha1_file
 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.
index 666b791..dca91ae 100644 (file)
Binary files a/src/archive/locale/pl/LC_MESSAGES/django.mo and b/src/archive/locale/pl/LC_MESSAGES/django.mo differ
index 6fb331d..526f50d 100644 (file)
@@ -7,8 +7,8 @@ msgid ""
 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"
@@ -17,89 +17,97 @@ msgstr ""
 "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."
@@ -107,241 +115,247 @@ msgstr ""
 "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"
 
diff --git a/src/archive/migrations/0023_project_info_flac.py b/src/archive/migrations/0023_project_info_flac.py
new file mode 100644 (file)
index 0000000..99897c4
--- /dev/null
@@ -0,0 +1,18 @@
+# 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'),
+        ),
+    ]
diff --git a/src/archive/migrations/0024_auto_20211222_1604.py b/src/archive/migrations/0024_auto_20211222_1604.py
new file mode 100644 (file)
index 0000000..f4c724c
--- /dev/null
@@ -0,0 +1,34 @@
+# 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'),
+        ),
+    ]
diff --git a/src/archive/migrations/0025_create_config.py b/src/archive/migrations/0025_create_config.py
new file mode 100644 (file)
index 0000000..f297676
--- /dev/null
@@ -0,0 +1,25 @@
+# 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
+        )
+    ]
diff --git a/src/archive/migrations/0026_auto_20211222_1605.py b/src/archive/migrations/0026_auto_20211222_1605.py
new file mode 100644 (file)
index 0000000..5b09693
--- /dev/null
@@ -0,0 +1,19 @@
+# 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'),
+        ),
+    ]
diff --git a/src/archive/migrations/0027_auto_20221025_1336.py b/src/archive/migrations/0027_auto_20221025_1336.py
new file mode 100644 (file)
index 0000000..6e6e8a8
--- /dev/null
@@ -0,0 +1,55 @@
+# 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'),
+        ),
+    ]
diff --git a/src/archive/migrations/0028_auto_20221025_1341.py b/src/archive/migrations/0028_auto_20221025_1341.py
new file mode 100644 (file)
index 0000000..bff3f8c
--- /dev/null
@@ -0,0 +1,29 @@
+# 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'),
+        ),
+    ]
diff --git a/src/archive/migrations/0029_auto_20221025_1357.py b/src/archive/migrations/0029_auto_20221025_1357.py
new file mode 100644 (file)
index 0000000..3aaae8f
--- /dev/null
@@ -0,0 +1,19 @@
+# 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'),
+        ),
+    ]
index 1fc033b..1dbb701 100644 (file)
@@ -1,5 +1,6 @@
 import io
 import json
+from os import unlink
 import os.path
 from urllib.parse import urljoin
 
@@ -14,6 +15,7 @@ import requests
 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):
@@ -30,8 +32,13 @@ class Project(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")
@@ -58,6 +65,34 @@ class Project(models.Model):
         )
 
 
+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?
 
@@ -89,26 +124,18 @@ class Audiobook(models.Model):
     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)
@@ -129,6 +156,18 @@ class Audiobook(models.Model):
     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
@@ -154,7 +193,7 @@ class Audiobook(models.Model):
         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()
         )
 
@@ -165,16 +204,16 @@ class Audiobook(models.Model):
         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:
@@ -220,10 +259,13 @@ class Audiobook(models.Model):
         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:
@@ -241,11 +283,9 @@ class Audiobook(models.Model):
         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()
index 3538a73..d95baac 100644 (file)
@@ -39,10 +39,7 @@ class AudioFormatTask(Task):
         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
@@ -61,10 +58,8 @@ class AudioFormatTask(Task):
             **{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,
         }
@@ -72,11 +67,10 @@ class AudioFormatTask(Task):
 
     @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,
@@ -108,19 +102,21 @@ class AudioFormatTask(Task):
         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]
@@ -175,12 +171,9 @@ class Mp3Task(AudioFormatTask):
             ])
 
     @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:]))
index 6f7bf1c..9cb1c80 100644 (file)
@@ -19,7 +19,7 @@
             <th>{% trans "Title" %}</th>
             <th>MP3</th>
             <th>Ogg</th>
-            <th colspan="3">YouTube</th>
+            <th colspan="5">YouTube</th>
           </tr>
         </thead>
         <tbody>
@@ -35,7 +35,7 @@
                       <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 %}
index 60d6b6c..c4e2a57 100644 (file)
@@ -17,7 +17,7 @@
 
 
 <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 %}
 
@@ -61,8 +73,6 @@
           <hr/>
           <h2>Ogg Vorbis</h2>
 
-          {% tags_table audiobook.get_ogg_tags.tags %}
-
           <p>Status: <b>{{ audiobook.get_ogg_status_display }}</b></p>
         {% endif %}
 
@@ -71,9 +81,6 @@
           <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 %}
@@ -85,9 +92,6 @@
           <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 %}
index ad13504..2a8512d 100644 (file)
     <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>
index c3dfa23..0fa23ac 100755 (executable)
@@ -34,3 +34,16 @@ def status(audiobook, format):
         "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}'
index 4743227..b057df4 100644 (file)
@@ -1,23 +1,23 @@
-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"),
 ]
index fab7a1c..6ae2927 100644 (file)
@@ -70,9 +70,8 @@ def file_new(request, filename):
 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)
@@ -131,9 +130,8 @@ def remove_to_archive(request, aid):
 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)
@@ -157,20 +155,7 @@ def move_to_new(request, filename):
 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)
 
 
@@ -287,7 +272,7 @@ def list_unmanaged(request):
 
 
 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 = {}
     
@@ -299,6 +284,39 @@ class BookView(ListView):
     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)
+    
index f36fe81..87f53dc 100644 (file)
@@ -20,6 +20,9 @@ DATABASES = {
     }
 }
 
+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.
index 1e86547..c6101b8 100644 (file)
Binary files a/src/youtube/locale/pl/LC_MESSAGES/django.mo and b/src/youtube/locale/pl/LC_MESSAGES/django.mo differ
index 6446cb2..87d3e6d 100644 (file)
@@ -7,23 +7,35 @@ msgid ""
 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"
index fd50e0c..c480830 100644 (file)
@@ -16,8 +16,8 @@ class Command(BaseCommand):
             .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"])
index a54cf12..15909f0 100644 (file)
@@ -18,7 +18,7 @@ def move_definitions(apps, schema_editor):
     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']
index cad3d11..7cf5eac 100644 (file)
@@ -10,7 +10,7 @@ class YouTubeTask(AudioFormatTask):
     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
@@ -23,12 +23,16 @@ class YouTubeTask(AudioFormatTask):
 
     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
index e40b552..c64e5b5 100644 (file)
@@ -70,7 +70,7 @@ def draw_box_with_scaling(img, d, context, get_font_path):
 
 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()
index 8a4618b..91dbeb4 100644 (file)
@@ -1,9 +1,8 @@
-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'),
index 3b64018..87fcf1a 100644 (file)
@@ -1,3 +1,4 @@
+import hashlib
 import os
 import shutil
 import subprocess
@@ -31,7 +32,16 @@ def process_to_file(cmdline, prefix='', suffix='', cache_key=None, output_path=N
         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)