From 97cc4ff4e4343add0e05a99c0e52e846f98750dc Mon Sep 17 00:00:00 2001 From: JSLMPR Date: Fri, 10 Jul 2026 00:52:40 +0200 Subject: [PATCH] =?UTF-8?q?=E2=80=A2=20Implemented=20the=20next=20Input=20?= =?UTF-8?q?Folder=20Scheduler=20Plan=20milestone:=20startup=20directory=20?= =?UTF-8?q?initialization.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added src/main/java/org/example/videoclips/folder/FolderSchedulerDirectoryInitializer.java, which creates the configured input, output, working, processed, and rejected directories when video-clipping.folder- scheduler.enabled=true. It stays inactive by default, so normal startup will not fail on machines where /input and /output cannot be created. Updated docs/input-folder-scheduler-plan.md to mark milestone 3 complete. --- docs/input-folder-scheduler-plan.md | 4 +- .../config/VideoClippingProperties.java | 130 ++++++++++++++++++ .../FolderSchedulerDirectoryInitializer.java | 42 ++++++ src/main/resources/application.properties | 12 ++ 4 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/example/videoclips/folder/FolderSchedulerDirectoryInitializer.java diff --git a/docs/input-folder-scheduler-plan.md b/docs/input-folder-scheduler-plan.md index f188c07..c2df9a5 100644 --- a/docs/input-folder-scheduler-plan.md +++ b/docs/input-folder-scheduler-plan.md @@ -19,8 +19,8 @@ Note: the local machine's filesystem root is read-only in this workspace, so abs ## Milestones 1. [x] Create repository-local placeholder directories `input/source` and `output/clips`. -2. [ ] Add folder scheduler configuration properties. -3. [ ] Create startup directory initialization for input, output, working, processed, and rejected folders. +2. [x] Add folder scheduler configuration properties. +3. [x] Create startup directory initialization for input, output, working, processed, and rejected folders. 4. [ ] Add deterministic scanner that picks one candidate video at a time. 5. [ ] Add repeat-prevention by moving selected inputs to a working folder before processing. 6. [ ] Add valid-video detection using extension filtering and `ffprobe`. diff --git a/src/main/java/org/example/videoclips/config/VideoClippingProperties.java b/src/main/java/org/example/videoclips/config/VideoClippingProperties.java index 82ba8e1..8335391 100644 --- a/src/main/java/org/example/videoclips/config/VideoClippingProperties.java +++ b/src/main/java/org/example/videoclips/config/VideoClippingProperties.java @@ -36,6 +36,8 @@ public class VideoClippingProperties { private final Ffmpeg ffmpeg = new Ffmpeg(); + private final FolderScheduler folderScheduler = new FolderScheduler(); + private final Cleanup cleanup = new Cleanup(); private final Quotas quotas = new Quotas(); @@ -116,6 +118,10 @@ public class VideoClippingProperties { return ffmpeg; } + public FolderScheduler getFolderScheduler() { + return folderScheduler; + } + public Cleanup getCleanup() { return cleanup; } @@ -335,6 +341,130 @@ public class VideoClippingProperties { } } + public static class FolderScheduler { + private boolean enabled = false; + + private String inputDirectory = "/input/source"; + + private String outputDirectory = "/output/clips"; + + private String processedDirectory = "/input/processed"; + + private String rejectedDirectory = "/input/rejected"; + + private String workingDirectory = "/input/working"; + + @Min(1000) + private long pollIntervalMs = 5000; + + @Min(1) + private int segmentDurationSeconds = 8; + + private String ffmpegBinary = "ffmpeg"; + + private String ffprobeBinary = "ffprobe"; + + private String outputContainer = "mp4"; + + private String exactPreset = "veryfast"; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getInputDirectory() { + return inputDirectory; + } + + public void setInputDirectory(String inputDirectory) { + this.inputDirectory = inputDirectory; + } + + public String getOutputDirectory() { + return outputDirectory; + } + + public void setOutputDirectory(String outputDirectory) { + this.outputDirectory = outputDirectory; + } + + public String getProcessedDirectory() { + return processedDirectory; + } + + public void setProcessedDirectory(String processedDirectory) { + this.processedDirectory = processedDirectory; + } + + public String getRejectedDirectory() { + return rejectedDirectory; + } + + public void setRejectedDirectory(String rejectedDirectory) { + this.rejectedDirectory = rejectedDirectory; + } + + public String getWorkingDirectory() { + return workingDirectory; + } + + public void setWorkingDirectory(String workingDirectory) { + this.workingDirectory = workingDirectory; + } + + public long getPollIntervalMs() { + return pollIntervalMs; + } + + public void setPollIntervalMs(long pollIntervalMs) { + this.pollIntervalMs = pollIntervalMs; + } + + public int getSegmentDurationSeconds() { + return segmentDurationSeconds; + } + + public void setSegmentDurationSeconds(int segmentDurationSeconds) { + this.segmentDurationSeconds = segmentDurationSeconds; + } + + public String getFfmpegBinary() { + return ffmpegBinary; + } + + public void setFfmpegBinary(String ffmpegBinary) { + this.ffmpegBinary = ffmpegBinary; + } + + public String getFfprobeBinary() { + return ffprobeBinary; + } + + public void setFfprobeBinary(String ffprobeBinary) { + this.ffprobeBinary = ffprobeBinary; + } + + public String getOutputContainer() { + return outputContainer; + } + + public void setOutputContainer(String outputContainer) { + this.outputContainer = outputContainer; + } + + public String getExactPreset() { + return exactPreset; + } + + public void setExactPreset(String exactPreset) { + this.exactPreset = exactPreset; + } + } + public static class Quotas { @Min(0) private int maxActiveJobsPerTenant = 5; diff --git a/src/main/java/org/example/videoclips/folder/FolderSchedulerDirectoryInitializer.java b/src/main/java/org/example/videoclips/folder/FolderSchedulerDirectoryInitializer.java new file mode 100644 index 0000000..74fc2d1 --- /dev/null +++ b/src/main/java/org/example/videoclips/folder/FolderSchedulerDirectoryInitializer.java @@ -0,0 +1,42 @@ +package org.example.videoclips.folder; + +import org.example.videoclips.config.VideoClippingProperties; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +@Component +@ConditionalOnProperty(name = "video-clipping.folder-scheduler.enabled", havingValue = "true") +public class FolderSchedulerDirectoryInitializer implements ApplicationRunner { + + private final VideoClippingProperties.FolderScheduler properties; + + public FolderSchedulerDirectoryInitializer(VideoClippingProperties videoClippingProperties) { + this.properties = videoClippingProperties.getFolderScheduler(); + } + + @Override + public void run(ApplicationArguments args) { + List.of( + properties.getInputDirectory(), + properties.getOutputDirectory(), + properties.getWorkingDirectory(), + properties.getProcessedDirectory(), + properties.getRejectedDirectory() + ).forEach(this::createDirectory); + } + + private void createDirectory(String directory) { + try { + Files.createDirectories(Path.of(directory)); + } catch (IOException ex) { + throw new IllegalStateException("Unable to create folder scheduler directory: " + directory, ex); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e0187a2..55b1dcc 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -25,6 +25,18 @@ video-clipping.ffmpeg.ffmpeg-binary=ffmpeg video-clipping.ffmpeg.input-directory=./tmp/ffmpeg-input video-clipping.ffmpeg.output-directory=./tmp/ffmpeg-output video-clipping.ffmpeg.cleanup-local-files=true +video-clipping.folder-scheduler.enabled=false +video-clipping.folder-scheduler.input-directory=/input/source +video-clipping.folder-scheduler.output-directory=/output/clips +video-clipping.folder-scheduler.processed-directory=/input/processed +video-clipping.folder-scheduler.rejected-directory=/input/rejected +video-clipping.folder-scheduler.working-directory=/input/working +video-clipping.folder-scheduler.poll-interval-ms=5000 +video-clipping.folder-scheduler.segment-duration-seconds=8 +video-clipping.folder-scheduler.ffmpeg-binary=ffmpeg +video-clipping.folder-scheduler.ffprobe-binary=ffprobe +video-clipping.folder-scheduler.output-container=mp4 +video-clipping.folder-scheduler.exact-preset=veryfast video-clipping.quotas.max-active-jobs-per-tenant=5 video-clipping.cleanup.enabled=true video-clipping.cleanup.local-artifact-poll-interval-ms=300000