mirror of
https://github.com/Kunsi/pretalx-plugin-broadcast-tools
synced 2024-09-28 15:27:14 +00:00
Compare commits
78 commits
Author | SHA1 | Date | |
---|---|---|---|
1a5aaa90fc | |||
513f3ebe75 | |||
|
7c05357067 | ||
|
c9e72db1f7 | ||
|
545bdfb966 | ||
|
6f8c536cb8 | ||
|
d54087b7d0 | ||
|
2e2efbff9d | ||
3917fa65cf | |||
81494bed54 | |||
819e8ea2aa | |||
77148e41f7 | |||
cb60e02671 | |||
25d8be0397 | |||
af4e3408db | |||
dabc8e5443 | |||
341a9c072c | |||
|
98f8374b66 | ||
2b22f6a155 | |||
5335e911b7 | |||
|
210a18f6b3 | ||
3063adcc73 | |||
8f8f8a90e2 | |||
433f316719 | |||
ca8a2e3c65 | |||
85893d9c9c | |||
c6ceedf041 | |||
1ad4c73ed4 | |||
|
57351a4e1a | ||
|
190ce8f222 | ||
3bd74850a5 | |||
0d3d96e96d | |||
d858f8d039 | |||
ab42dade2b | |||
6c058cca1f | |||
4c114d6cee | |||
de73a288b8 | |||
8b7acb10fb | |||
aa91af001c | |||
788273e870 | |||
0486cd44da | |||
46be2a02b5 | |||
3f7649b09a | |||
12864749f9 | |||
fa31e72db1 | |||
498782a962 | |||
c5cb67d969 | |||
737a72f296 | |||
bb55df2723 | |||
e0f47458cd | |||
08998e7535 | |||
6bc110b9fb | |||
04a7a0e3a9 | |||
c9b01acb6e | |||
c1604efb08 | |||
275935e747 | |||
7d5f278536 | |||
5eabb5fa6a | |||
078341e4ed | |||
11afb88602 | |||
97784373c0 | |||
16350548f5 | |||
de8065cf22 | |||
327981eade | |||
2da018775b | |||
d86cd011f3 | |||
dd0f0ad4f0 | |||
8dd9bec525 | |||
d42a2744d0 | |||
f618654ee0 | |||
40245ec86d | |||
d22f7826ac | |||
a272f21498 | |||
280458f6aa | |||
c9bfd7e4aa | |||
191ce772c5 | |||
2b02350e29 | |||
8cdb391dae |
31 changed files with 926 additions and 402 deletions
19
.github/workflows/release-on-tag.yml
vendored
19
.github/workflows/release-on-tag.yml
vendored
|
@ -1,19 +0,0 @@
|
||||||
name: Create release when creating a tag
|
|
||||||
on:
|
|
||||||
create:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
jobs:
|
|
||||||
build-and-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: create release
|
|
||||||
id: create_release
|
|
||||||
uses: actions/create-release@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
release_name: Release ${{ github.ref }}
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
112
.github/workflows/style.yml
vendored
112
.github/workflows/style.yml
vendored
|
@ -18,14 +18,14 @@ jobs:
|
||||||
name: isort
|
name: isort
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: install gettext
|
- name: install gettext
|
||||||
run: sudo apt install gettext
|
run: sudo apt install gettext
|
||||||
- name: Set up Python 3.8
|
- name: Set up Python 3.12
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.12
|
||||||
- uses: actions/cache@v1
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
||||||
|
@ -41,14 +41,14 @@ jobs:
|
||||||
name: flake8
|
name: flake8
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: install gettext
|
- name: install gettext
|
||||||
run: sudo apt install gettext
|
run: sudo apt install gettext
|
||||||
- name: Set up Python 3.8
|
- name: Set up Python 3.12
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.12
|
||||||
- uses: actions/cache@v1
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
||||||
|
@ -65,14 +65,14 @@ jobs:
|
||||||
name: black
|
name: black
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: install gettext
|
- name: install gettext
|
||||||
run: sudo apt install gettext
|
run: sudo apt install gettext
|
||||||
- name: Set up Python 3.8
|
- name: Set up Python 3.12
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.12
|
||||||
- uses: actions/cache@v1
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
||||||
|
@ -89,14 +89,14 @@ jobs:
|
||||||
name: docformatter
|
name: docformatter
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: install gettext
|
- name: install gettext
|
||||||
run: sudo apt install gettext
|
run: sudo apt install gettext
|
||||||
- name: Set up Python 3.8
|
- name: Set up Python 3.12
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.12
|
||||||
- uses: actions/cache@v1
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
||||||
|
@ -113,14 +113,14 @@ jobs:
|
||||||
name: djhtml
|
name: djhtml
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: install gettext
|
- name: install gettext
|
||||||
run: sudo apt install gettext
|
run: sudo apt install gettext
|
||||||
- name: Set up Python 3.8
|
- name: Set up Python 3.12
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.12
|
||||||
- uses: actions/cache@v1
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
||||||
|
@ -133,34 +133,34 @@ jobs:
|
||||||
- name: Run docformatter
|
- name: Run docformatter
|
||||||
run: find -name "*.html" | xargs djhtml -c
|
run: find -name "*.html" | xargs djhtml -c
|
||||||
working-directory: .
|
working-directory: .
|
||||||
packaging:
|
# packaging:
|
||||||
name: packaging
|
# name: packaging
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
steps:
|
# steps:
|
||||||
- uses: actions/checkout@v2
|
# - uses: actions/checkout@v4
|
||||||
- name: install gettext
|
# - name: install gettext
|
||||||
run: sudo apt install gettext
|
# run: sudo apt install gettext
|
||||||
- name: Set up Python 3.8
|
# - name: Set up Python 3.12
|
||||||
uses: actions/setup-python@v1
|
# uses: actions/setup-python@v5
|
||||||
with:
|
# with:
|
||||||
python-version: 3.8
|
# python-version: 3.12
|
||||||
- uses: actions/cache@v1
|
# - uses: actions/cache@v4
|
||||||
with:
|
# with:
|
||||||
path: ~/.cache/pip
|
# path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
# key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
||||||
restore-keys: |
|
# restore-keys: |
|
||||||
${{ runner.os }}-pip-
|
# ${{ runner.os }}-pip-
|
||||||
- name: Install pretalx
|
# - name: Install pretalx
|
||||||
run: pip3 install pretalx
|
# run: pip3 install pretalx
|
||||||
- name: Install Dependencies
|
# - name: Install Dependencies
|
||||||
run: pip3 install twine check-manifest -Ue .
|
# run: pip3 install twine check-manifest -Ue .
|
||||||
- name: Run check-manifest
|
# - name: Run check-manifest
|
||||||
run: check-manifest .
|
# run: check-manifest .
|
||||||
working-directory: .
|
# working-directory: .
|
||||||
- name: Build package
|
# - name: Build package
|
||||||
run: python setup.py sdist
|
# run: python setup.py sdist
|
||||||
working-directory: .
|
# working-directory: .
|
||||||
- name: Check package
|
# - name: Check package
|
||||||
run: twine check dist/*
|
# run: twine check dist/*
|
||||||
working-directory: .
|
# working-directory: .
|
||||||
|
#
|
||||||
|
|
42
CHANGELOG.md
42
CHANGELOG.md
|
@ -1,3 +1,45 @@
|
||||||
|
# 2.2.0
|
||||||
|
|
||||||
|
* add plugin category (#16)
|
||||||
|
* add placeholders `{TRACK_NAME}` and `{TRACK_NAME_COLOURED}`
|
||||||
|
|
||||||
|
# 2.1.0
|
||||||
|
|
||||||
|
* fixed installation procedure
|
||||||
|
* add some more information to the json outputs to be able to be compatible
|
||||||
|
with [scheduled-plugin-pretalx-broadcast-tools](https://github.com/Kunsi/scheduled-plugin-pretalx-broadcast-tools)
|
||||||
|
(a plugin for [info-beamer hosted](https://info-beamer.com/))
|
||||||
|
|
||||||
|
# 2.0.1 (no longer available due to bugs during installation)
|
||||||
|
|
||||||
|
* fixes to support pretalx 2023.1.0
|
||||||
|
* use non-deprecated gettext call
|
||||||
|
* safe timezone handling
|
||||||
|
* usage of pyproject.toml
|
||||||
|
|
||||||
|
# 2.0.0
|
||||||
|
|
||||||
|
* room info page can now show more content on the lower half of the view
|
||||||
|
* **BREAKING:** The option to select which content should be shown
|
||||||
|
is now a ChoiceField, the old setting will be ignored.
|
||||||
|
* **BREAKING:** lower thirds now use css selectors using the same rules
|
||||||
|
as the other css selectors
|
||||||
|
* `#l3box` is now `#broadcast_tools_lower_thirds_box`
|
||||||
|
* `#l3info_line` is now `#broadcast_tools_lower_thirds_infoline`
|
||||||
|
* `#l3speaker` is now `#broadcast_tools_lower_thirds_speaker`
|
||||||
|
* `#l3title` is now `#broadcast_tools_lower_thirds_title`
|
||||||
|
* `.lower3rd` is now `broadcast_tools_lower_thirds`
|
||||||
|
|
||||||
|
# 1.1.0
|
||||||
|
|
||||||
|
* add a "room info" page to show conference attendees the currently running talk
|
||||||
|
* fix more compatibility issues with pretalx 2.3.x
|
||||||
|
|
||||||
|
# 1.0.4
|
||||||
|
|
||||||
|
* fix compatibility with pretalx 2.3.x
|
||||||
|
* always localize text using the selected default event locale
|
||||||
|
|
||||||
# 1.0.3
|
# 1.0.3
|
||||||
|
|
||||||
* fix a bug where questions could not be sorted
|
* fix a bug where questions could not be sorted
|
||||||
|
|
64
README.rst
64
README.rst
|
@ -1,30 +1,54 @@
|
||||||
Lower Thirds
|
Pretalx: Broadcast Tools (and more)
|
||||||
==========================
|
===================================
|
||||||
|
|
||||||
This is a plugin for `pretalx`_.
|
This is a plugin for `pretalx`_.
|
||||||
|
|
||||||
This plugin allows you to add configurable lower thirds ("Bauchbinden"
|
This adds the following features to your pretalx instance:
|
||||||
in German) to your pretalx instance. Most likely this will be used in
|
|
||||||
(for example) a Browser Source inside `OBS Studio`_.
|
* Lower Thirds ("Bauchbinden") for using with something like OBS
|
||||||
|
* a "room info" screen, if you want to show information about the
|
||||||
|
currently running talk outside the room
|
||||||
|
* a pdf export containing information about a talk, so video helpers
|
||||||
|
can have easy access to the needed information
|
||||||
|
|
||||||
|
Screenshots
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The first two screenshots show the talk "Compatible static software" by
|
||||||
|
"Stephanie Fisher", which was generated by the `create_test_event` command.
|
||||||
|
The event color is `#EA652D`, a bright orange. The track color is `#857EB0`,
|
||||||
|
a light purple. The last screenshot shows "Multi-layered encompassing
|
||||||
|
paradigm" by "Michael Rodriguez".
|
||||||
|
|
||||||
.. image:: img/lower_thirds.png
|
.. image:: img/lower_thirds.png
|
||||||
:width: 400
|
:width: 400
|
||||||
:alt: Screenshot of the lower third output. There's currently a talk running.
|
:alt: Screenshot of "lower thirds" view. The box is located in the
|
||||||
|
bottom quarter of the screen, taking about half the screen width.
|
||||||
|
The box is mostly colored in the event color, with a small strip
|
||||||
|
showing the track color at the bottom. Inside the box the talk
|
||||||
|
title is shown in large text on top, the speaker name below that.
|
||||||
|
On the bottom right of the box the configured info line is shown.
|
||||||
|
|
||||||
The colours will be automatically determined from the event and track
|
.. image:: img/room_info.png
|
||||||
colours set inside pretalx.
|
|
||||||
|
|
||||||
You can also add a configurable third line to the lower thirds, for
|
|
||||||
example to hint your audience to vote for the talks. To make this easier,
|
|
||||||
the plugin will automatically replace some placeholders inside the text,
|
|
||||||
so you can have individual text for all talks.
|
|
||||||
|
|
||||||
.. image:: img/orga_view.png
|
|
||||||
:width: 400
|
:width: 400
|
||||||
:alt: Screenshot of the orga view
|
:alt: Screenshot of the "room info" view. The whole screen is coloured
|
||||||
|
in the track color. On top of the screen you see the room name
|
||||||
|
in small font, below that the talk title in large letters. Below
|
||||||
|
that there's the speaker name listed. On the remainder of the
|
||||||
|
screen you see a large QR code linking to the talk detail page
|
||||||
|
in the schedule. The URL is also shown in plain-text below the
|
||||||
|
QR code.
|
||||||
|
|
||||||
Inside the orga view, you can also configure the text snippet that's
|
.. image:: img/pdf_export.png
|
||||||
shown if there's no talk running currently.
|
:width: 400
|
||||||
|
:alt: Screenshot of the first half of a pdf page. On the top you see
|
||||||
|
a large text "DO NOT RECORD - DO NOT STREAM", because the speaker
|
||||||
|
selected "Do not record" in the CfP. Below that you find
|
||||||
|
information like event- and room-name, date and time, talk title,
|
||||||
|
checkboxes for each speaker, length of the talk, talk abstract.
|
||||||
|
Also you can find answers to select questions and notes entered
|
||||||
|
inside pretalx on the pdf export. In this screenshot most info
|
||||||
|
has been extended by adding lots of "lorem ipsum" text.
|
||||||
|
|
||||||
Development setup
|
Development setup
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -35,7 +59,7 @@ Development setup
|
||||||
|
|
||||||
3. Activate the virtual environment you use for pretalx development.
|
3. Activate the virtual environment you use for pretalx development.
|
||||||
|
|
||||||
4. Execute ``python setup.py develop`` within this directory to register
|
4. Execute ``python -m pip install -e .`` within this directory to register
|
||||||
this application with pretalx's plugin registry.
|
this application with pretalx's plugin registry.
|
||||||
|
|
||||||
5. Execute ``make`` within this directory to compile translations.
|
5. Execute ``make`` within this directory to compile translations.
|
||||||
|
@ -48,7 +72,7 @@ Development setup
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Copyright 2021 Franziska 'kuns' Kunsmann
|
Copyright 2021-2023 Franziska 'kunsi' Kunsmann
|
||||||
|
|
||||||
Released under the terms of the Apache License 2.0
|
Released under the terms of the Apache License 2.0
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 54 KiB |
Binary file not shown.
Before Width: | Height: | Size: 87 KiB |
BIN
img/pdf_export.png
Normal file
BIN
img/pdf_export.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 275 KiB |
BIN
img/room_info.png
Normal file
BIN
img/room_info.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
1
pretalx_broadcast_tools/__init__.py
Normal file
1
pretalx_broadcast_tools/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
__version__ = "2.2.1"
|
|
@ -1,6 +1,8 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.utils.translation import gettext_lazy
|
from django.utils.translation import gettext_lazy
|
||||||
|
|
||||||
|
from pretalx_broadcast_tools import __version__
|
||||||
|
|
||||||
|
|
||||||
class PluginApp(AppConfig):
|
class PluginApp(AppConfig):
|
||||||
name = "pretalx_broadcast_tools"
|
name = "pretalx_broadcast_tools"
|
||||||
|
@ -15,7 +17,8 @@ class PluginApp(AppConfig):
|
||||||
"embedded into your broadcasting software"
|
"embedded into your broadcasting software"
|
||||||
)
|
)
|
||||||
visible = True
|
visible = True
|
||||||
version = "1.0.3"
|
version = __version__
|
||||||
|
category = "FEATURE"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from . import signals # NOQA
|
from . import signals # NOQA
|
||||||
|
|
|
@ -17,6 +17,8 @@ from reportlab.platypus import (
|
||||||
TableStyle,
|
TableStyle,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from pretalx_broadcast_tools.utils.placeholders import placeholders
|
||||||
|
|
||||||
A4_WIDTH, A4_HEIGHT = A4
|
A4_WIDTH, A4_HEIGHT = A4
|
||||||
PAGE_PADDING = 10 * mm
|
PAGE_PADDING = 10 * mm
|
||||||
|
|
||||||
|
@ -79,11 +81,12 @@ class PDFInfoPage(Flowable):
|
||||||
def draw(self):
|
def draw(self):
|
||||||
if hasattr(self.talk, "local_start"):
|
if hasattr(self.talk, "local_start"):
|
||||||
talk_start = self.talk.local_start
|
talk_start = self.talk.local_start
|
||||||
talk_end = self.talk.local_end
|
|
||||||
else:
|
else:
|
||||||
talk_start = self.talk.start.astimezone(self.event.tz)
|
talk_start = self.talk.start.astimezone(self.event.tz)
|
||||||
|
if hasattr(self.talk, "local_end"):
|
||||||
|
talk_end = self.talk.local_end
|
||||||
|
else:
|
||||||
talk_end = self.talk.end.astimezone(self.event.tz)
|
talk_end = self.talk.end.astimezone(self.event.tz)
|
||||||
|
|
||||||
# add some information horizontally to the side of the page
|
# add some information horizontally to the side of the page
|
||||||
self.canv.saveState()
|
self.canv.saveState()
|
||||||
self.canv.rotate(90)
|
self.canv.rotate(90)
|
||||||
|
@ -94,11 +97,13 @@ class PDFInfoPage(Flowable):
|
||||||
" | ".join(
|
" | ".join(
|
||||||
[
|
[
|
||||||
self.talk.submission.code,
|
self.talk.submission.code,
|
||||||
str(self.talk.submission.submission_type.name),
|
self.talk.submission.submission_type.name.localize(
|
||||||
str(self.event.name),
|
self.event.locale
|
||||||
|
),
|
||||||
|
self.event.name.localize(self.event.locale),
|
||||||
talk_start.isoformat(),
|
talk_start.isoformat(),
|
||||||
f"Day {self.day['index']}",
|
f"Day {self.day['index']}",
|
||||||
str(self.room["name"]),
|
self.room["name"].localize(self.event.locale),
|
||||||
f"Schedule {self.schedule.version}",
|
f"Schedule {self.schedule.version}",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -116,9 +121,10 @@ class PDFInfoPage(Flowable):
|
||||||
Paragraph(
|
Paragraph(
|
||||||
" | ".join(
|
" | ".join(
|
||||||
[
|
[
|
||||||
str(self.event.name),
|
self.event.name.localize(self.event.locale),
|
||||||
str(self.room["name"]),
|
self.room["name"].localize(self.event.locale),
|
||||||
talk_start.strftime("%F %T"),
|
talk_start.strftime("%F"),
|
||||||
|
f'{talk_start.strftime("%T")} - {talk_end.strftime("%T")}',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
style=self.style["Meta"],
|
style=self.style["Meta"],
|
||||||
|
@ -172,6 +178,17 @@ class PDFInfoPage(Flowable):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.event.settings.broadcast_tools_pdf_additional_content:
|
||||||
|
self._space()
|
||||||
|
self._add(
|
||||||
|
Paragraph(
|
||||||
|
self.event.settings.broadcast_tools_pdf_additional_content.format(
|
||||||
|
**placeholders(self.schedule, self.talk)
|
||||||
|
),
|
||||||
|
style=self.style["Meta"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if self.talk.submission.answers and self._questions:
|
if self.talk.submission.answers and self._questions:
|
||||||
self._space()
|
self._space()
|
||||||
self._add(
|
self._add(
|
||||||
|
@ -184,7 +201,7 @@ class PDFInfoPage(Flowable):
|
||||||
if answer.question.id not in self._questions:
|
if answer.question.id not in self._questions:
|
||||||
continue
|
continue
|
||||||
self._question_text(
|
self._question_text(
|
||||||
str(answer.question.question),
|
answer.question.question.localize(self.event.locale),
|
||||||
answer.answer,
|
answer.answer,
|
||||||
style=self.style["Question"],
|
style=self.style["Question"],
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.forms import BooleanField, CharField
|
from django.forms import BooleanField, CharField, ChoiceField, Textarea
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from hierarkey.forms import HierarkeyForm
|
from hierarkey.forms import HierarkeyForm
|
||||||
from i18nfield.forms import I18nFormField, I18nFormMixin, I18nTextInput
|
from i18nfield.forms import I18nFormField, I18nFormMixin, I18nTextInput
|
||||||
|
@ -9,16 +9,47 @@ class BroadcastToolsSettingsForm(I18nFormMixin, HierarkeyForm):
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"Will be shown as talk title if there's currently no talk running."
|
"Will be shown as talk title if there's currently no talk running."
|
||||||
),
|
),
|
||||||
label=_('"no talk running" information'),
|
label=_('"No talk running" information'),
|
||||||
widget=I18nTextInput,
|
widget=I18nTextInput,
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
broadcast_tools_lower_thirds_info_string = I18nFormField(
|
broadcast_tools_lower_thirds_info_string = I18nFormField(
|
||||||
help_text=_("Will only be shown if there's a talk running."),
|
help_text=_(
|
||||||
label=_("info line"),
|
"Will only be shown if there's a talk running. You may use "
|
||||||
|
"the place holders mentioned below."
|
||||||
|
),
|
||||||
|
label=_("Info line"),
|
||||||
required=False,
|
required=False,
|
||||||
widget=I18nTextInput,
|
widget=I18nTextInput,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
broadcast_tools_room_info_lower_content = ChoiceField(
|
||||||
|
choices=(
|
||||||
|
("", "No lower content"),
|
||||||
|
("public_qr", "QR code linking to the 'talk detail' page"),
|
||||||
|
(
|
||||||
|
"feedback_qr",
|
||||||
|
"QR code linking to the feedback page of the currently running talk",
|
||||||
|
),
|
||||||
|
("talk_image", "session image uploaded by the speaker(s)"),
|
||||||
|
),
|
||||||
|
help_text=_(
|
||||||
|
"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."
|
||||||
|
),
|
||||||
|
label=_("lower content"),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
broadcast_tools_room_info_show_next_talk = BooleanField(
|
||||||
|
help_text=_(
|
||||||
|
"If no talk is running in the room, show the time and title "
|
||||||
|
"of the next talk in the room."
|
||||||
|
),
|
||||||
|
label=_("Show next talk"),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
broadcast_tools_pdf_show_internal_notes = BooleanField(
|
broadcast_tools_pdf_show_internal_notes = BooleanField(
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"If checked, the value of the 'internal notes' field in a "
|
"If checked, the value of the 'internal notes' field in a "
|
||||||
|
@ -43,3 +74,13 @@ class BroadcastToolsSettingsForm(I18nFormMixin, HierarkeyForm):
|
||||||
label=_("Questions to include"),
|
label=_("Questions to include"),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
broadcast_tools_pdf_additional_content = CharField(
|
||||||
|
help_text=_(
|
||||||
|
"Additional content to print onto the PDF export. "
|
||||||
|
"Will get printed as-is. You may use the place holders "
|
||||||
|
"mentioned below."
|
||||||
|
),
|
||||||
|
label=_("Additional text"),
|
||||||
|
required=False,
|
||||||
|
widget=Textarea,
|
||||||
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.urls import resolve, reverse
|
from django.urls import resolve, reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.translation import gettext_noop
|
from django.utils.translation import gettext_noop
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from i18nfield.strings import LazyI18nString
|
from i18nfield.strings import LazyI18nString
|
||||||
from pretalx.common.models.settings import hierarkey
|
from pretalx.common.models.settings import hierarkey
|
||||||
from pretalx.common.signals import register_data_exporters
|
from pretalx.common.signals import register_data_exporters
|
||||||
|
|
|
@ -2,35 +2,80 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1.2em;
|
line-height: 1.2em;
|
||||||
|
color: white;
|
||||||
|
font-family: "Muli","Open Sans","OpenSans","Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
#l3box {
|
body {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#broadcast_tools_room_info {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#broadcast_tools_room_info_header, #broadcast_tools_room_info_qr {
|
||||||
|
padding: 2em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#broadcast_tools_room_info_roomname {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#broadcast_tools_room_info_title {
|
||||||
|
font-size: 6em;
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#broadcast_tools_room_info_speaker {
|
||||||
|
font-size: 3em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#broadcast_tools_room_info_qr {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#broadcast_tools_room_info_qr img {
|
||||||
|
background-color: white;
|
||||||
|
height: calc(100% - 2em);
|
||||||
|
}
|
||||||
|
|
||||||
|
#broadcast_tools_room_info_qr p {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#broadcast_tools_lower_thirds_box {
|
||||||
width: 1020px;
|
width: 1020px;
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 80px;
|
bottom: 80px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
margin-left: -510px;
|
margin-left: -510px;
|
||||||
|
|
||||||
color: white;
|
|
||||||
font-family: "Muli","Open Sans","OpenSans","Helvetica Neue",Helvetica,Arial,sans-serif;
|
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
||||||
box-shadow: 5px 5px 10px 0px rgba(50, 50, 50, 0.75);
|
box-shadow: 5px 5px 10px 0px rgba(50, 50, 50, 0.75);
|
||||||
background-color: #3aa57c;
|
background-color: #3aa57c;
|
||||||
}
|
}
|
||||||
|
|
||||||
#l3title {
|
#broadcast_tools_lower_thirds_title {
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#l3speaker {
|
#broadcast_tools_lower_thirds_speaker {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#l3info_line {
|
#broadcast_tools_lower_thirds_infoline {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
schedule = null;
|
||||||
|
event_info = null;
|
||||||
|
|
||||||
|
function get_current_talk(max_offset) {
|
||||||
|
room_name = get_room_name();
|
||||||
|
|
||||||
|
if (!room_name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let offset = 0; offset <= max_offset; offset++) {
|
||||||
|
time_start = new Date(Date.now() + offset*60000).getTime();
|
||||||
|
time_end = new Date(Date.now() - offset*60000).getTime();
|
||||||
|
|
||||||
|
for (talk_i in schedule['talks']) {
|
||||||
|
talk = schedule['talks'][talk_i]
|
||||||
|
|
||||||
|
if (schedule['rooms'].length > 1 && talk['room'] != room_name) {
|
||||||
|
// not in this room
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
talk_start = new Date(talk['start']).getTime();
|
||||||
|
talk_end = new Date(talk['end']).getTime();
|
||||||
|
|
||||||
|
if (talk_start < time_start && talk_end > time_end) {
|
||||||
|
return talk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_next_talk() {
|
||||||
|
room_name = get_room_name();
|
||||||
|
|
||||||
|
if (!room_name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_start = new Date(Date.now()).getTime();
|
||||||
|
|
||||||
|
for (talk_i in schedule['talks']) {
|
||||||
|
talk = schedule['talks'][talk_i]
|
||||||
|
|
||||||
|
if (schedule['rooms'].length > 1 && talk['room'] != room_name) {
|
||||||
|
// not in this room
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
talk_start = new Date(talk['start']).getTime();
|
||||||
|
|
||||||
|
if (talk_start > time_start) {
|
||||||
|
return talk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_room_name() {
|
||||||
|
room_name = null;
|
||||||
|
try {
|
||||||
|
hash = decodeURIComponent(window.location.hash.substring(1));
|
||||||
|
room_name = hash;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
return room_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function format_time_from_pretalx(from_pretalx) {
|
||||||
|
d = new Date(from_pretalx);
|
||||||
|
|
||||||
|
h = d.getHours();
|
||||||
|
m = d.getMinutes();
|
||||||
|
|
||||||
|
if (h < 10) {
|
||||||
|
h = '0' + h;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m < 10) {
|
||||||
|
m = '0' + m;
|
||||||
|
}
|
||||||
|
|
||||||
|
return h + ':' + m;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_schedule() {
|
||||||
|
$.getJSON('../event.json', function(data) {
|
||||||
|
event_info = data;
|
||||||
|
});
|
||||||
|
$.getJSON('../schedule.json', function(data) {
|
||||||
|
if ('error' in data) {
|
||||||
|
console.error(data['error']);
|
||||||
|
} else {
|
||||||
|
console.info('schedule updated with ' + data['talks'].length + ' talks in ' + data['rooms'].length + ' rooms');
|
||||||
|
}
|
||||||
|
|
||||||
|
schedule = data;
|
||||||
|
|
||||||
|
window.setTimeout(update_schedule, 30000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
update_schedule();
|
|
@ -0,0 +1,47 @@
|
||||||
|
function update_lower_third() {
|
||||||
|
room_name = get_room_name();
|
||||||
|
|
||||||
|
if (!event_info) {
|
||||||
|
console.warn("Waiting for event info ...");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#broadcast_tools_lower_thirds_box').css('background-color', event_info['color']);
|
||||||
|
|
||||||
|
if (!schedule) {
|
||||||
|
$('#broadcast_tools_lower_thirds_title').text('Waiting for schedule ...')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('error' in schedule) {
|
||||||
|
$('#broadcast_tools_lower_thirds_title').text('Error')
|
||||||
|
$('#broadcast_tools_lower_thirds_speaker').html(schedule['error'].join('<br>'));
|
||||||
|
$('#broadcast_tools_lower_thirds_infoline').text('');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schedule['rooms'].length > 1 && !schedule['rooms'].includes(room_name)) {
|
||||||
|
$('#broadcast_tools_lower_thirds_title').text('Error')
|
||||||
|
$('#broadcast_tools_lower_thirds_speaker').text('Invalid room_name. Valid names: ' + schedule['rooms'].join(', '));
|
||||||
|
$('#broadcast_tools_lower_thirds_infoline').text('');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
current_talk = get_current_talk(5);
|
||||||
|
if (current_talk) {
|
||||||
|
$('#broadcast_tools_lower_thirds_title').text(current_talk['title']);
|
||||||
|
$('#broadcast_tools_lower_thirds_speaker').text(current_talk['persons'].join(', '));
|
||||||
|
$('#broadcast_tools_lower_thirds_infoline').text(current_talk['infoline']);
|
||||||
|
} else {
|
||||||
|
$('#broadcast_tools_lower_thirds_title').text(event_info['no_talk']);
|
||||||
|
$('#broadcast_tools_lower_thirds_speaker').text('');
|
||||||
|
$('#broadcast_tools_lower_thirds_infoline').text('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_talk && current_talk['track']) {
|
||||||
|
$('#broadcast_tools_lower_thirds_box').css('border-bottom', '10px solid ' + current_talk['track']['color']);
|
||||||
|
} else {
|
||||||
|
$('#broadcast_tools_lower_thirds_box').css('border-bottom', 'none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.setInterval(update_lower_third, 1000);
|
|
@ -0,0 +1,79 @@
|
||||||
|
$(function() {
|
||||||
|
$('#broadcast_tools_room_info_title').text('Content will appear soon.');
|
||||||
|
$('#broadcast_tools_room_info_speaker').text('');
|
||||||
|
$('#broadcast_tools_room_info_qr').text('');
|
||||||
|
});
|
||||||
|
|
||||||
|
function update_room_info() {
|
||||||
|
room_name = get_room_name();
|
||||||
|
|
||||||
|
if (!event_info) {
|
||||||
|
console.warn("Waiting for event info ...");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!room_name) {
|
||||||
|
$('#broadcast_tools_room_info_roomname').text(event_info['name']);
|
||||||
|
$('#broadcast_tools_room_info_title').text('Backstage');
|
||||||
|
$('#broadcast_tools_room_info_speaker').text('');
|
||||||
|
$('#broadcast_tools_room_info_qr').text('');
|
||||||
|
$('#broadcast_tools_room_info').css('background-color', event_info['color']);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!schedule) {
|
||||||
|
$('#broadcast_tools_room_info_speaker').text('Waiting for schedule ...')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('error' in schedule) {
|
||||||
|
$('#broadcast_tools_room_info_title').text('Error')
|
||||||
|
$('#broadcast_tools_room_info_speaker').html(schedule['error'].join('<br>'));
|
||||||
|
$('#broadcast_tools_room_info_qr').text('');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schedule['rooms'].length > 1 && !schedule['rooms'].includes(room_name)) {
|
||||||
|
$('#broadcast_tools_room_info_title').text('Error')
|
||||||
|
$('#broadcast_tools_room_info_speaker').text('Invalid room_name. Valid names: ' + schedule['rooms'].join(', '));
|
||||||
|
$('#broadcast_tools_room_info_qr').text('');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
current_talk = get_current_talk(15);
|
||||||
|
next_talk = get_next_talk();
|
||||||
|
|
||||||
|
if (current_talk) {
|
||||||
|
if (event_info['room-info']['lower_info'] == 'feedback_qr') {
|
||||||
|
qr_info = '<img src="' + current_talk['urls']['feedback_qr'] + '" alt="Feedback QR Code"><p>Leave Feedback by scanning the code or visiting ' + current_talk['urls']['feedback'] + '</p>';
|
||||||
|
} else if (event_info['room-info']['lower_info'] == 'public_qr') {
|
||||||
|
qr_info = '<img src="' + current_talk['urls']['public_qr'] + '" alt="QR Code linking to URL below"><p>' + current_talk['urls']['public'] + '</p>';
|
||||||
|
} else if (event_info['room-info']['lower_info'] == 'talk_image' && current_talk['image_url']) {
|
||||||
|
qr_info = '<img src="' + current_talk['image_url'] + '" alt="Talk image">';
|
||||||
|
} else {
|
||||||
|
qr_info = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#broadcast_tools_room_info_roomname').text(room_name);
|
||||||
|
$('#broadcast_tools_room_info_title').text(current_talk['title']);
|
||||||
|
$('#broadcast_tools_room_info_speaker').text(current_talk['persons'].join(', '));
|
||||||
|
$('#broadcast_tools_room_info_qr').html(qr_info);
|
||||||
|
} else {
|
||||||
|
$('#broadcast_tools_room_info_roomname').text(event_info['name']);
|
||||||
|
$('#broadcast_tools_room_info_title').text(room_name);
|
||||||
|
$('#broadcast_tools_room_info_qr').text('');
|
||||||
|
|
||||||
|
if (next_talk && event_info['room-info']['show_next_talk']) {
|
||||||
|
$('#broadcast_tools_room_info_speaker').text(format_time_from_pretalx(next_talk['start']) + ' ' + next_talk['title']);
|
||||||
|
} else {
|
||||||
|
$('#broadcast_tools_room_info_speaker').text('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_talk && current_talk['track']) {
|
||||||
|
$('#broadcast_tools_room_info').css('background-color', current_talk['track']['color']);
|
||||||
|
} else {
|
||||||
|
$('#broadcast_tools_room_info').css('background-color', event_info['color']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.setInterval(update_room_info, 1000);
|
|
@ -1,87 +0,0 @@
|
||||||
schedule = null;
|
|
||||||
room_name = null;
|
|
||||||
event_info = null;
|
|
||||||
|
|
||||||
$(function() {
|
|
||||||
$('#l3speaker').text('Content will appear soon.');
|
|
||||||
});
|
|
||||||
|
|
||||||
function update_lower_third() {
|
|
||||||
current_time = new Date(Date.now()).getTime()
|
|
||||||
|
|
||||||
try {
|
|
||||||
hash = decodeURIComponent(window.location.hash.substring(1));
|
|
||||||
room_name = hash;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!event_info) {
|
|
||||||
console.warn("There's no event info yet, exiting");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!schedule) {
|
|
||||||
$('#l3title').text('Waiting for schedule ...')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (schedule['rooms'].length > 1 && !schedule['rooms'].includes(room_name)) {
|
|
||||||
$('#l3title').text('Error')
|
|
||||||
$('#l3speaker').text('Invalid room_name. Valid names: ' + schedule['rooms'].join(', '));
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
current_talk = null;
|
|
||||||
|
|
||||||
for (talk_i in schedule['talks']) {
|
|
||||||
talk = schedule['talks'][talk_i]
|
|
||||||
|
|
||||||
if (schedule['rooms'].length > 1 && talk['room'] != room_name) {
|
|
||||||
// not in this room
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
talk_start = new Date(talk['start']).getTime();
|
|
||||||
talk_end = new Date(talk['end']).getTime();
|
|
||||||
|
|
||||||
if (talk_start < current_time && talk_end > current_time) {
|
|
||||||
current_talk = talk;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current_talk) {
|
|
||||||
$('#l3title').text(current_talk['title']);
|
|
||||||
$('#l3speaker').text(current_talk['persons'].join(', '));
|
|
||||||
$('#l3info_line').text(current_talk['infoline']);
|
|
||||||
} else {
|
|
||||||
$('#l3title').text(event_info['no_talk']);
|
|
||||||
$('#l3speaker').text('');
|
|
||||||
$('#l3info_line').text('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current_talk && current_talk['track']) {
|
|
||||||
$('#l3box').css('border-bottom', '10px solid ' + current_talk['track']['color']);
|
|
||||||
} else {
|
|
||||||
$('#l3box').css('border-bottom', 'none');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.setInterval(update_lower_third, 1000);
|
|
||||||
|
|
||||||
function update_schedule() {
|
|
||||||
$.getJSON('../event.json', function(data) {
|
|
||||||
event_info = data;
|
|
||||||
|
|
||||||
$('#l3box').css('background-color', data['color']);
|
|
||||||
});
|
|
||||||
$.getJSON('../schedule.json', function(data) {
|
|
||||||
console.info('schedule updated with ' + data['talks'].length + ' talks in ' + data['rooms'].length + ' rooms');
|
|
||||||
|
|
||||||
schedule = data;
|
|
||||||
|
|
||||||
window.setTimeout(update_schedule, 30000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
update_schedule();
|
|
|
@ -4,21 +4,23 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="content-type" content="text/html" charset="UTF-8">
|
<meta http-equiv="content-type" content="text/html" charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
<title>{{ request.event.name }} lower thirds</title>
|
<title>{{ request.event.name }} lower thirds</title>
|
||||||
{% compress js %}
|
{% compress js %}
|
||||||
<script src="{% static "vendored/jquery-3.1.1.js" %}"></script>
|
<script src="{% static "vendored/jquery-3.1.1.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
<script src="{% static "pretalx_broadcast_tools/update.js" %}"></script>
|
<script src="{% static "pretalx_broadcast_tools/generic.js" %}"></script>
|
||||||
|
<script src="{% static "pretalx_broadcast_tools/lower_thirds.js" %}"></script>
|
||||||
<link rel="stylesheet" href="{% static "pretalx_broadcast_tools/frontend.css" %}" />
|
<link rel="stylesheet" href="{% static "pretalx_broadcast_tools/frontend.css" %}" />
|
||||||
{% if request.event and request.event.custom_css %}
|
{% if request.event and request.event.custom_css %}
|
||||||
<link rel="stylesheet" type="text/css" href="{{ request.event.custom_css.url }}"/>
|
<link rel="stylesheet" type="text/css" href="{{ request.event.custom_css.url }}"/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</head>
|
</head>
|
||||||
<body class="lower3rd">
|
<body id="broadcast_tools_lower_thirds">
|
||||||
<div id="l3box">
|
<div id="broadcast_tools_lower_thirds_box">
|
||||||
<p id="l3title">Loading ...</p>
|
<p id="broadcast_tools_lower_thirds_title">Loading ...</p>
|
||||||
<p id="l3speaker">Content should appear soon. If not, please verify you have Javascript enabled.</p>
|
<p id="broadcast_tools_lower_thirds_speaker">Content should appear soon. If not, please verify you have Javascript enabled.</p>
|
||||||
<p id="l3info_line"></p>
|
<p id="broadcast_tools_lower_thirds_infoline"></p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -6,9 +6,53 @@
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{% if localized_rooms %}
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{% trans "room list" %}</th>
|
||||||
|
<th scope="col" colspan="2">Feature</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for room in localized_rooms %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{ room }}</th>
|
||||||
|
<td><a href="{% url 'plugins:pretalx_broadcast_tools:lowerthirds' request.event.slug %}#{{ room }}">Lower Thirds</a></td>
|
||||||
|
<td><a href="{% url 'plugins:pretalx_broadcast_tools:room_info' request.event.slug %}#{{ room }}">Room Info</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
pretalx will automatically replace some placeholders in your custom
|
||||||
|
content:
|
||||||
|
</p>
|
||||||
|
<dl>
|
||||||
|
<dt><code>{CODE}</code></dt>
|
||||||
|
<dd>talk code (<code>MUX9U3</code> for example) - most useful in combination with pretalx-proposal-redirects or something like that</dd>
|
||||||
|
|
||||||
|
<dt><code>{EVENT_SLUG}</code></dt>
|
||||||
|
<dd>The event slug (<code>{{ request.event.slug }}</code>)</dd>
|
||||||
|
|
||||||
|
<dt><code>{FEEDBACK_URL}</code></dt>
|
||||||
|
<dd>URL to the talk feedback page.</dd>
|
||||||
|
|
||||||
|
<dt><code>{TALK_SLUG}</code></dt>
|
||||||
|
<dd>The talk slug (<code>{{ request.event.slug }}-1-my-super-great-talk</code>)</dd>
|
||||||
|
|
||||||
|
<dt><code>{TALK_URL}</code></dt>
|
||||||
|
<dd>URL to the talk detail page.</dd>
|
||||||
|
|
||||||
|
<dt><code>{TRACK_NAME}</code> or <code>{TRACK_NAME_COLOURED}</code></dt>
|
||||||
|
<dd>Track name in plain text or coloured using the track colour.</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>
|
<legend>
|
||||||
{% translate "Set up lower thirds" %}
|
{% translate "Lower thirds" %}
|
||||||
</legend>
|
</legend>
|
||||||
{% bootstrap_field form.broadcast_tools_lower_thirds_no_talk_info layout='event' %}
|
{% bootstrap_field form.broadcast_tools_lower_thirds_no_talk_info layout='event' %}
|
||||||
{% bootstrap_field form.broadcast_tools_lower_thirds_info_string layout='event' %}
|
{% bootstrap_field form.broadcast_tools_lower_thirds_info_string layout='event' %}
|
||||||
|
@ -17,24 +61,13 @@
|
||||||
lower third. If you set it to an empty string, it will automatically
|
lower third. If you set it to an empty string, it will automatically
|
||||||
hide itself.
|
hide itself.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
</fieldset>
|
||||||
pretalx will automatically replace some placeholders in your info
|
<fieldset>
|
||||||
string.
|
<legend>
|
||||||
Use <code>{CODE}</code> to embed the talk code (<code>MUX9U3</code>
|
{% translate "Room info" %}
|
||||||
for example). You could use this to directly link to the talk
|
</legend>
|
||||||
feedback page.
|
{% bootstrap_field form.broadcast_tools_room_info_lower_content layout='event' %}
|
||||||
Use <code>{EVENT_SLUG}</code> to get the event slug.
|
{% bootstrap_field form.broadcast_tools_room_info_show_next_talk layout='event' %}
|
||||||
Use <code>{TALK_SLUG}</code> to get the talk slug.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if request.event.rooms %}
|
|
||||||
<h3>{% trans "room list" %}</h3>
|
|
||||||
<ul>
|
|
||||||
{% for room in request.event.rooms.all %}
|
|
||||||
<li><a href="{% url 'plugins:pretalx_broadcast_tools:lowerthirds' request.event.slug %}#{{ room.name }}">{{ room.name }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>
|
<legend>
|
||||||
|
@ -43,6 +76,7 @@
|
||||||
{% bootstrap_field form.broadcast_tools_pdf_show_internal_notes layout='event' %}
|
{% bootstrap_field form.broadcast_tools_pdf_show_internal_notes layout='event' %}
|
||||||
{% bootstrap_field form.broadcast_tools_pdf_ignore_do_not_record layout='event' %}
|
{% bootstrap_field form.broadcast_tools_pdf_ignore_do_not_record layout='event' %}
|
||||||
{% bootstrap_field form.broadcast_tools_pdf_questions_to_include layout='event' %}
|
{% bootstrap_field form.broadcast_tools_pdf_questions_to_include layout='event' %}
|
||||||
|
{% bootstrap_field form.broadcast_tools_pdf_additional_content layout='event' %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="submit-group panel">
|
<div class="submit-group panel">
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
{% load static %}
|
||||||
|
{% load compress %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html" charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
|
<title>{{ request.event.name }} room info</title>
|
||||||
|
{% compress js %}
|
||||||
|
<script src="{% static "vendored/jquery-3.1.1.js" %}"></script>
|
||||||
|
{% endcompress %}
|
||||||
|
<script src="{% static "pretalx_broadcast_tools/generic.js" %}"></script>
|
||||||
|
<script src="{% static "pretalx_broadcast_tools/room_info.js" %}"></script>
|
||||||
|
<link rel="stylesheet" href="{% static "pretalx_broadcast_tools/frontend.css" %}" />
|
||||||
|
{% if request.event and request.event.custom_css %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ request.event.custom_css.url }}"/>
|
||||||
|
{% endif %}
|
||||||
|
</head>
|
||||||
|
<body id="broadcast_tools_room_info">
|
||||||
|
<div id="broadcast_tools_room_info_header">
|
||||||
|
<h1 id="broadcast_tools_room_info_roomname"></h1>
|
||||||
|
<h2 id="broadcast_tools_room_info_title">Loading ...</h2>
|
||||||
|
<h3 id="broadcast_tools_room_info_speaker">Content should appear soon. If not, please verify you have Javascript enabled.</h3>
|
||||||
|
</div>
|
||||||
|
<div id="broadcast_tools_room_info_qr"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,27 +1,46 @@
|
||||||
from django.urls import re_path
|
from django.urls import re_path
|
||||||
from pretalx.event.models.event import SLUG_CHARS
|
from pretalx.event.models.event import SLUG_REGEX
|
||||||
|
|
||||||
from . import views
|
from .views.event_info import BroadcastToolsEventInfoView
|
||||||
|
from .views.orga import BroadcastToolsOrgaView
|
||||||
|
from .views.qr import BroadcastToolsFeedbackQrCodeSvg, BroadcastToolsPublicQrCodeSvg
|
||||||
|
from .views.schedule import BroadcastToolsScheduleView
|
||||||
|
from .views.static_html import BroadcastToolsLowerThirdsView, BroadcastToolsRoomInfoView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(
|
re_path(
|
||||||
f"^(?P<event>[{SLUG_CHARS}]+)/p/broadcast-tools/lower-thirds/$",
|
rf"^(?P<event>{SLUG_REGEX})/p/broadcast-tools/event.json$",
|
||||||
views.BroadcastToolsLowerThirdsView.as_view(),
|
BroadcastToolsEventInfoView.as_view(),
|
||||||
name="lowerthirds",
|
|
||||||
),
|
|
||||||
re_path(
|
|
||||||
f"^(?P<event>[{SLUG_CHARS}]+)/p/broadcast-tools/event.json$",
|
|
||||||
views.BroadcastToolsEventInfoView.as_view(),
|
|
||||||
name="event_info",
|
name="event_info",
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
f"^(?P<event>[{SLUG_CHARS}]+)/p/broadcast-tools/schedule.json$",
|
f"^(?P<event>{SLUG_REGEX})/p/broadcast-tools/schedule.json$",
|
||||||
views.BroadcastToolsScheduleView.as_view(),
|
BroadcastToolsScheduleView.as_view(),
|
||||||
name="schedule",
|
name="schedule",
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
f"^orga/event/(?P<event>[{SLUG_CHARS}]+)/settings/p/broadcast-tools/$",
|
f"^(?P<event>{SLUG_REGEX})/p/broadcast-tools/lower-thirds/$",
|
||||||
views.BroadcastToolsOrgaView.as_view(),
|
BroadcastToolsLowerThirdsView.as_view(),
|
||||||
|
name="lowerthirds",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
f"^(?P<event>{SLUG_REGEX})/p/broadcast-tools/feedback-qr/(?P<talk>[0-9]+).svg$",
|
||||||
|
BroadcastToolsFeedbackQrCodeSvg.as_view(),
|
||||||
|
name="feedback_qr_id",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
f"^(?P<event>{SLUG_REGEX})/p/broadcast-tools/public-qr/(?P<talk>[0-9]+).svg$",
|
||||||
|
BroadcastToolsPublicQrCodeSvg.as_view(),
|
||||||
|
name="public_qr_id",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
f"^(?P<event>{SLUG_REGEX})/p/broadcast-tools/room-info/$",
|
||||||
|
BroadcastToolsRoomInfoView.as_view(),
|
||||||
|
name="room_info",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
f"^orga/event/(?P<event>{SLUG_REGEX})/settings/p/broadcast-tools/$",
|
||||||
|
BroadcastToolsOrgaView.as_view(),
|
||||||
name="orga",
|
name="orga",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
32
pretalx_broadcast_tools/utils/placeholders.py
Normal file
32
pretalx_broadcast_tools/utils/placeholders.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
def placeholders(schedule, talk, supports_html_colour=False):
|
||||||
|
track_name = str(talk.submission.track.name) if talk.submission.track else ""
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"CODE": talk.submission.code,
|
||||||
|
"EVENT_SLUG": str(schedule.event.slug),
|
||||||
|
"FEEDBACK_URL": "{}{}".format(
|
||||||
|
schedule.event.custom_domain or settings.SITE_URL,
|
||||||
|
talk.submission.urls.feedback,
|
||||||
|
),
|
||||||
|
"TALK_SLUG": talk.frab_slug,
|
||||||
|
"TALK_URL": "{}{}".format(
|
||||||
|
schedule.event.custom_domain or settings.SITE_URL,
|
||||||
|
talk.submission.urls.public,
|
||||||
|
),
|
||||||
|
"TRACK_NAME": track_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if talk.submission.track and supports_html_colour:
|
||||||
|
result["TRACK_NAME_COLOURED"] = '<span style="color: {}">{}</span>'.format(
|
||||||
|
talk.submission.track.color, track_name
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result["TRACK_NAME_COLOURED"] = track_name
|
||||||
|
|
||||||
|
# for the americans
|
||||||
|
result["TRACK_NAME_COLORED"] = result["TRACK_NAME_COLOURED"]
|
||||||
|
|
||||||
|
return result
|
|
@ -1,112 +0,0 @@
|
||||||
import datetime as dt
|
|
||||||
|
|
||||||
import pytz
|
|
||||||
from django.http import JsonResponse
|
|
||||||
from django.views.generic import FormView
|
|
||||||
from django.views.generic.base import TemplateView
|
|
||||||
from pretalx.agenda.views.schedule import ScheduleMixin
|
|
||||||
from pretalx.common.mixins.views import EventPermissionRequired, PermissionRequired
|
|
||||||
from pretalx.schedule.exporters import ScheduleData
|
|
||||||
|
|
||||||
from .forms import BroadcastToolsSettingsForm
|
|
||||||
|
|
||||||
|
|
||||||
class BroadcastToolsLowerThirdsView(TemplateView):
|
|
||||||
template_name = "pretalx_broadcast_tools/lower_thirds.html"
|
|
||||||
|
|
||||||
|
|
||||||
class BroadcastToolsOrgaView(PermissionRequired, FormView):
|
|
||||||
form_class = BroadcastToolsSettingsForm
|
|
||||||
permission_required = "orga.change_settings"
|
|
||||||
template_name = "pretalx_broadcast_tools/orga.html"
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return self.request.path
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
form.save()
|
|
||||||
return super().form_valid(form)
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
return self.request.event
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
|
||||||
kwargs = super().get_form_kwargs()
|
|
||||||
return {
|
|
||||||
"obj": self.request.event,
|
|
||||||
"attribute_name": "settings",
|
|
||||||
"locales": self.request.event.locales,
|
|
||||||
**kwargs,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class BroadcastToolsEventInfoView(TemplateView):
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
color = self.request.event.primary_color or "#3aa57c"
|
|
||||||
return JsonResponse(
|
|
||||||
{
|
|
||||||
"slug": self.request.event.slug,
|
|
||||||
"name": str(self.request.event.name),
|
|
||||||
"no_talk": str(
|
|
||||||
self.request.event.settings.broadcast_tools_lower_thirds_no_talk_info
|
|
||||||
),
|
|
||||||
"color": color,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BroadcastToolsScheduleView(EventPermissionRequired, ScheduleMixin, TemplateView):
|
|
||||||
permission_required = "agenda.view_schedule"
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
schedule = ScheduleData(
|
|
||||||
event=self.request.event,
|
|
||||||
schedule=self.schedule,
|
|
||||||
)
|
|
||||||
tz = pytz.timezone(schedule.event.timezone)
|
|
||||||
infoline = str(
|
|
||||||
schedule.event.settings.broadcast_tools_lower_thirds_info_string or ""
|
|
||||||
)
|
|
||||||
return JsonResponse(
|
|
||||||
{
|
|
||||||
"rooms": sorted(
|
|
||||||
{
|
|
||||||
str(room["name"])
|
|
||||||
for day in schedule.data
|
|
||||||
for room in day["rooms"]
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"talks": [
|
|
||||||
{
|
|
||||||
"id": talk.submission.id,
|
|
||||||
"start": talk.start.astimezone(tz).isoformat(),
|
|
||||||
"end": (talk.start + dt.timedelta(minutes=talk.duration))
|
|
||||||
.astimezone(tz)
|
|
||||||
.isoformat(),
|
|
||||||
"slug": talk.frab_slug,
|
|
||||||
"title": talk.submission.title,
|
|
||||||
"persons": sorted(
|
|
||||||
{
|
|
||||||
person.get_display_name()
|
|
||||||
for person in talk.submission.speakers.all()
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"track": {
|
|
||||||
"color": talk.submission.track.color,
|
|
||||||
"name": str(talk.submission.track.name),
|
|
||||||
}
|
|
||||||
if talk.submission.track
|
|
||||||
else None,
|
|
||||||
"room": str(room["name"]),
|
|
||||||
"infoline": infoline.format(
|
|
||||||
EVENT_SLUG=str(schedule.event.slug),
|
|
||||||
TALK_SLUG=talk.frab_slug,
|
|
||||||
CODE=talk.submission.code,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
for day in schedule.data
|
|
||||||
for room in day["rooms"]
|
|
||||||
for talk in room["talks"]
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
|
30
pretalx_broadcast_tools/views/event_info.py
Normal file
30
pretalx_broadcast_tools/views/event_info.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastToolsEventInfoView(View):
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
color = self.request.event.primary_color or "#3aa57c"
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"color": color,
|
||||||
|
"name": self.request.event.name.localize(self.request.event.locale),
|
||||||
|
"no_talk": str(
|
||||||
|
self.request.event.settings.broadcast_tools_lower_thirds_no_talk_info
|
||||||
|
),
|
||||||
|
"room-info": {
|
||||||
|
"lower_info": self.request.event.settings.broadcast_tools_room_info_lower_content
|
||||||
|
or "",
|
||||||
|
"show_next_talk": (
|
||||||
|
True
|
||||||
|
if self.request.event.settings.broadcast_tools_room_info_show_next_talk
|
||||||
|
else False
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"slug": self.request.event.slug,
|
||||||
|
"start": self.request.event.date_from.isoformat(),
|
||||||
|
"end": self.request.event.date_to.isoformat(),
|
||||||
|
"timezone": str(self.request.event.tz),
|
||||||
|
"locale": self.request.event.locale,
|
||||||
|
},
|
||||||
|
)
|
37
pretalx_broadcast_tools/views/orga.py
Normal file
37
pretalx_broadcast_tools/views/orga.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
from django.views.generic import FormView
|
||||||
|
from pretalx.common.views.mixins import PermissionRequired
|
||||||
|
|
||||||
|
from ..forms import BroadcastToolsSettingsForm
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastToolsOrgaView(PermissionRequired, FormView):
|
||||||
|
form_class = BroadcastToolsSettingsForm
|
||||||
|
permission_required = "orga.change_settings"
|
||||||
|
template_name = "pretalx_broadcast_tools/orga.html"
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return self.request.path
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context["localized_rooms"] = [
|
||||||
|
room.name.localize(self.request.event.locale)
|
||||||
|
for room in self.request.event.rooms.all()
|
||||||
|
]
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.save()
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return self.request.event
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
return {
|
||||||
|
"obj": self.request.event,
|
||||||
|
"attribute_name": "settings",
|
||||||
|
"locales": self.request.event.locales,
|
||||||
|
**kwargs,
|
||||||
|
}
|
30
pretalx_broadcast_tools/views/qr.py
Normal file
30
pretalx_broadcast_tools/views/qr.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
|
import qrcode
|
||||||
|
import qrcode.image.svg
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastToolsFeedbackQrCodeSvg(View):
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
talk = self.request.event.submissions.filter(id=kwargs["talk"]).first()
|
||||||
|
domain = request.event.custom_domain or settings.SITE_URL
|
||||||
|
image = qrcode.make(
|
||||||
|
f"{domain}{talk.urls.feedback}", image_factory=qrcode.image.svg.SvgImage
|
||||||
|
)
|
||||||
|
svg_data = mark_safe(ElementTree.tostring(image.get_image()).decode())
|
||||||
|
return HttpResponse(svg_data, content_type="image/svg+xml")
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastToolsPublicQrCodeSvg(View):
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
talk = self.request.event.submissions.filter(id=kwargs["talk"]).first()
|
||||||
|
domain = request.event.custom_domain or settings.SITE_URL
|
||||||
|
image = qrcode.make(
|
||||||
|
f"{domain}{talk.urls.public}", image_factory=qrcode.image.svg.SvgImage
|
||||||
|
)
|
||||||
|
svg_data = mark_safe(ElementTree.tostring(image.get_image()).decode())
|
||||||
|
return HttpResponse(svg_data, content_type="image/svg+xml")
|
122
pretalx_broadcast_tools/views/schedule.py
Normal file
122
pretalx_broadcast_tools/views/schedule.py
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.views import View
|
||||||
|
from pretalx.agenda.views.schedule import ScheduleMixin
|
||||||
|
from pretalx.common.views.mixins import EventPermissionRequired
|
||||||
|
from pretalx.schedule.exporters import ScheduleData
|
||||||
|
|
||||||
|
from ..utils.placeholders import placeholders
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastToolsScheduleView(EventPermissionRequired, ScheduleMixin, View):
|
||||||
|
permission_required = "agenda.view_schedule"
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
schedule = ScheduleData(
|
||||||
|
event=self.request.event,
|
||||||
|
schedule=self.schedule,
|
||||||
|
)
|
||||||
|
infoline = str(
|
||||||
|
schedule.event.settings.broadcast_tools_lower_thirds_info_string or ""
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"rooms": sorted(
|
||||||
|
{
|
||||||
|
room["name"].localize(schedule.event.locale)
|
||||||
|
for day in schedule.data
|
||||||
|
for room in day["rooms"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"talks": [
|
||||||
|
{
|
||||||
|
"id": talk.submission.id,
|
||||||
|
"start": talk.start.astimezone(
|
||||||
|
schedule.event.tz
|
||||||
|
).isoformat(),
|
||||||
|
"start_ts": int(talk.start.timestamp()),
|
||||||
|
"end": (talk.start + dt.timedelta(minutes=talk.duration))
|
||||||
|
.astimezone(schedule.event.tz)
|
||||||
|
.isoformat(),
|
||||||
|
"end_ts": int(
|
||||||
|
(
|
||||||
|
talk.start + dt.timedelta(minutes=talk.duration)
|
||||||
|
).timestamp()
|
||||||
|
),
|
||||||
|
"slug": talk.frab_slug,
|
||||||
|
"title": talk.submission.title,
|
||||||
|
"persons": [
|
||||||
|
person.get_display_name()
|
||||||
|
for person in talk.submission.speakers.all()
|
||||||
|
],
|
||||||
|
"track": (
|
||||||
|
{
|
||||||
|
"color": talk.submission.track.color,
|
||||||
|
"name": str(talk.submission.track.name),
|
||||||
|
}
|
||||||
|
if talk.submission.track
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
"room": room["name"].localize(schedule.event.locale),
|
||||||
|
"infoline": infoline.format(
|
||||||
|
**placeholders(
|
||||||
|
schedule, talk, supports_html_colour=True
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"image_url": talk.submission.image_url,
|
||||||
|
"locale": talk.submission.content_locale,
|
||||||
|
"do_not_record": talk.submission.do_not_record,
|
||||||
|
"abstract": talk.submission.abstract,
|
||||||
|
"urls": {
|
||||||
|
"feedback": "{}{}".format(
|
||||||
|
schedule.event.custom_domain or settings.SITE_URL,
|
||||||
|
talk.submission.urls.feedback,
|
||||||
|
),
|
||||||
|
"feedback_qr": reverse(
|
||||||
|
"plugins:pretalx_broadcast_tools:feedback_qr_id",
|
||||||
|
kwargs={
|
||||||
|
"event": schedule.event.slug,
|
||||||
|
"talk": talk.submission.id,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"public": "{}{}".format(
|
||||||
|
schedule.event.custom_domain or settings.SITE_URL,
|
||||||
|
talk.submission.urls.public,
|
||||||
|
),
|
||||||
|
"public_qr": reverse(
|
||||||
|
"plugins:pretalx_broadcast_tools:public_qr_id",
|
||||||
|
kwargs={
|
||||||
|
"event": schedule.event.slug,
|
||||||
|
"talk": talk.submission.id,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for day in schedule.data
|
||||||
|
for room in day["rooms"]
|
||||||
|
for talk in room["talks"]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
except KeyError as e:
|
||||||
|
key = str(e)[1:-1]
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"error": [
|
||||||
|
f"Could not find value for placeholder {{{key}}} in info line.",
|
||||||
|
f"If you want to use {{{key}}} without evaluating it, please use as follows: {{{{{key}}}}}",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"error": [
|
||||||
|
repr(e),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
9
pretalx_broadcast_tools/views/static_html.py
Normal file
9
pretalx_broadcast_tools/views/static_html.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastToolsLowerThirdsView(TemplateView):
|
||||||
|
template_name = "pretalx_broadcast_tools/lower_thirds.html"
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastToolsRoomInfoView(TemplateView):
|
||||||
|
template_name = "pretalx_broadcast_tools/room_info.html"
|
41
pyproject.toml
Normal file
41
pyproject.toml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
[project]
|
||||||
|
name = "pretalx-broadcast-tools"
|
||||||
|
dynamic = ["version"]
|
||||||
|
description = """
|
||||||
|
Some tools which can be used for supporting a broadcasting software.
|
||||||
|
This currently includes a generator for PDF printouts, a 'lower thirds'
|
||||||
|
endpoint, and a full-screen webpage showing information about the
|
||||||
|
currently running talk.
|
||||||
|
"""
|
||||||
|
readme = "README.rst"
|
||||||
|
license = {text = "Apache Software License"}
|
||||||
|
keywords = ["pretalx"]
|
||||||
|
authors = [
|
||||||
|
{name = "Franziska Kunsmann", email = "git@kunsmann.eu"},
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{name = "Franziska Kunsmann", email = "git@kunsmann.eu"},
|
||||||
|
]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.entry-points."pretalx.plugin"]
|
||||||
|
pretalx_broadcast_tools = "pretalx_broadcast_tools:PretalxPluginMeta"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
requires = ["setuptools", "wheel"]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
homepage = "https://github.com/Kunsi/pretalx-plugin-broadcast-tools"
|
||||||
|
repository = "https://github.com/Kunsi/pretalx-plugin-broadcast-tools.git"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
include-package-data = true
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
version = {attr = "pretalx_broadcast_tools.__version__"}
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["pretalx*"]
|
46
setup.py
46
setup.py
|
@ -1,46 +0,0 @@
|
||||||
import os
|
|
||||||
from distutils.command.build import build
|
|
||||||
|
|
||||||
from django.core import management
|
|
||||||
from setuptools import find_packages, setup
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(
|
|
||||||
os.path.join(os.path.dirname(__file__), "README.rst"), encoding="utf-8"
|
|
||||||
) as f:
|
|
||||||
long_description = f.read()
|
|
||||||
except FileNotFoundError:
|
|
||||||
long_description = ""
|
|
||||||
|
|
||||||
|
|
||||||
class CustomBuild(build):
|
|
||||||
def run(self):
|
|
||||||
management.call_command("compilemessages", verbosity=1)
|
|
||||||
build.run(self)
|
|
||||||
|
|
||||||
|
|
||||||
cmdclass = {"build": CustomBuild}
|
|
||||||
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name="pretalx-plugin-broadcast-tools",
|
|
||||||
version="1.0.3",
|
|
||||||
description=(
|
|
||||||
"Some tools which can be used for supporting a broadcasting "
|
|
||||||
"software, for example a 'lower third' page which can be "
|
|
||||||
"embedded into your broadcasting software"
|
|
||||||
),
|
|
||||||
long_description=long_description,
|
|
||||||
url="https://github.com/Kunsi/pretalx-plugin-broadcast-tools",
|
|
||||||
author="kunsi",
|
|
||||||
author_email="git@kunsmann.eu",
|
|
||||||
license="Apache Software License",
|
|
||||||
install_requires=[],
|
|
||||||
packages=find_packages(exclude=["tests", "tests.*"]),
|
|
||||||
include_package_data=True,
|
|
||||||
cmdclass=cmdclass,
|
|
||||||
entry_points="""
|
|
||||||
[pretalx.plugin]
|
|
||||||
pretalx_broadcast_tools=pretalx_broadcast_tools:PretalxPluginMeta
|
|
||||||
""",
|
|
||||||
)
|
|
Loading…
Reference in a new issue