Audio songs: one-folder storage, version-aware download/share, GPU-checked renders

Storage structure
- All audio tracks (primary + per-language) now live in one folder per song with
  unique lowercase names ({slug}-{lang}-{id}); no more tracks/ subfolder.
- Generated renders (download video + HLS) moved into the song's local-only cache/
  subfolder, separated from source files (never synced to NAS, safe to wipe).
- tracks:reorganize artisan command (dry-run default) consolidates legacy files,
  updates DB paths, and deletes orphans + empty folders.
- CLAUDE.md documents the canonical layout as a global rule (identical local + NAS).

Version-aware download & share
- Download MP3/Video and Share now act on the version being played; ?track={id}
  is carried through share links and auto-selects audio + title + flag + about +
  OG/meta on open.

GPU + visualizer
- Setting::gpuUsable() runs a cached health probe (nvidia-smi + nvenc smoke test,
  256x144) before sending any encode to the GPU; falls back to CPU otherwise.
- Visualizer "Download Video" bakes in equal-width, cover-coloured, translucent
  frequency bars; loop-filter rebuild makes generation ~25x faster.

Image cropper
- result-callback mode + per-song cover-slide cropper in upload/edit (modal + mobile).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-05-23 14:03:43 +03:00
1 parent 66fd78c10f
commit a4384113c2
24 files changed
+1738 -258

No files matched your search

+12 -9
View File
@@ -224,24 +224,27 @@ class NasFreeLocalStorage extends Command
$this->line(' Done scanning banners.');
// ── Slideshow cache directories ───────────────────────────────────────
// The slideshow/ directory is a render cache that is always regenerated on
// demand, so its contents are safe to delete unconditionally.
// ── Generated/derived renders inside song folders ─────────────────────
// Everything under a song's cache/ subfolder (download videos + HLS) is a
// render that regenerates on demand, so it is always safe to delete to free
// space. Sources (audio, tracks, slides) live outside cache/ and are untouched.
$this->newLine();
$this->info('Scanning slideshow cache…');
$this->info('Scanning generated renders (song cache/ folders)…');
$slideshowDir = storage_path('app/public/slideshow');
if (is_dir($slideshowDir)) {
$usersRoot = storage_path('app/users');
if (is_dir($usersRoot)) {
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($slideshowDir, \FilesystemIterator::SKIP_DOTS)
new \RecursiveDirectoryIterator($usersRoot, \FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if (! $file->isFile()) continue;
$path = $file->getPathname();
if (! str_contains($path, '/cache/')) continue;
$bytes = $file->getSize();
$totalBytes += $bytes;
$toDelete[] = ['label' => 'slideshow', 'path' => $file->getPathname(), 'bytes' => $bytes];
$toDelete[] = ['label' => str_contains($path, '/cache/hls/') ? 'hls' : 'download-video', 'path' => $path, 'bytes' => $bytes];
}
$this->line(' Done scanning slideshow cache.');
$this->line(' Done scanning generated renders.');
}
// ── NAS stream cache (nas_cache/videos/) ──────────────────────────────