From bdf7d99ef3167bb7520e715ee859270bf5b0bdd5 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Sat, 21 Dec 2019 17:04:27 +0100 Subject: [PATCH] Fix handling of recording structure --- .../ctbrec/recorder/RecordingManager.java | 2 +- .../recorder/download/dash/DashDownload.java | 39 ++++++++++++++++--- .../recorder/download/dash/FfmpegMuxer.java | 7 +++- .../download/hls/MergedHlsDownload.java | 23 +++++++++-- 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/ctbrec/recorder/RecordingManager.java b/common/src/main/java/ctbrec/recorder/RecordingManager.java index bf08264c..25a51e3d 100644 --- a/common/src/main/java/ctbrec/recorder/RecordingManager.java +++ b/common/src/main/java/ctbrec/recorder/RecordingManager.java @@ -143,7 +143,7 @@ public class RecordingManager { } } - private void deleteEmptyParents(File parent) throws IOException { + 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)) { diff --git a/common/src/main/java/ctbrec/recorder/download/dash/DashDownload.java b/common/src/main/java/ctbrec/recorder/download/dash/DashDownload.java index 15fa8267..45c04d11 100644 --- a/common/src/main/java/ctbrec/recorder/download/dash/DashDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/dash/DashDownload.java @@ -11,6 +11,7 @@ import java.nio.file.Path; import java.text.DecimalFormat; import java.time.Duration; import java.time.Instant; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -31,6 +32,7 @@ import ctbrec.Model; import ctbrec.Recording; import ctbrec.io.HttpClient; import ctbrec.recorder.download.Download; +import ctbrec.recorder.download.dash.FfmpegMuxer.ProcessExitedUncleanException; import ctbrec.recorder.download.dash.SegmentTimelineType.S; import okhttp3.Request; import okhttp3.Response; @@ -54,8 +56,10 @@ public class DashDownload implements Download { private Path downloadDir; private String manifestUrl; private boolean running = false; + private ZonedDateTime splitRecStartTime; private File targetFile; + private File finalFile; public DashDownload(HttpClient httpClient, String manifestUrl) { this.httpClient = httpClient; @@ -166,7 +170,7 @@ public class DashDownload implements Download { String prefix = isVideo ? "video" : "audio"; int c = isVideo ? videoCounter++ : audioCounter++; File segmentFile = new File(dir, prefix + '_' + df.format(c) + '_' + new File(absFile).getName()); - while(tries <= 10) { + while (tries <= 10) { if (!segmentFile.exists() || segmentFile.length() == 0) { if (tries > 1) { LOG.debug("Loading segment, try {}, {} {} {}", tries, response.code(), response.headers().values("Content-Length"), url); @@ -194,18 +198,26 @@ public class DashDownload implements Download { this.config = config; this.model = model; startTime = Instant.now(); - targetFile = Config.getInstance().getFileForRecording(model, "mp4"); - downloadDir = targetFile.getParentFile().toPath(); + finalFile = Config.getInstance().getFileForRecording(model, "mp4"); + targetFile = new File(finalFile.getParentFile(), finalFile.getName() + ".part"); + downloadDir = targetFile.toPath(); } @Override public void start() throws IOException { try { running = true; + splitRecStartTime = ZonedDateTime.now(); JAXBContext jc = JAXBContext.newInstance(MPDtype.class.getPackage().getName()); Unmarshaller u = jc.createUnmarshaller(); while (running && !Thread.currentThread().isInterrupted()) { downloadManifestAndItsSegments(u); + + // split recordings + boolean split = splitRecording(); + if (split) { + break; + } } } catch (Exception e) { LOG.error("Error while downloading dash stream", e); @@ -218,6 +230,18 @@ public class DashDownload implements Download { } } + private boolean splitRecording() { + if(config.getSettings().splitRecordings > 0) { + Duration recordingDuration = Duration.between(splitRecStartTime, ZonedDateTime.now()); + long seconds = recordingDuration.getSeconds(); + if(seconds >= config.getSettings().splitRecordings) { + internalStop(); + return true; + } + } + return false; + } + private void downloadManifestAndItsSegments(Unmarshaller u) throws IOException, JAXBException, ExecutionException { String manifest = getManifest(manifestUrl); LOG.trace("Manifest: {}", manifest); @@ -276,7 +300,7 @@ public class DashDownload implements Download { if (running) { internalStop(); try { - while(running) { + while (running) { synchronized (downloadFinished) { downloadFinished.wait(TimeUnit.SECONDS.toMillis(1)); } @@ -310,8 +334,11 @@ public class DashDownload implements Download { @Override public void postprocess(Recording recording) { try { - new FfmpegMuxer(downloadDir.toFile(), targetFile.getName()); - } catch (IOException e) { + new FfmpegMuxer(downloadDir.toFile(), finalFile); + targetFile = finalFile; + String path = recording.getPath(); + recording.setPath(path.substring(0, path.length() - 5)); + } catch (ProcessExitedUncleanException | IOException e) { LOG.error("Error while merging dash segments", e); } } diff --git a/common/src/main/java/ctbrec/recorder/download/dash/FfmpegMuxer.java b/common/src/main/java/ctbrec/recorder/download/dash/FfmpegMuxer.java index 81abdddd..f5c3504e 100644 --- a/common/src/main/java/ctbrec/recorder/download/dash/FfmpegMuxer.java +++ b/common/src/main/java/ctbrec/recorder/download/dash/FfmpegMuxer.java @@ -19,7 +19,7 @@ public class FfmpegMuxer { File segmentDir; - public FfmpegMuxer(File segmentDir, String targetFileName) throws IOException { + public FfmpegMuxer(File segmentDir, File targetFile) throws IOException { this.segmentDir = segmentDir; String[] videoSegments = segmentDir.list((dir, name) -> name.startsWith("video_")); Arrays.sort(videoSegments); @@ -31,7 +31,7 @@ public class FfmpegMuxer { File mp4AudioTrack = new File(segmentDir, "audio.mp4"); mergeSegments(audioSegments, mp4AudioTrack); - int exitCode = mergeTracks(mp4VideoTrack, mp4AudioTrack, new File(segmentDir, targetFileName)); + int exitCode = mergeTracks(mp4VideoTrack, mp4AudioTrack, targetFile); if (exitCode == ALL_GOOD) { LOG.debug("Deleting merged video and audio tracks"); Files.delete(mp4VideoTrack.toPath()); @@ -39,6 +39,9 @@ public class FfmpegMuxer { LOG.debug("Deleting segments"); deleteFiles(segmentDir, videoSegments); deleteFiles(segmentDir, audioSegments); + if (segmentDir.list().length == 0) { + Files.delete(segmentDir.toPath()); + } } else { throw new ProcessExitedUncleanException("FFMPEG exited with " + exitCode); } diff --git a/common/src/main/java/ctbrec/recorder/download/hls/MergedHlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/MergedHlsDownload.java index 29a86217..b708fe00 100644 --- a/common/src/main/java/ctbrec/recorder/download/hls/MergedHlsDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/hls/MergedHlsDownload.java @@ -18,20 +18,30 @@ import com.iheartradio.m3u8.PlaylistException; import ctbrec.Config; import ctbrec.Hmac; +import ctbrec.Model; import ctbrec.OS; import ctbrec.io.HttpClient; import ctbrec.io.StreamRedirectThread; import ctbrec.recorder.ProgressListener; +import ctbrec.recorder.RecordingManager; import okhttp3.Request; import okhttp3.Response; public class MergedHlsDownload extends HlsDownload { private static final Logger LOG = LoggerFactory.getLogger(MergedHlsDownload.class); + private File finalFile; + public MergedHlsDownload(HttpClient client) { super(client); } + @Override + public void init(Config config, Model model) { + super.init(config, model); + finalFile = Config.getInstance().getFileForRecording(model, "mp4"); + } + @Override public void postprocess(ctbrec.Recording recording) { super.postprocess(recording); @@ -40,11 +50,16 @@ public class MergedHlsDownload extends HlsDownload { if (!playlist.exists()) { super.generatePlaylist(recording); } - File targetFile = new File(dir, "0merged.mp4"); + try { - postprocess(playlist, targetFile); - recording.setPath(recording.getPath() + '/' + "0merged.mp4"); // TODO set the actual name - } catch (PostProcessingException e) { + postprocess(playlist, finalFile); + String recordingsDir = Config.getInstance().getSettings().recordingsDir; + String path = finalFile.getAbsolutePath().substring(recordingsDir.length()); + recording.setPath(path); + if (dir.list().length == 0) { + RecordingManager.deleteEmptyParents(dir); + } + } catch (PostProcessingException | IOException e) { LOG.error("An error occurred during post-processing", e); } }