From e93ccd2ecda23183ce0760b3ec1e941c2ce72de2 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Wed, 25 Jun 2025 19:07:38 +0200 Subject: [PATCH 01/30] ADD Clip database insertion & UPDATE schema --- .../vodsystem/controllers/EditController.java | 1 + .../java/com/ddf/vodsystem/entities/Clip.java | 6 ++-- .../vodsystem/security/CustomOAuth2User.java | 36 +++++++++++++++++++ .../security/CustomOAuth2UserService.java | 34 +++++++++--------- .../ddf/vodsystem/services/EditService.java | 34 +++++++++++++++--- src/main/resources/db/data.sql | 22 ++++++------ src/main/resources/db/schema.sql | 6 ++-- 7 files changed, 101 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/ddf/vodsystem/security/CustomOAuth2User.java diff --git a/src/main/java/com/ddf/vodsystem/controllers/EditController.java b/src/main/java/com/ddf/vodsystem/controllers/EditController.java index 4c6c2ca..d341902 100644 --- a/src/main/java/com/ddf/vodsystem/controllers/EditController.java +++ b/src/main/java/com/ddf/vodsystem/controllers/EditController.java @@ -6,6 +6,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import com.ddf.vodsystem.entities.APIResponse; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/ddf/vodsystem/entities/Clip.java b/src/main/java/com/ddf/vodsystem/entities/Clip.java index f58d6ea..bf655cb 100644 --- a/src/main/java/com/ddf/vodsystem/entities/Clip.java +++ b/src/main/java/com/ddf/vodsystem/entities/Clip.java @@ -36,13 +36,13 @@ public class Clip { private Integer height; @Column(name = "fps", nullable = false) - private Integer fps; + private Float fps; @Column(name = "duration", nullable = false) - private Integer duration; + private Float duration; @Column(name = "file_size", nullable = false) - private Long fileSize; + private Float fileSize; @Column(name = "video_path", nullable = false, length = 255) private String videoPath; diff --git a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2User.java b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2User.java new file mode 100644 index 0000000..e724d14 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2User.java @@ -0,0 +1,36 @@ +package com.ddf.vodsystem.security; + +import com.ddf.vodsystem.entities.User; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import java.util.Collection; +import java.util.Map; + +public class CustomOAuth2User implements OAuth2User { + + private final OAuth2User oauth2User; + @Getter + private final User user; + + public CustomOAuth2User(OAuth2User oauth2User, User user) { + this.oauth2User = oauth2User; + this.user = user; + } + + @Override + public Map getAttributes() { + return oauth2User.getAttributes(); + } + + @Override + public Collection getAuthorities() { + return oauth2User.getAuthorities(); + } + + @Override + public String getName() { + return oauth2User.getName(); + } +} diff --git a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java index 2cf1212..a611b0a 100644 --- a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java +++ b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java @@ -4,16 +4,16 @@ import com.ddf.vodsystem.repositories.UserRepository; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import java.time.LocalDateTime; -import java.util.Optional; @Service -public class CustomOAuth2UserService extends DefaultOAuth2UserService { +public class CustomOAuth2UserService implements OAuth2UserService { private final UserRepository userRepository; @@ -22,26 +22,26 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService { } @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - OAuth2User oAuth2User = super.loadUser(userRequest); + public CustomOAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + var delegate = new DefaultOAuth2UserService(); + var oAuth2User = delegate.loadUser(userRequest); String email = oAuth2User.getAttribute("email"); String name = oAuth2User.getAttribute("name"); String googleId = oAuth2User.getAttribute("sub"); - Optional existingUser = userRepository.findByGoogleId(googleId); + User user = userRepository.findByGoogleId(googleId) + .orElseGet(() -> { + User newUser = new User(); + newUser.setEmail(email); + newUser.setName(name); + newUser.setGoogleId(googleId); + newUser.setUsername(email); + newUser.setRole(0); + newUser.setCreatedAt(LocalDateTime.now()); + return userRepository.save(newUser); + }); - if (existingUser.isEmpty()) { - User user = new User(); - user.setEmail(email); - user.setName(name); - user.setGoogleId(googleId); - user.setUsername(email); - user.setRole(0); - user.setCreatedAt(LocalDateTime.now()); - userRepository.save(user); - } - - return oAuth2User; + return new CustomOAuth2User(oAuth2User, user); } } diff --git a/src/main/java/com/ddf/vodsystem/services/EditService.java b/src/main/java/com/ddf/vodsystem/services/EditService.java index 83b4caf..3f13bb1 100644 --- a/src/main/java/com/ddf/vodsystem/services/EditService.java +++ b/src/main/java/com/ddf/vodsystem/services/EditService.java @@ -1,16 +1,22 @@ package com.ddf.vodsystem.services; -import com.ddf.vodsystem.entities.VideoMetadata; -import com.ddf.vodsystem.entities.Job; -import com.ddf.vodsystem.entities.JobStatus; +import com.ddf.vodsystem.entities.*; +import com.ddf.vodsystem.repositories.ClipRepository; +import com.ddf.vodsystem.security.CustomOAuth2User; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; + @Service public class EditService { private final JobService jobService; + private final ClipRepository clipRepository; - public EditService(JobService jobService) { + public EditService(JobService jobService, ClipRepository clipRepository) { this.jobService = jobService; + this.clipRepository = clipRepository; } public void edit(String uuid, VideoMetadata videoMetadata) { @@ -21,6 +27,26 @@ public class EditService { public void process(String uuid) { jobService.jobReady(uuid); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof CustomOAuth2User oAuth2user) { + VideoMetadata videoMetadata = jobService.getJob(uuid).getOutputVideoMetadata(); + User user = oAuth2user.getUser(); + + Clip clip = new Clip(); + clip.setTitle("test"); + clip.setUser(user); + clip.setDescription("This is a test"); + clip.setCreatedAt(LocalDateTime.now()); + clip.setWidth(videoMetadata.getWidth()); + clip.setHeight(videoMetadata.getHeight()); + clip.setFps(videoMetadata.getFps()); + clip.setDuration(videoMetadata.getEndPoint() - + videoMetadata.getStartPoint()); + clip.setFileSize(videoMetadata.getFileSize()); + clip.setVideoPath("test"); + clipRepository.save(clip); + } } public float getProgress(String uuid) { diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql index ddb0c87..8b831b3 100644 --- a/src/main/resources/db/data.sql +++ b/src/main/resources/db/data.sql @@ -11,15 +11,15 @@ VALUES ( 'google-uid-009', 'detective', 'holmes@bakerstreet.uk', 'Sherlock Holmes'), ( 'google-uid-010', 'timey', 'docbrown@delorean.net', 'Dr. Emmett Brown'); -INSERT INTO clips (id, user_id, title, description, width, height, fps, duration, file_size, video_path) +INSERT INTO clips (user_id, title, description, width, height, fps, duration, file_size, video_path) VALUES - (1, 4, 'Fireworks Over Hobbiton', 'A magical display of fireworks by Gandalf.', 1920, 1080, 30, 120, 104857600, '/videos/fireworks_hobbiton.mp4'), - (2, 5, 'Catnap Chronicles', 'Sir Whiskers McFluff naps in 12 different positions.', 1280, 720, 24, 60, 52428800, '/videos/catnap_chronicles.mp4'), - (3, 6, 'Bite My Shiny Metal...', 'Bender shows off his new upgrades.', 1920, 1080, 60, 45, 73400320, '/videos/bender_upgrades.mp4'), - (4, 7, 'Rainbow Dash', 'Princess Sparklehoof gallops across a double rainbow.', 1920, 1080, 30, 90, 67108864, '/videos/rainbow_dash.mp4'), - (5, 8, 'Pirate Karaoke Night', 'Blackbeard sings sea shanties with his crew.', 1280, 720, 25, 180, 157286400, '/videos/pirate_karaoke.mp4'), - (6, 9, 'The Case of the Missing Sandwich', 'Sherlock Holmes investigates a lunchtime mystery.', 1920, 1080, 30, 75, 50331648, '/videos/missing_sandwich.mp4'), - (7, 10, '88 Miles Per Hour', 'Doc Brown demonstrates time travel with style.', 1920, 1080, 60, 30, 41943040, '/videos/88mph.mp4'), - (8, 1, 'Alice in Videoland', 'Alice explores a surreal digital wonderland.', 1280, 720, 30, 150, 94371840, '/videos/alice_videoland.mp4'), - (9, 2, 'Bob''s Building Bonanza', 'Bob constructs a house out of cheese.', 1920, 1080, 24, 200, 209715200, '/videos/bob_cheesehouse.mp4'), - (10, 3, 'Carol''s Coding Catastrophe', 'Carol debugs a spaghetti codebase.', 1280, 720, 30, 100, 73400320, '/videos/carol_coding.mp4'); \ No newline at end of file + (4, 'Fireworks Over Hobbiton', 'A magical display of fireworks by Gandalf.', 1920, 1080, 30, 120, 104857600, '/videos/fireworks_hobbiton.mp4'), + (5, 'Catnap Chronicles', 'Sir Whiskers McFluff naps in 12 different positions.', 1280, 720, 24, 60, 52428800, '/videos/catnap_chronicles.mp4'), + (6, 'Bite My Shiny Metal...', 'Bender shows off his new upgrades.', 1920, 1080, 60, 45, 73400320, '/videos/bender_upgrades.mp4'), + (7, 'Rainbow Dash', 'Princess Sparklehoof gallops across a double rainbow.', 1920, 1080, 30, 90, 67108864, '/videos/rainbow_dash.mp4'), + ( 8, 'Pirate Karaoke Night', 'Blackbeard sings sea shanties with his crew.', 1280, 720, 25, 180, 157286400, '/videos/pirate_karaoke.mp4'), + ( 9, 'The Case of the Missing Sandwich', 'Sherlock Holmes investigates a lunchtime mystery.', 1920, 1080, 30, 75, 50331648, '/videos/missing_sandwich.mp4'), + ( 10, '88 Miles Per Hour', 'Doc Brown demonstrates time travel with style.', 1920, 1080, 60, 30, 41943040, '/videos/88mph.mp4'), + ( 1, 'Alice in Videoland', 'Alice explores a surreal digital wonderland.', 1280, 720, 30, 150, 94371840, '/videos/alice_videoland.mp4'), + ( 2, 'Bob''s Building Bonanza', 'Bob constructs a house out of cheese.', 1920, 1080, 24, 200, 209715200, '/videos/bob_cheesehouse.mp4'), + ( 3, 'Carol''s Coding Catastrophe', 'Carol debugs a spaghetti codebase.', 1280, 720, 30, 100, 73400320, '/videos/carol_coding.mp4'); \ No newline at end of file diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index bb261e5..64160d3 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -19,9 +19,9 @@ CREATE TABLE IF NOT EXISTS clips ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, width INTEGER NOT NULL, height INTEGER NOT NULL, - fps INTEGER NOT NULL, - duration INTEGER NOT NULL, - file_size BIGINT NOT NULL, + fps FLOAT NOT NULL, + duration FLOAT NOT NULL, + file_size FLOAT NOT NULL, video_path VARCHAR(255) NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); \ No newline at end of file From f37c4fc75d1853f88a89c3104a98e7b10a1305be Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Wed, 25 Jun 2025 19:19:10 +0200 Subject: [PATCH 02/30] REFACTOR Clip creation in Editservice --- .../ddf/vodsystem/services/EditService.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/ddf/vodsystem/services/EditService.java b/src/main/java/com/ddf/vodsystem/services/EditService.java index 3f13bb1..f749e32 100644 --- a/src/main/java/com/ddf/vodsystem/services/EditService.java +++ b/src/main/java/com/ddf/vodsystem/services/EditService.java @@ -14,6 +14,8 @@ public class EditService { private final JobService jobService; private final ClipRepository clipRepository; + private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(EditService.class); + public EditService(JobService jobService, ClipRepository clipRepository) { this.jobService = jobService; this.clipRepository = clipRepository; @@ -30,22 +32,11 @@ public class EditService { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof CustomOAuth2User oAuth2user) { + logger.debug("Saving clip {} for user {}", uuid, oAuth2user.getName()); VideoMetadata videoMetadata = jobService.getJob(uuid).getOutputVideoMetadata(); User user = oAuth2user.getUser(); - Clip clip = new Clip(); - clip.setTitle("test"); - clip.setUser(user); - clip.setDescription("This is a test"); - clip.setCreatedAt(LocalDateTime.now()); - clip.setWidth(videoMetadata.getWidth()); - clip.setHeight(videoMetadata.getHeight()); - clip.setFps(videoMetadata.getFps()); - clip.setDuration(videoMetadata.getEndPoint() - - videoMetadata.getStartPoint()); - clip.setFileSize(videoMetadata.getFileSize()); - clip.setVideoPath("test"); - clipRepository.save(clip); + createClip(videoMetadata, user); } } @@ -95,4 +86,19 @@ public class EditService { throw new IllegalArgumentException("FPS cannot be less than 1"); } } + + private void createClip(VideoMetadata videoMetadata, User user) { + Clip clip = new Clip(); + clip.setTitle("test"); + clip.setUser(user); + clip.setDescription("This is a test"); + clip.setCreatedAt(LocalDateTime.now()); + clip.setWidth(videoMetadata.getWidth()); + clip.setHeight(videoMetadata.getHeight()); + clip.setFps(videoMetadata.getFps()); + clip.setDuration(videoMetadata.getEndPoint() - videoMetadata.getStartPoint()); + clip.setFileSize(videoMetadata.getFileSize()); + clip.setVideoPath("test"); + clipRepository.save(clip); + } } From 6cf454c484f94bd6ee8a461f0c827ee6519f2f0c Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Wed, 25 Jun 2025 19:31:37 +0200 Subject: [PATCH 03/30] RENAME CompressionService to ClipService and update references --- .../vodsystem/security/CustomOAuth2User.java | 2 +- ...mpressionService.java => ClipService.java} | 68 +++++++++---------- .../ddf/vodsystem/services/JobService.java | 10 +-- 3 files changed, 40 insertions(+), 40 deletions(-) rename src/main/java/com/ddf/vodsystem/services/{CompressionService.java => ClipService.java} (98%) diff --git a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2User.java b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2User.java index e724d14..0c204c8 100644 --- a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2User.java +++ b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2User.java @@ -8,10 +8,10 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import java.util.Collection; import java.util.Map; +@Getter public class CustomOAuth2User implements OAuth2User { private final OAuth2User oauth2User; - @Getter private final User user; public CustomOAuth2User(OAuth2User oauth2User, User user) { diff --git a/src/main/java/com/ddf/vodsystem/services/CompressionService.java b/src/main/java/com/ddf/vodsystem/services/ClipService.java similarity index 98% rename from src/main/java/com/ddf/vodsystem/services/CompressionService.java rename to src/main/java/com/ddf/vodsystem/services/ClipService.java index 8df71a5..012bb77 100644 --- a/src/main/java/com/ddf/vodsystem/services/CompressionService.java +++ b/src/main/java/com/ddf/vodsystem/services/ClipService.java @@ -19,8 +19,8 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service -public class CompressionService { - private static final Logger logger = LoggerFactory.getLogger(CompressionService.class); +public class ClipService { + private static final Logger logger = LoggerFactory.getLogger(ClipService.class); private static final float AUDIO_RATIO = 0.15f; private static final float MAX_AUDIO_BITRATE = 128f; @@ -28,6 +28,38 @@ public class CompressionService { private final Pattern timePattern = Pattern.compile("out_time_ms=(\\d+)"); + public void run(Job job) throws IOException, InterruptedException { + logger.info("FFMPEG starting..."); + + validateVideoMetadata(job.getInputVideoMetadata(), job.getOutputVideoMetadata()); + + ProcessBuilder pb = buildCommand(job.getInputFile(), job.getOutputFile(), job.getOutputVideoMetadata()); + Process process = pb.start(); + job.setStatus(JobStatus.RUNNING); + + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + float length = job.getOutputVideoMetadata().getEndPoint() - job.getOutputVideoMetadata().getStartPoint(); + + String line; + while ((line = reader.readLine()) != null) { + logger.debug(line); + Matcher matcher = timePattern.matcher(line); + + if (matcher.find()) { + Float progress = Long.parseLong(matcher.group(1))/(length*1000000); + job.setProgress(progress); + } + } + + if (process.waitFor() != 0) { + job.setStatus(JobStatus.FAILED); + throw new FFMPEGException("FFMPEG process failed"); + } + + job.setStatus(JobStatus.FINISHED); + logger.info("FFMPEG finished"); + } + private void validateVideoMetadata(VideoMetadata inputFileMetadata, VideoMetadata outputFileMetadata) { if (outputFileMetadata.getStartPoint() == null) { outputFileMetadata.setStartPoint(0f); @@ -109,36 +141,4 @@ public class CompressionService { return new ProcessBuilder(command); } - public void run(Job job) throws IOException, InterruptedException { - logger.info("FFMPEG starting..."); - - validateVideoMetadata(job.getInputVideoMetadata(), job.getOutputVideoMetadata()); - - ProcessBuilder pb = buildCommand(job.getInputFile(), job.getOutputFile(), job.getOutputVideoMetadata()); - Process process = pb.start(); - job.setStatus(JobStatus.RUNNING); - - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - float length = job.getOutputVideoMetadata().getEndPoint() - job.getOutputVideoMetadata().getStartPoint(); - - String line; - while ((line = reader.readLine()) != null) { - logger.debug(line); - Matcher matcher = timePattern.matcher(line); - - if (matcher.find()) { - Float progress = Long.parseLong(matcher.group(1))/(length*1000000); - job.setProgress(progress); - } - } - - if (process.waitFor() != 0) { - job.setStatus(JobStatus.FAILED); - throw new FFMPEGException("FFMPEG process failed"); - } - - job.setStatus(JobStatus.FINISHED); - logger.info("FFMPEG finished"); - } - } diff --git a/src/main/java/com/ddf/vodsystem/services/JobService.java b/src/main/java/com/ddf/vodsystem/services/JobService.java index cb0aa46..6b752f0 100644 --- a/src/main/java/com/ddf/vodsystem/services/JobService.java +++ b/src/main/java/com/ddf/vodsystem/services/JobService.java @@ -24,14 +24,14 @@ public class JobService { private static final Logger logger = LoggerFactory.getLogger(JobService.class); private final ConcurrentHashMap jobs = new ConcurrentHashMap<>(); private final BlockingQueue jobQueue = new LinkedBlockingQueue<>(); - private final CompressionService compressionService; + private final ClipService clipService; /** * Constructs a JobService with the given CompressionService. - * @param compressionService the compression service to use for processing jobs + * @param clipService the compression service to use for processing jobs */ - public JobService(CompressionService compressionService) { - this.compressionService = compressionService; + public JobService(ClipService clipService) { + this.clipService = clipService; } /** @@ -78,7 +78,7 @@ public class JobService { */ private void processJob(Job job) { try { - compressionService.run(job); + clipService.run(job); } catch (IOException | InterruptedException e) { Thread.currentThread().interrupt(); logger.error("Error while running job {}", job.getUuid(), e); From 3ce1c1486a23d2fd0edee0390c5ed72d5fb0b5fa Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Wed, 2 Jul 2025 21:02:07 +0200 Subject: [PATCH 04/30] ADD title and description fields to VideoMetadata type --- frontend/src/utils/types.ts | 2 ++ src/main/java/com/ddf/vodsystem/entities/VideoMetadata.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts index ffb6931..c1af16b 100644 --- a/frontend/src/utils/types.ts +++ b/frontend/src/utils/types.ts @@ -1,4 +1,6 @@ type VideoMetadata = { + title: string, + description: string, startPoint: number, endPoint: number, fps: number, diff --git a/src/main/java/com/ddf/vodsystem/entities/VideoMetadata.java b/src/main/java/com/ddf/vodsystem/entities/VideoMetadata.java index 144bf99..c3e5621 100644 --- a/src/main/java/com/ddf/vodsystem/entities/VideoMetadata.java +++ b/src/main/java/com/ddf/vodsystem/entities/VideoMetadata.java @@ -4,6 +4,8 @@ import lombok.Data; @Data public class VideoMetadata { + private String title; + private String description; private Float startPoint; private Float endPoint; private Float fps; From 159bcac5c42fb76835498445a47a8db2c2b44d71 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Wed, 2 Jul 2025 22:03:39 +0200 Subject: [PATCH 05/30] MOVE user authentication and clip creation to ClipService --- frontend/src/utils/endpoints.ts | 2 + .../java/com/ddf/vodsystem/entities/Job.java | 4 ++ .../ddf/vodsystem/services/ClipService.java | 42 +++++++++++++++++-- .../ddf/vodsystem/services/EditService.java | 34 +-------------- .../ddf/vodsystem/services/JobService.java | 15 +++++++ 5 files changed, 61 insertions(+), 36 deletions(-) diff --git a/frontend/src/utils/endpoints.ts b/frontend/src/utils/endpoints.ts index a5b20fe..43933d1 100644 --- a/frontend/src/utils/endpoints.ts +++ b/frontend/src/utils/endpoints.ts @@ -119,6 +119,8 @@ const getMetadata = async (uuid: string): Promise => { console.error('Error fetching metadata:', error); return { + title: '', + description: '', startPoint: 0, endPoint: 0, fps: 0, diff --git a/src/main/java/com/ddf/vodsystem/entities/Job.java b/src/main/java/com/ddf/vodsystem/entities/Job.java index 3038af4..4f1c6ad 100644 --- a/src/main/java/com/ddf/vodsystem/entities/Job.java +++ b/src/main/java/com/ddf/vodsystem/entities/Job.java @@ -1,6 +1,7 @@ package com.ddf.vodsystem.entities; import java.io.File; +import org.springframework.security.core.context.SecurityContext; import lombok.Data; @@ -14,6 +15,9 @@ public class Job { private VideoMetadata inputVideoMetadata; private VideoMetadata outputVideoMetadata = new VideoMetadata(); + // security + private SecurityContext securityContext; + // job status private JobStatus status = JobStatus.NOT_READY; private Float progress = 0.0f; diff --git a/src/main/java/com/ddf/vodsystem/services/ClipService.java b/src/main/java/com/ddf/vodsystem/services/ClipService.java index 012bb77..b1aaadc 100644 --- a/src/main/java/com/ddf/vodsystem/services/ClipService.java +++ b/src/main/java/com/ddf/vodsystem/services/ClipService.java @@ -1,21 +1,24 @@ package com.ddf.vodsystem.services; -import com.ddf.vodsystem.entities.VideoMetadata; -import com.ddf.vodsystem.entities.JobStatus; -import com.ddf.vodsystem.entities.Job; +import com.ddf.vodsystem.entities.*; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.ddf.vodsystem.exceptions.FFMPEGException; +import com.ddf.vodsystem.repositories.ClipRepository; +import com.ddf.vodsystem.security.CustomOAuth2User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; @Service @@ -26,8 +29,13 @@ public class ClipService { private static final float MAX_AUDIO_BITRATE = 128f; private static final float BITRATE_MULTIPLIER = 0.9f; + private final ClipRepository clipRepository; private final Pattern timePattern = Pattern.compile("out_time_ms=(\\d+)"); + public ClipService(ClipRepository clipRepository) { + this.clipRepository = clipRepository; + } + public void run(Job job) throws IOException, InterruptedException { logger.info("FFMPEG starting..."); @@ -56,10 +64,23 @@ public class ClipService { throw new FFMPEGException("FFMPEG process failed"); } + User user = getUser(); + if (user != null) { + createClip(job.getOutputVideoMetadata(), user); + } + job.setStatus(JobStatus.FINISHED); logger.info("FFMPEG finished"); } + private User getUser() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof CustomOAuth2User oAuth2user) { + return oAuth2user.getUser(); + } + return null; + } + private void validateVideoMetadata(VideoMetadata inputFileMetadata, VideoMetadata outputFileMetadata) { if (outputFileMetadata.getStartPoint() == null) { outputFileMetadata.setStartPoint(0f); @@ -141,4 +162,19 @@ public class ClipService { return new ProcessBuilder(command); } + private void createClip(VideoMetadata videoMetadata, User user) { + Clip clip = new Clip(); + clip.setTitle(videoMetadata.getTitle() != null ? videoMetadata.getTitle() : "Untitled Clip"); + clip.setUser(user); + clip.setDescription(videoMetadata.getDescription()); + clip.setCreatedAt(LocalDateTime.now()); + clip.setWidth(videoMetadata.getWidth()); + clip.setHeight(videoMetadata.getHeight()); + clip.setFps(videoMetadata.getFps()); + clip.setDuration(videoMetadata.getEndPoint() - videoMetadata.getStartPoint()); + clip.setFileSize(videoMetadata.getFileSize()); + clip.setVideoPath("test"); + clipRepository.save(clip); + } + } diff --git a/src/main/java/com/ddf/vodsystem/services/EditService.java b/src/main/java/com/ddf/vodsystem/services/EditService.java index f749e32..5ccf654 100644 --- a/src/main/java/com/ddf/vodsystem/services/EditService.java +++ b/src/main/java/com/ddf/vodsystem/services/EditService.java @@ -1,24 +1,15 @@ package com.ddf.vodsystem.services; import com.ddf.vodsystem.entities.*; -import com.ddf.vodsystem.repositories.ClipRepository; -import com.ddf.vodsystem.security.CustomOAuth2User; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; - @Service public class EditService { private final JobService jobService; - private final ClipRepository clipRepository; - private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(EditService.class); - public EditService(JobService jobService, ClipRepository clipRepository) { + public EditService(JobService jobService) { this.jobService = jobService; - this.clipRepository = clipRepository; } public void edit(String uuid, VideoMetadata videoMetadata) { @@ -29,15 +20,6 @@ public class EditService { public void process(String uuid) { jobService.jobReady(uuid); - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - - if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof CustomOAuth2User oAuth2user) { - logger.debug("Saving clip {} for user {}", uuid, oAuth2user.getName()); - VideoMetadata videoMetadata = jobService.getJob(uuid).getOutputVideoMetadata(); - User user = oAuth2user.getUser(); - - createClip(videoMetadata, user); - } } public float getProgress(String uuid) { @@ -87,18 +69,4 @@ public class EditService { } } - private void createClip(VideoMetadata videoMetadata, User user) { - Clip clip = new Clip(); - clip.setTitle("test"); - clip.setUser(user); - clip.setDescription("This is a test"); - clip.setCreatedAt(LocalDateTime.now()); - clip.setWidth(videoMetadata.getWidth()); - clip.setHeight(videoMetadata.getHeight()); - clip.setFps(videoMetadata.getFps()); - clip.setDuration(videoMetadata.getEndPoint() - videoMetadata.getStartPoint()); - clip.setFileSize(videoMetadata.getFileSize()); - clip.setVideoPath("test"); - clipRepository.save(clip); - } } diff --git a/src/main/java/com/ddf/vodsystem/services/JobService.java b/src/main/java/com/ddf/vodsystem/services/JobService.java index 6b752f0..ec15b0d 100644 --- a/src/main/java/com/ddf/vodsystem/services/JobService.java +++ b/src/main/java/com/ddf/vodsystem/services/JobService.java @@ -7,6 +7,8 @@ import java.util.concurrent.LinkedBlockingQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import com.ddf.vodsystem.entities.Job; @@ -67,6 +69,9 @@ public class JobService { Job job = getJob(uuid); job.setProgress(0f); + SecurityContext context = SecurityContextHolder.getContext(); + job.setSecurityContext(context); + logger.info("Job ready: {}", job.getUuid()); job.setStatus(JobStatus.PENDING); jobQueue.add(job); @@ -77,11 +82,21 @@ public class JobService { * @param job the job to process */ private void processJob(Job job) { + SecurityContext previousContext = SecurityContextHolder.getContext(); // optional, for restoring later try { + if (job.getSecurityContext() != null) { + SecurityContextHolder.setContext(job.getSecurityContext()); + } + clipService.run(job); + } catch (IOException | InterruptedException e) { Thread.currentThread().interrupt(); logger.error("Error while running job {}", job.getUuid(), e); + + } finally { + // 🔄 Restore previous context to avoid leaking across jobs + SecurityContextHolder.setContext(previousContext); } } From 8de4a147f2c6a3e4ab3541eec2766df61721e59a Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Thu, 3 Jul 2025 22:17:22 +0200 Subject: [PATCH 06/30] REMOVE JobService dependency from MetadataService --- .../ddf/vodsystem/services/MetadataService.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/main/java/com/ddf/vodsystem/services/MetadataService.java b/src/main/java/com/ddf/vodsystem/services/MetadataService.java index b89c159..f3bf829 100644 --- a/src/main/java/com/ddf/vodsystem/services/MetadataService.java +++ b/src/main/java/com/ddf/vodsystem/services/MetadataService.java @@ -18,12 +18,6 @@ import java.io.InputStreamReader; public class MetadataService { private static Logger logger = LoggerFactory.getLogger(MetadataService.class); - private final JobService jobService; - - public MetadataService(JobService jobService) { - this.jobService = jobService; - } - public VideoMetadata getVideoMetadata(File file) { logger.info("Getting metadata for file {}", file.getAbsolutePath()); @@ -50,16 +44,6 @@ public class MetadataService { } } - public VideoMetadata getInputFileMetadata(String uuid) { - Job job = jobService.getJob(uuid); - return getVideoMetadata(job.getInputFile()); - } - - public VideoMetadata getOutputFileMetadata(String uuid) { - Job job = jobService.getJob(uuid); - return getVideoMetadata(job.getOutputFile()); - } - private JsonNode readStandardOutput(Process process) throws IOException{ // Read the standard output (JSON metadata) BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); From 3abdfe3978f85a51c37024408bf77eded7bc5962 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Thu, 3 Jul 2025 22:40:29 +0200 Subject: [PATCH 07/30] ADD file size retrieval and update VideoMetadata object --- .../ddf/vodsystem/services/ClipService.java | 48 +++++++++++++------ .../vodsystem/services/MetadataService.java | 23 ++++++++- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/ddf/vodsystem/services/ClipService.java b/src/main/java/com/ddf/vodsystem/services/ClipService.java index b1aaadc..14e202b 100644 --- a/src/main/java/com/ddf/vodsystem/services/ClipService.java +++ b/src/main/java/com/ddf/vodsystem/services/ClipService.java @@ -30,12 +30,25 @@ public class ClipService { private static final float BITRATE_MULTIPLIER = 0.9f; private final ClipRepository clipRepository; + private final MetadataService metadataService; private final Pattern timePattern = Pattern.compile("out_time_ms=(\\d+)"); - public ClipService(ClipRepository clipRepository) { + public ClipService(ClipRepository clipRepository, MetadataService metadataService) { this.clipRepository = clipRepository; + this.metadataService = metadataService; } + /** + * Runs the FFMPEG command to create a video clip based on the provided job. + * Updates the job status and progress as the command executes. + * This method validates the input and output video metadata, + * Updates the job VideoMetadata with the output file size, + * + * @param job the job containing input and output video metadata + * @throws IOException if an I/O error occurs during command execution + * @throws InterruptedException if the thread is interrupted while waiting for the process to finish + * + */ public void run(Job job) throws IOException, InterruptedException { logger.info("FFMPEG starting..."); @@ -45,6 +58,26 @@ public class ClipService { Process process = pb.start(); job.setStatus(JobStatus.RUNNING); + updateJobProgress(process, job); + + if (process.waitFor() != 0) { + job.setStatus(JobStatus.FAILED); + throw new FFMPEGException("FFMPEG process failed"); + } + + Float fileSize = metadataService.getFileSize(job.getOutputFile()); + job.getOutputVideoMetadata().setFileSize(fileSize); + + User user = getUser(); + if (user != null) { + createClip(job.getOutputVideoMetadata(), user); + } + + job.setStatus(JobStatus.FINISHED); + logger.info("FFMPEG finished"); + } + + private void updateJobProgress(Process process, Job job) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); float length = job.getOutputVideoMetadata().getEndPoint() - job.getOutputVideoMetadata().getStartPoint(); @@ -58,19 +91,6 @@ public class ClipService { job.setProgress(progress); } } - - if (process.waitFor() != 0) { - job.setStatus(JobStatus.FAILED); - throw new FFMPEGException("FFMPEG process failed"); - } - - User user = getUser(); - if (user != null) { - createClip(job.getOutputVideoMetadata(), user); - } - - job.setStatus(JobStatus.FINISHED); - logger.info("FFMPEG finished"); } private User getUser() { diff --git a/src/main/java/com/ddf/vodsystem/services/MetadataService.java b/src/main/java/com/ddf/vodsystem/services/MetadataService.java index f3bf829..3c19a06 100644 --- a/src/main/java/com/ddf/vodsystem/services/MetadataService.java +++ b/src/main/java/com/ddf/vodsystem/services/MetadataService.java @@ -1,6 +1,5 @@ package com.ddf.vodsystem.services; -import com.ddf.vodsystem.entities.Job; import com.ddf.vodsystem.entities.VideoMetadata; import com.ddf.vodsystem.exceptions.FFMPEGException; import com.fasterxml.jackson.databind.JsonNode; @@ -16,7 +15,7 @@ import java.io.InputStreamReader; @Service public class MetadataService { - private static Logger logger = LoggerFactory.getLogger(MetadataService.class); + private static final Logger logger = LoggerFactory.getLogger(MetadataService.class); public VideoMetadata getVideoMetadata(File file) { logger.info("Getting metadata for file {}", file.getAbsolutePath()); @@ -44,6 +43,26 @@ public class MetadataService { } } + public Float getFileSize(File file) { + logger.info("Getting file size for {}", file.getAbsolutePath()); + VideoMetadata metadata = getVideoMetadata(file); + + if (metadata.getFileSize() == null) { + throw new FFMPEGException("File size not found"); + } + + return metadata.getFileSize(); + } + + public Float getVideoDuration(File file) { + logger.info("Getting video duration for {}", file.getAbsolutePath()); + VideoMetadata metadata = getVideoMetadata(file); + if (metadata.getEndPoint() == null) { + throw new FFMPEGException("Video duration not found"); + } + return metadata.getEndPoint(); + } + private JsonNode readStandardOutput(Process process) throws IOException{ // Read the standard output (JSON metadata) BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); From ea1508d1cfdcf2f1cc1d2a7aa28afaccc243f5c4 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Fri, 4 Jul 2025 22:47:52 +0200 Subject: [PATCH 08/30] ADD title and description fields to outputMetadata --- frontend/src/pages/ClipEdit.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/pages/ClipEdit.tsx b/frontend/src/pages/ClipEdit.tsx index db93065..6241fd9 100644 --- a/frontend/src/pages/ClipEdit.tsx +++ b/frontend/src/pages/ClipEdit.tsx @@ -17,6 +17,8 @@ const ClipEdit = () => { const [playbackValue, setPlaybackValue] = useState(0); const [outputMetadata, setOutputMetadata] = useState({ // default values + title: "", + description: "", startPoint: 0, endPoint: 5, width: 1280, From 12eafcdd3e09a9b840d32355a6c134c20b57b62f Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Sat, 5 Jul 2025 10:37:27 +0200 Subject: [PATCH 09/30] REFactor storage configuration and introduce DirectoryService for file management --- .../vodsystem/services/DirectoryService.java | 70 +++++++++++++++++++ .../ddf/vodsystem/services/UploadService.java | 60 +++------------- src/main/resources/application.properties | 5 +- 3 files changed, 81 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/ddf/vodsystem/services/DirectoryService.java diff --git a/src/main/java/com/ddf/vodsystem/services/DirectoryService.java b/src/main/java/com/ddf/vodsystem/services/DirectoryService.java new file mode 100644 index 0000000..3283b4d --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/services/DirectoryService.java @@ -0,0 +1,70 @@ +package com.ddf.vodsystem.services; + +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import org.slf4j.Logger; + +@Service +public class DirectoryService { + + private static final Logger logger = org.slf4j.LoggerFactory.getLogger(DirectoryService.class); + + @Value("${storage.outputs}") + private String outputDir; + + @Value("${storage.temp.inputs}") + private String tempInputsDir; + + @Value("${storage.temp.outputs}") + private String tempOutputsDir; + + public File getTempInputFile(String id) { + String dir = tempInputsDir + File.separator + id; + return new File(dir); + } + + public File getTempOutputFile(String id) { + String dir = tempOutputsDir + File.separator + id; + return new File(dir); + } + + public File getOutputFile(String id) { + String dir = outputDir + File.separator + id; + return new File(dir); + } + + public void saveData(File file, MultipartFile multipartFile) { + try { + Path filePath = Paths.get(file.getAbsolutePath()); + Files.copy(multipartFile.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + logger.error(e.getMessage()); + } + } + + public void createDirectory(String dir) throws IOException { + // Create the directory if it doesn't exist + Path outputPath = Paths.get(dir); + if (Files.notExists(outputPath)) { + Files.createDirectories(outputPath); + logger.info("Created directory: {}", outputPath); + } + } + + @PostConstruct + public void createDirectoriesIfNotExist() throws IOException { + createDirectory(tempInputsDir); + createDirectory(tempOutputsDir); + createDirectory(outputDir); + } +} \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/services/UploadService.java b/src/main/java/com/ddf/vodsystem/services/UploadService.java index 18c81e7..370c508 100644 --- a/src/main/java/com/ddf/vodsystem/services/UploadService.java +++ b/src/main/java/com/ddf/vodsystem/services/UploadService.java @@ -2,19 +2,14 @@ package com.ddf.vodsystem.services; import com.ddf.vodsystem.entities.Job; import com.ddf.vodsystem.entities.VideoMetadata; -import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; import java.util.Base64; import java.util.UUID; @@ -25,18 +20,17 @@ import org.slf4j.LoggerFactory; public class UploadService { private static final Logger logger = LoggerFactory.getLogger(UploadService.class); - @Value("${temp.vod.storage}") - private String inputDir; - @Value("${temp.vod.output}") - private String outputDir; - private final JobService jobService; private final MetadataService metadataService; + private final DirectoryService directoryService; @Autowired - public UploadService(JobService jobService, MetadataService metadataService) { + public UploadService(JobService jobService, + MetadataService metadataService, + DirectoryService directoryService) { this.jobService = jobService; this.metadataService = metadataService; + this.directoryService = directoryService; } public String upload(MultipartFile file) { @@ -45,13 +39,9 @@ public class UploadService { String extension = getFileExtension(file.getOriginalFilename()); String filename = uuid + (extension.isEmpty() ? "" : "." + extension); - Path inputPath = Paths.get(inputDir, filename); - File inputFile = inputPath.toFile(); - - Path outputPath = Paths.get(outputDir, filename); - File outputFile = outputPath.toFile(); - - moveToFile(file, inputFile); + File inputFile = directoryService.getTempInputFile(filename); + File outputFile = directoryService.getTempOutputFile(filename); + directoryService.saveData(inputFile, file); // add job VideoMetadata videoMetadata = metadataService.getVideoMetadata(inputFile); @@ -61,15 +51,6 @@ public class UploadService { return uuid; } - private void moveToFile(MultipartFile inputFile, File outputFile) { - try { - Path filePath = Paths.get(outputFile.getAbsolutePath()); - Files.copy(inputFile.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - logger.error(e.getMessage()); - } - } - private static String generateShortUUID() { UUID uuid = UUID.randomUUID(); ByteBuffer bb = ByteBuffer.wrap(new byte[16]); @@ -88,29 +69,4 @@ public class UploadService { } return fileName.substring(dotIndex + 1); } - - private void createDirectories() throws IOException { - // Create INPUT_DIR if it doesn't exist - Path inputDirPath = Paths.get(inputDir); - if (Files.notExists(inputDirPath)) { - Files.createDirectories(inputDirPath); - logger.info("Created directory: {}", inputDir); - } - - // Create OUTPUT_DIR if it doesn't exist - Path outputDirPath = Paths.get(outputDir); - if (Files.notExists(outputDirPath)) { - Files.createDirectories(outputDirPath); - logger.info("Created directory: {}", outputDir); - } - } - - @PostConstruct - public void init() { - try { - createDirectories(); - } catch (IOException e) { - logger.error("Failed to create directories: " + e.getMessage(), e); - } - } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 773728b..fde3f3f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,8 +4,9 @@ spring.profiles.active=local # VODs spring.servlet.multipart.max-file-size=2GB spring.servlet.multipart.max-request-size=2GB -temp.vod.storage=videos/inputs/ -temp.vod.output=videos/outputs/ +storage.temp.inputs=videos/inputs/ +storage.temp.outputs=videos/outputs/ +storage.outputs=videos/clips/ # Logging logging.level.org.springframework.web=DEBUG \ No newline at end of file From dcb2bcff223ff8abc06505cd9db7251479957e3b Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Mon, 7 Jul 2025 21:59:54 +0200 Subject: [PATCH 10/30] ADD DirectoryService integration for clip persistence and file management --- .../ddf/vodsystem/services/ClipService.java | 21 +++++++---- .../vodsystem/services/DirectoryService.java | 35 +++++++++++++++---- .../ddf/vodsystem/services/UploadService.java | 19 ++-------- 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/ddf/vodsystem/services/ClipService.java b/src/main/java/com/ddf/vodsystem/services/ClipService.java index 14e202b..b1bd7f4 100644 --- a/src/main/java/com/ddf/vodsystem/services/ClipService.java +++ b/src/main/java/com/ddf/vodsystem/services/ClipService.java @@ -31,11 +31,15 @@ public class ClipService { private final ClipRepository clipRepository; private final MetadataService metadataService; + private final DirectoryService directoryService; private final Pattern timePattern = Pattern.compile("out_time_ms=(\\d+)"); - public ClipService(ClipRepository clipRepository, MetadataService metadataService) { + public ClipService(ClipRepository clipRepository, + MetadataService metadataService, + DirectoryService directoryService) { this.clipRepository = clipRepository; this.metadataService = metadataService; + this.directoryService = directoryService; } /** @@ -70,7 +74,7 @@ public class ClipService { User user = getUser(); if (user != null) { - createClip(job.getOutputVideoMetadata(), user); + persistClip(job.getOutputVideoMetadata(), user, job); } job.setStatus(JobStatus.FINISHED); @@ -182,10 +186,16 @@ public class ClipService { return new ProcessBuilder(command); } - private void createClip(VideoMetadata videoMetadata, User user) { + private void persistClip(VideoMetadata videoMetadata, User user, Job job) { + // Move clip from temp to output directory + String fileExtension = directoryService.getFileExtension(job.getOutputFile().getAbsolutePath()); + File outputFile = directoryService.getOutputFile(job.getUuid(), fileExtension); + directoryService.copyFile(job.getOutputFile(), outputFile); + + // Save clip to database Clip clip = new Clip(); - clip.setTitle(videoMetadata.getTitle() != null ? videoMetadata.getTitle() : "Untitled Clip"); clip.setUser(user); + clip.setTitle(videoMetadata.getTitle() != null ? videoMetadata.getTitle() : "Untitled Clip"); clip.setDescription(videoMetadata.getDescription()); clip.setCreatedAt(LocalDateTime.now()); clip.setWidth(videoMetadata.getWidth()); @@ -193,8 +203,7 @@ public class ClipService { clip.setFps(videoMetadata.getFps()); clip.setDuration(videoMetadata.getEndPoint() - videoMetadata.getStartPoint()); clip.setFileSize(videoMetadata.getFileSize()); - clip.setVideoPath("test"); + clip.setVideoPath(outputFile.getPath()); clipRepository.save(clip); } - } diff --git a/src/main/java/com/ddf/vodsystem/services/DirectoryService.java b/src/main/java/com/ddf/vodsystem/services/DirectoryService.java index 3283b4d..efc8fc4 100644 --- a/src/main/java/com/ddf/vodsystem/services/DirectoryService.java +++ b/src/main/java/com/ddf/vodsystem/services/DirectoryService.java @@ -28,18 +28,18 @@ public class DirectoryService { @Value("${storage.temp.outputs}") private String tempOutputsDir; - public File getTempInputFile(String id) { - String dir = tempInputsDir + File.separator + id; + public File getTempInputFile(String id, String extension) { + String dir = tempInputsDir + File.separator + id + (extension.isEmpty() ? "" : "." + extension); return new File(dir); } - public File getTempOutputFile(String id) { - String dir = tempOutputsDir + File.separator + id; + public File getTempOutputFile(String id, String extension) { + String dir = tempOutputsDir + File.separator + id + (extension.isEmpty() ? "" : "." + extension); return new File(dir); } - public File getOutputFile(String id) { - String dir = outputDir + File.separator + id; + public File getOutputFile(String id, String extension) { + String dir = outputDir + File.separator + id + (extension.isEmpty() ? "" : "." + extension); return new File(dir); } @@ -52,6 +52,29 @@ public class DirectoryService { } } + public void copyFile(File source, File target) { + Path sourcePath = Paths.get(source.getAbsolutePath()); + Path destPath = Paths.get(target.getAbsolutePath()); + + try { + Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING); + logger.info("Copied file from {} to {}", sourcePath, destPath); + } catch (IOException e) { + logger.error(e.getMessage()); + } + } + + public String getFileExtension(String filePath) { + Path path = Paths.get(filePath); + String fileName = path.getFileName().toString(); + + int dotIndex = fileName.lastIndexOf('.'); + if (dotIndex == -1) { + return ""; // No extension + } + return fileName.substring(dotIndex + 1); + } + public void createDirectory(String dir) throws IOException { // Create the directory if it doesn't exist Path outputPath = Paths.get(dir); diff --git a/src/main/java/com/ddf/vodsystem/services/UploadService.java b/src/main/java/com/ddf/vodsystem/services/UploadService.java index 370c508..991f07b 100644 --- a/src/main/java/com/ddf/vodsystem/services/UploadService.java +++ b/src/main/java/com/ddf/vodsystem/services/UploadService.java @@ -8,8 +8,6 @@ import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.nio.ByteBuffer; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Base64; import java.util.UUID; @@ -36,11 +34,10 @@ public class UploadService { public String upload(MultipartFile file) { // generate uuid, filename String uuid = generateShortUUID(); - String extension = getFileExtension(file.getOriginalFilename()); - String filename = uuid + (extension.isEmpty() ? "" : "." + extension); + String extension = directoryService.getFileExtension(file.getOriginalFilename()); - File inputFile = directoryService.getTempInputFile(filename); - File outputFile = directoryService.getTempOutputFile(filename); + File inputFile = directoryService.getTempInputFile(uuid, extension); + File outputFile = directoryService.getTempOutputFile(uuid, extension); directoryService.saveData(inputFile, file); // add job @@ -59,14 +56,4 @@ public class UploadService { return Base64.getUrlEncoder().withoutPadding().encodeToString(bb.array()); } - private static String getFileExtension(String filePath) { - Path path = Paths.get(filePath); - String fileName = path.getFileName().toString(); - - int dotIndex = fileName.lastIndexOf('.'); - if (dotIndex == -1) { - return ""; // No extension - } - return fileName.substring(dotIndex + 1); - } } From 466b6b35f5ddc55789e2879edda443d93374a692 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Mon, 7 Jul 2025 22:16:03 +0200 Subject: [PATCH 11/30] ADD scheduled cleanup for temporary directories in DirectoryService --- .../ddf/vodsystem/VodSystemApplication.java | 2 ++ .../vodsystem/services/DirectoryService.java | 28 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ddf/vodsystem/VodSystemApplication.java b/src/main/java/com/ddf/vodsystem/VodSystemApplication.java index f8c3ef9..17f4d61 100644 --- a/src/main/java/com/ddf/vodsystem/VodSystemApplication.java +++ b/src/main/java/com/ddf/vodsystem/VodSystemApplication.java @@ -2,8 +2,10 @@ package com.ddf.vodsystem; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class VodSystemApplication { public static void main(String[] args) { diff --git a/src/main/java/com/ddf/vodsystem/services/DirectoryService.java b/src/main/java/com/ddf/vodsystem/services/DirectoryService.java index efc8fc4..698b2e2 100644 --- a/src/main/java/com/ddf/vodsystem/services/DirectoryService.java +++ b/src/main/java/com/ddf/vodsystem/services/DirectoryService.java @@ -2,6 +2,7 @@ package com.ddf.vodsystem.services; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -28,6 +29,8 @@ public class DirectoryService { @Value("${storage.temp.outputs}") private String tempOutputsDir; + private static final long TEMP_DIR_TIMELIMIT = 3 * 60 * 60 * (long) 1000; // 3 hours + public File getTempInputFile(String id, String extension) { String dir = tempInputsDir + File.separator + id + (extension.isEmpty() ? "" : "." + extension); return new File(dir); @@ -75,7 +78,7 @@ public class DirectoryService { return fileName.substring(dotIndex + 1); } - public void createDirectory(String dir) throws IOException { + private void createDirectory(String dir) throws IOException { // Create the directory if it doesn't exist Path outputPath = Paths.get(dir); if (Files.notExists(outputPath)) { @@ -84,10 +87,33 @@ public class DirectoryService { } } + private void cleanUpDirectory(String dir) throws IOException { + File file = new File(dir); + File[] files = file.listFiles(); + + if (files == null) { + logger.warn("No files found in directory: {}", dir); + return; + } + + for (File f : files){ + if (f.isFile() && f.lastModified() < (System.currentTimeMillis() - TEMP_DIR_TIMELIMIT)) { + Files.delete(f.toPath()); + } + } + + } + @PostConstruct public void createDirectoriesIfNotExist() throws IOException { createDirectory(tempInputsDir); createDirectory(tempOutputsDir); createDirectory(outputDir); } + + @Scheduled(fixedRate = (30 * 60 * 1000)) + public void cleanTempDirectories() throws IOException { + cleanUpDirectory(tempInputsDir); + cleanUpDirectory(tempOutputsDir); + } } \ No newline at end of file From 2958dd8cd7bb6f90293a09e3ad5e9237eeefe4ed Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Mon, 7 Jul 2025 22:35:40 +0200 Subject: [PATCH 12/30] ADD ClipNames component for title and description input in ClipEdit --- frontend/src/components/video/ClipNames.tsx | 37 +++++++++++++++++++ frontend/src/pages/ClipEdit.tsx | 4 ++ .../ddf/vodsystem/services/UploadService.java | 1 + 3 files changed, 42 insertions(+) create mode 100644 frontend/src/components/video/ClipNames.tsx diff --git a/frontend/src/components/video/ClipNames.tsx b/frontend/src/components/video/ClipNames.tsx new file mode 100644 index 0000000..c972297 --- /dev/null +++ b/frontend/src/components/video/ClipNames.tsx @@ -0,0 +1,37 @@ +import clsx from "clsx"; +import type {VideoMetadata} from "../../utils/types.ts"; + +type ClipNamesProps = { + setMetadata: Function + className?: string; +} + +const ClipNames = ({setMetadata, className}: ClipNamesProps) => { + return ( +
+ + setMetadata((prevState: VideoMetadata) => ({ + ...prevState, + title: e.target.value + }))} + className={"border-black bg-gray-200 rounded-md w-full p-2"} + /> + + + setMetadata((prevState: VideoMetadata) => ({ + ...prevState, + description: e.target.value + }))} + className={"border-black bg-gray-200 rounded-md w-full p-2"} + /> +
+ ) +} + +export default ClipNames; \ No newline at end of file diff --git a/frontend/src/pages/ClipEdit.tsx b/frontend/src/pages/ClipEdit.tsx index 6241fd9..28c1be2 100644 --- a/frontend/src/pages/ClipEdit.tsx +++ b/frontend/src/pages/ClipEdit.tsx @@ -8,6 +8,7 @@ import ExportWidget from "../components/video/ExportWidget.tsx"; import {editFile, getMetadata, processFile, getProgress} from "../utils/endpoints" import type { VideoMetadata } from "../utils/types.ts"; import Box from "../components/Box.tsx"; +import ClipNames from "../components/video/ClipNames.tsx"; const ClipEdit = () => { const { id } = useParams(); @@ -100,6 +101,9 @@ const ClipEdit = () => { + diff --git a/src/main/java/com/ddf/vodsystem/services/UploadService.java b/src/main/java/com/ddf/vodsystem/services/UploadService.java index 991f07b..bd010d3 100644 --- a/src/main/java/com/ddf/vodsystem/services/UploadService.java +++ b/src/main/java/com/ddf/vodsystem/services/UploadService.java @@ -41,6 +41,7 @@ public class UploadService { directoryService.saveData(inputFile, file); // add job + logger.info("Uploaded file and creating job with UUID: {}", uuid); VideoMetadata videoMetadata = metadataService.getVideoMetadata(inputFile); Job job = new Job(uuid, inputFile, outputFile, videoMetadata); jobService.add(job); From 6d4146ed85e358c2c5ef2e2f95d38e0c238efc32 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Tue, 8 Jul 2025 18:11:41 +0200 Subject: [PATCH 13/30] ADD input structure for metadata --- frontend/src/components/video/ClipNames.tsx | 37 ----------------- .../video/{ClipConfig.tsx => ConfigBox.tsx} | 2 +- frontend/src/components/video/MetadataBox.tsx | 41 +++++++++++++++++++ frontend/src/pages/ClipEdit.tsx | 8 ++-- .../vodsystem/services/DirectoryService.java | 3 +- 5 files changed, 48 insertions(+), 43 deletions(-) delete mode 100644 frontend/src/components/video/ClipNames.tsx rename frontend/src/components/video/{ClipConfig.tsx => ConfigBox.tsx} (97%) create mode 100644 frontend/src/components/video/MetadataBox.tsx diff --git a/frontend/src/components/video/ClipNames.tsx b/frontend/src/components/video/ClipNames.tsx deleted file mode 100644 index c972297..0000000 --- a/frontend/src/components/video/ClipNames.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import clsx from "clsx"; -import type {VideoMetadata} from "../../utils/types.ts"; - -type ClipNamesProps = { - setMetadata: Function - className?: string; -} - -const ClipNames = ({setMetadata, className}: ClipNamesProps) => { - return ( -
- - setMetadata((prevState: VideoMetadata) => ({ - ...prevState, - title: e.target.value - }))} - className={"border-black bg-gray-200 rounded-md w-full p-2"} - /> - - - setMetadata((prevState: VideoMetadata) => ({ - ...prevState, - description: e.target.value - }))} - className={"border-black bg-gray-200 rounded-md w-full p-2"} - /> -
- ) -} - -export default ClipNames; \ No newline at end of file diff --git a/frontend/src/components/video/ClipConfig.tsx b/frontend/src/components/video/ConfigBox.tsx similarity index 97% rename from frontend/src/components/video/ClipConfig.tsx rename to frontend/src/components/video/ConfigBox.tsx index fe18d2c..42e5052 100644 --- a/frontend/src/components/video/ClipConfig.tsx +++ b/frontend/src/components/video/ConfigBox.tsx @@ -7,7 +7,7 @@ type prop = { className?: string; } -export default function ClipConfig({setMetadata, className}: prop) { +export default function ConfigBox({setMetadata, className}: prop) { const updateRes = (e: React.ChangeEvent) => { var vals = e.target.value.split(","); setMetadata((prevState: VideoMetadata) => ({ diff --git a/frontend/src/components/video/MetadataBox.tsx b/frontend/src/components/video/MetadataBox.tsx new file mode 100644 index 0000000..a69ba6c --- /dev/null +++ b/frontend/src/components/video/MetadataBox.tsx @@ -0,0 +1,41 @@ +import clsx from "clsx"; +import type {VideoMetadata} from "../../utils/types.ts"; +import Selector from "../Selector.tsx"; + +type MetadataBoxProps = { + setMetadata: Function + className?: string; +} + +const MetadataBox = ({setMetadata, className}: MetadataBoxProps) => { + return ( +
+

Metadata Settings

+ + + setMetadata((prevState: VideoMetadata) => ({ + ...prevState, + title: e.target.value + }))} + className={"border-black bg-gray-200 rounded-md w-full p-2"} + /> + + + +