diff --git a/client/src/main/java/ctbrec/ui/JavaFxRecording.java b/client/src/main/java/ctbrec/ui/JavaFxRecording.java index e5ecf139..f74f666c 100644 --- a/client/src/main/java/ctbrec/ui/JavaFxRecording.java +++ b/client/src/main/java/ctbrec/ui/JavaFxRecording.java @@ -157,6 +157,7 @@ public class JavaFxRecording extends Recording { setStatus(updated.getStatus()); setProgress(updated.getProgress()); setSizeInByte(updated.getSizeInByte()); + setSingleFile(updated.isSingleFile()); } @Override @@ -188,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(); diff --git a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java index c48b41ad..1583606e 100644 --- a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java +++ b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java @@ -559,7 +559,6 @@ public class RecordingsTab extends Tab implements TabSelectionListener { } private void onOpenDirectory(JavaFxRecording first) { - String recordingsDir = Config.getInstance().getSettings().recordingsDir; File tsFile = first.getAbsoluteFile(); new Thread(() -> DesktopIntegration.open(tsFile.getParent())).start(); } diff --git a/common/pom.xml b/common/pom.xml index eaf358d7..541444dc 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -50,6 +50,10 @@ com.google.guava guava + + commons-io + commons-io + org.openjfx javafx-controls diff --git a/common/src/main/java/ctbrec/NotImplementedExcetion.java b/common/src/main/java/ctbrec/NotImplementedExcetion.java index 4fb3d3c9..9ecfda88 100644 --- a/common/src/main/java/ctbrec/NotImplementedExcetion.java +++ b/common/src/main/java/ctbrec/NotImplementedExcetion.java @@ -3,7 +3,7 @@ package ctbrec; public class NotImplementedExcetion extends RuntimeException { public NotImplementedExcetion() { - super(); + super("Not implemented"); } public NotImplementedExcetion(String mesg) { diff --git a/common/src/main/java/ctbrec/io/IoUtils.java b/common/src/main/java/ctbrec/io/IoUtils.java index f3be9bbb..644e540b 100644 --- a/common/src/main/java/ctbrec/io/IoUtils.java +++ b/common/src/main/java/ctbrec/io/IoUtils.java @@ -17,12 +17,14 @@ public class 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) { + while (parent != null && (parent.list() != null && parent.list().length == 0 || !parent.exists()) ) { if (parent.equals(recDir)) { return; } - LOG.debug("Deleting empty directory {}", parent.getAbsolutePath()); - Files.delete(parent.toPath()); + if(parent.exists()) { + LOG.debug("Deleting empty directory {}", parent.getAbsolutePath()); + Files.delete(parent.toPath()); + } parent = parent.getParentFile(); } } diff --git a/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java b/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java index 3afe4366..83a8e484 100644 --- a/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java +++ b/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java @@ -165,7 +165,7 @@ public class NextGenLocalRecorder implements Recorder { List postProcessors = config.getSettings().postProcessors; for (PostProcessor postProcessor : postProcessors) { LOG.debug("Running post-processor: {}", postProcessor.getName()); - postProcessor.postprocess(recording); + postProcessor.postprocess(recording, config); } setRecordingStatus(recording, State.FINISHED); recordingManager.saveRecording(recording); @@ -652,6 +652,7 @@ public class NextGenLocalRecorder implements Recorder { Download download = other.getModel().createDownload(); download.init(Config.getInstance(), other.getModel(), other.getStartDate()); other.setDownload(download); + other.setPostProcessedFile(null); submitPostProcessingJob(other); return; } diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java b/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java index d89d9bf4..b790a724 100644 --- a/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java +++ b/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java @@ -29,7 +29,7 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost "${recordingsDir}" }; - public String fillInPlaceHolders(String input, Recording rec) { + public String fillInPlaceHolders(String input, Recording rec, Config config) { // @formatter:off String output = input .replace("${modelName}", ofNullable(rec.getModel().getName()).orElse("modelName")) @@ -39,8 +39,8 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost .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) + .replace("${modelNotes}", config.getModelNotes(rec.getModel())) + .replace("${recordingsDir}", config.getSettings().recordingsDir) ; output = replaceUtcDateTime(rec, output); diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Copy.java b/common/src/main/java/ctbrec/recorder/postprocessing/Copy.java index d77eefcf..4dbf0224 100644 --- a/common/src/main/java/ctbrec/recorder/postprocessing/Copy.java +++ b/common/src/main/java/ctbrec/recorder/postprocessing/Copy.java @@ -2,12 +2,13 @@ package ctbrec.recorder.postprocessing; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.io.Files; - +import ctbrec.Config; import ctbrec.Recording; public class Copy extends AbstractPostProcessor { @@ -20,20 +21,28 @@ public class Copy extends AbstractPostProcessor { } @Override - public void postprocess(Recording rec) throws IOException, InterruptedException { + public void postprocess(Recording rec, Config config) throws IOException, InterruptedException { File orig = rec.getPostProcessedFile(); String copyFilename = getFilenameForCopy(orig); File copy = new File(orig.getParentFile(), copyFilename); LOG.info("Creating a copy {}", copy); - Files.copy(rec.getPostProcessedFile(), copy); + if (orig.isFile()) { + Files.copy(rec.getPostProcessedFile().toPath(), copy.toPath()); + } else { + FileUtils.copyDirectory(orig, copy, true); + } rec.setPostProcessedFile(copy); rec.getAssociatedFiles().add(copy.getCanonicalPath()); } private String getFilenameForCopy(File orig) { String filename = orig.getName(); - String name = filename.substring(0, filename.lastIndexOf('.')); - String ext = filename.substring(filename.lastIndexOf('.') + 1); - return name + "_copy." + ext; + if (orig.isFile()) { + String name = filename.substring(0, filename.lastIndexOf('.')); + String ext = filename.substring(filename.lastIndexOf('.') + 1); + return name + "_copy." + ext; + } else { + return filename + "_copy"; + } } } diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/CreateContactSheet.java b/common/src/main/java/ctbrec/recorder/postprocessing/CreateContactSheet.java new file mode 100644 index 00000000..2f090ad6 --- /dev/null +++ b/common/src/main/java/ctbrec/recorder/postprocessing/CreateContactSheet.java @@ -0,0 +1,20 @@ +package ctbrec.recorder.postprocessing; + +import java.io.IOException; + +import ctbrec.Config; +import ctbrec.NotImplementedExcetion; +import ctbrec.Recording; + +public class CreateContactSheet extends AbstractPlaceholderAwarePostProcessor { + + @Override + public String getName() { + return "create contact sheet"; + } + + @Override + public void postprocess(Recording rec, Config config) throws IOException, InterruptedException { + throw new NotImplementedExcetion(); + } +} diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/CreateTimelineThumbs.java b/common/src/main/java/ctbrec/recorder/postprocessing/CreateTimelineThumbs.java new file mode 100644 index 00000000..c17bcec1 --- /dev/null +++ b/common/src/main/java/ctbrec/recorder/postprocessing/CreateTimelineThumbs.java @@ -0,0 +1,20 @@ +package ctbrec.recorder.postprocessing; + +import java.io.IOException; + +import ctbrec.Config; +import ctbrec.NotImplementedExcetion; +import ctbrec.Recording; + +public class CreateTimelineThumbs extends AbstractPlaceholderAwarePostProcessor { + + @Override + public String getName() { + return "create timeline thumbnails"; + } + + @Override + public void postprocess(Recording rec, Config config) throws IOException, InterruptedException { + throw new NotImplementedExcetion(); + } +} diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/DeleteOriginal.java b/common/src/main/java/ctbrec/recorder/postprocessing/DeleteOriginal.java index 49337be3..71e1af8d 100644 --- a/common/src/main/java/ctbrec/recorder/postprocessing/DeleteOriginal.java +++ b/common/src/main/java/ctbrec/recorder/postprocessing/DeleteOriginal.java @@ -5,6 +5,7 @@ import static ctbrec.io.IoUtils.*; import java.io.IOException; import java.nio.file.Files; +import ctbrec.Config; import ctbrec.Recording; public class DeleteOriginal extends AbstractPostProcessor { @@ -15,7 +16,7 @@ public class DeleteOriginal extends AbstractPostProcessor { } @Override - public void postprocess(Recording rec) throws IOException, InterruptedException { + public void postprocess(Recording rec, Config config) throws IOException, InterruptedException { if (rec.getAbsoluteFile().isFile()) { Files.deleteIfExists(rec.getAbsoluteFile().toPath()); deleteEmptyParents(rec.getAbsoluteFile().getParentFile()); diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Move.java b/common/src/main/java/ctbrec/recorder/postprocessing/Move.java index 36dbcbcf..40823ddd 100644 --- a/common/src/main/java/ctbrec/recorder/postprocessing/Move.java +++ b/common/src/main/java/ctbrec/recorder/postprocessing/Move.java @@ -3,13 +3,13 @@ import static ctbrec.io.IoUtils.*; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.io.Files; - +import ctbrec.Config; import ctbrec.Recording; public class Move extends AbstractPlaceholderAwarePostProcessor { @@ -24,17 +24,17 @@ public class Move extends AbstractPlaceholderAwarePostProcessor { } @Override - public void postprocess(Recording rec) throws IOException { + public void postprocess(Recording rec, Config config) throws IOException { String pathTemplate = getConfig().getOrDefault(PATH_TEMPLATE, DEFAULT); - String path = fillInPlaceHolders(pathTemplate, rec); + String path = fillInPlaceHolders(pathTemplate, rec, config); File src = rec.getPostProcessedFile(); File target = new File(path, src.getName()); if (Objects.equals(src, target)) { return; } LOG.info("Moving {} to {}", src.getName(), target.getParentFile().getCanonicalPath()); - Files.createParentDirs(target); - Files.move(rec.getPostProcessedFile(), target); + Files.createDirectories(target.getParentFile().toPath()); + Files.move(rec.getPostProcessedFile().toPath(), target.toPath()); rec.setPostProcessedFile(target); if (Objects.equals(src, rec.getAbsoluteFile())) { rec.setAbsoluteFile(target); diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/PostProcessor.java b/common/src/main/java/ctbrec/recorder/postprocessing/PostProcessor.java index cc366520..9427655d 100644 --- a/common/src/main/java/ctbrec/recorder/postprocessing/PostProcessor.java +++ b/common/src/main/java/ctbrec/recorder/postprocessing/PostProcessor.java @@ -4,12 +4,13 @@ import java.io.IOException; import java.io.Serializable; import java.util.Map; +import ctbrec.Config; import ctbrec.Recording; public interface PostProcessor extends Serializable { String getName(); - void postprocess(Recording rec) throws IOException, InterruptedException; + void postprocess(Recording rec, Config config) throws IOException, InterruptedException; Map getConfig(); void setConfig(Map conf); diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/RemoveKeepFile.java b/common/src/main/java/ctbrec/recorder/postprocessing/RemoveKeepFile.java new file mode 100644 index 00000000..1e76bd41 --- /dev/null +++ b/common/src/main/java/ctbrec/recorder/postprocessing/RemoveKeepFile.java @@ -0,0 +1,20 @@ +package ctbrec.recorder.postprocessing; + +import java.io.IOException; + +import ctbrec.Config; +import ctbrec.NotImplementedExcetion; +import ctbrec.Recording; + +public class RemoveKeepFile extends AbstractPostProcessor { + + @Override + public String getName() { + return "remove recording, but keep the files"; + } + + @Override + public void postprocess(Recording rec, Config config) throws IOException, InterruptedException { + throw new NotImplementedExcetion(); + } +} diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java b/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java index cd7071bb..81142ef8 100644 --- a/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java +++ b/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java @@ -5,12 +5,15 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.util.Arrays; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ctbrec.Config; import ctbrec.OS; import ctbrec.Recording; +import ctbrec.io.IoUtils; import ctbrec.io.StreamRedirectThread; import ctbrec.recorder.download.ProcessExitedUncleanException; @@ -27,15 +30,18 @@ public class Remux extends AbstractPostProcessor { } @Override - public void postprocess(Recording rec) throws IOException, InterruptedException { + public void postprocess(Recording rec, Config config) throws IOException, InterruptedException { String fileExt = getConfig().get(FILE_EXT); String[] args = getConfig().get(FFMPEG_ARGS).split(" "); String[] argsPlusFile = new String[args.length + 3]; + File inputFile = rec.getPostProcessedFile(); + if (inputFile.isDirectory()) { + inputFile = new File(inputFile, "playlist.m3u8"); + } int i = 0; argsPlusFile[i++] = "-i"; - argsPlusFile[i++] = rec.getPostProcessedFile().getAbsolutePath(); + argsPlusFile[i++] = inputFile.getCanonicalPath(); 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); @@ -43,16 +49,29 @@ public class Remux extends AbstractPostProcessor { Process ffmpeg = Runtime.getRuntime().exec(cmdline, new String[0], rec.getPostProcessedFile().getParentFile()); setupLogging(ffmpeg, rec); rec.setPostProcessedFile(remuxedFile); - rec.setAbsoluteFile(remuxedFile); - Files.deleteIfExists(inputFile.toPath()); + if (inputFile.getName().equals("playlist.m3u8")) { + IoUtils.deleteDirectory(inputFile.getParentFile()); + if (Objects.equals(inputFile.getParentFile(), rec.getAbsoluteFile())) { + rec.setAbsoluteFile(remuxedFile); + } + } else { + Files.deleteIfExists(inputFile.toPath()); + if (Objects.equals(inputFile, rec.getAbsoluteFile())) { + rec.setAbsoluteFile(remuxedFile); + } + } + IoUtils.deleteEmptyParents(inputFile.getParentFile()); rec.getAssociatedFiles().remove(inputFile.getCanonicalPath()); rec.getAssociatedFiles().add(remuxedFile.getCanonicalPath()); + rec.setSingleFile(true); + rec.setSizeInByte(remuxedFile.length()); } private void setupLogging(Process ffmpeg, Recording rec) throws IOException, InterruptedException { int exitCode = 1; File video = rec.getPostProcessedFile(); File ffmpegLog = new File(video.getParentFile(), video.getName() + ".ffmpeg.log"); + rec.getAssociatedFiles().add(ffmpegLog.getCanonicalPath()); try (FileOutputStream mergeLogStream = new FileOutputStream(ffmpegLog)) { Thread stdout = new Thread(new StreamRedirectThread(ffmpeg.getInputStream(), mergeLogStream)); Thread stderr = new Thread(new StreamRedirectThread(ffmpeg.getErrorStream(), mergeLogStream)); @@ -67,6 +86,7 @@ public class Remux extends AbstractPostProcessor { if (exitCode != 1) { if (ffmpegLog.exists()) { Files.delete(ffmpegLog.toPath()); + rec.getAssociatedFiles().remove(ffmpegLog.getCanonicalPath()); } } else { rec.getAssociatedFiles().add(ffmpegLog.getAbsolutePath()); diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Rename.java b/common/src/main/java/ctbrec/recorder/postprocessing/Rename.java index 441121e9..4789f029 100644 --- a/common/src/main/java/ctbrec/recorder/postprocessing/Rename.java +++ b/common/src/main/java/ctbrec/recorder/postprocessing/Rename.java @@ -1,13 +1,13 @@ package ctbrec.recorder.postprocessing; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.io.Files; - +import ctbrec.Config; import ctbrec.Recording; public class Rename extends AbstractPlaceholderAwarePostProcessor { @@ -22,16 +22,16 @@ public class Rename extends AbstractPlaceholderAwarePostProcessor { } @Override - public void postprocess(Recording rec) throws IOException { + public void postprocess(Recording rec, Config config) throws IOException { String filenameTemplate = getConfig().getOrDefault(FILE_NAME_TEMPLATE, DEFAULT); - String filename = fillInPlaceHolders(filenameTemplate, rec); + String filename = fillInPlaceHolders(filenameTemplate, rec, config); File src = rec.getPostProcessedFile(); File target = new File(src.getParentFile(), filename); if (Objects.equals(src, target)) { return; } LOG.info("Renaming {} to {}", src.getName(), target.getName()); - Files.move(rec.getPostProcessedFile(), target); + Files.move(rec.getPostProcessedFile().toPath(), target.toPath()); rec.setPostProcessedFile(target); if (Objects.equals(src, rec.getAbsoluteFile())) { rec.setAbsoluteFile(target); diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Script.java b/common/src/main/java/ctbrec/recorder/postprocessing/Script.java new file mode 100644 index 00000000..a842867a --- /dev/null +++ b/common/src/main/java/ctbrec/recorder/postprocessing/Script.java @@ -0,0 +1,21 @@ +package ctbrec.recorder.postprocessing; + +import java.io.IOException; + +import ctbrec.Config; +import ctbrec.NotImplementedExcetion; +import ctbrec.Recording; + +public class Script extends AbstractPlaceholderAwarePostProcessor { + + @Override + public String getName() { + return "execute script"; + } + + @Override + public void postprocess(Recording rec, Config config) throws IOException, InterruptedException { + // TODO make it possible to choose, which placeholders to pass to the script + throw new NotImplementedExcetion(); + } +} diff --git a/master/pom.xml b/master/pom.xml index 40e91c83..f55c7825 100644 --- a/master/pom.xml +++ b/master/pom.xml @@ -103,6 +103,11 @@ guava 17.0 + + commons-io + commons-io + 2.8.0 + junit junit diff --git a/server/src/main/java/ctbrec/recorder/server/HlsServlet.java b/server/src/main/java/ctbrec/recorder/server/HlsServlet.java index 9498aede..72dd7858 100644 --- a/server/src/main/java/ctbrec/recorder/server/HlsServlet.java +++ b/server/src/main/java/ctbrec/recorder/server/HlsServlet.java @@ -40,8 +40,6 @@ public class HlsServlet extends AbstractCtbrecServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - - String contextPath = getServletContext().getContextPath(); String request = req.getRequestURI().substring(contextPath.length() + 5); Path recordingsDirPath = Paths.get(config.getSettings().recordingsDir).toAbsolutePath().normalize(); @@ -68,7 +66,10 @@ public class HlsServlet extends AbstractCtbrecServlet { String id = request.split("/")[0]; Optional rec = getRecordingById(id); if (rec.isPresent()) { - File file = rec.get().getAbsoluteFile(); + File path = rec.get().getAbsoluteFile(); + if (!path.isFile()) { + path = new File(path, requestedFile.getName()); + } if (LOG.isTraceEnabled()) { Enumeration headerNames = req.getHeaderNames(); while (headerNames.hasMoreElements()) { @@ -76,7 +77,7 @@ public class HlsServlet extends AbstractCtbrecServlet { LOG.trace("{}: {}", header, req.getHeader(header)); } } - serveSegment(req, resp, file); + serveSegment(req, resp, path); } else { error404(req, resp); return;