Skip to content

Let an imported audio file be split again - #646

Merged
thcp merged 2 commits into
mainfrom
fix/resplit-imported-audio-635
Sep 21, 2026
Merged

thcp merged 2 commits into
mainfrom
fix/resplit-imported-audio-635

Conversation

@thcp

@thcp thcp commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Closes #635

Thanks to @wastedbits for the report, and for separating the error from the usability point. The two turned out to have the same cause.

Pressing Split stems on a track imported from a file threw an error and left the button reading "Process" for the rest of the session. The same press on a YouTube track worked. Three faults, reported together.

The composer held something it could never submit

Opening a track writes its source back into the composer. For a link that is a URL the button can act on. For an upload the source is the synthetic local:my song.mp3, and stripping the prefix put the bare filename in the box, so pressing the button POSTed my song.mp3 as though it were a link. Non-empty is also what carried it past the browser's own required check.

The box now holds only something importable, and one predicate answers "can this be fetched again" for both the composer and the re-import of an unavailable track. It existed in three spellings and they had stopped agreeing.

The button renamed itself permanently

setSubmitProcessing restored t("job.process") while index.html starts the button as t("process.splitStems"). The first submit of a session renamed it for good, on a successful import as much as a failed one.

The real one: the button acted on the composer, not on the track

For an upload there was nothing to act on at all, because the source file is deleted once separation finishes.

That deletion is the bulk of disk reclaim per job, but only a job that can fetch its source again may give it up. A link costs a re-download; an upload costs the recording, and the person who imported it may no longer have the file. Uploads now keep their source, and POST /api/jobs/{id}/resplit separates one again from it.

The button aims at the open track whenever the composer has nothing to submit, and a pill in the composer names that track so it is clear what will be split. A staged file or a typed URL is still a new import and wins.

The label does not change. What the button does is the same thing it always did; only where it reads the track from differs.

Worth review: the source of the new job

A re-split produces a new job, the way re-importing a link does. Its source is deliberately not the one it was asked with.

addTrackToLibrary replaces any existing track sharing a sourceUrl. Reusing the original's deleted the very track the re-split came from, and left that job on disk with nothing referencing it, which syncWithServer then re-adopts as a duplicate on the next launch. I found this by asserting the original was still in the library after a re-split, and it was not.

Giving the pair the same row is not an option either: two jobs sharing a source are collapsed again on every launch, so they have to differ on disk rather than just in one session. The marker goes before the extension, since deriveQuality reads the suffix to tell a lossless WAV from a compressed MP3.

The cost, stated plainly

Every uploaded track now retains its source, 100-300 MB each, where before only the stems survived. That is the price of being able to split an imported file a second time at all.

Testing

Full local gate: ruff check, ruff format --check, pytest 1074 passed, npx playwright test 181 passed, npm run test:js clean, node --check over every module, bandit clean at medium and above, uv.lock untouched, i18n coverage clean. No Rust in the diff.

New coverage:

  • Nine endpoint tests: happy path, an unusable stem list falling back to every stem, an over-long list refused at 422, unknown job, 409 when no source was kept, a source.* with an extension the importer would never accept, and crafted job ids that must be turned away before any filesystem access rather than 500.
  • Four for the source-naming rule, including the no-extension and dotted-name cases.
  • A parametrised runner test that lets the real cleanup_source run and asserts on the file: upload keeps it, link loses it, no recorded source loses it.
  • Six browser tests, including one that presses the button and checks the request reaches /resplit, one that a typed URL still wins, and the one above that the original track survives.

Verified by hand on macOS against a real import as well as in CI.

Pressing Split stems on a track imported from a file threw an error and
left the button reading "Process" for the rest of the session. The same
press on a YouTube track worked. Three separate faults, all reported
together in #635.

Opening a track writes its source back into the composer. For a link that
is a URL the button can submit. For an upload the source is the synthetic
"local:my song.mp3", and stripping the prefix put the bare filename in the
box: pressing the button POSTed "my song.mp3" as though it were a link.
Being non-empty is also what carried it past the browser's own required
check. The composer now holds only something that can actually be
imported.

setSubmitProcessing restored t("job.process") -- "Process" -- while
index.html starts the button as t("process.splitStems"). The first submit
of a session therefore renamed the button for good, on a successful import
as much as on a failed one, and nothing put it back short of a reload.

The third fault is the one behind the other two: the button acted on the
composer, not on the track. For an upload there was nothing it could act
on at all, because the source file is deleted once separation finishes.
That deletion is the bulk of disk reclaim per job, but only a job that can
fetch its source again may give it up: a link costs a re-download, an
upload costs the recording. Uploads now keep theirs, and POST
/api/jobs/{id}/resplit separates one again from it. The button aims at the
open track whenever the composer has nothing to submit, and a pill in the
composer names the track so it is clear what will be split. A staged file
or a typed URL is still a new import and wins.

The re-split produces a new job, the way re-importing a link does. Its
source is deliberately not the one it was asked with: the library replaces
any existing track sharing a sourceUrl, so reusing it deleted the very
track the re-split came from and left that job on disk with nothing
referencing it, for syncWithServer to re-adopt as a duplicate. The marker
goes before the extension, since deriveQuality reads the suffix to tell a
lossless WAV from a compressed MP3.

Costs a decision worth stating: every uploaded track now retains its
source, 100-300 MB each, where before only the stems survived.

The button's label does not change. What it does is the same thing it
always did.
Comment thread tests/test_jobs_api.py Fixed
@thcp
thcp marked this pull request as ready for review September 21, 2026 08:11
The code quality check caught a real inconsistency: this file reaches for
app.api.jobs as `jobs_mod` in six places, and the source-naming test I
added imported a name from it directly instead. Two styles for one module
in one file.

Follows what was already here rather than converting the six.
@thcp
thcp merged commit be52bf0 into main Sep 21, 2026
10 checks passed
@thcp
thcp deleted the fix/resplit-imported-audio-635 branch September 21, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Error when creating Stems when they are already created

1 participant