• Implemented the next Input Folder Scheduler Plan milestone: startup directory initialization.

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.
This commit is contained in:
JSLMPR 2026-07-10 00:52:40 +02:00
parent 1cda571c30
commit 97cc4ff4e4
4 changed files with 186 additions and 2 deletions

View File

@ -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`.

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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