remove head encoding in favour of no bugged streams
Some checks failed
CI / Maven unit tests (push) Failing after 15s
CI / Build and push images (push) Has been skipped
CI / Deploy to server (push) Has been skipped

this leads to slightly inaccurate starts and ends
This commit is contained in:
2026-09-16 19:29:02 +01:00
parent 3c350ccf2a
commit 4b40661d09

View File

@@ -16,6 +16,7 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
@@ -131,37 +132,18 @@ public class StreamActionsService {
"trimOffset must be >= 0 and duration must be > 0 (got " + trimOffset + ", " + duration + ")")); "trimOffset must be >= 0 and duration must be > 0 (got " + trimOffset + ", " + duration + ")"));
} }
Path headFragment = null;
Path concatList = null;
try { try {
// Only the first segment is trimmed, so re-encode just that fragment and String concatInput = "concat:" + segments.stream()
// stream-copy the rest. A zero offset means the first segment already .map(p -> p.toAbsolutePath().toString())
// starts at the requested point, so re-encoding it would be wasted work. .collect(Collectors.joining("|"));
List<Path> fragments;
if (trimOffset > 0) {
headFragment = Files.createTempFile("reencode-section-", ".ts");
encodeHead(segments.getFirst(), trimOffset, headFragment);
fragments = new ArrayList<>();
fragments.add(headFragment);
fragments.addAll(segments.subList(1, segments.size()));
} else {
fragments = segments;
}
// The concat demuxer (unlike the concat: byte protocol) rebases each
// fragment's timestamps to run continuously across the joins, absorbing
// the discontinuity between the PTS-0 head and the copied fragments.
concatList = writeConcatList(fragments);
List<String> command = List.of( List<String> command = List.of(
"ffmpeg", "ffmpeg",
"-y", "-y",
"-f", "concat", "-i", concatInput,
"-safe", "0", "-t", String.valueOf(duration + trimOffset),
"-i", concatList.toAbsolutePath().toString(),
"-t", String.valueOf(duration),
"-c", "copy", "-c", "copy",
"-avoid_negative_ts", "make_zero",
outputFile.toAbsolutePath().toString() outputFile.toAbsolutePath().toString()
); );
@@ -180,9 +162,6 @@ public class StreamActionsService {
} catch (RuntimeException e) { } catch (RuntimeException e) {
logger.error("Failed to save section to '{}': {}", outputFile, e.toString()); logger.error("Failed to save section to '{}': {}", outputFile, e.toString());
return CompletableFuture.failedFuture(e); return CompletableFuture.failedFuture(e);
} finally {
deleteTempQuietly(headFragment);
deleteTempQuietly(concatList);
} }
} }
@@ -257,52 +236,4 @@ public class StreamActionsService {
throw new IOException("Failed to probe segment duration: " + segment, e); throw new IOException("Failed to probe segment duration: " + segment, e);
} }
} }
private void encodeHead(Path firstSegment, float trimOffset, Path head)
throws IOException, InterruptedException {
List<String> command = List.of(
"ffmpeg",
"-y",
"-i", firstSegment.toAbsolutePath().toString(),
"-ss", String.valueOf(trimOffset),
"-c:v", "libx264",
"-preset", "veryfast",
"-crf", "20",
"-pix_fmt", "yuv420p",
"-c:a", "aac",
"-b:a", "160k",
"-muxdelay", "0",
"-muxpreload", "0",
"-f", "mpegts",
head.toAbsolutePath().toString()
);
logger.debug("Re-encoding head fragment '{}' from {}s", firstSegment, trimOffset);
commandRunner.run(command, line -> { /* head pass is short; no progress reporting */ });
if (!Files.exists(head) || Files.size(head) == 0) {
throw new IOException("Head re-encode produced no output — trimOffset " + trimOffset
+ "s may exceed the duration of " + firstSegment);
}
}
private Path writeConcatList(List<Path> fragments) throws IOException {
Path listFile = Files.createTempFile("concat-list-", ".txt");
List<String> lines = fragments.stream()
.map(p -> "file '" + p.toAbsolutePath().toString().replace("'", "'\\''") + "'")
.toList();
Files.write(listFile, lines);
return listFile;
}
private void deleteTempQuietly(Path tempFile) {
if (tempFile != null) {
try {
Files.deleteIfExists(tempFile);
} catch (IOException e) {
logger.warn("Failed to delete temp file '{}': {}", tempFile, e.toString());
}
}
}
} }