re-encode first segment
This commit is contained in:
@@ -64,32 +64,31 @@ public class StreamActionsService {
|
|||||||
float duration,
|
float duration,
|
||||||
Path outputFile,
|
Path outputFile,
|
||||||
ProgressTracker progressTracker
|
ProgressTracker progressTracker
|
||||||
) throws IOException {
|
) {
|
||||||
|
|
||||||
if (segments.isEmpty()) {
|
if (segments.isEmpty()) {
|
||||||
return CompletableFuture.failedFuture(new IllegalArgumentException("Segment list cannot be empty"));
|
return CompletableFuture.failedFuture(new IllegalArgumentException("Segment list cannot be empty"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Path firstSegment = segments.getFirst();
|
if (trimOffset < 0 || duration <= 0) {
|
||||||
Path trimmedFirst = Files.createTempFile("trimmed-first-", ".ts");
|
return CompletableFuture.failedFuture(new IllegalArgumentException(
|
||||||
try {
|
"trimOffset must be >= 0 and duration must be > 0 (got " + trimOffset + ", " + duration + ")"));
|
||||||
// First segment must be trimmed manually due to keyframe issues with the concat protocol
|
}
|
||||||
List<String> trimCommand = List.of(
|
|
||||||
"ffmpeg", "-y",
|
|
||||||
"-ss", String.valueOf(trimOffset),
|
|
||||||
"-i", firstSegment.toAbsolutePath().toString(),
|
|
||||||
"-c", "copy",
|
|
||||||
trimmedFirst.toAbsolutePath().toString()
|
|
||||||
);
|
|
||||||
commandRunner.run(trimCommand);
|
|
||||||
|
|
||||||
String concatInput = Stream.concat(Stream.of(trimmedFirst), segments.stream().skip(1))
|
Path headSegment = null;
|
||||||
|
try {
|
||||||
|
// Unique per invocation so concurrent saves by different users never
|
||||||
|
// collide on the same head fragment; cleaned up in the finally block.
|
||||||
|
headSegment = Files.createTempFile("reencode-section-", ".ts");
|
||||||
|
encodeHead(segments.getFirst(), trimOffset, headSegment);
|
||||||
|
|
||||||
|
String concatInput = Stream.concat(Stream.of(headSegment), segments.stream().skip(1))
|
||||||
.map(p -> p.toAbsolutePath().toString())
|
.map(p -> p.toAbsolutePath().toString())
|
||||||
.collect(Collectors.joining("|"));
|
.collect(Collectors.joining("|"));
|
||||||
|
|
||||||
List<String> command = List.of(
|
List<String> command = List.of(
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
"-y",
|
"-y",
|
||||||
"-ss", String.valueOf(trimOffset),
|
|
||||||
"-i", "concat:" + concatInput,
|
"-i", "concat:" + concatInput,
|
||||||
"-t", String.valueOf(duration),
|
"-t", String.valueOf(duration),
|
||||||
"-c", "copy",
|
"-c", "copy",
|
||||||
@@ -113,7 +112,13 @@ public class StreamActionsService {
|
|||||||
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 {
|
} finally {
|
||||||
Files.deleteIfExists(trimmedFirst);
|
if (headSegment != null) {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(headSegment);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to delete temp head fragment '{}': {}", headSegment, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,4 +167,38 @@ public class StreamActionsService {
|
|||||||
// nginx hls_fragment_naming system uses seconds; convert to ms
|
// nginx hls_fragment_naming system uses seconds; convert to ms
|
||||||
return value < 1_000_000_000_000L ? value * 1000L : value;
|
return value < 1_000_000_000_000L ? value * 1000L : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-encodes {@code firstSegment} from {@code trimOffset} to its end,
|
||||||
|
* writing an MPEG-TS fragment that can be stream-copy concatenated with
|
||||||
|
* the untouched fragments that follow it.
|
||||||
|
*/
|
||||||
|
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; not worth 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user