feat(stream): clipping stream now saves to db

This commit is contained in:
2026-06-21 18:31:26 +01:00
parent b05ae62230
commit 6c1f1112dc
4 changed files with 99 additions and 48 deletions

View File

@@ -1,54 +1,80 @@
package com.ddf.vodsystem.services.media;
import com.ddf.vodsystem.services.DirectoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/**
* Service responsible for performing stream-related media actions, such as
* extracting and saving a trimmed section of video from a sequence of
* recorded segments.
* <p>
* Operations in this service are backed by {@code ffmpeg} and run
* asynchronously on a dedicated executor so that potentially long-running
* encoding/concatenation work does not block request-handling threads.
*/
@Service
public class StreamActionsService {
private static final Logger logger = LoggerFactory.getLogger(StreamActionsService.class);
private final DirectoryService directoryService;
public StreamActionsService(DirectoryService directoryService) {
this.directoryService = directoryService;
}
/**
* Concatenates a list of video segments and extracts a trimmed section
* from the result, writing the output to {@code outputFile}.
* <p>
* Failures (process launch errors, ffmpeg failures, interruption) are
* surfaced via the returned future rather than thrown, in line with
* normal {@link CompletableFuture} conventions — callers should use
* {@code .exceptionally()} / {@code .handle()} rather than a try/catch.
*
* @param segments the ordered list of segment files to concatenate,
* in playback order
* @param trimOffset the offset, in seconds, from the start of the
* concatenated stream at which to begin the trimmed
* section
* @param duration the duration, in seconds, of the trimmed section
* to extract
* @param outputFile the destination file the trimmed section will be
* written to; overwritten if it already exists
* @return a {@link CompletableFuture} that completes with
* {@code outputFile} on success, or completes exceptionally on
* failure
*/
@Async("ffmpegTaskExecutor")
public CompletableFuture<File> saveSection(
List<File> segments,
float trimOffset,
float duration,
File outputFile
) throws IOException, InterruptedException {
String concatInput = segments.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining("|"));
) {
try {
String concatInput = segments.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining("|"));
List<String> command = List.of(
"ffmpeg",
"-y",
"-i", "concat:" + concatInput,
"-ss", String.valueOf(trimOffset),
"-t", String.valueOf(duration),
"-c", "copy",
outputFile.getAbsolutePath()
);
List<String> command = List.of(
"ffmpeg",
"-y",
"-i", "concat:" + concatInput,
"-ss", String.valueOf(trimOffset),
"-t", String.valueOf(duration),
"-c", "copy",
outputFile.getAbsolutePath()
);
logger.info("Saving section with ({} segments) to '{}'",
segments.size(), outputFile.getAbsolutePath());
CommandRunner.run(command, line -> logger.debug("{}", line));
logger.info("Saving section with ({} segments) to '{}'",
segments.size(), outputFile.getAbsolutePath());
CommandRunner.run(command, line -> logger.debug("{}", line));
return CompletableFuture.completedFuture(outputFile);
return CompletableFuture.completedFuture(outputFile);
} catch (Exception e) {
logger.error("Failed to save section to '{}'", outputFile.getAbsolutePath(), e);
return CompletableFuture.failedFuture(e);
}
}
}
}