diff --git a/README.md b/README.md index 66068e3..8cee995 100644 --- a/README.md +++ b/README.md @@ -152,3 +152,6 @@ Here is a complex example using multiple assets and operations: ] } ``` + +## What's up with the name? +I dunno, a cross between a javelin and an icicle? does that have any positive connotations? ok then... diff --git a/src/main/java/jvcl/model/JAsset.java b/src/main/java/jvcl/model/JAsset.java index d1c80a6..62fe528 100644 --- a/src/main/java/jvcl/model/JAsset.java +++ b/src/main/java/jvcl/model/JAsset.java @@ -20,6 +20,7 @@ import java.math.BigDecimal; import static org.cobbzilla.util.daemon.ZillaRuntime.*; import static org.cobbzilla.util.http.HttpSchemes.isHttpOrHttps; import static org.cobbzilla.util.io.FileUtil.abs; +import static org.cobbzilla.util.io.FileUtil.mkdirOrDie; import static org.cobbzilla.util.io.StreamUtil.loadResourceAsStream; import static org.cobbzilla.util.json.JsonUtil.json; import static org.cobbzilla.util.reflect.ReflectionUtil.copy; @@ -42,6 +43,10 @@ public class JAsset { @Getter @Setter private String dest; public boolean hasDest() { return !empty(dest); } public boolean destExists() { return new File(dest).exists(); } + public boolean destIsDirectory() { return new File(dest).isDirectory(); } + public File destDirectory() { + return mkdirOrDie(new File(dest.endsWith("/") ? dest.substring(0, dest.length()-1) : dest)); + } // if path was not a file, it got resolved to a file // the original value of 'path' is stored here diff --git a/src/main/java/jvcl/op/SplitOperation.java b/src/main/java/jvcl/op/SplitOperation.java index 28c1d17..e0a8ecf 100644 --- a/src/main/java/jvcl/op/SplitOperation.java +++ b/src/main/java/jvcl/op/SplitOperation.java @@ -19,8 +19,10 @@ import java.util.Map; import static jvcl.model.JAsset.json2asset; import static jvcl.service.Toolbox.getDuration; +import static org.cobbzilla.util.daemon.ZillaRuntime.die; import static org.cobbzilla.util.daemon.ZillaRuntime.empty; import static org.cobbzilla.util.io.FileUtil.abs; +import static org.cobbzilla.util.io.FileUtil.mkdirOrDie; import static org.cobbzilla.util.json.JsonUtil.json; import static org.cobbzilla.util.system.CommandShell.execScript; @@ -54,10 +56,27 @@ public class SplitOperation implements JOperator { i.compareTo(endTime) < 0; i = i.add(incr)) { - final File outfile = assetManager.assetPath(op, source, formatType, new Object[]{i, incr}); + final File outfile; + if (output.hasDest()) { + if (!output.destExists()) { + outfile = sliceFile(output, formatType, i, incr); + } else { + if (output.destIsDirectory()) { + outfile = sliceFile(output, formatType, i, incr); + } else { + die("dest exists and is not a directory: "+output.getDest()); + return; + } + } + } else { + outfile = assetManager.assetPath(op, source, formatType, new Object[]{i, incr}); + } + if (outfile.exists()) { log.info("operate: outfile exists, not re-creating: "+abs(outfile)); return; + } else { + mkdirOrDie(outfile.getParentFile()); } final JAsset slice = new JAsset(output); slice.setPath(abs(outfile)); @@ -75,6 +94,10 @@ public class SplitOperation implements JOperator { log.info("operate: completed"); } + private File sliceFile(JAsset output, JFileExtension formatType, BigDecimal i, BigDecimal incr) { + return new File(output.destDirectory(), output.getName() + "_" + i + "_" + i.add(incr) + formatType.ext()); + } + @NoArgsConstructor private static class SplitConfig { diff --git a/src/test/java/javicle/test/SplitTest.java b/src/test/java/javicle/test/BasicTest.java similarity index 85% rename from src/test/java/javicle/test/SplitTest.java rename to src/test/java/javicle/test/BasicTest.java index 0952173..6654e7d 100644 --- a/src/test/java/javicle/test/SplitTest.java +++ b/src/test/java/javicle/test/BasicTest.java @@ -11,9 +11,9 @@ import static org.cobbzilla.util.io.FileUtil.abs; import static org.cobbzilla.util.io.StreamUtil.loadResourceAsStream; import static org.cobbzilla.util.io.StreamUtil.stream2file; -public class SplitTest { +public class BasicTest { - @Test public void testSplit () throws Exception { + @Test public void testSplitAndConcat () throws Exception { @Cleanup("delete") final File specFile = stream2file(loadResourceAsStream("tests/test_split.json")); Jvcl.main(new String[]{JvclOptions.LONGOPT_SPEC, abs(specFile)}); } diff --git a/src/test/resources/outputs/..json b/src/test/resources/outputs/..json new file mode 100644 index 0000000..0c6474b --- /dev/null +++ b/src/test/resources/outputs/..json @@ -0,0 +1,98 @@ +{ +"media": { +"@ref": "/Users/jonathan/git/javicle/src/test/resources/outputs/vid1_splits_65_75.mp4", +"track": [ +{ +"@type": "General", +"VideoCount": "1", +"AudioCount": "1", +"FileExtension": "mp4", +"Format": "MPEG-4", +"Format_Profile": "Base Media", +"CodecID": "isom", +"CodecID_Compatible": "isom/iso2/avc1/mp41", +"FileSize": "4887637", +"Duration": "75.022", +"OverallBitRate_Mode": "VBR", +"OverallBitRate": "521195", +"FrameRate": "29.970", +"FrameCount": "2248", +"StreamSize": "82479", +"HeaderSize": "40", +"DataSize": "4805166", +"FooterSize": "82431", +"IsStreamable": "No", +"Title": "Moonwalk One, ca. 1970 - http://www.archive.org/details/gov.archives.arc.1257628", +"Movie": "Moonwalk One, ca. 1970 - http://www.archive.org/details/gov.archives.arc.1257628", +"File_Modified_Date": "UTC 2020-12-12 00:09:41", +"File_Modified_Date_Local": "2020-12-11 19:09:41", +"Encoded_Application": "Lavf58.45.100", +"Comment": "license:http://creativecommons.org/licenses/publicdomain/" +}, +{ +"@type": "Video", +"StreamOrder": "0", +"ID": "1", +"Format": "AVC", +"Format_Profile": "High", +"Format_Level": "1.3", +"Format_Settings_CABAC": "Yes", +"Format_Settings_RefFrames": "4", +"CodecID": "avc1", +"Duration": "75.009", +"BitRate": "386043", +"Width": "320", +"Height": "240", +"Sampled_Width": "320", +"Sampled_Height": "240", +"PixelAspectRatio": "1.000", +"DisplayAspectRatio": "1.333", +"Rotation": "0.000", +"FrameRate_Mode": "CFR", +"FrameRate_Mode_Original": "VFR", +"FrameRate": "29.970", +"FrameCount": "2248", +"ColorSpace": "YUV", +"ChromaSubsampling": "4:2:0", +"BitDepth": "8", +"ScanType": "Progressive", +"StreamSize": "3619556", +"Encoded_Library": "x264 - core 161 r3027 4121277", +"Encoded_Library_Name": "x264", +"Encoded_Library_Version": "core 161 r3027 4121277", +"Encoded_Library_Settings": "cabac=1 / ref=3 / deblock=1:0:0 / analyse=0x3:0x113 / me=hex / subme=7 / psy=1 / psy_rd=1.00:0.00 / mixed_ref=1 / me_range=16 / chroma_me=1 / trellis=1 / 8x8dct=1 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=7 / lookahead_threads=1 / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=1 / b_bias=0 / direct=1 / weightb=1 / open_gop=0 / weightp=2 / keyint=250 / keyint_min=25 / scenecut=40 / intra_refresh=0 / rc_lookahead=40 / rc=crf / mbtree=1 / crf=23.0 / qcomp=0.60 / qpmin=0 / qpmax=69 / qpstep=4 / ip_ratio=1.40 / aq=1:1.00", +"extra": { +"CodecConfigurationBox": "avcC" +} +}, +{ +"@type": "Audio", +"StreamOrder": "1", +"ID": "2", +"Format": "AAC", +"Format_Settings_SBR": "No (Explicit)", +"Format_AdditionalFeatures": "LC", +"CodecID": "mp4a-40-2", +"Duration": "75.022", +"Duration_LastFrame": "-0.008", +"BitRate_Mode": "VBR", +"BitRate": "126428", +"BitRate_Maximum": "128000", +"Channels": "2", +"ChannelPositions": "Front: L R", +"ChannelLayout": "L R", +"SamplesPerFrame": "1024", +"SamplingRate": "48000", +"SamplingCount": "3601056", +"FrameRate": "46.875", +"FrameCount": "3517", +"Compression_Mode": "Lossy", +"StreamSize": "1185602", +"StreamSize_Proportion": "0.24257", +"Default": "Yes", +"AlternateGroup": "1" +} +] +} +} + diff --git a/src/test/resources/outputs/vid1_splits_65_75.mp4 b/src/test/resources/outputs/vid1_splits_65_75.mp4 new file mode 100644 index 0000000..e371b59 Binary files /dev/null and b/src/test/resources/outputs/vid1_splits_65_75.mp4 differ diff --git a/src/test/resources/outputs/vid1_splits_75_85.mp4 b/src/test/resources/outputs/vid1_splits_75_85.mp4 new file mode 100644 index 0000000..e69de29 diff --git a/src/test/resources/tests/test_concat.json b/src/test/resources/tests/test_concat.json new file mode 100644 index 0000000..874587b --- /dev/null +++ b/src/test/resources/tests/test_concat.json @@ -0,0 +1,14 @@ +{ + "assets": [ + { "name": "vid1_splits" } + ], + "operations": [ + { + "operation": "concat", + "creates": "combined_vid", + "perform": { + "concat": ["vid1_splits[1..{{vid1_splits.length}}"] + } + } + ] +} diff --git a/src/test/resources/tests/test_split.json b/src/test/resources/tests/test_split.json index a63b503..71fbc8a 100644 --- a/src/test/resources/tests/test_split.json +++ b/src/test/resources/tests/test_split.json @@ -14,7 +14,10 @@ "operations": [ { "operation": "split", // name of the operation - "creates": "vid1_splits", // assets it creates, will be an array asset + "creates": { + "name": "vid1_splits", + "dest": "src/test/resources/outputs/" + }, "perform": { "split": "vid1", // split this source asset "interval": "10s", // split every ten seconds