Loading debian/changelog +11 −0 Original line number Diff line number Diff line mediadb (20260423+72) unstable; urgency=medium * Preview: flush FFmpeg decoder after read loop so codecs that buffer the single frame (e.g. WebP VP8X with XMP/EXIF chunks) emit it. Fixes "could not decode a frame" for extended-format WebP images. * Track premature EOF in PrefetchBuffer: when cluster data is unavailable, report "cluster data timed out" so the error triggers 503 + Retry-After instead of a permanent 422. -- Jan Koester <jan.koester@tuxist.de> Wed, 23 Apr 2026 20:10:00 +0200 mediadb (20260423+71) unstable; urgency=medium * Read client pool: replace single read_client_ with a pool of 4 Loading src/preview.cpp +17 −2 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ struct PrefetchBuffer { std::string media_id; std::uint64_t total_size; std::uint64_t pos = 0; bool premature_eof = false; // set when read returns empty before total_size // prefetch ring — holds in-flight async reads struct PendingBlock { Loading Loading @@ -131,7 +132,7 @@ struct PrefetchBuffer { std::uint64_t remaining = total_size - pos; std::uint64_t to_read = std::min<std::uint64_t>(want, remaining); auto data = db->read_media_data_range(media_id, pos, to_read); if (data.empty()) return AVERROR_EOF; if (data.empty()) { premature_eof = (pos < total_size); return AVERROR_EOF; } std::memcpy(buf, data.data(), data.size()); pos += data.size(); return static_cast<int>(data.size()); Loading @@ -143,6 +144,7 @@ struct PrefetchBuffer { } if (front.data.empty()) { premature_eof = (pos < total_size); ring.pop_front(); return AVERROR_EOF; } Loading Loading @@ -951,7 +953,20 @@ std::optional<PreviewResult> FFmpegPreviewer::render_streaming(MediaBackendApi& } if (have_frame) break; } // Flush decoder — some codecs (notably WebP with extended format / // VP8X chunks) buffer the single frame until they receive an EOF // signal via a NULL packet. if (!have_frame) { avcodec_send_packet(dec_ctx, nullptr); while (avcodec_receive_frame(dec_ctx, decoded) >= 0) { have_frame = true; break; } } if (!have_frame) { if (prefetch_buf.premature_eof) error_out = "could not decode a frame: cluster data timed out"; else error_out = "could not decode a frame"; return std::nullopt; } Loading Loading
debian/changelog +11 −0 Original line number Diff line number Diff line mediadb (20260423+72) unstable; urgency=medium * Preview: flush FFmpeg decoder after read loop so codecs that buffer the single frame (e.g. WebP VP8X with XMP/EXIF chunks) emit it. Fixes "could not decode a frame" for extended-format WebP images. * Track premature EOF in PrefetchBuffer: when cluster data is unavailable, report "cluster data timed out" so the error triggers 503 + Retry-After instead of a permanent 422. -- Jan Koester <jan.koester@tuxist.de> Wed, 23 Apr 2026 20:10:00 +0200 mediadb (20260423+71) unstable; urgency=medium * Read client pool: replace single read_client_ with a pool of 4 Loading
src/preview.cpp +17 −2 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ struct PrefetchBuffer { std::string media_id; std::uint64_t total_size; std::uint64_t pos = 0; bool premature_eof = false; // set when read returns empty before total_size // prefetch ring — holds in-flight async reads struct PendingBlock { Loading Loading @@ -131,7 +132,7 @@ struct PrefetchBuffer { std::uint64_t remaining = total_size - pos; std::uint64_t to_read = std::min<std::uint64_t>(want, remaining); auto data = db->read_media_data_range(media_id, pos, to_read); if (data.empty()) return AVERROR_EOF; if (data.empty()) { premature_eof = (pos < total_size); return AVERROR_EOF; } std::memcpy(buf, data.data(), data.size()); pos += data.size(); return static_cast<int>(data.size()); Loading @@ -143,6 +144,7 @@ struct PrefetchBuffer { } if (front.data.empty()) { premature_eof = (pos < total_size); ring.pop_front(); return AVERROR_EOF; } Loading Loading @@ -951,7 +953,20 @@ std::optional<PreviewResult> FFmpegPreviewer::render_streaming(MediaBackendApi& } if (have_frame) break; } // Flush decoder — some codecs (notably WebP with extended format / // VP8X chunks) buffer the single frame until they receive an EOF // signal via a NULL packet. if (!have_frame) { avcodec_send_packet(dec_ctx, nullptr); while (avcodec_receive_frame(dec_ctx, decoded) >= 0) { have_frame = true; break; } } if (!have_frame) { if (prefetch_buf.premature_eof) error_out = "could not decode a frame: cluster data timed out"; else error_out = "could not decode a frame"; return std::nullopt; } Loading