diff --git a/client/src/main/java/ctbrec/ui/settings/PostProcessingDialogFactory.java b/client/src/main/java/ctbrec/ui/settings/PostProcessingDialogFactory.java index eccad370..d4b45a64 100644 --- a/client/src/main/java/ctbrec/ui/settings/PostProcessingDialogFactory.java +++ b/client/src/main/java/ctbrec/ui/settings/PostProcessingDialogFactory.java @@ -11,6 +11,7 @@ import ctbrec.recorder.postprocessing.Move; import ctbrec.recorder.postprocessing.PostProcessor; import ctbrec.recorder.postprocessing.Remux; import ctbrec.recorder.postprocessing.Rename; +import ctbrec.recorder.postprocessing.Script; import ctbrec.ui.controls.Dialogs; import ctbrec.ui.settings.api.Preferences; import javafx.collections.ObservableList; @@ -22,6 +23,7 @@ public class PostProcessingDialogFactory { static Map, Class> ppToDialogMap = new HashMap<>(); static { ppToDialogMap.put(Remux.class, RemuxerPaneFactory.class); + ppToDialogMap.put(Script.class, ScriptPaneFactory.class); ppToDialogMap.put(Rename.class, RenamerPaneFactory.class); ppToDialogMap.put(Move.class, MoverPaneFactory.class); } diff --git a/client/src/main/java/ctbrec/ui/settings/PostProcessingStepPanel.java b/client/src/main/java/ctbrec/ui/settings/PostProcessingStepPanel.java index 0e2c13e6..78cb4d12 100644 --- a/client/src/main/java/ctbrec/ui/settings/PostProcessingStepPanel.java +++ b/client/src/main/java/ctbrec/ui/settings/PostProcessingStepPanel.java @@ -13,6 +13,7 @@ import ctbrec.recorder.postprocessing.PostProcessor; import ctbrec.recorder.postprocessing.RemoveKeepFile; import ctbrec.recorder.postprocessing.Remux; import ctbrec.recorder.postprocessing.Rename; +import ctbrec.recorder.postprocessing.Script; import ctbrec.ui.controls.Dialogs; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; @@ -34,9 +35,10 @@ public class PostProcessingStepPanel extends GridPane { private static final Class[] POST_PROCESSOR_CLASSES = new Class[] { // @formatter: off Copy.class, - Remux.class, Rename.class, Move.class, + Remux.class, + Script.class, DeleteOriginal.class, RemoveKeepFile.class }; // @formatter: on diff --git a/client/src/main/java/ctbrec/ui/settings/ScriptPaneFactory.java b/client/src/main/java/ctbrec/ui/settings/ScriptPaneFactory.java new file mode 100644 index 00000000..1c56cc5b --- /dev/null +++ b/client/src/main/java/ctbrec/ui/settings/ScriptPaneFactory.java @@ -0,0 +1,27 @@ +package ctbrec.ui.settings; + +import ctbrec.recorder.postprocessing.PostProcessor; +import ctbrec.recorder.postprocessing.Script; +import ctbrec.ui.settings.api.Category; +import ctbrec.ui.settings.api.Preferences; +import ctbrec.ui.settings.api.Setting; +import javafx.beans.property.SimpleStringProperty; + +public class ScriptPaneFactory extends AbstractPostProcessingPaneFactory { + + @Override + public Preferences doCreatePostProcessorPane(PostProcessor pp) { + SimpleStringProperty script = new SimpleStringProperty(null, Script.SCRIPT_EXECUTABLE, pp.getConfig().getOrDefault(Script.SCRIPT_EXECUTABLE, "c:\\users\\johndoe\\somescript")); + SimpleStringProperty params = new SimpleStringProperty(null, Script.SCRIPT_PARAMS, pp.getConfig().getOrDefault(Script.SCRIPT_PARAMS, "${absolutePath}")); + properties.add(script); + properties.add(params); + + return Preferences.of(new MapPreferencesStorage(), + Category.of(pp.getName(), + Setting.of("Script", script), + Setting.of("Parameters", params) + ) + ); + } + +} diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java b/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java index 8831b687..5007d97f 100644 --- a/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java +++ b/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java @@ -25,7 +25,9 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost "${epochSecond}", "${fileSuffix}", "${modelNotes}", - "${recordingsDir}" + "${recordingsDir}", + "${absolutePath}", + "${absoluteParentPath}" }; public String fillInPlaceHolders(String input, Recording rec, Config config) { @@ -40,6 +42,8 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost .replace("${epochSecond}", Long.toString(rec.getStartDate().getEpochSecond())) .replace("${modelNotes}", config.getModelNotes(rec.getModel())) .replace("${recordingsDir}", config.getSettings().recordingsDir) + .replace("${absolutePath}", rec.getPostProcessedFile().getAbsolutePath()) + .replace("${absoluteParentPath}", rec.getPostProcessedFile().getParentFile().getAbsolutePath()) ; output = replaceUtcDateTime(rec, output); diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Script.java b/common/src/main/java/ctbrec/recorder/postprocessing/Script.java index 0b278082..a07b9bfc 100644 --- a/common/src/main/java/ctbrec/recorder/postprocessing/Script.java +++ b/common/src/main/java/ctbrec/recorder/postprocessing/Script.java @@ -1,14 +1,26 @@ package ctbrec.recorder.postprocessing; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ctbrec.Config; -import ctbrec.NotImplementedExcetion; +import ctbrec.OS; import ctbrec.Recording; +import ctbrec.io.StreamRedirectThread; import ctbrec.recorder.RecordingManager; +import ctbrec.recorder.download.ProcessExitedUncleanException; public class Script extends AbstractPlaceholderAwarePostProcessor { + private static final Logger LOG = LoggerFactory.getLogger(Script.class); + public static final String SCRIPT_EXECUTABLE = "script.executable"; + public static final String SCRIPT_PARAMS = "script.params"; + @Override public String getName() { return "execute script"; @@ -16,7 +28,46 @@ public class Script extends AbstractPlaceholderAwarePostProcessor { @Override public void postprocess(Recording rec, RecordingManager recordingManager, Config config) throws IOException, InterruptedException { - // TODO make it possible to choose, which placeholders to pass to the script - throw new NotImplementedExcetion(); + List cmdline = buildCommandLine(rec, config); + Runtime rt = Runtime.getRuntime(); + String[] args = cmdline.toArray(new String[0]); + if (LOG.isDebugEnabled()) { + LOG.debug("Running {}", Arrays.toString(args)); + } + Process process = rt.exec(args, OS.getEnvironment()); + startLogging(process); + int exitCode = process.waitFor(); + LOG.debug("Process finished with exit code {}", exitCode); + if (exitCode != 0) { + throw new ProcessExitedUncleanException("Script finished with exit code " + exitCode); + } + } + + private List buildCommandLine(Recording rec, Config config) throws IOException { + String script = getConfig().getOrDefault(SCRIPT_EXECUTABLE, "somescript"); + String params = getConfig().getOrDefault(SCRIPT_PARAMS, "${absolutePath}"); + List cmdline = new ArrayList<>(); + cmdline.add(script); + String replacedParams = fillInPlaceHolders(params, rec, config); + Arrays.stream(replacedParams.split(" ")).forEach(cmdline::add); + return cmdline; + } + + private void startLogging(Process process) { + // TODO maybe write these to a separate log file, e.g. recname.ts.script.log + Thread std = new Thread(new StreamRedirectThread(process.getInputStream(), System.out)); + std.setName("Process stdout pipe"); + std.setDaemon(true); + std.start(); + Thread err = new Thread(new StreamRedirectThread(process.getErrorStream(), System.err)); + err.setName("Process stderr pipe"); + err.setDaemon(true); + err.start(); + } + + @Override + public String toString() { + return (getName() + " " + getConfig().getOrDefault(Script.SCRIPT_EXECUTABLE, "")).trim(); } } +