Add lyrics pipeline, playlist views, admin toggles, and player polish

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 <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-05-31 22:01:47 +03:00
1 parent 73527f3781
commit f98e5415a3
60 files changed
+7920 -2675

No files matched your search

+1 -1
View File
@@ -178,7 +178,7 @@
<div class="playlist-info">
<div class="playlist-name">{{ $playlist->name }}</div>
<div class="playlist-meta">
{{ $playlist->video_count }} videos • {{ $playlist->formatted_duration }}
{{ $playlist->video_count }} videos • {{ $playlist->formatted_duration }} • {{ \Illuminate\Support\Number::abbreviate($playlist->view_count, precision: 1) }} views
</div>
@if($playlist->description)
<div class="playlist-description">{{ $playlist->description }}</div>
+8
View File
@@ -319,6 +319,8 @@
<span class="sep">·</span>
<span>{{ $playlist->formatted_duration }}</span>
@endif
<span class="sep">·</span>
<span><i class="bi bi-eye"></i> {{ number_format($playlist->view_count) }} {{ Str::plural('view', $playlist->view_count) }}</span>
@if($playlist->is_default)
<span class="pl-hero-badge" style="color:#60a5fa;">
<i class="bi bi-clock"></i> Watch Later
@@ -630,6 +632,12 @@ document.addEventListener('DOMContentLoaded', function () {
});
function openEditPlaylistModal() { const m = document.getElementById('editPlaylistModal'); if (m) m.style.display = 'flex'; }
// Auto-open the edit modal when arriving with #edit (e.g. from playlist-card menu).
@if($canEdit ?? false)
if (window.location.hash === '#edit') {
document.addEventListener('DOMContentLoaded', function () { openEditPlaylistModal(); });
}
@endif
function closeEditPlaylistModal() { const m = document.getElementById('editPlaylistModal'); if (m) m.style.display = 'none'; }
function handleThumbUpload(input) {