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

View File

@ -7,9 +7,11 @@ import java.util.Optional;
import ctbrec.Config;
import ctbrec.recorder.postprocessing.Copy;
import ctbrec.recorder.postprocessing.DeleteOriginal;
import ctbrec.recorder.postprocessing.Move;
import ctbrec.recorder.postprocessing.PostProcessor;
import ctbrec.recorder.postprocessing.Remuxer;
import ctbrec.recorder.postprocessing.Renamer;
import ctbrec.recorder.postprocessing.Remux;
import ctbrec.recorder.postprocessing.Rename;
import ctbrec.ui.controls.Dialogs;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
@ -28,7 +30,14 @@ public class PostProcessingStepPanel extends GridPane {
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;
ObservableList<PostProcessor> stepList;

View File

@ -1,7 +1,7 @@
package ctbrec.ui.settings;
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.Preferences;
import ctbrec.ui.settings.api.Setting;
@ -11,8 +11,8 @@ public class RemuxerPaneFactory extends AbstractPostProcessingPaneFactory {
@Override
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 fileExt = new SimpleStringProperty(null, Remuxer.FILE_EXT, pp.getConfig().getOrDefault(Remuxer.FILE_EXT, "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, Remux.FILE_EXT, pp.getConfig().getOrDefault(Remux.FILE_EXT, "mp4"));
properties.add(ffmpegParams);
properties.add(fileExt);

View File

@ -1,7 +1,7 @@
package ctbrec.ui.settings;
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.Preferences;
import ctbrec.ui.settings.api.Setting;
@ -11,7 +11,7 @@ public class RenamerPaneFactory extends AbstractPostProcessingPaneFactory {
@Override
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);
return Preferences.of(new MapPreferencesStorage(),

View File

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

View File

@ -193,4 +193,8 @@ public class Config {
}
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();
List<PostProcessor> postProcessors = config.getSettings().postProcessors;
for (PostProcessor postProcessor : postProcessors) {
LOG.debug("Running post-processor: {}", postProcessor.getName());
postProcessor.postprocess(recording);
}
setRecordingStatus(recording, State.FINISHED);

View File

@ -1,6 +1,7 @@
package ctbrec.recorder;
import static ctbrec.Recording.State.*;
import static ctbrec.io.IoUtils.*;
import static java.nio.charset.StandardCharsets.*;
import static java.nio.file.StandardOpenOption.*;
@ -142,8 +143,10 @@ public class RecordingManager {
File f = new File(associated);
if (f.isFile()) {
Files.delete(f.toPath());
deleteEmptyParents(f.getParentFile());
} else {
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 {
recordingsLock.lock();
try {

View File

@ -1,8 +1,7 @@
package ctbrec.recorder.postprocessing;
import static java.util.Optional.*;
import java.io.File;
import java.io.IOException;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
@ -10,18 +9,10 @@ import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.Files;
import ctbrec.Config;
import ctbrec.Recording;
public class Renamer 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}";
public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPostProcessor {
@SuppressWarnings("unused")
private String[] placeHolders = {
@ -34,18 +25,13 @@ public class Renamer extends AbstractPostProcessor {
"${localDateTime}",
"${epochSecond}",
"${fileSuffix}",
"${modelNotes}"
"${modelNotes}",
"${recordingsDir}"
};
@Override
public String getName() {
return "rename";
}
@Override
public void postprocess(Recording rec) throws IOException {
String filenameTemplate = getConfig().getOrDefault(FILE_NAME_TEMPLATE, DEFAULT);
String filename = filenameTemplate
public String fillInPlaceHolders(String input, Recording rec) {
// @formatter:off
String output = input
.replace("${modelName}", ofNullable(rec.getModel().getName()).orElse("modelName"))
.replace("${modelDisplayName}", ofNullable(rec.getModel().getDisplayName()).orElse("displayName"))
.replace("${modelSanitizedName}", ofNullable(rec.getModel().getSanitizedNamed()).orElse("sanitizedName"))
@ -53,17 +39,15 @@ public class Renamer extends AbstractPostProcessor {
.replace("${siteSanitizedName}", getSanitizedSiteName(rec))
.replace("${fileSuffix}", getFileSuffix(rec))
.replace("${epochSecond}", Long.toString(rec.getStartDate().getEpochSecond()))
.replace("${modelNotes}", Config.getInstance().getModelNotes(rec.getModel()))
.replace("${recordingsDir}", Config.getInstance().getSettings().recordingsDir)
;
filename = replaceUtcDateTime(rec, filename);
filename = replaceLocalDateTime(rec, filename);
output = replaceUtcDateTime(rec, output);
output = replaceLocalDateTime(rec, output);
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);
rec.getAssociatedFiles().add(target.getAbsolutePath());
return output;
// @formatter:on
}
private String replaceUtcDateTime(Recording rec, String filename) {
@ -99,17 +83,8 @@ public class Renamer extends AbstractPostProcessor {
return filename.substring(filename.lastIndexOf('.') + 1);
}
private CharSequence getSanitizedSiteName(Recording rec) {
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);
Files.copy(rec.getPostProcessedFile(), copy);
rec.setPostProcessedFile(copy);
rec.getAssociatedFiles().add(copy.getAbsolutePath());
rec.getAssociatedFiles().add(copy.getCanonicalPath());
}
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.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 FILE_EXT = "file.ext";
@ -35,6 +35,7 @@ public class Remuxer extends AbstractPostProcessor {
argsPlusFile[i++] = "-i";
argsPlusFile[i++] = rec.getPostProcessedFile().getAbsolutePath();
System.arraycopy(args, 0, argsPlusFile, i, args.length);
File inputFile = rec.getPostProcessedFile();
File remuxedFile = new File(rec.getPostProcessedFile().getAbsolutePath() + '.' + fileExt);
argsPlusFile[argsPlusFile.length - 1] = remuxedFile.getAbsolutePath();
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());
setupLogging(ffmpeg, rec);
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 {

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;
}
}