diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5fc1f8af..00a88bcb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+3.10.0
+========================
+* New post-processing
+* Fix: MV Live models with spaces in the name not indicated as recording
+
3.9.0
========================
* Added support for Manyvids Live.
diff --git a/client/pom.xml b/client/pom.xml
index 2bfba9df..5f7f04d4 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.9.0
+ 3.10.0
../master
diff --git a/client/src/main/java/ctbrec/ui/JavaFxRecording.java b/client/src/main/java/ctbrec/ui/JavaFxRecording.java
index 0009d9d2..f74f666c 100644
--- a/client/src/main/java/ctbrec/ui/JavaFxRecording.java
+++ b/client/src/main/java/ctbrec/ui/JavaFxRecording.java
@@ -1,5 +1,6 @@
package ctbrec.ui;
+import java.io.File;
import java.time.Instant;
import ctbrec.Config;
@@ -156,11 +157,7 @@ public class JavaFxRecording extends Recording {
setStatus(updated.getStatus());
setProgress(updated.getProgress());
setSizeInByte(updated.getSizeInByte());
- }
-
- @Override
- public String getPath() {
- return delegate.getPath();
+ setSingleFile(updated.isSingleFile());
}
@Override
@@ -192,6 +189,11 @@ public class JavaFxRecording extends Recording {
return delegate.isSingleFile();
}
+ @Override
+ public void setSingleFile(boolean singleFile) {
+ delegate.setSingleFile(singleFile);
+ }
+
@Override
public boolean isPinned() {
return delegate.isPinned();
@@ -223,4 +225,26 @@ public class JavaFxRecording extends Recording {
public StringProperty getNoteProperty() {
return notesProperty;
}
+
+ @Override
+ public File getAbsoluteFile() {
+ return delegate.getAbsoluteFile();
+ }
+
+ @Override
+ public void setAbsoluteFile(File absoluteFile) {
+ delegate.setAbsoluteFile(absoluteFile);
+ }
+
+ @Override
+ public String getId() {
+ return delegate.getId();
+ }
+
+ @Override
+ public void setId(String id) {
+ delegate.setId(id);
+ }
+
+
}
diff --git a/client/src/main/java/ctbrec/ui/Player.java b/client/src/main/java/ctbrec/ui/Player.java
index 96df79dd..af88b945 100644
--- a/client/src/main/java/ctbrec/ui/Player.java
+++ b/client/src/main/java/ctbrec/ui/Player.java
@@ -152,7 +152,7 @@ public class Player {
Config cfg = Config.getInstance();
try {
if (cfg.getSettings().localRecording && rec != null) {
- File file = new File(cfg.getSettings().recordingsDir, rec.getPath());
+ File file = rec.getAbsoluteFile();
String[] cmdline = createCmdline(file.getAbsolutePath());
playerProcess = rt.exec(cmdline, OS.getEnvironment(), file.getParentFile());
} else {
@@ -206,7 +206,7 @@ public class Player {
private String getRemoteRecordingUrl(Recording rec, Config cfg)
throws MalformedURLException, InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
String hlsBase = Config.getInstance().getServerUrl() + "/hls";
- String recUrl = hlsBase + rec.getPath() + (rec.isSingleFile() ? "" : "/playlist.m3u8");
+ String recUrl = hlsBase + '/' + rec.getId() + (rec.isSingleFile() ? "" : "/playlist.m3u8");
if (cfg.getSettings().requireAuthentication) {
URL u = new URL(recUrl);
String path = u.getPath();
diff --git a/client/src/main/java/ctbrec/ui/settings/AbstractPostProcessingPaneFactory.java b/client/src/main/java/ctbrec/ui/settings/AbstractPostProcessingPaneFactory.java
new file mode 100644
index 00000000..76c2fdc6
--- /dev/null
+++ b/client/src/main/java/ctbrec/ui/settings/AbstractPostProcessingPaneFactory.java
@@ -0,0 +1,194 @@
+package ctbrec.ui.settings;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ctbrec.recorder.postprocessing.PostProcessor;
+import ctbrec.ui.controls.DirectorySelectionBox;
+import ctbrec.ui.controls.ProgramSelectionBox;
+import ctbrec.ui.settings.api.ExclusiveSelectionProperty;
+import ctbrec.ui.settings.api.Preferences;
+import ctbrec.ui.settings.api.PreferencesStorage;
+import ctbrec.ui.settings.api.Setting;
+import ctbrec.ui.settings.api.SimpleDirectoryProperty;
+import ctbrec.ui.settings.api.SimpleFileProperty;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ListProperty;
+import javafx.beans.property.LongProperty;
+import javafx.beans.property.Property;
+import javafx.beans.property.StringProperty;
+import javafx.geometry.Insets;
+import javafx.scene.Node;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.Label;
+import javafx.scene.control.RadioButton;
+import javafx.scene.control.TextField;
+import javafx.scene.control.ToggleGroup;
+import javafx.scene.layout.HBox;
+import javafx.util.converter.NumberStringConverter;
+
+public abstract class AbstractPostProcessingPaneFactory {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractPostProcessingPaneFactory.class);
+ private PostProcessor pp;
+ Set> properties = new HashSet<>();
+
+ public abstract Preferences doCreatePostProcessorPane(PostProcessor pp);
+
+ public Preferences createPostProcessorPane(PostProcessor pp) {
+ this.pp = pp;
+ return doCreatePostProcessorPane(pp);
+ }
+
+ class MapPreferencesStorage implements PreferencesStorage {
+
+ @Override
+ public void save(Preferences preferences) throws IOException {
+ for (Property> property : properties) {
+ String key = property.getName();
+ Object value = preferences.getSetting(key).get().getProperty().getValue();
+ LOG.debug("{}={}", key, value.toString());
+ pp.getConfig().put(key, value.toString());
+ }
+ }
+
+ @Override
+ public void load(Preferences preferences) {
+ // no op
+ }
+
+ @Override
+ public Node createGui(Setting setting) throws Exception {
+ Property> prop = setting.getProperty();
+ if (prop instanceof ExclusiveSelectionProperty) {
+ return createRadioGroup(setting);
+ } else if (prop instanceof SimpleDirectoryProperty) {
+ return createDirectorySelector(setting);
+ } else if (prop instanceof SimpleFileProperty) {
+ return createFileSelector(setting);
+ } else if (prop instanceof IntegerProperty) {
+ return createIntegerProperty(setting);
+ } else if (prop instanceof LongProperty) {
+ return createLongProperty(setting);
+ } else if (prop instanceof BooleanProperty) {
+ return createBooleanProperty(setting);
+ } else if (prop instanceof ListProperty) {
+ return createComboBox(setting);
+ } else if (prop instanceof StringProperty) {
+ return createStringProperty(setting);
+ } else {
+ return new Label("Unsupported Type for key " + setting.getKey() + ": " + setting.getProperty());
+ }
+ }
+ }
+
+ private Node createRadioGroup(Setting setting) {
+ ExclusiveSelectionProperty prop = (ExclusiveSelectionProperty) setting.getProperty();
+ ToggleGroup toggleGroup = new ToggleGroup();
+ RadioButton optionA = new RadioButton(prop.getOptionA());
+ optionA.setSelected(prop.getValue());
+ optionA.setToggleGroup(toggleGroup);
+ RadioButton optionB = new RadioButton(prop.getOptionB());
+ optionB.setSelected(!optionA.isSelected());
+ optionB.setToggleGroup(toggleGroup);
+ optionA.selectedProperty().bindBidirectional(prop);
+ HBox row = new HBox();
+ row.getChildren().addAll(optionA, optionB);
+ HBox.setMargin(optionA, new Insets(5));
+ HBox.setMargin(optionB, new Insets(5));
+ return row;
+ }
+
+ private Node createFileSelector(Setting setting) {
+ ProgramSelectionBox programSelector = new ProgramSelectionBox("");
+ // programSelector.fileProperty().addListener((obs, o, n) -> saveValue(() -> {
+ // String path = n;
+ // Field field = Settings.class.getField(setting.getKey());
+ // String oldValue = (String) field.get(settings);
+ // if (!Objects.equals(path, oldValue)) {
+ // field.set(settings, path);
+ // config.save();
+ // }
+ // }));
+ StringProperty property = (StringProperty) setting.getProperty();
+ programSelector.fileProperty().bindBidirectional(property);
+ return programSelector;
+ }
+
+ private Node createDirectorySelector(Setting setting) {
+ DirectorySelectionBox directorySelector = new DirectorySelectionBox("");
+ directorySelector.prefWidth(400);
+ // directorySelector.fileProperty().addListener((obs, o, n) -> saveValue(() -> {
+ // String path = n;
+ // Field field = Settings.class.getField(setting.getKey());
+ // String oldValue = (String) field.get(settings);
+ // if (!Objects.equals(path, oldValue)) {
+ // field.set(settings, path);
+ // config.save();
+ // }
+ // }));
+ StringProperty property = (StringProperty) setting.getProperty();
+ directorySelector.fileProperty().bindBidirectional(property);
+ return directorySelector;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Node createStringProperty(Setting setting) {
+ TextField ctrl = new TextField();
+ ctrl.textProperty().bindBidirectional(setting.getProperty());
+ return ctrl;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Node createIntegerProperty(Setting setting) {
+ TextField ctrl = new TextField();
+ Property prop = setting.getProperty();
+ ctrl.textProperty().bindBidirectional(prop, new NumberStringConverter());
+ return ctrl;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Node createLongProperty(Setting setting) {
+ TextField ctrl = new TextField();
+ Property prop = setting.getProperty();
+ ctrl.textProperty().bindBidirectional(prop, new NumberStringConverter());
+ return ctrl;
+ }
+
+ private Node createBooleanProperty(Setting setting) {
+ CheckBox ctrl = new CheckBox();
+ BooleanProperty prop = (BooleanProperty) setting.getProperty();
+ ctrl.selectedProperty().bindBidirectional(prop);
+ return ctrl;
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private Node createComboBox(Setting setting) throws NoSuchFieldException, IllegalAccessException {
+ ListProperty> listProp = (ListProperty>) setting.getProperty();
+ ComboBox