feat: save as .feedpak; discover and load both .feedpak and .sloppak (#553)

* feat: save songs as .feedpak; discover and load both .feedpak and .sloppak

The open song format was renamed sloppak -> feedpak (public spec lives in
the feedback-feedpak-spec repo), but the server still wrote and recognized
only `.sloppak`. The two are byte-identical on disk.

Read both suffixes everywhere songs are discovered, uploaded, and loaded;
writing the new `.feedpak` suffix is handled in the editor plugin repo. Keep
the internal `format` tag `sloppak` so existing feature gates (stems, drums,
keys) are untouched, matching the "internal rename not landed yet" stance.

- lib/sloppak.py: add FEEDPAK_EXT / SLOPPAK_EXT / SONG_EXTS; is_sloppak()
  now matches either suffix (covers all 7 callers).
- server.py: union scan glob over SONG_EXTS; widen loose-folder exclusion,
  settings DLC count, upload gate (_ALLOWED_SONG_EXTS) and zip-magic check;
  refresh user-facing messages to .feedpak.
- static: library format filter relabeled Sloppak -> Feedpak (value stays
  sloppak, matches both); badge text SLOPPAK -> FEEDPAK in v2 + v3;
  filename-suffix detection and upload drag-drop filter accept both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: topkoa <topkoa@gmail.com>

* test: cover .feedpak/.sloppak dual-suffix support

Add tests/test_feedpak_extension.py pinning the four paths PR #553
widened so a refactor can't drop .sloppak back-compat or stop
accepting .feedpak:

- is_sloppak / SONG_EXTS suffix detection (file + dir form, case-insensitive)
- _background_scan discovery glob unions over both suffixes
- POST /api/songs/upload accepts both, rejects wrong suffix + non-zip
- save_settings DLC count includes both suffixes

19 tests, all passing; reuses the existing scan_module / TestClient /
isolate_logging fixtures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: topkoa <topkoa@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
This commit is contained in:
K. O. A.
2026-06-22 20:47:56 +02:00
committed by GitHub
co-authored by Claude Opus 4.8 byrongamatos
parent a07edd9971
commit 32127bc70b
8 changed files with 285 additions and 26 deletions
+4 -4
View File
@@ -1918,7 +1918,7 @@ function formatBadge(fmt, stemCount) {
return `<span class="fmt-badge absolute top-2 right-2 px-1.5 py-0.5 rounded text-[10px] font-bold bg-purple-900/80 text-purple-200 border border-purple-700">STEMS</span>`;
}
if (fmt === 'sloppak') {
return `<span class="fmt-badge absolute top-2 right-2 px-1.5 py-0.5 rounded text-[10px] font-bold bg-green-900/80 text-green-200 border border-green-700">SLOPPAK</span>`;
return `<span class="fmt-badge absolute top-2 right-2 px-1.5 py-0.5 rounded text-[10px] font-bold bg-green-900/80 text-green-200 border border-green-700">FEEDPAK</span>`;
}
if (fmt === 'loose') {
return `<span class="fmt-badge absolute top-2 right-2 px-1.5 py-0.5 rounded text-[10px] font-bold bg-amber-900/80 text-amber-200 border border-amber-700">FOLDER</span>`;
@@ -1931,7 +1931,7 @@ function formatBadgeInline(fmt, stemCount) {
return `<span class="px-1.5 py-0.5 rounded text-[10px] font-bold bg-purple-900/60 text-purple-300">STEMS</span>`;
}
if (fmt === 'sloppak') {
return `<span class="px-1.5 py-0.5 rounded text-[10px] font-bold bg-green-900/60 text-green-300">SLOPPAK</span>`;
return `<span class="px-1.5 py-0.5 rounded text-[10px] font-bold bg-green-900/60 text-green-300">FEEDPAK</span>`;
}
if (fmt === 'loose') {
return `<span class="px-1.5 py-0.5 rounded text-[10px] font-bold bg-amber-900/60 text-amber-300">FOLDER</span>`;
@@ -4154,10 +4154,10 @@ async function uploadSongs(fileList) {
const files = [];
for (const f of all) {
const lower = f.name.toLowerCase();
if (lower.endsWith('.sloppak')) {
if (lower.endsWith('.feedpak') || lower.endsWith('.sloppak')) {
files.push(f);
} else {
failures.push(`${f.name}: only .sloppak accepted`);
failures.push(`${f.name}: only .feedpak accepted`);
}
}
if (files.length === 0) {