forked from j62/ctbrec
Add setting to switch FFmpeg logging on / off
This commit is contained in:
parent
ae14844170
commit
605269b4a0
|
@ -1,6 +1,7 @@
|
|||
3.10.8
|
||||
========================
|
||||
* Fixed Bongacams "New" tab
|
||||
* Added setting to switch FFmpeg logging on/off (category Advanced/Devtools)
|
||||
|
||||
3.10.7
|
||||
========================
|
||||
|
|
|
@ -115,6 +115,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
private SimpleBooleanProperty onlineCheckSkipsPausedModels;
|
||||
private SimpleLongProperty leaveSpaceOnDevice;
|
||||
private SimpleStringProperty ffmpegParameters;
|
||||
private SimpleBooleanProperty logFFmpegOutput;
|
||||
private SimpleStringProperty fileExtension;
|
||||
private SimpleStringProperty server;
|
||||
private SimpleIntegerProperty port;
|
||||
|
@ -165,6 +166,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
onlineCheckIntervalInSecs = new SimpleIntegerProperty(null, "onlineCheckIntervalInSecs", settings.onlineCheckIntervalInSecs);
|
||||
leaveSpaceOnDevice = new SimpleLongProperty(null, "minimumSpaceLeftInBytes", (long) new GigabytesConverter().convertTo(settings.minimumSpaceLeftInBytes));
|
||||
ffmpegParameters = new SimpleStringProperty(null, "ffmpegMergedDownloadArgs", settings.ffmpegMergedDownloadArgs);
|
||||
logFFmpegOutput = new SimpleBooleanProperty(null, "logFFmpegOutput", settings.logFFmpegOutput);
|
||||
fileExtension = new SimpleStringProperty(null, "ffmpegFileSuffix", settings.ffmpegFileSuffix);
|
||||
server = new SimpleStringProperty(null, "httpServer", settings.httpServer);
|
||||
port = new SimpleIntegerProperty(null, "httpPort", settings.httpPort);
|
||||
|
@ -254,6 +256,11 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
Setting.of("Username", proxyUser).needsRestart(),
|
||||
Setting.of("Password", proxyPassword).needsRestart()
|
||||
)
|
||||
),
|
||||
Category.of("Advanced / Devtools",
|
||||
Group.of("Logging",
|
||||
Setting.of("Log FFmpeg output", logFFmpegOutput, "Log FFmpeg output to files in the system's temp directory")
|
||||
)
|
||||
)
|
||||
);
|
||||
Region preferencesView = prefs.getView();
|
||||
|
|
|
@ -151,8 +151,12 @@ public class Config {
|
|||
if (oldLocation.exists()) {
|
||||
File newLocation = new File(getConfigDir(), oldLocation.getName());
|
||||
try {
|
||||
if (!newLocation.exists()) {
|
||||
LOG.debug("Moving minimal browser config {} --> {}", oldLocation, newLocation);
|
||||
FileUtils.moveDirectory(oldLocation, newLocation);
|
||||
} else {
|
||||
LOG.debug("minimal browser settings have been migrated before");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Couldn't migrate minimal browser config location", e);
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ public class Settings {
|
|||
public String livejasminUsername = "";
|
||||
public boolean livePreviews = false;
|
||||
public boolean localRecording = true;
|
||||
public boolean logFFmpegOutput = false;
|
||||
public int minimumResolution = 0;
|
||||
public int maximumResolution = 8640;
|
||||
public int maximumResolutionPlayer = 0;
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
package ctbrec.recorder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.io.DevNull;
|
||||
import ctbrec.io.StreamRedirector;
|
||||
import ctbrec.recorder.download.ProcessExitedUncleanException;
|
||||
|
||||
public class FFmpeg {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FFmpeg.class);
|
||||
|
||||
private Process process;
|
||||
private boolean logOutput = false;
|
||||
private Consumer<Process> startCallback;
|
||||
private Consumer<Integer> exitCallback;
|
||||
private File ffmpegLog = null;
|
||||
private OutputStream ffmpegLogStream;
|
||||
private Thread stdout;
|
||||
private Thread stderr;
|
||||
|
||||
private FFmpeg() {}
|
||||
|
||||
public void exec(String[] cmdline, String[] env, File executionDir) throws IOException, InterruptedException {
|
||||
LOG.debug("FFmpeg command line: {}", Arrays.toString(cmdline));
|
||||
process = Runtime.getRuntime().exec(cmdline, env, executionDir);
|
||||
afterStart();
|
||||
int exitCode = process.waitFor();
|
||||
afterExit(exitCode);
|
||||
}
|
||||
|
||||
private void afterStart() throws IOException {
|
||||
notifyStartCallback(process);
|
||||
setupLogging();
|
||||
}
|
||||
|
||||
private void afterExit(int exitCode) throws InterruptedException, IOException {
|
||||
LOG.debug("FFmpeg exit code was {}", exitCode);
|
||||
notifyExitCallback(exitCode);
|
||||
stdout.join();
|
||||
stderr.join();
|
||||
ffmpegLogStream.flush();
|
||||
ffmpegLogStream.close();
|
||||
if (exitCode != 1) {
|
||||
if (ffmpegLog != null && ffmpegLog.exists()) {
|
||||
Files.delete(ffmpegLog.toPath());
|
||||
}
|
||||
} else {
|
||||
throw new ProcessExitedUncleanException("FFmpeg exit code was " + exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupLogging() throws IOException {
|
||||
if (logOutput) {
|
||||
if (ffmpegLog == null) {
|
||||
ffmpegLog = File.createTempFile("ffmpeg_", ".log");
|
||||
}
|
||||
LOG.debug("Logging FFmpeg output to {}", ffmpegLog);
|
||||
ffmpegLog.deleteOnExit();
|
||||
ffmpegLogStream = new FileOutputStream(ffmpegLog);
|
||||
} else {
|
||||
ffmpegLogStream = new DevNull();
|
||||
}
|
||||
stdout = new Thread(new StreamRedirector(process.getInputStream(), ffmpegLogStream));
|
||||
stderr = new Thread(new StreamRedirector(process.getErrorStream(), ffmpegLogStream));
|
||||
stdout.start();
|
||||
stderr.start();
|
||||
}
|
||||
|
||||
private void notifyStartCallback(Process process) {
|
||||
try {
|
||||
startCallback.accept(process);
|
||||
} catch(Exception e) {
|
||||
LOG.error("Exception in onStart callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyExitCallback(int exitCode) {
|
||||
try {
|
||||
exitCallback.accept(exitCode);
|
||||
} catch(Exception e) {
|
||||
LOG.error("Exception in onExit callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
public int waitFor() throws InterruptedException {
|
||||
return process.waitFor();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private boolean logOutput = false;
|
||||
private File logFile;
|
||||
private Consumer<Process> startCallback;
|
||||
private Consumer<Integer> exitCallback;
|
||||
|
||||
public Builder logOutput(boolean logOutput) {
|
||||
this.logOutput = logOutput;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder logFile(File logFile) {
|
||||
this.logFile = logFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder onStarted(Consumer<Process> callback) {
|
||||
this.startCallback = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder onExit(Consumer<Integer> callback) {
|
||||
this.exitCallback = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FFmpeg build() {
|
||||
FFmpeg instance = new FFmpeg();
|
||||
instance.logOutput = logOutput;
|
||||
instance.startCallback = startCallback != null ? startCallback : p -> {};
|
||||
instance.exitCallback = exitCallback != null ? exitCallback : exitCode -> {};
|
||||
instance.ffmpegLog = logFile;
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -4,7 +4,6 @@ import static java.util.Optional.*;
|
|||
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
@ -12,7 +11,6 @@ import java.net.MalformedURLException;
|
|||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
@ -38,7 +36,7 @@ import ctbrec.Recording;
|
|||
import ctbrec.io.BandwidthMeter;
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.io.StreamRedirector;
|
||||
import ctbrec.recorder.FFmpeg;
|
||||
import ctbrec.recorder.ProgressListener;
|
||||
import ctbrec.recorder.download.HttpHeaderFactory;
|
||||
import ctbrec.recorder.download.ProcessExitedUncleanException;
|
||||
|
@ -53,11 +51,13 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
|||
private static final boolean IGNORE_CACHE = true;
|
||||
private File targetFile;
|
||||
private transient Config config;
|
||||
private transient Process ffmpeg;
|
||||
private transient Process ffmpegProcess;
|
||||
private transient OutputStream ffmpegStdIn;
|
||||
protected transient Thread ffmpegThread;
|
||||
private transient Object ffmpegStartMonitor = new Object();
|
||||
private transient Queue<Future<byte[]>> downloads = new LinkedList<>();
|
||||
private transient int lastSegment = 0;
|
||||
private transient int nextSegment = 0;
|
||||
|
||||
public MergedFfmpegHlsDownload(HttpClient client) {
|
||||
super(client);
|
||||
|
@ -91,17 +91,17 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
|||
startFfmpegProcess(targetFile);
|
||||
synchronized (ffmpegStartMonitor) {
|
||||
int tries = 0;
|
||||
while (ffmpeg == null && tries++ < 15) {
|
||||
while (ffmpegProcess == null && tries++ < 15) {
|
||||
LOG.debug("Waiting for FFmpeg to spawn to record {}", model.getName());
|
||||
ffmpegStartMonitor.wait(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (ffmpeg == null) {
|
||||
if (ffmpegProcess == null) {
|
||||
throw new ProcessExitedUncleanException("Couldn't spawn FFmpeg");
|
||||
} else {
|
||||
LOG.debug("Starting to download segments");
|
||||
downloadSegments(segments, true);
|
||||
startDownloadLoop(segments, true);
|
||||
ffmpegThread.join();
|
||||
LOG.debug("FFmpeg thread terminated");
|
||||
}
|
||||
|
@ -131,47 +131,20 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
|||
private void startFfmpegProcess(File target) {
|
||||
ffmpegThread = new Thread(() -> {
|
||||
try {
|
||||
String[] args = config.getSettings().ffmpegMergedDownloadArgs.split(" ");
|
||||
String[] argsPlusFile = new String[args.length + 3];
|
||||
int i = 0;
|
||||
argsPlusFile[i++] = "-i";
|
||||
argsPlusFile[i++] = "-";
|
||||
System.arraycopy(args, 0, argsPlusFile, i, args.length);
|
||||
argsPlusFile[argsPlusFile.length - 1] = target.getAbsolutePath();
|
||||
String[] cmdline = OS.getFFmpegCommand(argsPlusFile);
|
||||
|
||||
LOG.debug("Command line: {}", Arrays.toString(cmdline));
|
||||
ffmpeg = Runtime.getRuntime().exec(cmdline, new String[0], target.getParentFile());
|
||||
String[] cmdline = prepareCommandLine(target);
|
||||
FFmpeg ffmpeg = new FFmpeg.Builder()
|
||||
.logOutput(config.getSettings().logFFmpegOutput)
|
||||
.onStarted(p -> {
|
||||
ffmpegProcess = p;
|
||||
ffmpegStdIn = ffmpegProcess.getOutputStream();
|
||||
synchronized (ffmpegStartMonitor) {
|
||||
ffmpegStartMonitor.notifyAll();
|
||||
}
|
||||
ffmpegStdIn = ffmpeg.getOutputStream();
|
||||
int exitCode = 1;
|
||||
File ffmpegLog = File.createTempFile(target.getName(), ".log");
|
||||
ffmpegLog.deleteOnExit();
|
||||
try (FileOutputStream mergeLogStream = new FileOutputStream(ffmpegLog)) {
|
||||
Thread stdout = new Thread(new StreamRedirector(ffmpeg.getInputStream(), mergeLogStream));
|
||||
Thread stderr = new Thread(new StreamRedirector(ffmpeg.getErrorStream(), mergeLogStream));
|
||||
stdout.start();
|
||||
stderr.start();
|
||||
exitCode = ffmpeg.waitFor();
|
||||
LOG.debug("FFmpeg exited with code {}", exitCode);
|
||||
stdout.join();
|
||||
stderr.join();
|
||||
mergeLogStream.flush();
|
||||
}
|
||||
if (exitCode != 1) {
|
||||
if (ffmpegLog.exists()) {
|
||||
Files.delete(ffmpegLog.toPath());
|
||||
}
|
||||
} else {
|
||||
if (running) {
|
||||
LOG.info("FFmpeg exit code was {}. Logfile: {}", exitCode, ffmpegLog.getAbsolutePath());
|
||||
throw new ProcessExitedUncleanException("FFmpeg exit code was " + exitCode);
|
||||
}
|
||||
}
|
||||
})
|
||||
.build();
|
||||
ffmpeg.exec(cmdline, new String[0], target.getParentFile());
|
||||
} catch (IOException | ProcessExitedUncleanException e) {
|
||||
LOG.error("Error in FFMpeg thread", e);
|
||||
LOG.error("Error in FFmpeg thread", e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
if (running) {
|
||||
|
@ -184,11 +157,36 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
|||
ffmpegThread.start();
|
||||
}
|
||||
|
||||
protected void downloadSegments(String segmentPlaylistUri, boolean livestreamDownload) throws IOException, ParseException, PlaylistException {
|
||||
int lastSegment = 0;
|
||||
int nextSegment = 0;
|
||||
private String[] prepareCommandLine(File target) {
|
||||
String[] args = config.getSettings().ffmpegMergedDownloadArgs.split(" ");
|
||||
String[] argsPlusFile = new String[args.length + 3];
|
||||
int i = 0;
|
||||
argsPlusFile[i++] = "-i";
|
||||
argsPlusFile[i++] = "-";
|
||||
System.arraycopy(args, 0, argsPlusFile, i, args.length);
|
||||
argsPlusFile[argsPlusFile.length - 1] = target.getAbsolutePath();
|
||||
return OS.getFFmpegCommand(argsPlusFile);
|
||||
}
|
||||
|
||||
protected void startDownloadLoop(String segmentPlaylistUri, boolean livestreamDownload) throws IOException, ParseException, PlaylistException {
|
||||
while (running) {
|
||||
try {
|
||||
downloadSegments(segmentPlaylistUri, livestreamDownload);
|
||||
} catch (HttpException e) {
|
||||
logHttpException(e);
|
||||
running = false;
|
||||
} catch (MalformedURLException e) {
|
||||
LOG.info("Malformed URL {} - {}", model, segmentPlaylistUri, e);
|
||||
running = false;
|
||||
} catch (Exception e) {
|
||||
LOG.info("Unexpected error while downloading {}", model, e);
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
ffmpegThread.interrupt();
|
||||
}
|
||||
|
||||
private void downloadSegments(String segmentPlaylistUri, boolean livestreamDownload) throws IOException, ParseException, PlaylistException, ExecutionException {
|
||||
SegmentPlaylist lsp = getNextSegments(segmentPlaylistUri);
|
||||
emptyPlaylistCheck(lsp);
|
||||
|
||||
|
@ -216,9 +214,11 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
|||
lastSegment = lsp.seq;
|
||||
nextSegment = lastSegment + lsp.segments.size();
|
||||
} else {
|
||||
break;
|
||||
running = false;
|
||||
}
|
||||
} catch (HttpException e) {
|
||||
}
|
||||
|
||||
private void logHttpException(HttpException e) {
|
||||
if (e.getResponseCode() == 404) {
|
||||
LOG.debug("Playlist not found (404). Model {} probably went offline", model);
|
||||
} else if (e.getResponseCode() == 403) {
|
||||
|
@ -226,16 +226,6 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
|||
} else {
|
||||
LOG.info("Unexpected error while downloading {}", model, e);
|
||||
}
|
||||
running = false;
|
||||
} catch (MalformedURLException e) {
|
||||
LOG.info("Malformed URL {} - {}", model, segmentPlaylistUri, e);
|
||||
running = false;
|
||||
} catch (Exception e) {
|
||||
LOG.info("Unexpected error while downloading {}", model, e);
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
ffmpegThread.interrupt();
|
||||
}
|
||||
|
||||
protected void splitRecordingIfNecessary() {
|
||||
|
@ -291,6 +281,12 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
|||
} catch (CancellationException e) {
|
||||
LOG.info("Segment download cancelled");
|
||||
} catch (ExecutionException e) {
|
||||
handleExecutionExceptione(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleExecutionExceptione(ExecutionException e) throws HttpException, ExecutionException {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof MissingSegmentException) {
|
||||
if (model != null && !isModelOnline()) {
|
||||
|
@ -300,7 +296,13 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
|||
LOG.debug("Segment not available, but model {} still online. Going on", ofNullable(model).map(Model::getName).orElse("n/a"));
|
||||
}
|
||||
} else if (cause instanceof HttpException) {
|
||||
HttpException he = (HttpException) cause;
|
||||
handleHttpException((HttpException)cause);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHttpException(HttpException he) throws HttpException {
|
||||
if (model != null && !isModelOnline()) {
|
||||
LOG.debug("Error {} while downloading segment, because model {} is offline. Stopping now", he.getResponseCode(), model.getName());
|
||||
running = false;
|
||||
|
@ -315,11 +317,6 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
|||
throw he;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeSegment(byte[] segmentData, int offset, int length) throws IOException {
|
||||
|
@ -396,15 +393,15 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
|||
}
|
||||
}
|
||||
|
||||
if (ffmpeg != null) {
|
||||
if (ffmpegProcess != null) {
|
||||
try {
|
||||
boolean waitFor = ffmpeg.waitFor(45, TimeUnit.SECONDS);
|
||||
if (!waitFor && ffmpeg.isAlive()) {
|
||||
ffmpeg.destroy();
|
||||
if (ffmpeg.isAlive()) {
|
||||
boolean waitFor = ffmpegProcess.waitFor(45, TimeUnit.SECONDS);
|
||||
if (!waitFor && ffmpegProcess.isAlive()) {
|
||||
ffmpegProcess.destroy();
|
||||
if (ffmpegProcess.isAlive()) {
|
||||
LOG.info("FFmpeg didn't terminate. Destroying the process with force!");
|
||||
ffmpeg.destroyForcibly();
|
||||
ffmpeg = null;
|
||||
ffmpegProcess.destroyForcibly();
|
||||
ffmpegProcess = null;
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package ctbrec.recorder.postprocessing;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
@ -14,7 +12,7 @@ import org.slf4j.LoggerFactory;
|
|||
import ctbrec.Config;
|
||||
import ctbrec.OS;
|
||||
import ctbrec.Recording;
|
||||
import ctbrec.io.StreamRedirector;
|
||||
import ctbrec.recorder.FFmpeg;
|
||||
import ctbrec.recorder.RecordingManager;
|
||||
|
||||
public class CreateContactSheet extends AbstractPlaceholderAwarePostProcessor {
|
||||
|
@ -80,28 +78,15 @@ public class CreateContactSheet extends AbstractPlaceholderAwarePostProcessor {
|
|||
};
|
||||
String[] cmdline = OS.getFFmpegCommand(args);
|
||||
LOG.info("Executing {} in working directory {}", Arrays.toString(cmdline), executionDir);
|
||||
Process ffmpeg = Runtime.getRuntime().exec(cmdline, OS.getEnvironment(), executionDir);
|
||||
int exitCode = 1;
|
||||
File ffmpegLog = File.createTempFile("create_contact_sheet_" + rec.getId() + '_', ".log");
|
||||
ffmpegLog.deleteOnExit();
|
||||
try (FileOutputStream mergeLogStream = new FileOutputStream(ffmpegLog)) {
|
||||
Thread stdout = new Thread(new StreamRedirector(ffmpeg.getInputStream(), mergeLogStream));
|
||||
Thread stderr = new Thread(new StreamRedirector(ffmpeg.getErrorStream(), mergeLogStream));
|
||||
stdout.start();
|
||||
stderr.start();
|
||||
exitCode = ffmpeg.waitFor();
|
||||
LOG.debug("FFmpeg exited with code {}", exitCode);
|
||||
stdout.join();
|
||||
stderr.join();
|
||||
mergeLogStream.flush();
|
||||
}
|
||||
File ffmpegLog = new File(System.getProperty("java.io.tmpdir"), "create_contact_sheet_" + rec.getId() + ".log");
|
||||
FFmpeg ffmpeg = new FFmpeg.Builder()
|
||||
.logOutput(config.getSettings().logFFmpegOutput)
|
||||
.logFile(ffmpegLog)
|
||||
.build();
|
||||
ffmpeg.exec(cmdline, OS.getEnvironment(), executionDir);
|
||||
int exitCode = ffmpeg.waitFor();
|
||||
rec.getAssociatedFiles().add(output.getCanonicalPath());
|
||||
if (exitCode != 1) {
|
||||
if (ffmpegLog.exists()) {
|
||||
Files.delete(ffmpegLog.toPath());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return exitCode != 1;
|
||||
}
|
||||
|
||||
private File getInputFile(Recording rec) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ctbrec.recorder.postprocessing;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
|
@ -14,9 +13,8 @@ import ctbrec.Config;
|
|||
import ctbrec.OS;
|
||||
import ctbrec.Recording;
|
||||
import ctbrec.io.IoUtils;
|
||||
import ctbrec.io.StreamRedirector;
|
||||
import ctbrec.recorder.FFmpeg;
|
||||
import ctbrec.recorder.RecordingManager;
|
||||
import ctbrec.recorder.download.ProcessExitedUncleanException;
|
||||
|
||||
public class Remux extends AbstractPostProcessor {
|
||||
|
||||
|
@ -24,6 +22,8 @@ public class Remux extends AbstractPostProcessor {
|
|||
|
||||
public static final String FFMPEG_ARGS = "ffmpeg.args";
|
||||
public static final String FILE_EXT = "file.ext";
|
||||
private transient File inputFile;
|
||||
private transient File remuxedFile;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
|
@ -32,24 +32,30 @@ public class Remux extends AbstractPostProcessor {
|
|||
|
||||
@Override
|
||||
public boolean postprocess(Recording rec, RecordingManager recordingManager, 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();
|
||||
inputFile = rec.getPostProcessedFile();
|
||||
if (inputFile.isDirectory()) {
|
||||
inputFile = new File(inputFile, "playlist.m3u8");
|
||||
}
|
||||
int i = 0;
|
||||
argsPlusFile[i++] = "-i";
|
||||
argsPlusFile[i++] = inputFile.getCanonicalPath();
|
||||
System.arraycopy(args, 0, argsPlusFile, i, args.length);
|
||||
File remuxedFile = new File(rec.getPostProcessedFile().getAbsolutePath() + '.' + fileExt);
|
||||
argsPlusFile[argsPlusFile.length - 1] = remuxedFile.getAbsolutePath();
|
||||
String[] cmdline = OS.getFFmpegCommand(argsPlusFile);
|
||||
String fileExt = getConfig().get(FILE_EXT);
|
||||
remuxedFile = new File(rec.getPostProcessedFile().getAbsolutePath() + '.' + fileExt);
|
||||
String[] cmdline = prepareCommandline(inputFile, remuxedFile);
|
||||
File executionDir = rec.getPostProcessedFile().isDirectory() ? rec.getPostProcessedFile() : rec.getPostProcessedFile().getParentFile();
|
||||
LOG.info("Executing {} in working directory {}", Arrays.toString(cmdline), executionDir);
|
||||
Process ffmpeg = Runtime.getRuntime().exec(cmdline, new String[0], executionDir);
|
||||
setupLogging(ffmpeg, rec);
|
||||
|
||||
File ffmpegLog = new File(System.getProperty("java.io.tmpdir"), "remux_" + rec.getId() + ".log");
|
||||
FFmpeg ffmpeg = new FFmpeg.Builder()
|
||||
.logOutput(config.getSettings().logFFmpegOutput)
|
||||
.logFile(ffmpegLog)
|
||||
.onExit(exitCode -> finalizeStep(exitCode, rec))
|
||||
.build();
|
||||
ffmpeg.exec(cmdline, new String[0], executionDir);
|
||||
int exitCode = ffmpeg.waitFor();
|
||||
return exitCode != 1;
|
||||
}
|
||||
|
||||
private void finalizeStep(int exitCode, Recording rec) {
|
||||
if (exitCode != 1) {
|
||||
try {
|
||||
rec.setPostProcessedFile(remuxedFile);
|
||||
if (inputFile.getName().equals("playlist.m3u8")) {
|
||||
IoUtils.deleteDirectory(inputFile.getParentFile());
|
||||
|
@ -67,35 +73,21 @@ public class Remux extends AbstractPostProcessor {
|
|||
IoUtils.deleteEmptyParents(inputFile.getParentFile());
|
||||
rec.getAssociatedFiles().remove(inputFile.getCanonicalPath());
|
||||
rec.getAssociatedFiles().add(remuxedFile.getCanonicalPath());
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
LOG.error("Couldn't finalize remux post-processing step", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 StreamRedirector(ffmpeg.getInputStream(), mergeLogStream));
|
||||
Thread stderr = new Thread(new StreamRedirector(ffmpeg.getErrorStream(), mergeLogStream));
|
||||
stdout.start();
|
||||
stderr.start();
|
||||
exitCode = ffmpeg.waitFor();
|
||||
LOG.debug("FFmpeg exited with code {}", exitCode);
|
||||
stdout.join();
|
||||
stderr.join();
|
||||
mergeLogStream.flush();
|
||||
}
|
||||
if (exitCode != 1) {
|
||||
if (ffmpegLog.exists()) {
|
||||
Files.delete(ffmpegLog.toPath());
|
||||
rec.getAssociatedFiles().remove(ffmpegLog.getCanonicalPath());
|
||||
}
|
||||
} else {
|
||||
rec.getAssociatedFiles().add(ffmpegLog.getAbsolutePath());
|
||||
LOG.info("FFmpeg exit code was {}. Logfile: {}", exitCode, ffmpegLog.getAbsolutePath());
|
||||
throw new ProcessExitedUncleanException("FFmpeg exit code was " + exitCode);
|
||||
}
|
||||
private String[] prepareCommandline(File inputFile, File remuxedFile) throws IOException {
|
||||
String[] args = getConfig().get(FFMPEG_ARGS).split(" ");
|
||||
String[] argsPlusFile = new String[args.length + 3];
|
||||
int i = 0;
|
||||
argsPlusFile[i++] = "-i";
|
||||
argsPlusFile[i++] = inputFile.getCanonicalPath();
|
||||
System.arraycopy(args, 0, argsPlusFile, i, args.length);
|
||||
argsPlusFile[argsPlusFile.length - 1] = remuxedFile.getAbsolutePath();
|
||||
return OS.getFFmpegCommand(argsPlusFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -28,7 +28,7 @@ public class ShowupMergedDownload extends MergedFfmpegHlsDownload {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void downloadSegments(String segmentPlaylistUri, boolean livestreamDownload) throws IOException, ParseException, PlaylistException {
|
||||
protected void startDownloadLoop(String segmentPlaylistUri, boolean livestreamDownload) throws IOException, ParseException, PlaylistException {
|
||||
try {
|
||||
SegmentPlaylist lsp = getNextSegments(segmentPlaylistUri);
|
||||
emptyPlaylistCheck(lsp);
|
||||
|
|
Loading…
Reference in New Issue