Lyrics pipeline (Whisper + Demucs + description alignment):
- New GenerateLyricsJob runs WhisperX with VAD filtering and forced word
alignment, writes per-track JSON to NAS.
- New DecorateLyricsJob calls the active LLM provider to bake one to
several emojis into each line (heavy decoration prompt).
- LyricsDescriptionParser strips heading content, section markers, and
emoji-decoration from a song's description while preserving every
language verbatim.
- correct_whisper_with_description aligner: strong-match anchors only,
vocal-region-aware gap-fill so missing verses land on actual singing.
- Owner UI for generate/regenerate/edit/delete in the player gear.
Admin pages:
- /admin/lyrics toggles for VAD, vocal gap-fill, Demucs, master
- /admin/gpu extracted GPU section, encoder picker, FFmpeg path
- /admin/backup extracted users-and-settings export/import
- /admin/settings now AI/LLM only with provider list and Test button
- /admin/nas-storage hosts NAS settings, repair, disable flow, browser
- Shared partials/settings-styles for a uniform look across pages.
Playlist view tracking:
- Migration adds playlists.view_count and playlist_views dedup table.
- Playlist::bumpViewIfNew increments per device with a one-hour window.
- Tracked from /playlists/{id}, /playlists/share/{token}, /ps/{token},
and /videos/{id}?playlist={token}. Dispatched after-response so it
never blocks the page render.
- Loading a playlist on the video page now runs one query instead of
the four the old getNextVideo/getPreviousVideo path triggered.
- View counts shown on every playlist card and the playlist hero.
Player polish:
- Floating mini-player is draggable, persists its position in
localStorage, clamps to viewport on resize.
- Mini disabled entirely on mobile (less than 768px).
- New gear-menu Mini Player toggle (persists in localStorage) lets the
user disable both scroll-activation and SPA-nav-activation.
- Close button keeps media playing when used on the player's own page.
- SPA navigator now swaps a #page-scripts container so per-page JS
(channel tabs, etc.) gets re-executed after content swaps.
Storage layout:
- Runtime data moved from /storage/* to /data/* and gitignored.
- /ml/venv, /ml/cache, /ml/__pycache__ excluded.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
70 lines
2.6 KiB
PHP
70 lines
2.6 KiB
PHP
@extends('admin.layout')
|
|
|
|
@section('title', 'Backup & Restore')
|
|
@section('page_title', 'Backup & Restore')
|
|
|
|
@section('extra_styles')
|
|
@include('admin.partials.settings-styles')
|
|
@endsection
|
|
|
|
@section('content')
|
|
<div class="adm-page-header">
|
|
<h1 class="adm-page-title"><i class="bi bi-archive"></i> Backup & Restore</h1>
|
|
</div>
|
|
|
|
@if(session('success'))
|
|
<div class="adm-alert adm-alert-success" style="margin-bottom:20px;">
|
|
<i class="bi bi-check-circle-fill"></i>
|
|
<span>{{ session('success') }}</span>
|
|
<button class="adm-alert-close" type="button"><i class="bi bi-x"></i></button>
|
|
</div>
|
|
@endif
|
|
|
|
@if($errors->any())
|
|
<div class="adm-alert adm-alert-error" style="margin-bottom:20px;">
|
|
<i class="bi bi-exclamation-triangle-fill"></i>
|
|
<span>{{ $errors->first() }}</span>
|
|
<button class="adm-alert-close" type="button"><i class="bi bi-x"></i></button>
|
|
</div>
|
|
@endif
|
|
|
|
<div class="settings-section">
|
|
<div class="settings-section-header">
|
|
<i class="bi bi-archive"></i>
|
|
Users & Settings Backup
|
|
</div>
|
|
<div class="settings-section-body">
|
|
|
|
<div class="setting-row">
|
|
<div class="setting-label">
|
|
<strong>Export users & settings</strong>
|
|
<small>Downloads a JSON file containing all user accounts and system settings. Does not include media files.</small>
|
|
</div>
|
|
<div class="setting-control">
|
|
<a href="{{ route('admin.backup.users-settings') }}" class="adm-btn adm-btn-primary">
|
|
<i class="bi bi-download"></i> Download Backup
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-row">
|
|
<div class="setting-label">
|
|
<strong>Restore users & settings</strong>
|
|
<small>Upload a previously exported backup JSON. Existing users are matched by email and updated; new users are created. Settings are merged.</small>
|
|
</div>
|
|
<div class="setting-control">
|
|
<form method="POST" action="{{ route('admin.backup.restore') }}" enctype="multipart/form-data" id="restoreForm">
|
|
@csrf
|
|
<input type="file" name="backup" id="restoreFile" accept=".json" style="display:none"
|
|
onchange="document.getElementById('restoreForm').submit()">
|
|
<button type="button" class="adm-btn" onclick="document.getElementById('restoreFile').click()">
|
|
<i class="bi bi-upload"></i> Upload & Restore
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
@endsection
|