mirror of
https://github.com/Kunsi/pretalx-plugin-broadcast-tools
synced 2024-12-03 17:12:43 +00:00
add downloadable lower thirds images to be used in voctomix
This commit is contained in:
parent
f459c6c498
commit
c0b3bdb55e
11 changed files with 651 additions and 83 deletions
|
@ -22,3 +22,4 @@ class PluginApp(AppConfig):
|
|||
|
||||
def ready(self):
|
||||
from . import signals # NOQA
|
||||
from . import tasks # NOQA
|
||||
|
|
94
pretalx_broadcast_tools/assets/titilium-web-regular.LICENSE
Normal file
94
pretalx_broadcast_tools/assets/titilium-web-regular.LICENSE
Normal file
|
@ -0,0 +1,94 @@
|
|||
Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino and students
|
||||
of MA course of Visual design. Some rights reserved.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
pretalx_broadcast_tools/assets/titilium-web-regular.ttf
Normal file
BIN
pretalx_broadcast_tools/assets/titilium-web-regular.ttf
Normal file
Binary file not shown.
|
@ -16,12 +16,22 @@ class BroadcastToolsSettingsForm(I18nFormMixin, HierarkeyForm):
|
|||
broadcast_tools_lower_thirds_info_string = I18nFormField(
|
||||
help_text=_(
|
||||
"Will only be shown if there's a talk running. You may use "
|
||||
"the place holders mentioned below."
|
||||
"the place holders mentioned below. The info line will be shown "
|
||||
"on the bottom right side of the lower third. Setting this to an "
|
||||
"empty string will hide the line entirely."
|
||||
),
|
||||
label=_("Info line"),
|
||||
required=False,
|
||||
widget=I18nTextInput,
|
||||
)
|
||||
broadcast_tools_lower_thirds_export_voctomix = BooleanField(
|
||||
help_text=_(
|
||||
"If checked, pretalx will periodically generate voctomix-compatible "
|
||||
"lower thirds images and make them available as <code>.tar.gz</code>."
|
||||
),
|
||||
label=_("Generate voctomix lower thirds"),
|
||||
required=False,
|
||||
)
|
||||
|
||||
broadcast_tools_room_info_lower_content = ChoiceField(
|
||||
choices=(
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-10-31 21:10+0100\n"
|
||||
"POT-Creation-Date: 2024-11-03 13:40+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -74,16 +74,31 @@ msgstr "\"Derzeit kein Vortrag\"-Informationen"
|
|||
#: forms.py:18
|
||||
msgid ""
|
||||
"Will only be shown if there's a talk running. You may use the place holders "
|
||||
"mentioned below."
|
||||
"mentioned below. The info line will be shown on the bottom right side of the "
|
||||
"lower third. Setting this to an empty string will hide the line entirely."
|
||||
msgstr ""
|
||||
"Wird nur angezeigt, wenn derzeit ein Vortrag läuft. Du kannst die oben "
|
||||
"genannten Platzhalter benutzen."
|
||||
"genannten Platzhalter benutzen. Die Info-Zeile wird unten rechts in den "
|
||||
"Bauchbinden angezeigt. Wenn das Feld leer ist, wird die Zeile automatisch "
|
||||
"ausgeblendet."
|
||||
|
||||
#: forms.py:21
|
||||
#: forms.py:23
|
||||
msgid "Info line"
|
||||
msgstr "Info-Zeile"
|
||||
|
||||
#: forms.py:37
|
||||
#: forms.py:29
|
||||
msgid ""
|
||||
"If checked, pretalx will periodically generate voctomix-compatible lower "
|
||||
"thirds images and make them available as <code>.tar.gz</code>."
|
||||
msgstr ""
|
||||
"Wenn aktiviert, wird pretalx automatisch voctomix-kompatible bauchbinden "
|
||||
"generieren und diese als <code>.tar.gz</code> zur Verfügung stellen."
|
||||
|
||||
#: forms.py:32
|
||||
msgid "Generate voctomix lower thirds"
|
||||
msgstr "Erzeuge voctomix-Bauchbinden"
|
||||
|
||||
#: forms.py:47
|
||||
msgid ""
|
||||
"If a talk is running, the room info page will always show the talk title and "
|
||||
"the list of speakers. The content below is configurable here."
|
||||
|
@ -92,11 +107,11 @@ msgstr ""
|
|||
"und die Liste der Vortragenden an. Der Inhalt unterhalb dessen ist hier "
|
||||
"konfigurierbar."
|
||||
|
||||
#: forms.py:41
|
||||
#: forms.py:51
|
||||
msgid "lower content"
|
||||
msgstr "Unterer Inhalt"
|
||||
|
||||
#: forms.py:46
|
||||
#: forms.py:56
|
||||
msgid ""
|
||||
"If no talk is running in the room, show the time and title of the next talk "
|
||||
"in the room."
|
||||
|
@ -104,11 +119,11 @@ msgstr ""
|
|||
"Wenn derzeit kein Vortrag läuft, soll die Startzeit und der Titel des "
|
||||
"nächsten Vortrags angezeigt werden."
|
||||
|
||||
#: forms.py:49
|
||||
#: forms.py:59
|
||||
msgid "Show next talk"
|
||||
msgstr "Zeige nächsten Vortrag"
|
||||
|
||||
#: forms.py:55
|
||||
#: forms.py:65
|
||||
msgid ""
|
||||
"If checked, the value of the 'internal notes' field in a submission will get "
|
||||
"added to the pdf export."
|
||||
|
@ -116,22 +131,22 @@ msgstr ""
|
|||
"Wenn aktiviert, wird der Inhalt des Feldes 'Interne Notizen' im PDF-Export "
|
||||
"angezeigt."
|
||||
|
||||
#: forms.py:58
|
||||
#: forms.py:68
|
||||
msgid "Show internal notes in pdf export"
|
||||
msgstr "Zeige interne Notizen im PDF-Export"
|
||||
|
||||
#: forms.py:63
|
||||
#: forms.py:73
|
||||
msgid ""
|
||||
"If checked, 'do not record' talks will not generate a page in the pdf export."
|
||||
msgstr ""
|
||||
"Wenn aktiviert, werden Vorträge mit 'Zeichnet meinen Vortrag nicht auf'-Flag "
|
||||
"keine Seite im PDF-Export generieren."
|
||||
|
||||
#: forms.py:66
|
||||
#: forms.py:76
|
||||
msgid "Ignore 'do not record' talks when generating pdf"
|
||||
msgstr "Ignoriere 'Zeichnet meinen Vortrag nicht auf'-Vorträge im PDF-Export"
|
||||
|
||||
#: forms.py:71
|
||||
#: forms.py:81
|
||||
msgid ""
|
||||
"Comma-Separated list of question ids to include in pdf export. If empty, no "
|
||||
"questions will get added."
|
||||
|
@ -139,11 +154,11 @@ msgstr ""
|
|||
"Komma-separierte Liste von Fragen, die im PDF-Export eingebunden werden. "
|
||||
"Falls dieses Feld leer ist, werden keine Fragen im PDF angezeigt."
|
||||
|
||||
#: forms.py:74
|
||||
#: forms.py:84
|
||||
msgid "Questions to include"
|
||||
msgstr "Eingebundene Fragen"
|
||||
|
||||
#: forms.py:79
|
||||
#: forms.py:89
|
||||
msgid ""
|
||||
"Additional content to print onto the PDF export. Will get printed as-is. You "
|
||||
"may use the place holders mentioned below."
|
||||
|
@ -151,7 +166,7 @@ msgstr ""
|
|||
"Zusätzlicher Inhalt, der auf dem PDF-Export angezeigt wird. Wird wie hier "
|
||||
"eingegeben angezeigt. Du kannst die oben genannten Platzhalter benutzen."
|
||||
|
||||
#: forms.py:83
|
||||
#: forms.py:93
|
||||
msgid "Additional text"
|
||||
msgstr "Zusätzlicher Text"
|
||||
|
||||
|
@ -167,36 +182,40 @@ msgstr ""
|
|||
msgid "broadcasting tools"
|
||||
msgstr "Broadcasting-Tools"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:14
|
||||
#: templates/pretalx_broadcast_tools/orga.html:13
|
||||
msgid "room"
|
||||
msgstr "Raum"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:15
|
||||
#: templates/pretalx_broadcast_tools/orga.html:14
|
||||
msgid "Feature"
|
||||
msgstr "Funktion"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:22
|
||||
#: templates/pretalx_broadcast_tools/orga.html:21
|
||||
#, fuzzy
|
||||
#| msgid "Lower thirds"
|
||||
msgid "Lower Thirds"
|
||||
msgstr "Bauchbinden"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:23
|
||||
#: templates/pretalx_broadcast_tools/orga.html:22
|
||||
#, fuzzy
|
||||
#| msgid "Room info"
|
||||
msgid "Room Info"
|
||||
msgstr "Raum-Information"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:30
|
||||
#: templates/pretalx_broadcast_tools/orga.html:27
|
||||
msgid "Download voctomix-compatible lower thirds images"
|
||||
msgstr "Lade voctomix-kompatible Bauchbinden-Bilder herunter"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:29
|
||||
msgid "Placeholders"
|
||||
msgstr "Platzhalter"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:31
|
||||
#: templates/pretalx_broadcast_tools/orga.html:30
|
||||
msgid ""
|
||||
"pretalx will automatically replace some placeholders in your custom content:"
|
||||
msgstr "pretalx ersetzt automatisch einige Platzhalter in deinen Texten:"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:34
|
||||
#: templates/pretalx_broadcast_tools/orga.html:33
|
||||
msgid ""
|
||||
"talk code (<code>MUX9U3</code> for example) - most useful in combination "
|
||||
"with pretalx-proposal-redirects or something like that"
|
||||
|
@ -204,54 +223,46 @@ msgstr ""
|
|||
"Vortrags-Slug (zum Beispiel <code>MUX9U3</code>) - am hilfreichsten in "
|
||||
"Kombination mit pretalx-proposal-redirects oder ähnlichen Plugins"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:37
|
||||
#: templates/pretalx_broadcast_tools/orga.html:36
|
||||
msgid "The event slug"
|
||||
msgstr "Der Event-Slug"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:40
|
||||
#: templates/pretalx_broadcast_tools/orga.html:39
|
||||
msgid "URL to the talk feedback page."
|
||||
msgstr "Adresse der Vortrags-Feedback-Seite"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:43
|
||||
#: templates/pretalx_broadcast_tools/orga.html:42
|
||||
msgid "The talk slug"
|
||||
msgstr "Der Vortrags-Slug"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:46
|
||||
#: templates/pretalx_broadcast_tools/orga.html:45
|
||||
msgid "URL to the talk detail page."
|
||||
msgstr "Adresse der Vortrags-Seite"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:48
|
||||
#: templates/pretalx_broadcast_tools/orga.html:47
|
||||
msgid "or"
|
||||
msgstr "oder"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:49
|
||||
#: templates/pretalx_broadcast_tools/orga.html:48
|
||||
msgid "Track name in plain text or coloured using the track colour."
|
||||
msgstr "Track-Name in einfachem Text oder in der Track-Farbe eingefärbt."
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:52
|
||||
#: templates/pretalx_broadcast_tools/orga.html:51
|
||||
msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:55
|
||||
#: templates/pretalx_broadcast_tools/orga.html:54
|
||||
msgid "Lower thirds"
|
||||
msgstr "Bauchbinden"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:59
|
||||
msgid ""
|
||||
"The info line will be shown on the bottom right side of your lower third. If "
|
||||
"you set it to an empty string, it will automatically hide itself."
|
||||
msgstr ""
|
||||
"Die Info-Zeile wird unten rechts in den Bauchbinden angezeigt. Wenn das Feld "
|
||||
"leer ist, wird die Zeile automatisch ausgeblendet."
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:63
|
||||
#: templates/pretalx_broadcast_tools/orga.html:62
|
||||
msgid "Room info"
|
||||
msgstr "Raum-Information"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:70
|
||||
#: templates/pretalx_broadcast_tools/orga.html:69
|
||||
msgid "PDF export"
|
||||
msgstr "PDF-Export"
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:86
|
||||
#: templates/pretalx_broadcast_tools/orga.html:85
|
||||
msgid "Save"
|
||||
msgstr "Speichern"
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-10-31 21:10+0100\n"
|
||||
"POT-Creation-Date: 2024-11-03 13:40+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -71,69 +71,80 @@ msgstr ""
|
|||
#: forms.py:18
|
||||
msgid ""
|
||||
"Will only be shown if there's a talk running. You may use the place holders "
|
||||
"mentioned below."
|
||||
"mentioned below. The info line will be shown on the bottom right side of the "
|
||||
"lower third. Setting this to an empty string will hide the line entirely."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:21
|
||||
#: forms.py:23
|
||||
msgid "Info line"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:37
|
||||
#: forms.py:29
|
||||
msgid ""
|
||||
"If checked, pretalx will periodically generate voctomix-compatible lower "
|
||||
"thirds images and make them available as <code>.tar.gz</code>."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:32
|
||||
msgid "Generate voctomix lower thirds"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:47
|
||||
msgid ""
|
||||
"If a talk is running, the room info page will always show the talk title and "
|
||||
"the list of speakers. The content below is configurable here."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:41
|
||||
#: forms.py:51
|
||||
msgid "lower content"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:46
|
||||
#: forms.py:56
|
||||
msgid ""
|
||||
"If no talk is running in the room, show the time and title of the next talk "
|
||||
"in the room."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:49
|
||||
#: forms.py:59
|
||||
msgid "Show next talk"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:55
|
||||
#: forms.py:65
|
||||
msgid ""
|
||||
"If checked, the value of the 'internal notes' field in a submission will get "
|
||||
"added to the pdf export."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:58
|
||||
#: forms.py:68
|
||||
msgid "Show internal notes in pdf export"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:63
|
||||
#: forms.py:73
|
||||
msgid ""
|
||||
"If checked, 'do not record' talks will not generate a page in the pdf export."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:66
|
||||
#: forms.py:76
|
||||
msgid "Ignore 'do not record' talks when generating pdf"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:71
|
||||
#: forms.py:81
|
||||
msgid ""
|
||||
"Comma-Separated list of question ids to include in pdf export. If empty, no "
|
||||
"questions will get added."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:74
|
||||
#: forms.py:84
|
||||
msgid "Questions to include"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:79
|
||||
#: forms.py:89
|
||||
msgid ""
|
||||
"Additional content to print onto the PDF export. Will get printed as-is. You "
|
||||
"may use the place holders mentioned below."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:83
|
||||
#: forms.py:93
|
||||
msgid "Additional text"
|
||||
msgstr ""
|
||||
|
||||
|
@ -149,83 +160,81 @@ msgstr ""
|
|||
msgid "broadcasting tools"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:14
|
||||
#: templates/pretalx_broadcast_tools/orga.html:13
|
||||
msgid "room"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:15
|
||||
#: templates/pretalx_broadcast_tools/orga.html:14
|
||||
msgid "Feature"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:22
|
||||
#: templates/pretalx_broadcast_tools/orga.html:21
|
||||
msgid "Lower Thirds"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:23
|
||||
#: templates/pretalx_broadcast_tools/orga.html:22
|
||||
msgid "Room Info"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:30
|
||||
#: templates/pretalx_broadcast_tools/orga.html:27
|
||||
msgid "Download voctomix-compatible lower thirds images"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:29
|
||||
msgid "Placeholders"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:31
|
||||
#: templates/pretalx_broadcast_tools/orga.html:30
|
||||
msgid ""
|
||||
"pretalx will automatically replace some placeholders in your custom content:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:34
|
||||
#: templates/pretalx_broadcast_tools/orga.html:33
|
||||
msgid ""
|
||||
"talk code (<code>MUX9U3</code> for example) - most useful in combination "
|
||||
"with pretalx-proposal-redirects or something like that"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:37
|
||||
#: templates/pretalx_broadcast_tools/orga.html:36
|
||||
msgid "The event slug"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:40
|
||||
#: templates/pretalx_broadcast_tools/orga.html:39
|
||||
msgid "URL to the talk feedback page."
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:43
|
||||
#: templates/pretalx_broadcast_tools/orga.html:42
|
||||
msgid "The talk slug"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:46
|
||||
#: templates/pretalx_broadcast_tools/orga.html:45
|
||||
msgid "URL to the talk detail page."
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:48
|
||||
#: templates/pretalx_broadcast_tools/orga.html:47
|
||||
msgid "or"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:49
|
||||
#: templates/pretalx_broadcast_tools/orga.html:48
|
||||
msgid "Track name in plain text or coloured using the track colour."
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:52
|
||||
#: templates/pretalx_broadcast_tools/orga.html:51
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:55
|
||||
#: templates/pretalx_broadcast_tools/orga.html:54
|
||||
msgid "Lower thirds"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:59
|
||||
msgid ""
|
||||
"The info line will be shown on the bottom right side of your lower third. If "
|
||||
"you set it to an empty string, it will automatically hide itself."
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:63
|
||||
#: templates/pretalx_broadcast_tools/orga.html:62
|
||||
msgid "Room info"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:70
|
||||
#: templates/pretalx_broadcast_tools/orga.html:69
|
||||
msgid "PDF export"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pretalx_broadcast_tools/orga.html:86
|
||||
#: templates/pretalx_broadcast_tools/orga.html:85
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
|
|
@ -0,0 +1,336 @@
|
|||
import logging
|
||||
import tarfile
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django_scopes import scope, scopes_disabled
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from pretalx.agenda.management.commands.export_schedule_html import delete_directory
|
||||
from pretalx.event.models import Event
|
||||
|
||||
from pretalx_broadcast_tools.utils.placeholders import placeholders
|
||||
|
||||
IMG_WIDTH = 1920 # px
|
||||
IMG_HEIGHT = 1080 # px
|
||||
|
||||
FONT_SIZE_TITLE = 30 # px
|
||||
FONT_SIZE_SPEAKER = 25 # px
|
||||
FONT_SIZE_INFOLINE = 18 # px
|
||||
FONT_FILE = (
|
||||
Path(__file__).resolve().parent.parent.parent
|
||||
/ "assets"
|
||||
/ "titilium-web-regular.ttf"
|
||||
)
|
||||
|
||||
BOX_PADDING = 10 # px
|
||||
|
||||
BOX_WIDTH = int(IMG_WIDTH * 0.8)
|
||||
BOX_BOTTOM = int(IMG_HEIGHT - (IMG_HEIGHT * 0.1))
|
||||
BOX_LEFT = int((IMG_WIDTH - BOX_WIDTH) / 2)
|
||||
|
||||
|
||||
def get_export_path(event):
|
||||
return settings.HTMLEXPORT_ROOT / event.slug / "broadcast-tools"
|
||||
|
||||
|
||||
def get_export_targz_path(event):
|
||||
return get_export_path(event).with_suffix(".voctomix.tar.gz")
|
||||
|
||||
|
||||
def make_targz(generated_files, targz_path):
|
||||
tmp_name = targz_path.with_suffix(".tmp")
|
||||
tmp_name.unlink(missing_ok=True)
|
||||
with tarfile.open(tmp_name, "w:gz") as tar:
|
||||
for file in generated_files:
|
||||
tar.add(file, arcname=file.name)
|
||||
tmp_name.rename(targz_path)
|
||||
|
||||
|
||||
class VoctomixLowerThirdsExporter:
|
||||
def __init__(self, event, tmp_dir):
|
||||
self.log = logging.getLogger(event.slug)
|
||||
self.event = event
|
||||
self.tmp_dir = tmp_dir
|
||||
self.exported = set()
|
||||
|
||||
if event.primary_color:
|
||||
self.primary_colour = self._hex2rgb(event.primary_color)
|
||||
else:
|
||||
# pretalx.settings.DEFAULT_EVENT_PRIMARY_COLOR
|
||||
self.primary__color = (58, 165, 124)
|
||||
|
||||
self.infoline = event.settings.broadcast_tools_lower_thirds_info_string or ""
|
||||
|
||||
self.font_title = ImageFont.truetype(
|
||||
FONT_FILE,
|
||||
size=FONT_SIZE_TITLE,
|
||||
encoding="unic",
|
||||
)
|
||||
self.font_speaker = ImageFont.truetype(
|
||||
FONT_FILE,
|
||||
size=FONT_SIZE_SPEAKER,
|
||||
encoding="unic",
|
||||
)
|
||||
self.font_infoline = ImageFont.truetype(
|
||||
FONT_FILE,
|
||||
size=FONT_SIZE_INFOLINE,
|
||||
encoding="unic",
|
||||
)
|
||||
|
||||
def _hex2rgb(self, hex_value):
|
||||
hex_value = hex_value.lstrip("#")
|
||||
# black insists this should have spaces around the :, but flake8
|
||||
# complains about spaces around the :, soooooo ....
|
||||
return tuple(int(hex_value[i : i + 2], 16) for i in (0, 2, 4)) # NOQA
|
||||
|
||||
def _fit_text(self, input_text, font, max_width):
|
||||
words = [i.strip() for i in input_text.split()]
|
||||
lines = []
|
||||
line = []
|
||||
for word in words:
|
||||
new_line = " ".join([*line, word])
|
||||
_, _, w, _ = font.getbbox(new_line)
|
||||
if w > max_width:
|
||||
# append old line to list of lines, then start new line with
|
||||
# current word
|
||||
lines.append(" ".join(line))
|
||||
line = [word]
|
||||
elif word.endswith(":"):
|
||||
lines.append(new_line)
|
||||
line = []
|
||||
else:
|
||||
line.append(word)
|
||||
|
||||
if line:
|
||||
lines.append(" ".join(line))
|
||||
return lines
|
||||
|
||||
def _get_infoline(self, talk):
|
||||
infoline = self.infoline.localize(self.event.locale).format(
|
||||
**placeholders(
|
||||
self.event,
|
||||
talk,
|
||||
)
|
||||
)
|
||||
_, _, w, _ = self.font_infoline.getbbox(infoline)
|
||||
return w, infoline
|
||||
|
||||
def export_speaker(self, talk, speaker):
|
||||
img = Image.new(
|
||||
mode="RGBA",
|
||||
size=(IMG_WIDTH, IMG_HEIGHT),
|
||||
color=(0, 0, 0, 0),
|
||||
)
|
||||
|
||||
speaker_text = self._fit_text(
|
||||
speaker.get_display_name(),
|
||||
self.font_speaker,
|
||||
BOX_WIDTH,
|
||||
)
|
||||
infoline_width, infoline_text = self._get_infoline(talk)
|
||||
|
||||
y_pos = BOX_BOTTOM - BOX_PADDING
|
||||
if speaker_text:
|
||||
y_pos -= len(speaker_text) * FONT_SIZE_SPEAKER
|
||||
if infoline_text:
|
||||
y_pos -= FONT_SIZE_INFOLINE
|
||||
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.rectangle(
|
||||
[
|
||||
(BOX_LEFT - BOX_PADDING, y_pos),
|
||||
(BOX_LEFT + BOX_WIDTH + BOX_PADDING, BOX_BOTTOM + BOX_PADDING),
|
||||
],
|
||||
fill=self.primary_colour,
|
||||
)
|
||||
if talk.submission.track and talk.submission.track.color:
|
||||
draw.rectangle(
|
||||
[
|
||||
(BOX_LEFT - BOX_PADDING, BOX_BOTTOM + BOX_PADDING),
|
||||
(
|
||||
BOX_LEFT + BOX_WIDTH + BOX_PADDING,
|
||||
BOX_BOTTOM + (BOX_PADDING * 2),
|
||||
),
|
||||
],
|
||||
fill=self._hex2rgb(talk.submission.track.color),
|
||||
)
|
||||
|
||||
for line in speaker_text:
|
||||
draw.text(
|
||||
(BOX_LEFT, y_pos),
|
||||
line,
|
||||
font=self.font_speaker,
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
y_pos += FONT_SIZE_SPEAKER
|
||||
|
||||
if infoline_text:
|
||||
draw.text(
|
||||
(BOX_LEFT + BOX_WIDTH - infoline_width, y_pos),
|
||||
infoline_text,
|
||||
font=self.font_infoline,
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
|
||||
filename = self.tmp_dir / f"event_{talk.submission_id}_person_{speaker.id}.png"
|
||||
img.save(filename)
|
||||
self.log.debug(
|
||||
f"Generated single-speaker image for {speaker.get_display_name()!r} "
|
||||
"of talk {talk.submission.title!r}, saved as {filename}"
|
||||
)
|
||||
return filename
|
||||
|
||||
def export_talk(self, talk):
|
||||
img = Image.new(
|
||||
mode="RGBA",
|
||||
size=(IMG_WIDTH, IMG_HEIGHT),
|
||||
color=(0, 0, 0, 0),
|
||||
)
|
||||
|
||||
title_text = self._fit_text(talk.submission.title, self.font_title, BOX_WIDTH)
|
||||
speaker_text = self._fit_text(
|
||||
", ".join(
|
||||
[person.get_display_name() for person in talk.submission.speakers.all()]
|
||||
),
|
||||
self.font_speaker,
|
||||
BOX_WIDTH,
|
||||
)
|
||||
infoline_width, infoline_text = self._get_infoline(talk)
|
||||
|
||||
y_pos = BOX_BOTTOM - BOX_PADDING
|
||||
if title_text:
|
||||
y_pos -= len(title_text) * FONT_SIZE_TITLE
|
||||
if speaker_text:
|
||||
y_pos -= len(speaker_text) * FONT_SIZE_SPEAKER
|
||||
if title_text and speaker_text:
|
||||
y_pos -= BOX_PADDING
|
||||
if infoline_text:
|
||||
y_pos -= FONT_SIZE_INFOLINE
|
||||
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.rectangle(
|
||||
[
|
||||
(BOX_LEFT - BOX_PADDING, y_pos),
|
||||
(BOX_LEFT + BOX_WIDTH + BOX_PADDING, BOX_BOTTOM + BOX_PADDING),
|
||||
],
|
||||
fill=self.primary_colour,
|
||||
)
|
||||
if talk.submission.track and talk.submission.track.color:
|
||||
draw.rectangle(
|
||||
[
|
||||
(BOX_LEFT - BOX_PADDING, BOX_BOTTOM + BOX_PADDING),
|
||||
(
|
||||
BOX_LEFT + BOX_WIDTH + BOX_PADDING,
|
||||
BOX_BOTTOM + (BOX_PADDING * 2),
|
||||
),
|
||||
],
|
||||
fill=self._hex2rgb(talk.submission.track.color),
|
||||
)
|
||||
|
||||
for line in title_text:
|
||||
draw.text(
|
||||
(BOX_LEFT, y_pos),
|
||||
line,
|
||||
font=self.font_title,
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
y_pos += FONT_SIZE_TITLE
|
||||
|
||||
if title_text and speaker_text:
|
||||
y_pos += BOX_PADDING
|
||||
|
||||
for line in speaker_text:
|
||||
draw.text(
|
||||
(BOX_LEFT, y_pos),
|
||||
line,
|
||||
font=self.font_speaker,
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
y_pos += FONT_SIZE_SPEAKER
|
||||
|
||||
if infoline_text:
|
||||
draw.text(
|
||||
(BOX_LEFT + BOX_WIDTH - infoline_width, y_pos),
|
||||
infoline_text,
|
||||
font=self.font_infoline,
|
||||
fill=(255, 255, 255),
|
||||
)
|
||||
|
||||
filename = self.tmp_dir / f"event_{talk.submission_id}_persons.png"
|
||||
img.save(filename)
|
||||
self.log.debug(
|
||||
f"Generated image for talk {talk.submission.title!r}, saved as {filename}"
|
||||
)
|
||||
return filename
|
||||
|
||||
def export(self):
|
||||
generated_files = set()
|
||||
if not self.event.current_schedule:
|
||||
raise CommandError(
|
||||
f"event {self.event.slug} ({self.event.name}) does not have a schedule to be exported!"
|
||||
)
|
||||
|
||||
self.log.info(
|
||||
f"Generating voctomix-compatible lower thirds for event {self.event.name}"
|
||||
)
|
||||
|
||||
for talk in self.event.current_schedule.talks.filter(
|
||||
is_visible=True
|
||||
).select_related("submission"):
|
||||
if talk.id in self.exported:
|
||||
# account for talks that are scheduled multiple times
|
||||
self.log.warning(
|
||||
f"Talk {talk.submission.title!r} was already generated, skipping. "
|
||||
"(Possibly scheduled multiple times?)"
|
||||
)
|
||||
continue
|
||||
|
||||
self.log.info(f"Generating image(s) for talk {talk.submission.title!r}")
|
||||
generated_files.add(self.export_talk(talk))
|
||||
for speaker in talk.submission.speakers.all():
|
||||
generated_files.add(self.export_speaker(talk, speaker))
|
||||
self.exported.add(talk.id)
|
||||
|
||||
return generated_files
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def add_arguments(self, parser):
|
||||
super().add_arguments(parser)
|
||||
parser.add_argument("event", type=str)
|
||||
parser.add_argument("--no-delete-source-files", action="store_true")
|
||||
|
||||
def handle(self, *args, **options):
|
||||
event_slug = options.get("event")
|
||||
|
||||
with scopes_disabled():
|
||||
try:
|
||||
event = Event.objects.get(slug__iexact=event_slug)
|
||||
except Event.DoesNotExist:
|
||||
raise CommandError(f"could not find event with slug {event_slug!r}")
|
||||
|
||||
with scope(event=event):
|
||||
logging.info(f"Exporting {event.name}")
|
||||
|
||||
export_dir = get_export_path(event)
|
||||
targz_path = get_export_targz_path(event)
|
||||
|
||||
delete_directory(export_dir)
|
||||
# for the first export of the conference, the "broadcast-tools"
|
||||
# directory does not exist
|
||||
export_dir.mkdir(parents=True)
|
||||
|
||||
try:
|
||||
exporter = VoctomixLowerThirdsExporter(event, export_dir)
|
||||
generated_files = exporter.export()
|
||||
make_targz(generated_files, targz_path)
|
||||
except Exception:
|
||||
logging.exception(f"Export of {event.name} failed")
|
||||
else:
|
||||
logging.info(
|
||||
f"Export of {event.name} succeeded, export available at {targz_path}"
|
||||
)
|
||||
finally:
|
||||
if not options.get("no_delete_source_files"):
|
||||
delete_directory(export_dir)
|
78
pretalx_broadcast_tools/tasks.py
Normal file
78
pretalx_broadcast_tools/tasks.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scope, scopes_disabled
|
||||
from pretalx.celery_app import app
|
||||
from pretalx.common.signals import periodic_task
|
||||
from pretalx.event.models import Event
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@app.task(name="pretalx_broadcast_tools.export_voctomix_lower_thirds")
|
||||
def export_voctomix_lower_thirds(*, event_id):
|
||||
from django.core.management import call_command
|
||||
|
||||
with scopes_disabled():
|
||||
event = Event.objects.filter(pk=event_id).first()
|
||||
if not event:
|
||||
LOG.error(f"Could not find event {event_id=} for export")
|
||||
return
|
||||
|
||||
with scope(event=event):
|
||||
if not event.current_schedule:
|
||||
LOG.error(f"event {event.slug} does not have schedule, can't export")
|
||||
return
|
||||
|
||||
call_command(
|
||||
"export_voctomix_lower_thirds",
|
||||
event.slug,
|
||||
)
|
||||
|
||||
|
||||
@app.task(name="pretalx_broadcast_tools.periodic_voctomix_export")
|
||||
def task_periodic_voctomix_export(*, event_slug):
|
||||
from pretalx_broadcast_tools.management.commands.export_voctomix_lower_thirds import (
|
||||
get_export_targz_path,
|
||||
)
|
||||
|
||||
with scopes_disabled():
|
||||
event = Event.objects.filter(slug=event_slug).first()
|
||||
|
||||
with scope(event=event):
|
||||
if (
|
||||
not event.settings.broadcast_tools_lower_thirds_export_voctomix
|
||||
or not event.current_schedule
|
||||
):
|
||||
return
|
||||
|
||||
targz_path = get_export_targz_path(event)
|
||||
needs_rebuild = False
|
||||
last_rebuild = event.cache.get("broadcast_tools_last_voctomix_export")
|
||||
_now = now()
|
||||
if not targz_path.exists():
|
||||
needs_rebuild = True
|
||||
if not last_rebuild or _now - last_rebuild >= timedelta(hours=1):
|
||||
needs_rebuild = True
|
||||
if event.cache.get("broadcast_tools_force_new_voctomix_export"):
|
||||
needs_rebuild = True
|
||||
|
||||
if needs_rebuild:
|
||||
event.cache.delete("broadcast_tools_force_new_voctomix_export")
|
||||
event.cache.set("broadcast_tools_last_voctomix_export", _now, None)
|
||||
export_voctomix_lower_thirds.apply_async(
|
||||
kwargs={"event_id": event.id}, ignore_result=True
|
||||
)
|
||||
|
||||
|
||||
@receiver(periodic_task)
|
||||
def periodic_event_services(sender, **kwargs):
|
||||
for event in Event.objects.all():
|
||||
with scope(event=event):
|
||||
if (event.date_to + timedelta(days=2)) < now().date():
|
||||
continue
|
||||
task_periodic_voctomix_export.apply_async(
|
||||
kwargs={"event_slug": event.slug}, ignore_result=True
|
||||
)
|
|
@ -24,6 +24,7 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p><a href="{% url 'plugins:pretalx_broadcast_tools:lowerthirds_voctomix_download' request.event.slug %}">{% translate "Download voctomix-compatible lower thirds images" %}</a></p>
|
||||
|
||||
<h2>{% translate "Placeholders" %}</h2>
|
||||
<p>{% translate "pretalx will automatically replace some placeholders in your custom content:" %}</p>
|
||||
|
@ -54,7 +55,7 @@
|
|||
</legend>
|
||||
{{ form.broadcast_tools_lower_thirds_no_talk_info.as_field_group }}
|
||||
{{ form.broadcast_tools_lower_thirds_info_string.as_field_group }}
|
||||
<p>{% translate "The info line will be shown on the bottom right side of your lower third. If you set it to an empty string, it will automatically hide itself." %}</p>
|
||||
{{ form.broadcast_tools_lower_thirds_export_voctomix.as_field_group }}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>
|
||||
|
|
|
@ -5,6 +5,7 @@ from .views.orga import BroadcastToolsOrgaView
|
|||
from .views.qr import BroadcastToolsFeedbackQrCodeSvg, BroadcastToolsPublicQrCodeSvg
|
||||
from .views.schedule import BroadcastToolsScheduleView
|
||||
from .views.static_html import BroadcastToolsLowerThirdsView, BroadcastToolsRoomInfoView
|
||||
from .views.voctomix_export import BroadcastToolsLowerThirdsVoctomixDownloadView
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
|
@ -26,6 +27,11 @@ urlpatterns = [
|
|||
BroadcastToolsLowerThirdsView.as_view(),
|
||||
name="lowerthirds",
|
||||
),
|
||||
path(
|
||||
"lower-thirds.voctomix.tar.gz",
|
||||
BroadcastToolsLowerThirdsVoctomixDownloadView.as_view(),
|
||||
name="lowerthirds_voctomix_download",
|
||||
),
|
||||
path(
|
||||
"feedback-qr/<talk>.svg",
|
||||
BroadcastToolsFeedbackQrCodeSvg.as_view(),
|
||||
|
|
22
pretalx_broadcast_tools/views/voctomix_export.py
Normal file
22
pretalx_broadcast_tools/views/voctomix_export.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from django.http import FileResponse, Http404
|
||||
from django.views import View
|
||||
from pretalx.common.text.path import safe_filename
|
||||
from pretalx.common.views.mixins import EventPermissionRequired
|
||||
|
||||
from pretalx_broadcast_tools.management.commands.export_voctomix_lower_thirds import (
|
||||
get_export_targz_path,
|
||||
)
|
||||
|
||||
|
||||
class BroadcastToolsLowerThirdsVoctomixDownloadView(EventPermissionRequired, View):
|
||||
permission_required = "agenda.view_schedule"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
targz_path = get_export_targz_path(self.request.event)
|
||||
if not targz_path.exists():
|
||||
raise Http404()
|
||||
response = FileResponse(open(targz_path, "rb"), as_attachment=True)
|
||||
response["Content-Disposition"] = (
|
||||
f"attachment; filename={safe_filename(targz_path.name)}"
|
||||
)
|
||||
return response
|
Loading…
Reference in a new issue