Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ Thumbs.db
*.swp
*.bak
*.bak_*
/demo-data/
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,18 @@ All notable changes to this project are documented here. The format follows
tabs and tables.
- Browsers kept stale CSS/JS after updates; static URLs now carry a version
derived from the shipped files.
- Storage figures showed "N/A" on non-Linux hosts and swallowed errors
silently; they now use `shutil.disk_usage` and log failures.
- The VOD chat panel was always labelled "Twitch Chat", untranslated, also
for YouTube recordings.
- The sidebar language switcher was cut off to "A…"; it now shows a globe and
the language code.
- Channel pages showed a stray separator when a recording had no size.

### Added

- GitHub Actions CI (Python 3.10–3.12, ruff, unit tests, shell syntax),
Dependabot, contribution and security guidelines.
- Web UI in 12 languages, chosen from the browser language or the switcher.
- `scripts/demo_seed.py` creates a sample archive for trying StreamVault
without recording anything.
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ review; corrections are very welcome (see [CONTRIBUTING.md](CONTRIBUTING.md#tran
| :---: | :---: |
| ![Dashboard](docs/screenshots/dashboard.png) | ![VOD player](docs/screenshots/vod_player.png) |

| Channel archive | Users and groups |
| Channel archive | Cloud storage |
| :---: | :---: |
| ![Channel archive](docs/screenshots/streamer_hub.png) | ![User management](docs/screenshots/admin_users.png) |
| ![Channel archive](docs/screenshots/streamer_hub.png) | ![Cloud storage](docs/screenshots/cloud_storage.png) |

| System status | Phone layout |
| :---: | :---: |
Expand Down Expand Up @@ -77,6 +77,26 @@ flowchart LR

Three services run side by side: `recorder`, `worker` and `webui`.

## Try it locally

You can look around without recording anything. `scripts/demo_seed.py`
renders a few short sample recordings with chat (FFmpeg test sources, no
third-party content) into an empty directory:

```bash
git clone https://github.com/Blackwellcode/streamvault.git && cd streamvault
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
python scripts/demo_seed.py --base-dir ./demo-data
VOD_BASE_DIR=./demo-data VOD_USE_X_ACCEL=false \
VOD_USER=admin VOD_PASS='choose-a-password' \
VOD_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(32))')" \
python -m WEBUI.webui --host 127.0.0.1 --port 8080
```

Requires FFmpeg on the `PATH`. Open `http://127.0.0.1:8080` and sign in with
the `VOD_USER` and `VOD_PASS` values from the command.

## Installation

### Ubuntu 22.04+ / Debian 12+
Expand Down Expand Up @@ -140,7 +160,7 @@ The full list with explanations is in [.env.example](.env.example).

## Cloud storage

Add providers in the web UI (**Systemsteuerung → Cloud-Speicher**) or on the
Add providers in the web UI (**Options → Cloud storage**) or on the
command line:

```bash
Expand Down
7 changes: 7 additions & 0 deletions WEBUI/templates/components/language_switcher.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="next" value="{{ request.full_path.rstrip('?') if request.method == 'GET' else request.path }}">
<label for="ns-lang-select" class="sr-only">{{ _('Language') }}</label>
{% if lang_compact %}
{# Narrow rail: show a globe and the active language code; the native select sits invisibly on top so the dropdown keeps the full names. #}
<span class="ns-lang-compact" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
<span>{{ current_locale.split('_')[0]|upper }}</span>
</span>
{% endif %}
<select id="ns-lang-select" name="lang" onchange="this.form.submit()">
<option value="">{{ _('Automatic (browser)') }}</option>
{% for code, name in available_locales %}
Expand Down
8 changes: 6 additions & 2 deletions WEBUI/templates/components/sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
</button>
<style>
.ns-rail-lang .ns-lang-switch select { min-height: 30px; width: 44px; padding: 0 2px; font-size: 11px; text-overflow: ellipsis; cursor: pointer; }
.ns-rail-lang .ns-lang-switch { position: relative; }
.ns-rail-lang .ns-lang-compact { display: flex; align-items: center; justify-content: center; gap: 3px; width: 44px; height: 30px; border: 1px solid var(--nebula-border, #26344F); border-radius: 8px; color: var(--ns-text-muted); font-size: 10px; font-weight: 700; letter-spacing: 0.02em; }
.ns-rail-lang:hover .ns-lang-compact { color: #FFF; }
.ns-rail-lang .ns-lang-switch:focus-within .ns-lang-compact { outline: 2px solid #4D63FF; outline-offset: 2px; }
.ns-rail-lang .ns-lang-switch select { position: absolute; inset: 0; width: 100%; height: 100%; min-height: 0; padding: 0; opacity: 0; cursor: pointer; }
</style>
<div class="ns-rail-lang" title="{{ _('Language') }}" style="display: flex; justify-content: center; margin: 0 auto 4px;">
{% include 'components/language_switcher.html' %}
{% with lang_compact = true %}{% include 'components/language_switcher.html' %}{% endwith %}
</div>
{% endif %}
<form method="post" action="{{ url_for('logout') }}" class="ns-logout-btn">
Expand Down
4 changes: 2 additions & 2 deletions WEBUI/templates/storage.html
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ <h4 class="storage-card-title">{{ p.name }}</h4>
{% else %}
<div class="storage-empty">
{{ _('No cloud storage configured yet. Add a provider or use the console:') }}
<code>streamvault storage add --type google_drive --name "Mein Drive"</code>
<code>streamvault storage add --type google_drive --name "My Drive"</code>
</div>
{% endif %}
</section>
Expand Down Expand Up @@ -273,7 +273,7 @@ <h3>{{ _('Manual sync') }}</h3>
</div>
<div class="storage-field" style="min-width:220px;">
<label>{{ _('Streamer (optional)') }}</label>
<input type="text" name="streamer" placeholder="{{ _('e.g. montanablack88') }}">
<input type="text" name="streamer" placeholder="{{ _('e.g. channel_name') }}">
</div>
<button type="submit" class="storage-btn primary">{{ _('Start sync') }}</button>
<button type="submit" class="storage-btn" formaction="{{ url_for('admin_storage_cleanup') }}">{{ _('Start local cleanup') }}</button>
Expand Down
2 changes: 2 additions & 0 deletions WEBUI/templates/streamer.html
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,10 @@ <h2 class="nebula-hero-title">{{ hero_stream.title }}</h2>
</a>
<div class="ns-video-subline">
<span>{{ f.date_str }}</span>
{% if f.size_str %}
<span>•</span>
<span>{{ f.size_str }}</span>
{% endif %}
{% if f.category %}
<span>•</span>
<span style="color: #BB78FF;">{{ _('Game: %(category)s', category=f.category) }}</span>
Expand Down
2 changes: 1 addition & 1 deletion WEBUI/templates/vod.html
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ <h2 id="vodTranscriptTitle">{{ _('Transcript') }}</h2>
<aside class="chat-panel">
<div class="chat-split-col chat-split-twitch">
<div class="chat-header">
<span>Twitch Chat</span>
<span>{{ _('Chat') }}</span>
<button id="chatAutoToggle" class="chat-toggle" type="button">{{ _('Auto') }}</button>
</div>
{% if not is_mobile %}
Expand Down
Binary file modified WEBUI/translations/de/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions WEBUI/translations/de/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -3362,8 +3362,8 @@ msgstr "z.B. alice, bob"
msgid "e.g. john_doe"
msgstr "z.B. max_mustermann"

msgid "e.g. montanablack88"
msgstr "z. B. montanablack88"
msgid "e.g. channel_name"
msgstr "z. B. channel_name"

#, python-format
msgid "effective from %(date)s"
Expand Down
Binary file modified WEBUI/translations/es/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions WEBUI/translations/es/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -3362,8 +3362,8 @@ msgstr "p. ej., alice, bob"
msgid "e.g. john_doe"
msgstr "p. ej., juan_perez"

msgid "e.g. montanablack88"
msgstr "p. ej., montanablack88"
msgid "e.g. channel_name"
msgstr "p. ej., channel_name"

#, python-format
msgid "effective from %(date)s"
Expand Down
Binary file modified WEBUI/translations/fr/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions WEBUI/translations/fr/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -3362,8 +3362,8 @@ msgstr "ex. alice, bob"
msgid "e.g. john_doe"
msgstr "ex. jean_dupont"

msgid "e.g. montanablack88"
msgstr "ex. montanablack88"
msgid "e.g. channel_name"
msgstr "ex. channel_name"

#, python-format
msgid "effective from %(date)s"
Expand Down
Binary file modified WEBUI/translations/it/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions WEBUI/translations/it/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -3362,8 +3362,8 @@ msgstr "es. alice, bob"
msgid "e.g. john_doe"
msgstr "es. mario_rossi"

msgid "e.g. montanablack88"
msgstr "es. montanablack88"
msgid "e.g. channel_name"
msgstr "es. channel_name"

#, python-format
msgid "effective from %(date)s"
Expand Down
Binary file modified WEBUI/translations/ja/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions WEBUI/translations/ja/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -3356,8 +3356,8 @@ msgstr "例: alice, bob"
msgid "e.g. john_doe"
msgstr "例: john_doe"

msgid "e.g. montanablack88"
msgstr "例: montanablack88"
msgid "e.g. channel_name"
msgstr "例: channel_name"

#, python-format
msgid "effective from %(date)s"
Expand Down
Binary file modified WEBUI/translations/ko/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions WEBUI/translations/ko/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -3356,8 +3356,8 @@ msgstr "예: alice, bob"
msgid "e.g. john_doe"
msgstr "예: hong_gildong"

msgid "e.g. montanablack88"
msgstr "예: montanablack88"
msgid "e.g. channel_name"
msgstr "예: channel_name"

#, python-format
msgid "effective from %(date)s"
Expand Down
2 changes: 1 addition & 1 deletion WEBUI/translations/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -3482,7 +3482,7 @@ msgstr ""
msgid "e.g. john_doe"
msgstr ""

msgid "e.g. montanablack88"
msgid "e.g. channel_name"
msgstr ""

#, python-format
Expand Down
Binary file modified WEBUI/translations/nl/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions WEBUI/translations/nl/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -3362,8 +3362,8 @@ msgstr "bijv. alice, bob"
msgid "e.g. john_doe"
msgstr "bijv. jan_jansen"

msgid "e.g. montanablack88"
msgstr "bijv. montanablack88"
msgid "e.g. channel_name"
msgstr "bijv. channel_name"

#, python-format
msgid "effective from %(date)s"
Expand Down
Binary file modified WEBUI/translations/pl/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions WEBUI/translations/pl/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -3368,8 +3368,8 @@ msgstr "np. alice, bob"
msgid "e.g. john_doe"
msgstr "np. jan_kowalski"

msgid "e.g. montanablack88"
msgstr "np. montanablack88"
msgid "e.g. channel_name"
msgstr "np. channel_name"

#, python-format
msgid "effective from %(date)s"
Expand Down
Binary file modified WEBUI/translations/pt_BR/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions WEBUI/translations/pt_BR/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -3362,8 +3362,8 @@ msgstr "ex.: alice, bob"
msgid "e.g. john_doe"
msgstr "ex.: joao_silva"

msgid "e.g. montanablack88"
msgstr "ex.: montanablack88"
msgid "e.g. channel_name"
msgstr "ex.: channel_name"

#, python-format
msgid "effective from %(date)s"
Expand Down
Binary file modified WEBUI/translations/tr/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions WEBUI/translations/tr/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -3356,8 +3356,8 @@ msgstr "ör. alice, bob"
msgid "e.g. john_doe"
msgstr "ör. john_doe"

msgid "e.g. montanablack88"
msgstr "ör. montanablack88"
msgid "e.g. channel_name"
msgstr "ör. channel_name"

#, python-format
msgid "effective from %(date)s"
Expand Down
Binary file modified WEBUI/translations/zh_Hans/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions WEBUI/translations/zh_Hans/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -3356,8 +3356,8 @@ msgstr "如:alice, bob"
msgid "e.g. john_doe"
msgstr "如:zhang_san"

msgid "e.g. montanablack88"
msgstr "如:montanablack88"
msgid "e.g. channel_name"
msgstr "如:channel_name"

#, python-format
msgid "effective from %(date)s"
Expand Down
11 changes: 7 additions & 4 deletions WEBUI/webui.py
Original file line number Diff line number Diff line change
Expand Up @@ -1829,9 +1829,11 @@ def format_bytes(num_bytes):

def storage_info():
try:
st = os.statvfs(BASE_DIR)
total = st.f_frsize * st.f_blocks
free = st.f_frsize * st.f_bavail
# shutil.disk_usage works on every platform; free is the space available
# to unprivileged users (statvfs f_bavail on POSIX).
usage = shutil.disk_usage(BASE_DIR)
total = usage.total
free = usage.free
used = total - free
pct = (used / total) * 100 if total else 0
return {
Expand All @@ -1842,7 +1844,8 @@ def storage_info():
"total_str": format_bytes(total),
"free_str": format_bytes(free),
}
except Exception:
except OSError as exc:
logging.warning("Storage info unavailable for %s: %s", BASE_DIR, exc)
return {
"total": 0,
"free": 0,
Expand Down
4 changes: 2 additions & 2 deletions core/storage/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Usage:
python -m core.storage.cli list
python -m core.storage.cli add --type google_drive --name "Mein Drive"
python -m core.storage.cli add --type google_drive --name "My Drive"
python -m core.storage.cli test 1
python -m core.storage.cli sync
python -m core.storage.cli upload /pfad/zum/video.mp4
Expand Down Expand Up @@ -87,7 +87,7 @@ def _cmd_list(args):
return 0
if not rows:
_print("Keine Cloud-Speicher konfiguriert.")
_print("Anlegen: python -m core.storage.cli add --type google_drive --name 'Mein Drive'")
_print("Anlegen: python -m core.storage.cli add --type google_drive --name 'My Drive'")
return 0
settings = service.get_settings()
default_id = settings.get("default_provider_id")
Expand Down
Binary file modified docs/screenshots/admin_panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/screenshots/admin_users.png
Binary file not shown.
Binary file added docs/screenshots/cloud_storage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/mobile_view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/streamer_hub.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/vod_player.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading