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

@@ -11,6 +11,7 @@
$outputWidth = (int) $attributes->get('output-width', 0); // final output px width (0 = viewport size)
$targetInput = $attributes->get('target-input', ''); // form mode: ID of file input to set result on
$previewImg = $attributes->get('preview-img', ''); // ID of img to update with preview
$resultCallback = $attributes->get('result-callback', ''); // callback mode: JS fn called with the cropped File
@endphp
@once
@@ -202,8 +203,22 @@
var outputWidth = {{ $outputWidth > 0 ? $outputWidth : 0 }};
var targetInputId = '{{ $targetInput }}'; // form mode: ID of the file input to intercept
var previewImgId = '{{ $previewImg }}';
var resultCbName = '{{ $resultCallback }}'; // callback mode: name of a global fn given the cropped File
var isFormMode = targetInputId !== '';
var isFormMode = targetInputId !== '';
var isCallbackMode = resultCbName !== '';
// In callback mode the host function decides when to close / advance, so we
// never auto-close here — we just hand the File back and reset the button.
function deliverResult(file) {
if (typeof window[resultCbName] === 'function') window[resultCbName](file);
var sb = document.getElementById('tcSaveBtn_' + id);
var st = document.getElementById('tcSaveBtnText_' + id);
var ab = document.getElementById('tcAsIsBtn_' + id);
if (sb) sb.disabled = false;
if (ab) ab.disabled = false;
if (st) st.textContent = 'Crop & Save';
}
function getCsrf() {
var m = document.querySelector('meta[name="csrf-token"]');
@@ -343,6 +358,11 @@
var cropOpts = outputWidth > 0 ? { type: 'base64', width: outputWidth } : { type: 'base64' };
cropperInst.crop(cropOpts).then(function (base64) {
if (isCallbackMode) {
var cbName = originalFile ? originalFile.name : 'cropped.png';
deliverResult(base64ToFile(base64, cbName));
return;
}
if (isFormMode) {
var fname = originalFile ? originalFile.name : 'cropped.png';
setOnTargetInput(base64ToFile(base64, fname));
@@ -370,7 +390,9 @@
window['tcUploadAsIs_' + id] = function () {
if (!originalFile) { window.closeCropperModal(id); return; }
if (isFormMode) {
if (isCallbackMode) {
deliverResult(originalFile);
} else if (isFormMode) {
// Put the original (un-cropped) file on the target input so the form sees it.
setOnTargetInput(originalFile);
window.closeCropperModal(id);