Improve video length detection
This commit is contained in:
parent
3bf9c5fa26
commit
ce908bded2
|
@ -23,7 +23,7 @@ public class MpegUtil {
|
||||||
|
|
||||||
private MpegUtil() {}
|
private MpegUtil() {}
|
||||||
|
|
||||||
public static double getFileDuration(File file) throws IOException {
|
public static double getFileDurationInSecs(File file) throws IOException {
|
||||||
try(FileChannelWrapper ch = NIOUtils.readableChannel(file)) {
|
try(FileChannelWrapper ch = NIOUtils.readableChannel(file)) {
|
||||||
_2<Integer,Demuxer> m2tsDemuxer = createM2TSDemuxer(ch, TrackType.VIDEO);
|
_2<Integer,Demuxer> m2tsDemuxer = createM2TSDemuxer(ch, TrackType.VIDEO);
|
||||||
Demuxer demuxer = m2tsDemuxer.v1;
|
Demuxer demuxer = m2tsDemuxer.v1;
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class PlaylistGenerator {
|
||||||
try {
|
try {
|
||||||
float duration = 0;
|
float duration = 0;
|
||||||
if (file.getName().toLowerCase().endsWith(".ts")) {
|
if (file.getName().toLowerCase().endsWith(".ts")) {
|
||||||
duration = (float) MpegUtil.getFileDuration(file);
|
duration = (float) MpegUtil.getFileDurationInSecs(file);
|
||||||
if (duration <= 0) {
|
if (duration <= 0) {
|
||||||
throw new InvalidTrackLengthException("Track has negative duration: " + file.getName());
|
throw new InvalidTrackLengthException("Track has negative duration: " + file.getName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package ctbrec.recorder.download;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.OS;
|
||||||
|
import ctbrec.io.DevNull;
|
||||||
|
import ctbrec.io.StreamRedirectThread;
|
||||||
|
|
||||||
|
public class VideoLengthDetector {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(VideoLengthDetector.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to determine the length of a video file by calling FFmpeg and parsing it's output
|
||||||
|
*
|
||||||
|
* @param videoFile
|
||||||
|
* @return the length as Duration object. The duration is negative, if the length couldn't be determined
|
||||||
|
*/
|
||||||
|
public static Duration getLength(File videoFile) {
|
||||||
|
try {
|
||||||
|
String[] args = {
|
||||||
|
"-hide_banner",
|
||||||
|
"-i",
|
||||||
|
videoFile.getCanonicalPath(),
|
||||||
|
"-f",
|
||||||
|
"ffmetadata",
|
||||||
|
"-"
|
||||||
|
};
|
||||||
|
String[] cmdline = OS.getFFmpegCommand(args);
|
||||||
|
LOG.debug("Command line: {}", Arrays.toString(cmdline));
|
||||||
|
Process ffmpeg = Runtime.getRuntime().exec(cmdline, new String[0], videoFile.getParentFile());
|
||||||
|
int exitCode = 1;
|
||||||
|
ByteArrayOutputStream stdErrBuffer = new ByteArrayOutputStream();
|
||||||
|
Thread stdout = new Thread(new StreamRedirectThread(ffmpeg.getInputStream(), new DevNull()));
|
||||||
|
Thread stderr = new Thread(new StreamRedirectThread(ffmpeg.getErrorStream(), stdErrBuffer));
|
||||||
|
stdout.start();
|
||||||
|
stderr.start();
|
||||||
|
exitCode = ffmpeg.waitFor();
|
||||||
|
LOG.debug("FFmpeg exited with code {}", exitCode);
|
||||||
|
stdout.join();
|
||||||
|
stderr.join();
|
||||||
|
String ffmpegStderr = new String(stdErrBuffer.toByteArray());
|
||||||
|
return parseDuration(ffmpegStderr);
|
||||||
|
} catch (IOException | ProcessExitedUncleanException e) {
|
||||||
|
LOG.error("Error in FFMpeg thread", e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
LOG.info("Interrupted while waiting for ffmpeg", e);
|
||||||
|
}
|
||||||
|
return Duration.ZERO.minusSeconds(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Duration parseDuration(String ffmpegStderr) {
|
||||||
|
Matcher m = Pattern.compile("Duration: (\\d{2}):(\\d{2}):(\\d{2}).(\\d{2}),").matcher(ffmpegStderr);
|
||||||
|
if (m.find()) {
|
||||||
|
int hours = Integer.parseInt(m.group(1));
|
||||||
|
int minutes = Integer.parseInt(m.group(2));
|
||||||
|
int seconds = Integer.parseInt(m.group(3));
|
||||||
|
int millis = Integer.parseInt(m.group(4));
|
||||||
|
return Duration.ofHours(hours)
|
||||||
|
.plusMinutes(minutes)
|
||||||
|
.plusSeconds(seconds)
|
||||||
|
.plusMillis(millis);
|
||||||
|
}
|
||||||
|
return Duration.ofSeconds(-1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -348,7 +348,7 @@ public class HlsDownload extends AbstractHlsDownload {
|
||||||
} catch (IOException | ParseException | PlaylistException e) {
|
} catch (IOException | ParseException | PlaylistException e) {
|
||||||
LOG.error("Couldn't determine recording length", e);
|
LOG.error("Couldn't determine recording length", e);
|
||||||
}
|
}
|
||||||
return Duration.ofSeconds(0);
|
return Duration.ofSeconds(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double getPlaylistLength(File playlist) throws IOException, ParseException, PlaylistException {
|
private double getPlaylistLength(File playlist) throws IOException, ParseException, PlaylistException {
|
||||||
|
|
|
@ -44,6 +44,7 @@ import ctbrec.io.StreamRedirectThread;
|
||||||
import ctbrec.recorder.ProgressListener;
|
import ctbrec.recorder.ProgressListener;
|
||||||
import ctbrec.recorder.download.HttpHeaderFactory;
|
import ctbrec.recorder.download.HttpHeaderFactory;
|
||||||
import ctbrec.recorder.download.ProcessExitedUncleanException;
|
import ctbrec.recorder.download.ProcessExitedUncleanException;
|
||||||
|
import ctbrec.recorder.download.VideoLengthDetector;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Request.Builder;
|
import okhttp3.Request.Builder;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
@ -546,7 +547,11 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Duration getLength() {
|
public Duration getLength() {
|
||||||
return Duration.between(getStartTime(), Instant.now());
|
try {
|
||||||
|
return VideoLengthDetector.getLength(targetFile);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Duration.between(getStartTime(), Instant.now());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -25,7 +25,7 @@ public class DeleteTooShort extends AbstractPostProcessor {
|
||||||
Duration minimumLengthInSeconds = Duration.ofSeconds(Integer.parseInt(getConfig().getOrDefault(MIN_LEN_IN_SECS, "0")));
|
Duration minimumLengthInSeconds = Duration.ofSeconds(Integer.parseInt(getConfig().getOrDefault(MIN_LEN_IN_SECS, "0")));
|
||||||
if (minimumLengthInSeconds.getSeconds() > 0) {
|
if (minimumLengthInSeconds.getSeconds() > 0) {
|
||||||
Duration recordingLength = rec.getLength();
|
Duration recordingLength = rec.getLength();
|
||||||
if (recordingLength.compareTo(minimumLengthInSeconds) < 0) {
|
if (!(recordingLength.isNegative() || recordingLength.isZero()) && recordingLength.compareTo(minimumLengthInSeconds) < 0) {
|
||||||
LOG.info("Deleting too short recording {} [{} < {}]", rec, recordingLength, minimumLengthInSeconds);
|
LOG.info("Deleting too short recording {} [{} < {}]", rec, recordingLength, minimumLengthInSeconds);
|
||||||
recordingManager.delete(rec);
|
recordingManager.delete(rec);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue