* fix(server): capture ffmpeg stderr and warn on empty transcoded output
When ffmpeg fails during transcoding (e.g., missing codec like libopus),
the error was silently discarded because stderr was sent to io.Discard
and the HTTP response returned 200 OK with a 0-byte body.
- Capture ffmpeg stderr in a bounded buffer (4KB) and include it in the
error message when the process exits with a non-zero status code
- Log a warning when transcoded output is 0 bytes, guiding users to
check codec support and enable Trace logging for details
- Remove log level guard so transcoding errors are always logged, not
just at Debug level
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(server): return proper error responses for empty transcoded output
Instead of returning HTTP 200 with 0-byte body when transcoding fails,
return a Subsonic error response (for stream/download/getTranscodeStream)
or HTTP 500 (for public shared streams). This gives clients a clear
signal that the request failed rather than a misleading empty success.
Signed-off-by: Deluan <deluan@navidrome.org>
* test(e2e): add tests for empty transcoded stream error responses
Add E2E tests verifying that stream and download endpoints return
Subsonic error responses when transcoding produces empty output.
Extend spyStreamer with SimulateEmptyStream and SimulateError fields
to support failure injection in tests.
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(server): extract stream serving logic into Stream.Serve method
Extract the duplicated non-seekable stream serving logic (header setup,
estimateContentLength, HEAD draining, io.Copy with error/empty detection)
from server/subsonic/stream.go and server/public/handle_streams.go into a
single Stream.Serve method on core/stream. Both callers now delegate to it,
eliminating ~30 lines of near-identical code.
* fix(server): return 200 with empty body for stream/download on empty transcoded output
Don't return a Subsonic error response when transcoding produces empty
output on stream/download endpoints — just log the error and return 200
with an empty body. The getTranscodeStream and public share endpoints
still return HTTP 500 for empty output. Stream.Serve now returns
(int64, error) so callers can check the byte count.
---------
Signed-off-by: Deluan <deluan@navidrome.org>
When a client requests transcoding with an explicit format (e.g.,
format=opus) but no maxBitRate, buildLegacyClientInfo was adding a
direct play profile matching the source format. Since there was no
bitrate constraint to block it, MakeDecision would match the source
against the direct play profile and return the raw file instead of
transcoding. This fix only adds the direct play profile when no
explicit format was requested (bitrate-only downsampling) or when the
requested format matches the source format (allowing direct play when
no actual transcoding is needed).