From 7fd92a72df99622a1b65acbcd5d27631f7fc0a05 Mon Sep 17 00:00:00 2001 From: JSLMPR Date: Fri, 10 Jul 2026 12:24:16 +0200 Subject: [PATCH] Configure folder scheduler in YAML --- docs/input-folder-scheduler-plan.md | 62 +++++++++------- pom.xml | 9 +++ .../config/VideoClippingProperties.java | 10 +++ .../folder/FolderFfmpegClipper.java | 70 +++++++++++++----- .../folder/FolderVideoScanScheduler.java | 21 +++++- ...lication-folder-scheduler-local.properties | 13 ---- .../application-folder-scheduler-local.yml | 19 +++++ src/main/resources/application.properties | 12 --- src/main/resources/application.yml | 19 +++++ .../api/TenantQuotaControllerTest.java | 73 ------------------- .../folder/FolderFfmpegClipperTest.java | 23 +++++- .../FolderSchedulerLocalProfileTest.java | 17 ++++- .../folder/FolderVideoScanSchedulerTest.java | 24 ++++++ .../resources/application-test.properties | 1 + 14 files changed, 220 insertions(+), 153 deletions(-) delete mode 100644 src/main/resources/application-folder-scheduler-local.properties create mode 100644 src/main/resources/application-folder-scheduler-local.yml create mode 100644 src/main/resources/application.yml delete mode 100644 src/test/java/org/example/videoclips/api/TenantQuotaControllerTest.java create mode 100644 src/test/resources/application-test.properties diff --git a/docs/input-folder-scheduler-plan.md b/docs/input-folder-scheduler-plan.md index 92a2c1f..c66776a 100644 --- a/docs/input-folder-scheduler-plan.md +++ b/docs/input-folder-scheduler-plan.md @@ -58,21 +58,24 @@ Sources: Add a new scheduler config group under `video-clipping.folder-scheduler`. -Recommended properties: +Recommended YAML: -```properties -video-clipping.folder-scheduler.enabled=true -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 +```yaml +video-clipping: + folder-scheduler: + enabled: true + input-directory: /input/source + output-directory: /output/clips + processed-directory: /input/processed + rejected-directory: /input/rejected + working-directory: /input/working + poll-interval-ms: 5000 + segment-duration-seconds: 8 + ffmpeg-binary: ffmpeg + ffprobe-binary: ffprobe + output-container: mp4 + exact-preset: veryfast + preserve-input-quality: true ``` Keep the existing API-driven clipper config separate from this folder scheduler. The folder scheduler is a direct filesystem workflow, not an API/upload/session workflow. @@ -173,35 +176,29 @@ Do not log raw paths from untrusted metadata or full FFmpeg stderr if it contain ## FFmpeg Command -Default to exact 8-second MP4/H.264/AAC output: +Default to quality-preserving MP4 stream copy when the source already has usable keyframes: ```bash ffmpeg -hide_banner -y \ -i /input/working/1.mp4 \ - -map 0 \ - -c:v libx264 \ - -preset veryfast \ - -crf 20 \ - -force_key_frames "expr:gte(t,n_forced*8)" \ - -c:a aac \ - -b:a 128k \ + -map 0 -c copy \ -f segment \ -segment_time 8 \ - -segment_format_options movflags=+faststart \ -reset_timestamps 1 \ /output/clips/1/clip_%05d.mp4 ``` Expected behavior: -- each generated clip is approximately 8 seconds +- each generated clip preserves the input codec quality because no re-encode occurs +- clips are cut on keyframes, so boundaries may drift from exact 8-second marks - the final clip may be shorter than 8 seconds - output files are broadly playable MP4 files Implementation note: -- If exact boundaries are more important than speed, always transcode with `libx264` and force keyframes. -- If speed is more important and keyframe-aligned cuts are acceptable, add an optional `FAST` mode using `-c copy`, but document that clips may not start exactly at 8-second boundaries. +- If exact 8-second boundaries are more important than preserving source quality, transcode with `libx264` and force keyframes. +- If preserving source quality is more important, use stream copy and accept keyframe-aligned cuts. ## Proposed Code Structure @@ -268,6 +265,19 @@ This ensures the scheduler never processes the same source file repeatedly. The scheduler uses an initial delay and fixed delay of `5000` ms by default. The initial delay lets startup directory initialization finish; subsequent delays start after the previous scan finishes, so scans never intentionally overlap. Configure both with `video-clipping.folder-scheduler.poll-interval-ms` (minimum `1000` ms). +The base configuration enables repository-local folder processing for normal IDE starts. Production deployments can override it without changing the artifact: + +```bash +FOLDER_SCHEDULER_ENABLED=true +FOLDER_SCHEDULER_INPUT_DIRECTORY=/input/source +FOLDER_SCHEDULER_OUTPUT_DIRECTORY=/output/clips +FOLDER_SCHEDULER_WORKING_DIRECTORY=/input/working +FOLDER_SCHEDULER_PROCESSED_DIRECTORY=/input/processed +FOLDER_SCHEDULER_REJECTED_DIRECTORY=/input/rejected +FOLDER_SCHEDULER_POLL_INTERVAL_MS=5000 +FOLDER_SCHEDULER_LOG_LEVEL=INFO +``` + Lifecycle logs use stable `event=...` fields and a per-run `scan_id` for correlation. Active work and state transitions log at info or warn/error level; scan start, idle, completion, directory readiness, and probe timing log at debug level. Log these events: diff --git a/pom.xml b/pom.xml index f9d2ad5..5448785 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,15 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + test + + + org.springframework.boot spring-boot-maven-plugin diff --git a/src/main/java/org/example/videoclips/config/VideoClippingProperties.java b/src/main/java/org/example/videoclips/config/VideoClippingProperties.java index 8335391..a1779ba 100644 --- a/src/main/java/org/example/videoclips/config/VideoClippingProperties.java +++ b/src/main/java/org/example/videoclips/config/VideoClippingProperties.java @@ -368,6 +368,8 @@ public class VideoClippingProperties { private String exactPreset = "veryfast"; + private boolean preserveInputQuality = true; + public boolean isEnabled() { return enabled; } @@ -463,6 +465,14 @@ public class VideoClippingProperties { public void setExactPreset(String exactPreset) { this.exactPreset = exactPreset; } + + public boolean isPreserveInputQuality() { + return preserveInputQuality; + } + + public void setPreserveInputQuality(boolean preserveInputQuality) { + this.preserveInputQuality = preserveInputQuality; + } } public static class Quotas { diff --git a/src/main/java/org/example/videoclips/folder/FolderFfmpegClipper.java b/src/main/java/org/example/videoclips/folder/FolderFfmpegClipper.java index 0aa8a76..7720324 100644 --- a/src/main/java/org/example/videoclips/folder/FolderFfmpegClipper.java +++ b/src/main/java/org/example/videoclips/folder/FolderFfmpegClipper.java @@ -38,30 +38,13 @@ public class FolderFfmpegClipper { Path outputDirectory = createOutputDirectory(sourceFile); String segmentDuration = Integer.toString(properties.getSegmentDurationSeconds()); Path outputPattern = outputDirectory.resolve("clip_%05d." + properties.getOutputContainer()); - List command = List.of( - properties.getFfmpegBinary(), - "-hide_banner", "-y", - "-i", sourceFile.toString(), - "-map", "0:v:0", - "-map", "0:a?", - "-c:v", "libx264", - "-preset", properties.getExactPreset(), - "-crf", "20", - "-force_key_frames", "expr:gte(t,n_forced*" + segmentDuration + ")", - "-c:a", "aac", - "-b:a", "128k", - "-f", "segment", - "-segment_time", segmentDuration, - "-segment_format_options", "movflags=+faststart", - "-reset_timestamps", "1", - outputPattern.toString() - ); + List command = buildCommand(sourceFile, segmentDuration, outputPattern); ProcessResult processResult; long startedAt = System.nanoTime(); - log.info("event=ffmpeg_started file={} output_directory={} segment_duration_seconds={} preset={}", + log.info("event=ffmpeg_started file={} output_directory={} segment_duration_seconds={} quality_mode={} preset={}", sourceFile.getFileName(), outputDirectory, properties.getSegmentDurationSeconds(), - properties.getExactPreset()); + qualityMode(), properties.getExactPreset()); try { processResult = processExecutor.execute(command); } catch (InterruptedException ex) { @@ -83,6 +66,53 @@ public class FolderFfmpegClipper { return new ClipResult(outputDirectory, clipCount); } + private List buildCommand(Path sourceFile, String segmentDuration, Path outputPattern) { + List command = new java.util.ArrayList<>(); + command.add(properties.getFfmpegBinary()); + command.add("-hide_banner"); + command.add("-y"); + command.add("-i"); + command.add(sourceFile.toString()); + command.add("-map"); + command.add("0"); + if (properties.isPreserveInputQuality()) { + command.add("-c"); + command.add("copy"); + } else { + command.add("-c:v"); + command.add("libx264"); + command.add("-preset"); + command.add(properties.getExactPreset()); + command.add("-crf"); + command.add("20"); + command.add("-force_key_frames"); + command.add("expr:gte(t,n_forced*" + segmentDuration + ")"); + command.add("-c:a"); + command.add("aac"); + command.add("-b:a"); + command.add("128k"); + } + command.add("-f"); + command.add("segment"); + command.add("-segment_time"); + command.add(segmentDuration); + if (properties.isPreserveInputQuality()) { + command.add("-reset_timestamps"); + command.add("1"); + } else { + command.add("-segment_format_options"); + command.add("movflags=+faststart"); + command.add("-reset_timestamps"); + command.add("1"); + } + command.add(outputPattern.toString()); + return command; + } + + private String qualityMode() { + return properties.isPreserveInputQuality() ? "copy" : "transcode"; + } + private long elapsedMillis(long startedAt) { return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startedAt); } diff --git a/src/main/java/org/example/videoclips/folder/FolderVideoScanScheduler.java b/src/main/java/org/example/videoclips/folder/FolderVideoScanScheduler.java index dcdea3d..2924bb4 100644 --- a/src/main/java/org/example/videoclips/folder/FolderVideoScanScheduler.java +++ b/src/main/java/org/example/videoclips/folder/FolderVideoScanScheduler.java @@ -4,6 +4,8 @@ import org.example.videoclips.config.VideoClippingProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -41,6 +43,15 @@ public class FolderVideoScanScheduler { this.clipper = clipper; } + @EventListener(ApplicationReadyEvent.class) + public void logStartup() { + log.info("event=folder_scheduler_started enabled=true initial_delay_ms={} fixed_delay_ms={} " + + "segment_duration_seconds={} input_directory={} output_directory={} quality_mode={}", + properties.getPollIntervalMs(), properties.getPollIntervalMs(), + properties.getSegmentDurationSeconds(), properties.getInputDirectory(), + properties.getOutputDirectory(), properties.isPreserveInputQuality() ? "copy" : "transcode"); + } + @Scheduled( initialDelayString = "${video-clipping.folder-scheduler.poll-interval-ms:5000}", fixedDelayString = "${video-clipping.folder-scheduler.poll-interval-ms:5000}" @@ -115,9 +126,15 @@ public class FolderVideoScanScheduler { FolderVideoValidator.ValidationResult result = validator.validate(workingFile); if (result.valid()) { log.info("event=validation_succeeded scan_id={} file={}", scanId, workingFile.getFileName()); + long clippingStartedAt = System.nanoTime(); + log.info("event=video_processing_started scan_id={} file={} segment_duration_seconds={} quality_mode={}", + scanId, workingFile.getFileName(), properties.getSegmentDurationSeconds(), + properties.isPreserveInputQuality() ? "copy" : "transcode"); FolderFfmpegClipper.ClipResult clipResult = clipper.clip(workingFile); - log.info("event=clipping_succeeded scan_id={} file={} clip_count={} output_directory={}", - scanId, workingFile.getFileName(), clipResult.clipCount(), clipResult.outputDirectory()); + log.info("event=video_processing_completed scan_id={} file={} elapsed_ms={} clip_count={} " + + "clips_folder_name={} clips_folder={}", + scanId, workingFile.getFileName(), elapsedMillis(clippingStartedAt), clipResult.clipCount(), + clipResult.outputDirectory().getFileName(), clipResult.outputDirectory()); Path processedFile = moveToDirectory(workingFile, Path.of(properties.getProcessedDirectory())); log.info("event=processing_succeeded scan_id={} file={} processed_directory={}", scanId, processedFile.getFileName(), processedFile.getParent()); diff --git a/src/main/resources/application-folder-scheduler-local.properties b/src/main/resources/application-folder-scheduler-local.properties deleted file mode 100644 index 5b2d2e7..0000000 --- a/src/main/resources/application-folder-scheduler-local.properties +++ /dev/null @@ -1,13 +0,0 @@ -video-clipping.folder-scheduler.enabled=true -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 -logging.level.org.example.videoclips.folder=DEBUG diff --git a/src/main/resources/application-folder-scheduler-local.yml b/src/main/resources/application-folder-scheduler-local.yml new file mode 100644 index 0000000..485e941 --- /dev/null +++ b/src/main/resources/application-folder-scheduler-local.yml @@ -0,0 +1,19 @@ +video-clipping: + folder-scheduler: + enabled: true + input-directory: ./input/source + output-directory: ./output/clips + processed-directory: ./input/processed + rejected-directory: ./input/rejected + working-directory: ./input/working + poll-interval-ms: 5000 + segment-duration-seconds: 8 + ffmpeg-binary: ffmpeg + ffprobe-binary: ffprobe + output-container: mp4 + exact-preset: veryfast + preserve-input-quality: true + +logging: + level: + org.example.videoclips.folder: INFO diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 55b1dcc..e0187a2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -25,18 +25,6 @@ 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 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..bc13fe4 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,19 @@ +video-clipping: + folder-scheduler: + enabled: ${FOLDER_SCHEDULER_ENABLED:true} + input-directory: ${FOLDER_SCHEDULER_INPUT_DIRECTORY:./input/source} + output-directory: ${FOLDER_SCHEDULER_OUTPUT_DIRECTORY:./output/clips} + processed-directory: ${FOLDER_SCHEDULER_PROCESSED_DIRECTORY:./input/processed} + rejected-directory: ${FOLDER_SCHEDULER_REJECTED_DIRECTORY:./input/rejected} + working-directory: ${FOLDER_SCHEDULER_WORKING_DIRECTORY:./input/working} + poll-interval-ms: ${FOLDER_SCHEDULER_POLL_INTERVAL_MS:5000} + segment-duration-seconds: 8 + ffmpeg-binary: ffmpeg + ffprobe-binary: ffprobe + output-container: mp4 + exact-preset: veryfast + preserve-input-quality: ${FOLDER_SCHEDULER_PRESERVE_INPUT_QUALITY:true} + +logging: + level: + org.example.videoclips.folder: ${FOLDER_SCHEDULER_LOG_LEVEL:INFO} diff --git a/src/test/java/org/example/videoclips/api/TenantQuotaControllerTest.java b/src/test/java/org/example/videoclips/api/TenantQuotaControllerTest.java deleted file mode 100644 index fcb47dd..0000000 --- a/src/test/java/org/example/videoclips/api/TenantQuotaControllerTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.example.videoclips.api; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest(properties = "video-clipping.quotas.max-active-jobs-per-tenant=0") -@AutoConfigureMockMvc -class TenantQuotaControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Test - void rejectsClipJobCreationWhenTenantActiveJobQuotaIsExceeded() throws Exception { - String assetResponse = mockMvc.perform(post("/v1/video-assets") - .header("X-Tenant-Id", "tenant-a") - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "fileName": "quota.mp4", - "contentType": "video/mp4", - "contentLengthBytes": 4096, - "checksumSha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - } - """)) - .andExpect(status().isCreated()) - .andReturn() - .getResponse() - .getContentAsString(); - - String assetId = JsonTestHelper.read(assetResponse, "$.assetId"); - String uploadId = JsonTestHelper.read(assetResponse, "$.upload.uploadId"); - - mockMvc.perform(post("/v1/video-assets/{assetId}/uploads:complete", assetId) - .header("X-Tenant-Id", "tenant-a") - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "uploadId": "%s", - "sourceDurationSeconds": 17, - "parts": [ - { - "partNumber": 1, - "etag": "\\"etag-1\\"" - } - ] - } - """.formatted(uploadId))) - .andExpect(status().isOk()); - - mockMvc.perform(post("/v1/video-assets/{assetId}/clip-jobs", assetId) - .header("X-Tenant-Id", "tenant-a") - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "segmentDurationSeconds": 8, - "accuracyMode": "FAST", - "outputContainer": "mp4", - "overwriteExisting": false - } - """)) - .andExpect(status().isTooManyRequests()) - .andExpect(jsonPath("$.type").value("https://api.example.com/problems/quota-exceeded")); - } -} diff --git a/src/test/java/org/example/videoclips/folder/FolderFfmpegClipperTest.java b/src/test/java/org/example/videoclips/folder/FolderFfmpegClipperTest.java index 3600f3d..e75233a 100644 --- a/src/test/java/org/example/videoclips/folder/FolderFfmpegClipperTest.java +++ b/src/test/java/org/example/videoclips/folder/FolderFfmpegClipperTest.java @@ -20,7 +20,7 @@ class FolderFfmpegClipperTest { Path tempDir; @Test - void buildsExactEightSecondMp4CommandAndCountsClips() throws Exception { + void buildsQualityPreservingMp4CommandAndCountsClips() throws Exception { AtomicReference> command = new AtomicReference<>(); FolderFfmpegClipper clipper = clipper(arguments -> { command.set(arguments); @@ -36,11 +36,28 @@ class FolderFfmpegClipperTest { assertEquals(tempDir.resolve("output/movie"), result.outputDirectory()); assertEquals(2, result.clipCount()); + assertCommandPair(command.get(), "-c", "copy"); + assertCommandPair(command.get(), "-segment_time", "8"); + assertTrue(command.get().getLast().endsWith("clip_%05d.mp4")); + } + + @Test + void buildsTranscodingCommandWhenPreserveInputQualityIsDisabled() throws Exception { + AtomicReference> command = new AtomicReference<>(); + VideoClippingProperties properties = properties(); + properties.getFolderScheduler().setPreserveInputQuality(false); + FolderFfmpegClipper clipper = new FolderFfmpegClipper(properties, arguments -> { + command.set(arguments); + Path outputDirectory = Path.of(arguments.getLast()).getParent(); + Files.writeString(outputDirectory.resolve("clip_00000.mp4"), "clip"); + return new FolderFfmpegClipper.ProcessResult(0, "ok"); + }); + + clipper.clip(tempDir.resolve("source/movie.mp4")); + assertCommandPair(command.get(), "-c:v", "libx264"); assertCommandPair(command.get(), "-c:a", "aac"); - assertCommandPair(command.get(), "-segment_time", "8"); assertCommandPair(command.get(), "-force_key_frames", "expr:gte(t,n_forced*8)"); - assertTrue(command.get().getLast().endsWith("clip_%05d.mp4")); } @Test diff --git a/src/test/java/org/example/videoclips/folder/FolderSchedulerLocalProfileTest.java b/src/test/java/org/example/videoclips/folder/FolderSchedulerLocalProfileTest.java index 43a4297..b6f8145 100644 --- a/src/test/java/org/example/videoclips/folder/FolderSchedulerLocalProfileTest.java +++ b/src/test/java/org/example/videoclips/folder/FolderSchedulerLocalProfileTest.java @@ -2,7 +2,7 @@ package org.example.videoclips.folder; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import java.util.Properties; @@ -12,8 +12,7 @@ class FolderSchedulerLocalProfileTest { @Test void enablesSchedulerWithRepositoryLocalEightSecondPaths() throws Exception { - Properties properties = PropertiesLoaderUtils.loadProperties( - new ClassPathResource("application-folder-scheduler-local.properties")); + Properties properties = yamlProperties("application-folder-scheduler-local.yml"); assertEquals("true", properties.getProperty("video-clipping.folder-scheduler.enabled")); assertEquals("./input/source", properties.getProperty("video-clipping.folder-scheduler.input-directory")); @@ -23,6 +22,16 @@ class FolderSchedulerLocalProfileTest { assertEquals("./input/rejected", properties.getProperty("video-clipping.folder-scheduler.rejected-directory")); assertEquals("8", properties.getProperty("video-clipping.folder-scheduler.segment-duration-seconds")); assertEquals("mp4", properties.getProperty("video-clipping.folder-scheduler.output-container")); - assertEquals("DEBUG", properties.getProperty("logging.level.org.example.videoclips.folder")); + assertEquals("INFO", properties.getProperty("logging.level.org.example.videoclips.folder")); + } + + private Properties yamlProperties(String classpathResource) { + YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean(); + factoryBean.setResources(new ClassPathResource(classpathResource)); + Properties properties = factoryBean.getObject(); + if (properties == null) { + throw new IllegalStateException("Unable to load " + classpathResource); + } + return properties; } } diff --git a/src/test/java/org/example/videoclips/folder/FolderVideoScanSchedulerTest.java b/src/test/java/org/example/videoclips/folder/FolderVideoScanSchedulerTest.java index fcfea4d..fef9d90 100644 --- a/src/test/java/org/example/videoclips/folder/FolderVideoScanSchedulerTest.java +++ b/src/test/java/org/example/videoclips/folder/FolderVideoScanSchedulerTest.java @@ -88,6 +88,20 @@ class FolderVideoScanSchedulerTest { assertTrue(((AtomicBoolean) scanningField.get(scheduler)).get()); } + @Test + void logsSchedulerStartupConfiguration() { + scheduler(tempDir).logStartup(); + } + + @Test + void logsTranscodingQualityModeWhenConfigured() throws Exception { + Files.writeString(tempDir.resolve("1.mp4"), "video"); + FolderVideoScanScheduler scheduler = scheduler(tempDir, acceptedValidator(), successfulClipper(), false); + + scheduler.logStartup(); + scheduler.scan(); + } + @Test void scanReleasesGuardAfterSelectingCandidate() throws Exception { Files.writeString(tempDir.resolve("1.mp4"), "video"); @@ -257,9 +271,19 @@ class FolderVideoScanSchedulerTest { Path inputDirectory, FolderVideoValidator validator, FolderFfmpegClipper clipper + ) { + return scheduler(inputDirectory, validator, clipper, true); + } + + private FolderVideoScanScheduler scheduler( + Path inputDirectory, + FolderVideoValidator validator, + FolderFfmpegClipper clipper, + boolean preserveInputQuality ) { VideoClippingProperties properties = new VideoClippingProperties(); properties.getFolderScheduler().setInputDirectory(inputDirectory.toString()); + properties.getFolderScheduler().setPreserveInputQuality(preserveInputQuality); Path workingDirectory = tempDir.resolve("working"); if (!Files.exists(workingDirectory)) { try { diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..b36a7fd --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1 @@ +video-clipping.folder-scheduler.enabled=false