diff --git a/pom.xml b/pom.xml
index b992532..5d188e4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -81,6 +81,10 @@
google-api-client
2.8.0
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
diff --git a/src/main/java/com/ddf/vodsystem/controllers/StreamController.java b/src/main/java/com/ddf/vodsystem/controllers/StreamController.java
index c0b33c9..c53b8f0 100644
--- a/src/main/java/com/ddf/vodsystem/controllers/StreamController.java
+++ b/src/main/java/com/ddf/vodsystem/controllers/StreamController.java
@@ -1,7 +1,8 @@
package com.ddf.vodsystem.controllers;
+import com.ddf.vodsystem.controllers.input.ClipSectionRequest;
import com.ddf.vodsystem.dto.APIResponse;
-import com.ddf.vodsystem.dto.SaveSectionRequest;
+import com.ddf.vodsystem.controllers.input.SaveSectionRequest;
import com.ddf.vodsystem.entities.Stream;
import com.ddf.vodsystem.services.StreamService;
import org.slf4j.Logger;
@@ -53,6 +54,13 @@ public class StreamController {
return ResponseEntity.accepted().body(new APIResponse<>("success", "Section saved", null));
}
+ @PostMapping("/clip")
+ public ResponseEntity> saveClip(
+ @RequestBody ClipSectionRequest request) throws IOException, InterruptedException {
+ streamService.clip(request.duration());
+ return ResponseEntity.accepted().body(new APIResponse<>("success", "Clip saved", null));
+ }
+
@GetMapping("/history/{userId}")
public ResponseEntity>> getStreamHistory(@PathVariable Long userId) {
List history = streamService.getStreamHistory(userId);
diff --git a/src/main/java/com/ddf/vodsystem/controllers/input/ClipSectionRequest.java b/src/main/java/com/ddf/vodsystem/controllers/input/ClipSectionRequest.java
new file mode 100644
index 0000000..d683b43
--- /dev/null
+++ b/src/main/java/com/ddf/vodsystem/controllers/input/ClipSectionRequest.java
@@ -0,0 +1,7 @@
+package com.ddf.vodsystem.controllers.input;
+
+import jakarta.validation.constraints.NotNull;
+
+public record ClipSectionRequest(
+ @NotNull float duration
+) {}
\ No newline at end of file
diff --git a/src/main/java/com/ddf/vodsystem/controllers/input/SaveSectionRequest.java b/src/main/java/com/ddf/vodsystem/controllers/input/SaveSectionRequest.java
new file mode 100644
index 0000000..51988a5
--- /dev/null
+++ b/src/main/java/com/ddf/vodsystem/controllers/input/SaveSectionRequest.java
@@ -0,0 +1,8 @@
+package com.ddf.vodsystem.controllers.input;
+
+import java.time.Instant;
+import jakarta.validation.constraints.NotNull;
+
+public record SaveSectionRequest(
+ @NotNull Instant startTime,
+ @NotNull Instant endTime) {}
diff --git a/src/main/java/com/ddf/vodsystem/dto/SaveSectionRequest.java b/src/main/java/com/ddf/vodsystem/dto/SaveSectionRequest.java
deleted file mode 100644
index 0b1eb5e..0000000
--- a/src/main/java/com/ddf/vodsystem/dto/SaveSectionRequest.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.ddf.vodsystem.dto;
-
-import java.time.Instant;
-
-public record SaveSectionRequest(Instant startTime, Instant endTime) {}
diff --git a/src/main/java/com/ddf/vodsystem/services/StreamService.java b/src/main/java/com/ddf/vodsystem/services/StreamService.java
index f56a65f..d001e8f 100644
--- a/src/main/java/com/ddf/vodsystem/services/StreamService.java
+++ b/src/main/java/com/ddf/vodsystem/services/StreamService.java
@@ -17,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.io.IOException;
+import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Arrays;
@@ -28,7 +29,9 @@ import java.util.stream.Collectors;
@Service
public class StreamService {
private static final Logger logger = LoggerFactory.getLogger(StreamService.class);
+
private static final int HEARTBEAT_TIMEOUT_SECONDS = 60;
+ private static final float CLIP_MAX_LENGTH = 180;
private final StreamRepository streamRepository;
private final UserService userService;
@@ -118,6 +121,34 @@ public class StreamService {
});
}
+ public void clip(float duration) throws IOException, InterruptedException {
+ if (duration <= 0 || duration > CLIP_MAX_LENGTH) {
+ throw new IllegalArgumentException("Clip length must be between 0 and " + CLIP_MAX_LENGTH + " seconds");
+ }
+
+ User user = userService.getLoggedInUser()
+ .orElseThrow(() -> new NotAuthenticated("User is not authenticated"));
+
+ Instant endTime = Instant.now();
+ // minusSeconds() is possible, but only does integer seconds, not float
+ Instant startTime = Instant.now().minus(Duration.ofMillis((long) (duration * 1000)));
+
+ String streamDirectory = streamDataPath + File.separator + user.getStreamKey();
+ List fileSegments = getSegmentsInRange(streamDirectory, startTime, endTime);
+
+ if (fileSegments.isEmpty()) {
+ throw new IllegalArgumentException("No stream segments found in the given time range");
+ }
+
+ long firstSegmentMs = parseTimestampMs(fileSegments.getFirst());
+ float trimOffset = Math.max(0f, (startTime.toEpochMilli() - firstSegmentMs) / 1000f);
+
+ streamActionsService.saveSection(fileSegments, trimOffset, duration)
+ .exceptionally(ex -> {
+ throw new FFMPEGException("Saving stream section failed due to FFMPEG Error");
+ });
+ }
+
@Scheduled(fixedDelay = 30_000)
@Transactional
public void endStaleStreams() {