forked from j62/ctbrec
1
0
Fork 0

Implement proper handling of the orignal and pp files

If a copy is created, the original file is not touched anymore.
Otherwise the original file is used and the post-processing process is not repeatable anymore, or at least the results might get unpredictable
This commit is contained in:
0xb00bface 2020-09-20 18:14:24 +02:00
parent 90192d9b8f
commit bf39d9a639
16 changed files with 263 additions and 90 deletions

View File

@ -0,0 +1,24 @@
package ctbrec.ui.settings;
import ctbrec.recorder.postprocessing.Move;
import ctbrec.recorder.postprocessing.PostProcessor;
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 MoverPaneFactory extends AbstractPostProcessingPaneFactory {
@Override
public Preferences doCreatePostProcessorPane(PostProcessor pp) {
SimpleStringProperty pathTemplate = new SimpleStringProperty(null, Move.PATH_TEMPLATE, pp.getConfig().getOrDefault(Move.PATH_TEMPLATE, Move.DEFAULT));
properties.add(pathTemplate);
return Preferences.of(new MapPreferencesStorage(),
Category.of(pp.getName(),
Setting.of("Directory", pathTemplate)
)
);
}
}

View File

@ -7,9 +7,10 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.recorder.postprocessing.Move;
import ctbrec.recorder.postprocessing.PostProcessor; import ctbrec.recorder.postprocessing.PostProcessor;
import ctbrec.recorder.postprocessing.Remuxer; import ctbrec.recorder.postprocessing.Remux;
import ctbrec.recorder.postprocessing.Renamer; import ctbrec.recorder.postprocessing.Rename;
import ctbrec.ui.controls.Dialogs; import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.settings.api.Preferences; import ctbrec.ui.settings.api.Preferences;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
@ -20,8 +21,9 @@ public class PostProcessingDialogFactory {
static Map<Class<?>, Class<?>> ppToDialogMap = new HashMap<>(); static Map<Class<?>, Class<?>> ppToDialogMap = new HashMap<>();
static { static {
ppToDialogMap.put(Remuxer.class, RemuxerPaneFactory.class); ppToDialogMap.put(Remux.class, RemuxerPaneFactory.class);
ppToDialogMap.put(Renamer.class, RenamerPaneFactory.class); ppToDialogMap.put(Rename.class, RenamerPaneFactory.class);
ppToDialogMap.put(Move.class, MoverPaneFactory.class);
} }
private PostProcessingDialogFactory() { private PostProcessingDialogFactory() {

View File

@ -7,9 +7,11 @@ import java.util.Optional;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.recorder.postprocessing.Copy; import ctbrec.recorder.postprocessing.Copy;
import ctbrec.recorder.postprocessing.DeleteOriginal;
import ctbrec.recorder.postprocessing.Move;
import ctbrec.recorder.postprocessing.PostProcessor; import ctbrec.recorder.postprocessing.PostProcessor;
import ctbrec.recorder.postprocessing.Remuxer; import ctbrec.recorder.postprocessing.Remux;
import ctbrec.recorder.postprocessing.Renamer; import ctbrec.recorder.postprocessing.Rename;
import ctbrec.ui.controls.Dialogs; import ctbrec.ui.controls.Dialogs;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
@ -28,7 +30,14 @@ public class PostProcessingStepPanel extends GridPane {
private Config config; private Config config;
private static final Class<?>[] POST_PROCESSOR_CLASSES = new Class<?>[] { Copy.class, Remuxer.class, Renamer.class };
private static final Class<?>[] POST_PROCESSOR_CLASSES = new Class<?>[] { // @formatter: off
Copy.class,
Remux.class,
Rename.class,
Move.class,
DeleteOriginal.class
}; // @formatter: on
ListView<PostProcessor> stepListView; ListView<PostProcessor> stepListView;
ObservableList<PostProcessor> stepList; ObservableList<PostProcessor> stepList;

View File

@ -1,7 +1,7 @@
package ctbrec.ui.settings; package ctbrec.ui.settings;
import ctbrec.recorder.postprocessing.PostProcessor; import ctbrec.recorder.postprocessing.PostProcessor;
import ctbrec.recorder.postprocessing.Remuxer; import ctbrec.recorder.postprocessing.Remux;
import ctbrec.ui.settings.api.Category; import ctbrec.ui.settings.api.Category;
import ctbrec.ui.settings.api.Preferences; import ctbrec.ui.settings.api.Preferences;
import ctbrec.ui.settings.api.Setting; import ctbrec.ui.settings.api.Setting;
@ -11,8 +11,8 @@ public class RemuxerPaneFactory extends AbstractPostProcessingPaneFactory {
@Override @Override
public Preferences doCreatePostProcessorPane(PostProcessor pp) { public Preferences doCreatePostProcessorPane(PostProcessor pp) {
SimpleStringProperty ffmpegParams = new SimpleStringProperty(null, Remuxer.FFMPEG_ARGS, pp.getConfig().getOrDefault(Remuxer.FFMPEG_ARGS, "-c:v copy -c:a copy -movflags faststart -y -f mp4")); SimpleStringProperty ffmpegParams = new SimpleStringProperty(null, Remux.FFMPEG_ARGS, pp.getConfig().getOrDefault(Remux.FFMPEG_ARGS, "-c:v copy -c:a copy -movflags faststart -y -f mp4"));
SimpleStringProperty fileExt = new SimpleStringProperty(null, Remuxer.FILE_EXT, pp.getConfig().getOrDefault(Remuxer.FILE_EXT, "mp4")); SimpleStringProperty fileExt = new SimpleStringProperty(null, Remux.FILE_EXT, pp.getConfig().getOrDefault(Remux.FILE_EXT, "mp4"));
properties.add(ffmpegParams); properties.add(ffmpegParams);
properties.add(fileExt); properties.add(fileExt);

View File

@ -1,7 +1,7 @@
package ctbrec.ui.settings; package ctbrec.ui.settings;
import ctbrec.recorder.postprocessing.PostProcessor; import ctbrec.recorder.postprocessing.PostProcessor;
import ctbrec.recorder.postprocessing.Renamer; import ctbrec.recorder.postprocessing.Rename;
import ctbrec.ui.settings.api.Category; import ctbrec.ui.settings.api.Category;
import ctbrec.ui.settings.api.Preferences; import ctbrec.ui.settings.api.Preferences;
import ctbrec.ui.settings.api.Setting; import ctbrec.ui.settings.api.Setting;
@ -11,7 +11,7 @@ public class RenamerPaneFactory extends AbstractPostProcessingPaneFactory {
@Override @Override
public Preferences doCreatePostProcessorPane(PostProcessor pp) { public Preferences doCreatePostProcessorPane(PostProcessor pp) {
SimpleStringProperty fileTemplate = new SimpleStringProperty(null, Renamer.FILE_NAME_TEMPLATE, pp.getConfig().getOrDefault(Renamer.FILE_NAME_TEMPLATE, Renamer.DEFAULT)); SimpleStringProperty fileTemplate = new SimpleStringProperty(null, Rename.FILE_NAME_TEMPLATE, pp.getConfig().getOrDefault(Rename.FILE_NAME_TEMPLATE, Rename.DEFAULT));
properties.add(fileTemplate); properties.add(fileTemplate);
return Preferences.of(new MapPreferencesStorage(), return Preferences.of(new MapPreferencesStorage(),

View File

@ -228,7 +228,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
@Override @Override
public String get() { public String get() {
String modelNotes = Config.getInstance().getSettings().modelNotes.getOrDefault(m.getUrl(), ""); String modelNotes = Config.getInstance().getModelNotes(m);
return modelNotes; return modelNotes;
} }
}; };

View File

@ -193,4 +193,8 @@ public class Config {
} }
return context; return context;
} }
public String getModelNotes(Model m) {
return Config.getInstance().getSettings().modelNotes.getOrDefault(m.getUrl(), "");
}
} }

View File

@ -0,0 +1,51 @@
package ctbrec.io;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
public class IoUtils {
private static final Logger LOG = LoggerFactory.getLogger(IoUtils.class);
private IoUtils() {}
public static void deleteEmptyParents(File parent) throws IOException {
File recDir = new File(Config.getInstance().getSettings().recordingsDir);
while (parent != null && parent.list() != null && parent.list().length == 0) {
if (parent.equals(recDir)) {
return;
}
LOG.debug("Deleting empty directory {}", parent.getAbsolutePath());
Files.delete(parent.toPath());
parent = parent.getParentFile();
}
}
public static void deleteDirectory(File directory) throws IOException {
if (!directory.exists()) {
return;
}
File[] files = directory.listFiles();
boolean deletedAllFiles = true;
for (File file : files) {
try {
LOG.trace("Deleting {}", file.getAbsolutePath());
Files.delete(file.toPath());
} catch (Exception e) {
deletedAllFiles = false;
LOG.debug("Couldn't delete {}", file, e);
}
}
if (!deletedAllFiles) {
throw new IOException("Couldn't delete all files in " + directory);
}
}
}

View File

@ -164,6 +164,7 @@ public class NextGenLocalRecorder implements Recorder {
recording.postprocess(); recording.postprocess();
List<PostProcessor> postProcessors = config.getSettings().postProcessors; List<PostProcessor> postProcessors = config.getSettings().postProcessors;
for (PostProcessor postProcessor : postProcessors) { for (PostProcessor postProcessor : postProcessors) {
LOG.debug("Running post-processor: {}", postProcessor.getName());
postProcessor.postprocess(recording); postProcessor.postprocess(recording);
} }
setRecordingStatus(recording, State.FINISHED); setRecordingStatus(recording, State.FINISHED);

View File

@ -1,6 +1,7 @@
package ctbrec.recorder; package ctbrec.recorder;
import static ctbrec.Recording.State.*; import static ctbrec.Recording.State.*;
import static ctbrec.io.IoUtils.*;
import static java.nio.charset.StandardCharsets.*; import static java.nio.charset.StandardCharsets.*;
import static java.nio.file.StandardOpenOption.*; import static java.nio.file.StandardOpenOption.*;
@ -142,8 +143,10 @@ public class RecordingManager {
File f = new File(associated); File f = new File(associated);
if (f.isFile()) { if (f.isFile()) {
Files.delete(f.toPath()); Files.delete(f.toPath());
deleteEmptyParents(f.getParentFile());
} else { } else {
deleteDirectory(f); deleteDirectory(f);
deleteEmptyParents(f);
} }
} }
@ -202,40 +205,6 @@ public class RecordingManager {
} }
} }
public static void deleteEmptyParents(File parent) throws IOException {
File recDir = new File(Config.getInstance().getSettings().recordingsDir);
while (parent != null && parent.list() != null && parent.list().length == 0) {
if (parent.equals(recDir)) {
return;
}
LOG.debug("Deleting empty directory {}", parent.getAbsolutePath());
Files.delete(parent.toPath());
parent = parent.getParentFile();
}
}
private void deleteDirectory(File directory) throws IOException {
if (!directory.exists()) {
return;
}
File[] files = directory.listFiles();
boolean deletedAllFiles = true;
for (File file : files) {
try {
LOG.trace("Deleting {}", file.getAbsolutePath());
Files.delete(file.toPath());
} catch (Exception e) {
deletedAllFiles = false;
LOG.debug("Couldn't delete {}", file, e);
}
}
if (!deletedAllFiles) {
throw new IOException("Couldn't delete all files in " + directory);
}
}
public void pin(Recording recording) throws IOException { public void pin(Recording recording) throws IOException {
recordingsLock.lock(); recordingsLock.lock();
try { try {

View File

@ -1,8 +1,7 @@
package ctbrec.recorder.postprocessing; package ctbrec.recorder.postprocessing;
import static java.util.Optional.*; import static java.util.Optional.*;
import java.io.File;
import java.io.IOException;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -10,18 +9,10 @@ import java.util.Locale;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.slf4j.Logger; import ctbrec.Config;
import org.slf4j.LoggerFactory;
import com.google.common.io.Files;
import ctbrec.Recording; import ctbrec.Recording;
public class Renamer extends AbstractPostProcessor { public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(Renamer.class);
public static final String FILE_NAME_TEMPLATE = "filename.template";
public static final String DEFAULT = "${modelSanitizedName}_${localDateTime}.${fileSuffix}";
@SuppressWarnings("unused") @SuppressWarnings("unused")
private String[] placeHolders = { private String[] placeHolders = {
@ -34,18 +25,13 @@ public class Renamer extends AbstractPostProcessor {
"${localDateTime}", "${localDateTime}",
"${epochSecond}", "${epochSecond}",
"${fileSuffix}", "${fileSuffix}",
"${modelNotes}" "${modelNotes}",
"${recordingsDir}"
}; };
@Override public String fillInPlaceHolders(String input, Recording rec) {
public String getName() { // @formatter:off
return "rename"; String output = input
}
@Override
public void postprocess(Recording rec) throws IOException {
String filenameTemplate = getConfig().getOrDefault(FILE_NAME_TEMPLATE, DEFAULT);
String filename = filenameTemplate
.replace("${modelName}", ofNullable(rec.getModel().getName()).orElse("modelName")) .replace("${modelName}", ofNullable(rec.getModel().getName()).orElse("modelName"))
.replace("${modelDisplayName}", ofNullable(rec.getModel().getDisplayName()).orElse("displayName")) .replace("${modelDisplayName}", ofNullable(rec.getModel().getDisplayName()).orElse("displayName"))
.replace("${modelSanitizedName}", ofNullable(rec.getModel().getSanitizedNamed()).orElse("sanitizedName")) .replace("${modelSanitizedName}", ofNullable(rec.getModel().getSanitizedNamed()).orElse("sanitizedName"))
@ -53,17 +39,15 @@ public class Renamer extends AbstractPostProcessor {
.replace("${siteSanitizedName}", getSanitizedSiteName(rec)) .replace("${siteSanitizedName}", getSanitizedSiteName(rec))
.replace("${fileSuffix}", getFileSuffix(rec)) .replace("${fileSuffix}", getFileSuffix(rec))
.replace("${epochSecond}", Long.toString(rec.getStartDate().getEpochSecond())) .replace("${epochSecond}", Long.toString(rec.getStartDate().getEpochSecond()))
.replace("${modelNotes}", Config.getInstance().getModelNotes(rec.getModel()))
.replace("${recordingsDir}", Config.getInstance().getSettings().recordingsDir)
; ;
filename = replaceUtcDateTime(rec, filename); output = replaceUtcDateTime(rec, output);
filename = replaceLocalDateTime(rec, filename); output = replaceLocalDateTime(rec, output);
File src = rec.getPostProcessedFile(); return output;
File target = new File(src.getParentFile(), filename); // @formatter:on
LOG.info("Renaming {} to {}", src.getName(), target.getName());
Files.move(rec.getPostProcessedFile(), target);
rec.setPostProcessedFile(target);
rec.getAssociatedFiles().add(target.getAbsolutePath());
} }
private String replaceUtcDateTime(Recording rec, String filename) { private String replaceUtcDateTime(Recording rec, String filename) {
@ -99,17 +83,8 @@ public class Renamer extends AbstractPostProcessor {
return filename.substring(filename.lastIndexOf('.') + 1); return filename.substring(filename.lastIndexOf('.') + 1);
} }
private CharSequence getSanitizedSiteName(Recording rec) { private CharSequence getSanitizedSiteName(Recording rec) {
return rec.getModel().getSite().getName().replace(' ', '_').replace('\\', '_').replace('/', '_'); return rec.getModel().getSite().getName().replace(' ', '_').replace('\\', '_').replace('/', '_');
} }
@Override
public String toString() {
String s = getName();
if (getConfig().containsKey(FILE_NAME_TEMPLATE)) {
s += " [" + getConfig().get(FILE_NAME_TEMPLATE) + ']';
}
return s;
}
} }

View File

@ -27,7 +27,7 @@ public class Copy extends AbstractPostProcessor {
LOG.info("Creating a copy {}", copy); LOG.info("Creating a copy {}", copy);
Files.copy(rec.getPostProcessedFile(), copy); Files.copy(rec.getPostProcessedFile(), copy);
rec.setPostProcessedFile(copy); rec.setPostProcessedFile(copy);
rec.getAssociatedFiles().add(copy.getAbsolutePath()); rec.getAssociatedFiles().add(copy.getCanonicalPath());
} }
private String getFilenameForCopy(File orig) { private String getFilenameForCopy(File orig) {

View File

@ -0,0 +1,29 @@
package ctbrec.recorder.postprocessing;
import static ctbrec.io.IoUtils.*;
import java.io.IOException;
import java.nio.file.Files;
import ctbrec.Recording;
public class DeleteOriginal extends AbstractPostProcessor {
@Override
public String getName() {
return "delete original";
}
@Override
public void postprocess(Recording rec) throws IOException, InterruptedException {
if (rec.getAbsoluteFile().isFile()) {
Files.deleteIfExists(rec.getAbsoluteFile().toPath());
deleteEmptyParents(rec.getAbsoluteFile().getParentFile());
} else {
deleteDirectory(rec.getAbsoluteFile());
deleteEmptyParents(rec.getAbsoluteFile());
}
rec.setAbsoluteFile(rec.getPostProcessedFile());
rec.getAssociatedFiles().remove(rec.getAbsoluteFile().getCanonicalPath());
}
}

View File

@ -0,0 +1,57 @@
package ctbrec.recorder.postprocessing;
import static ctbrec.io.IoUtils.*;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.Files;
import ctbrec.Recording;
public class Move extends AbstractPlaceholderAwarePostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(Rename.class);
public static final String PATH_TEMPLATE = "path.template";
public static final String DEFAULT = "${modelSanitizedName}" + File.separatorChar + "${localDateTime}";
@Override
public String getName() {
return "move";
}
@Override
public void postprocess(Recording rec) throws IOException {
String pathTemplate = getConfig().getOrDefault(PATH_TEMPLATE, DEFAULT);
String path = fillInPlaceHolders(pathTemplate, rec);
File src = rec.getPostProcessedFile();
File target = new File(path, src.getName());
LOG.info("Moving {} to {}", src.getName(), target.getParentFile().getCanonicalPath());
Files.createParentDirs(target);
Files.move(rec.getPostProcessedFile(), target);
rec.setPostProcessedFile(target);
if (Objects.equals(src, rec.getAbsoluteFile())) {
rec.setAbsoluteFile(target);
}
rec.getAssociatedFiles().remove(src.getCanonicalPath());
rec.getAssociatedFiles().add(target.getCanonicalPath());
if (src.isFile()) {
deleteEmptyParents(src.getParentFile());
} else {
deleteEmptyParents(src);
}
}
@Override
public String toString() {
String s = getName();
if (getConfig().containsKey(PATH_TEMPLATE)) {
s += " [" + getConfig().get(PATH_TEMPLATE) + ']';
}
return s;
}
}

View File

@ -14,9 +14,9 @@ import ctbrec.Recording;
import ctbrec.io.StreamRedirectThread; import ctbrec.io.StreamRedirectThread;
import ctbrec.recorder.download.ProcessExitedUncleanException; import ctbrec.recorder.download.ProcessExitedUncleanException;
public class Remuxer extends AbstractPostProcessor { public class Remux extends AbstractPostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(Remuxer.class); private static final Logger LOG = LoggerFactory.getLogger(Remux.class);
public static final String FFMPEG_ARGS = "ffmpeg.args"; public static final String FFMPEG_ARGS = "ffmpeg.args";
public static final String FILE_EXT = "file.ext"; public static final String FILE_EXT = "file.ext";
@ -35,6 +35,7 @@ public class Remuxer extends AbstractPostProcessor {
argsPlusFile[i++] = "-i"; argsPlusFile[i++] = "-i";
argsPlusFile[i++] = rec.getPostProcessedFile().getAbsolutePath(); argsPlusFile[i++] = rec.getPostProcessedFile().getAbsolutePath();
System.arraycopy(args, 0, argsPlusFile, i, args.length); System.arraycopy(args, 0, argsPlusFile, i, args.length);
File inputFile = rec.getPostProcessedFile();
File remuxedFile = new File(rec.getPostProcessedFile().getAbsolutePath() + '.' + fileExt); File remuxedFile = new File(rec.getPostProcessedFile().getAbsolutePath() + '.' + fileExt);
argsPlusFile[argsPlusFile.length - 1] = remuxedFile.getAbsolutePath(); argsPlusFile[argsPlusFile.length - 1] = remuxedFile.getAbsolutePath();
String[] cmdline = OS.getFFmpegCommand(argsPlusFile); String[] cmdline = OS.getFFmpegCommand(argsPlusFile);
@ -42,7 +43,10 @@ public class Remuxer extends AbstractPostProcessor {
Process ffmpeg = Runtime.getRuntime().exec(cmdline, new String[0], rec.getPostProcessedFile().getParentFile()); Process ffmpeg = Runtime.getRuntime().exec(cmdline, new String[0], rec.getPostProcessedFile().getParentFile());
setupLogging(ffmpeg, rec); setupLogging(ffmpeg, rec);
rec.setPostProcessedFile(remuxedFile); rec.setPostProcessedFile(remuxedFile);
rec.getAssociatedFiles().add(remuxedFile.getAbsolutePath()); rec.setAbsoluteFile(remuxedFile);
Files.deleteIfExists(inputFile.toPath());
rec.getAssociatedFiles().remove(inputFile.getCanonicalPath());
rec.getAssociatedFiles().add(remuxedFile.getCanonicalPath());
} }
private void setupLogging(Process ffmpeg, Recording rec) throws IOException, InterruptedException { private void setupLogging(Process ffmpeg, Recording rec) throws IOException, InterruptedException {

View File

@ -0,0 +1,48 @@
package ctbrec.recorder.postprocessing;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.Files;
import ctbrec.Recording;
public class Rename extends AbstractPlaceholderAwarePostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(Rename.class);
public static final String FILE_NAME_TEMPLATE = "filename.template";
public static final String DEFAULT = "${modelSanitizedName}_${localDateTime}.${fileSuffix}";
@Override
public String getName() {
return "rename";
}
@Override
public void postprocess(Recording rec) throws IOException {
String filenameTemplate = getConfig().getOrDefault(FILE_NAME_TEMPLATE, DEFAULT);
String filename = fillInPlaceHolders(filenameTemplate, rec);
File src = rec.getPostProcessedFile();
File target = new File(src.getParentFile(), filename);
LOG.info("Renaming {} to {}", src.getName(), target.getName());
Files.move(rec.getPostProcessedFile(), target);
rec.setPostProcessedFile(target);
if (Objects.equals(src, rec.getAbsoluteFile())) {
rec.setAbsoluteFile(target);
}
rec.getAssociatedFiles().remove(src.getCanonicalPath());
rec.getAssociatedFiles().add(target.getCanonicalPath());
}
@Override
public String toString() {
String s = getName();
if (getConfig().containsKey(FILE_NAME_TEMPLATE)) {
s += " [" + getConfig().get(FILE_NAME_TEMPLATE) + ']';
}
return s;
}
}