forked from j62/ctbrec
Switch to MergedFfmpegHlsDownload
This commit is contained in:
parent
4f9c1606fc
commit
bc929cc6e1
|
@ -1,201 +0,0 @@
|
||||||
package ctbrec.recorder.download.hls;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.jcodec.containers.mp4.MP4Util;
|
|
||||||
import org.jcodec.containers.mp4.boxes.MovieBox;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import ctbrec.Config;
|
|
||||||
import ctbrec.Hmac;
|
|
||||||
import ctbrec.Model;
|
|
||||||
import ctbrec.OS;
|
|
||||||
import ctbrec.io.HttpClient;
|
|
||||||
import ctbrec.io.HttpException;
|
|
||||||
import ctbrec.io.StreamRedirectThread;
|
|
||||||
import ctbrec.recorder.ProgressListener;
|
|
||||||
import ctbrec.recorder.RecordingManager;
|
|
||||||
import ctbrec.recorder.download.ProcessExitedUncleanException;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
public class MergedHlsDownload extends HlsDownload {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MergedHlsDownload.class);
|
|
||||||
|
|
||||||
private File finalFile;
|
|
||||||
private File targetFile;
|
|
||||||
|
|
||||||
public MergedHlsDownload(HttpClient client) {
|
|
||||||
super(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Config config, Model model, Instant startTime) {
|
|
||||||
super.init(config, model, startTime);
|
|
||||||
try {
|
|
||||||
Thread.sleep(1000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
finalFile = Config.getInstance().getFileForRecording(model, "mp4", startTime);
|
|
||||||
targetFile = new File(finalFile.getParentFile(), finalFile.getName() + ".part");
|
|
||||||
downloadDir = targetFile.toPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postprocess(ctbrec.Recording recording) {
|
|
||||||
Thread.currentThread().setName("PP " + model.getName());
|
|
||||||
try {
|
|
||||||
File playlist = super.generatePlaylist(recording);
|
|
||||||
Objects.requireNonNull(playlist, "Generated playlist is null");
|
|
||||||
|
|
||||||
postprocess(playlist, finalFile);
|
|
||||||
String recordingsDir = Config.getInstance().getSettings().recordingsDir;
|
|
||||||
String path = finalFile.getAbsolutePath().substring(recordingsDir.length());
|
|
||||||
recording.setPath(path);
|
|
||||||
File dir = playlist.getParentFile();
|
|
||||||
if (dir.list().length == 0) {
|
|
||||||
RecordingManager.deleteEmptyParents(dir);
|
|
||||||
}
|
|
||||||
runPostProcessingScript(recording);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new PostProcessingException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void postprocess(File playlist, File target) {
|
|
||||||
File mergeLog = new File(playlist.getParentFile(), "merge.log");
|
|
||||||
try {
|
|
||||||
File dir = playlist.getParentFile();
|
|
||||||
// @formatter:off
|
|
||||||
String[] cmdline = OS.getFFmpegCommand(
|
|
||||||
"-i", playlist.getAbsolutePath(),
|
|
||||||
"-c:v", "copy",
|
|
||||||
"-c:a", "copy",
|
|
||||||
"-movflags", "faststart", // for streaming
|
|
||||||
"-y", // overwrite existing files
|
|
||||||
"-f", "mp4",
|
|
||||||
target.getAbsolutePath()
|
|
||||||
);
|
|
||||||
// @formatter:on
|
|
||||||
LOG.debug("Command line: {}", Arrays.toString(cmdline));
|
|
||||||
Process ffmpeg = Runtime.getRuntime().exec(cmdline, new String[0], playlist.getParentFile());
|
|
||||||
int exitCode = 1;
|
|
||||||
try (FileOutputStream mergeLogStream = new FileOutputStream(mergeLog)) {
|
|
||||||
Thread stdout = new Thread(new StreamRedirectThread(ffmpeg.getInputStream(), mergeLogStream));
|
|
||||||
Thread stderr = new Thread(new StreamRedirectThread(ffmpeg.getErrorStream(), mergeLogStream));
|
|
||||||
stdout.start();
|
|
||||||
stderr.start();
|
|
||||||
exitCode = ffmpeg.waitFor();
|
|
||||||
stdout.join();
|
|
||||||
stderr.join();
|
|
||||||
mergeLogStream.flush();
|
|
||||||
}
|
|
||||||
if (exitCode == 0) {
|
|
||||||
Files.delete(playlist.toPath());
|
|
||||||
Files.deleteIfExists(mergeLog.toPath());
|
|
||||||
File[] segments = dir.listFiles((directory, filename) -> filename.endsWith(".ts") || filename.endsWith(".corrupt"));
|
|
||||||
for (File segment : segments) {
|
|
||||||
Files.delete(segment.toPath());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new ProcessExitedUncleanException("FFmpeg exit code was " + exitCode);
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
LOG.error("Interrupted while waiting for FFMPEG", e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Couldn't execute FFMPEG", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void downloadFinishedRecording(String segmentPlaylistUri, File target, ProgressListener progressListener)
|
|
||||||
throws Exception {
|
|
||||||
if (Config.getInstance().getSettings().requireAuthentication) {
|
|
||||||
URL u = new URL(segmentPlaylistUri);
|
|
||||||
String path = u.getPath();
|
|
||||||
byte[] key = Config.getInstance().getSettings().key;
|
|
||||||
if (!Config.getInstance().getContextPath().isEmpty()) {
|
|
||||||
path = path.substring(Config.getInstance().getContextPath().length());
|
|
||||||
}
|
|
||||||
String hmac = Hmac.calculate(path, key);
|
|
||||||
segmentPlaylistUri = segmentPlaylistUri + "?hmac=" + hmac;
|
|
||||||
}
|
|
||||||
|
|
||||||
File tempDir = new File(target.getParentFile(), "ctbrec-download-tmp-" + target.getName());
|
|
||||||
Files.createDirectories(tempDir.toPath());
|
|
||||||
|
|
||||||
downloadFile(segmentPlaylistUri, tempDir);
|
|
||||||
SegmentPlaylist segmentPlaylist = getNextSegments(segmentPlaylistUri);
|
|
||||||
int fileCounter = 0;
|
|
||||||
for (String segmentUrl : segmentPlaylist.segments) {
|
|
||||||
downloadFile(segmentUrl, tempDir);
|
|
||||||
fileCounter++;
|
|
||||||
int total = segmentPlaylist.segments.size();
|
|
||||||
int progress = (int) (fileCounter / (double) total * 100);
|
|
||||||
progressListener.update(progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
File downloadedPlaylist = new File(tempDir, "playlist.m3u8");
|
|
||||||
postprocess(downloadedPlaylist, target);
|
|
||||||
Files.delete(tempDir.toPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadFile(String fileUri, File tempDir) throws IOException {
|
|
||||||
LOG.trace("Downloading file {} to {}", fileUri, tempDir);
|
|
||||||
Request request = new Request.Builder().url(fileUri).addHeader("connection", "keep-alive").build();
|
|
||||||
try (Response response = client.execute(request)) {
|
|
||||||
if (response.isSuccessful()) {
|
|
||||||
InputStream in = null;
|
|
||||||
File file = new File(request.url().encodedPath());
|
|
||||||
File downloadedSegment = new File(tempDir, file.getName());
|
|
||||||
try (FileOutputStream fos = new FileOutputStream(downloadedSegment)) {
|
|
||||||
in = response.body().byteStream();
|
|
||||||
byte[] b = new byte[1024 * 100];
|
|
||||||
int length = -1;
|
|
||||||
while ((length = in.read(b)) >= 0) {
|
|
||||||
fos.write(b, 0, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new HttpException(response.code(), response.message());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Duration getLength() {
|
|
||||||
try {
|
|
||||||
MovieBox movieBox = MP4Util.parseMovie(finalFile);
|
|
||||||
double lengthInSeconds = (double) movieBox.getDuration() / movieBox.getTimescale();
|
|
||||||
return Duration.ofSeconds((long) Math.ceil(lengthInSeconds));
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Couldn't determine length of MP4 file {}", getTarget(), e);
|
|
||||||
return Duration.ofSeconds(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public File getTarget() {
|
|
||||||
return targetFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPath(Model model) {
|
|
||||||
String absolutePath = targetFile.getAbsolutePath();
|
|
||||||
String recordingsDir = Config.getInstance().getSettings().recordingsDir;
|
|
||||||
String relativePath = absolutePath.replaceFirst(Pattern.quote(recordingsDir), "");
|
|
||||||
return relativePath;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,9 +6,9 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.io.HttpClient;
|
import ctbrec.io.HttpClient;
|
||||||
import ctbrec.recorder.download.hls.MergedHlsDownload;
|
import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload;
|
||||||
|
|
||||||
public class Fc2MergedHlsDownload extends MergedHlsDownload {
|
public class Fc2MergedHlsDownload extends MergedFfmpegHlsDownload {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Fc2MergedHlsDownload.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Fc2MergedHlsDownload.class);
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,9 @@ import com.iheartradio.m3u8.ParseException;
|
||||||
import com.iheartradio.m3u8.PlaylistException;
|
import com.iheartradio.m3u8.PlaylistException;
|
||||||
|
|
||||||
import ctbrec.io.HttpClient;
|
import ctbrec.io.HttpClient;
|
||||||
import ctbrec.recorder.download.hls.MergedHlsDownload;
|
import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload;
|
||||||
|
|
||||||
public class LiveJasminMergedHlsDownload extends MergedHlsDownload {
|
public class LiveJasminMergedHlsDownload extends MergedFfmpegHlsDownload {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(LiveJasminMergedHlsDownload.class);
|
private static final Logger LOG = LoggerFactory.getLogger(LiveJasminMergedHlsDownload.class);
|
||||||
private long lastMasterPlaylistUpdate = 0;
|
private long lastMasterPlaylistUpdate = 0;
|
||||||
|
|
Loading…
Reference in New Issue