Add setting to split up the recording after x minutes
This commit is contained in:
parent
01aa04b3da
commit
527f5f61ff
|
@ -40,4 +40,5 @@ public class Settings {
|
||||||
public boolean windowMaximized = false;
|
public boolean windowMaximized = false;
|
||||||
public int windowX;
|
public int windowX;
|
||||||
public int windowY;
|
public int windowY;
|
||||||
|
public int splitRecordings = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -471,7 +471,13 @@ public class LocalRecorder implements Recorder {
|
||||||
recording.setStatus(GENERATING_PLAYLIST);
|
recording.setStatus(GENERATING_PLAYLIST);
|
||||||
recording.setProgress(playlistGenerator.getProgress());
|
recording.setProgress(playlistGenerator.getProgress());
|
||||||
} else {
|
} else {
|
||||||
if (Recording.isMergedRecording(rec)) {
|
if (config.isServerMode()) {
|
||||||
|
if (recording.hasPlaylist()) {
|
||||||
|
recording.setStatus(FINISHED);
|
||||||
|
} else {
|
||||||
|
recording.setStatus(RECORDING);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
boolean dirUsedByRecordingProcess = false;
|
boolean dirUsedByRecordingProcess = false;
|
||||||
for (Download download : recordingProcesses.values()) {
|
for (Download download : recordingProcesses.values()) {
|
||||||
if(rec.equals(download.getDirectory())) {
|
if(rec.equals(download.getDirectory())) {
|
||||||
|
@ -484,12 +490,6 @@ public class LocalRecorder implements Recorder {
|
||||||
} else {
|
} else {
|
||||||
recording.setStatus(FINISHED);
|
recording.setStatus(FINISHED);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (recording.hasPlaylist()) {
|
|
||||||
recording.setStatus(FINISHED);
|
|
||||||
} else {
|
|
||||||
recording.setStatus(RECORDING);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recordings.add(recording);
|
recordings.add(recording);
|
||||||
|
|
|
@ -14,7 +14,10 @@ import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.LinkOption;
|
import java.nio.file.LinkOption;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
@ -48,15 +51,20 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
||||||
private BlockingMultiMTSSource multiSource;
|
private BlockingMultiMTSSource multiSource;
|
||||||
private Thread mergeThread;
|
private Thread mergeThread;
|
||||||
private Streamer streamer;
|
private Streamer streamer;
|
||||||
|
private ZonedDateTime startTime;
|
||||||
|
private Config config;
|
||||||
|
private File targetFile;
|
||||||
|
private DecimalFormat df = new DecimalFormat("00000");
|
||||||
|
private int splitCounter = 0;
|
||||||
|
|
||||||
public MergedHlsDownload(HttpClient client) {
|
public MergedHlsDownload(HttpClient client) {
|
||||||
super(client);
|
super(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(String segmentPlaylistUri, File target, ProgressListener progressListener) throws IOException {
|
public void start(String segmentPlaylistUri, File targetFile, ProgressListener progressListener) throws IOException {
|
||||||
try {
|
try {
|
||||||
running = true;
|
running = true;
|
||||||
mergeThread = createMergeThread(target, progressListener, false);
|
mergeThread = createMergeThread(targetFile, progressListener, false);
|
||||||
mergeThread.start();
|
mergeThread.start();
|
||||||
downloadSegments(segmentPlaylistUri, false);
|
downloadSegments(segmentPlaylistUri, false);
|
||||||
} catch(ParseException e) {
|
} catch(ParseException e) {
|
||||||
|
@ -72,8 +80,10 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Model model, Config config) throws IOException {
|
public void start(Model model, Config config) throws IOException {
|
||||||
|
this.config = config;
|
||||||
try {
|
try {
|
||||||
running = true;
|
running = true;
|
||||||
|
startTime = ZonedDateTime.now();
|
||||||
StreamInfo streamInfo = Chaturbate.getStreamInfo(model, client);
|
StreamInfo streamInfo = Chaturbate.getStreamInfo(model, client);
|
||||||
if(!Objects.equals(streamInfo.room_status, "public")) {
|
if(!Objects.equals(streamInfo.room_status, "public")) {
|
||||||
throw new IOException(model.getName() +"'s room is not public");
|
throw new IOException(model.getName() +"'s room is not public");
|
||||||
|
@ -87,8 +97,13 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
||||||
Files.createDirectories(downloadDir);
|
Files.createDirectories(downloadDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
File targetFile = Recording.mergedFileFromDirectory(downloadDir.toFile());
|
targetFile = Recording.mergedFileFromDirectory(downloadDir.toFile());
|
||||||
mergeThread = createMergeThread(targetFile, null, true);
|
File target = targetFile;
|
||||||
|
if(config.getSettings().splitRecordings > 0) {
|
||||||
|
LOG.debug("Splitting recordings every {} seconds", config.getSettings().splitRecordings);
|
||||||
|
target = new File(targetFile.getAbsolutePath().replaceAll("\\.ts", "-00000.ts"));
|
||||||
|
}
|
||||||
|
mergeThread = createMergeThread(target, null, true);
|
||||||
mergeThread.start();
|
mergeThread.start();
|
||||||
|
|
||||||
String segments = parseMaster(streamInfo.url, model.getStreamUrlIndex());
|
String segments = parseMaster(streamInfo.url, model.getStreamUrlIndex());
|
||||||
|
@ -159,6 +174,7 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(livestreamDownload) {
|
if(livestreamDownload) {
|
||||||
|
// download new segments
|
||||||
while(!mergeQueue.isEmpty()) {
|
while(!mergeQueue.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
writeSegment(mergeQueue.poll());
|
writeSegment(mergeQueue.poll());
|
||||||
|
@ -168,21 +184,32 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (livestreamDownload) {
|
// split up the recording, if configured
|
||||||
long wait = 0;
|
if(config.getSettings().splitRecordings > 0) {
|
||||||
if (lastSegment == lsp.seq) {
|
Duration recordingDuration = Duration.between(startTime, ZonedDateTime.now());
|
||||||
// playlist didn't change -> wait for at least half the target duration
|
long seconds = recordingDuration.getSeconds();
|
||||||
wait = (long) lsp.targetDuration * 1000 / 2;
|
if(seconds >= config.getSettings().splitRecordings) {
|
||||||
LOG.trace("Playlist didn't change... waiting for {}ms", wait);
|
streamer.stop();
|
||||||
} else {
|
File target = new File(targetFile.getAbsolutePath().replaceAll("\\.ts", "-"+df.format(++splitCounter)+".ts"));
|
||||||
// playlist did change -> wait for at least last segment duration
|
mergeThread = createMergeThread(target, null, true);
|
||||||
wait = 1;// (long) lsp.lastSegDuration * 1000;
|
mergeThread.start();
|
||||||
LOG.trace("Playlist changed... waiting for {}ms", wait);
|
startTime = ZonedDateTime.now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait some time until requesting the segment playlist again to not hammer the server
|
||||||
try {
|
try {
|
||||||
|
long wait = 0;
|
||||||
|
if (lastSegment == lsp.seq) {
|
||||||
|
// playlist didn't change -> wait for at least half the target duration
|
||||||
|
wait = (long) lsp.targetDuration * 1000 / 2;
|
||||||
|
LOG.trace("Playlist didn't change... waiting for {}ms", wait);
|
||||||
|
} else {
|
||||||
|
// playlist did change -> wait for at least last segment duration
|
||||||
|
wait = 1;// (long) lsp.lastSegDuration * 1000;
|
||||||
|
LOG.trace("Playlist changed... waiting for {}ms", wait);
|
||||||
|
}
|
||||||
Thread.sleep(wait);
|
Thread.sleep(wait);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
if (running) {
|
if (running) {
|
||||||
|
|
|
@ -2,11 +2,15 @@ package ctbrec.ui;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.sun.javafx.collections.ObservableListWrapper;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Hmac;
|
import ctbrec.Hmac;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
|
@ -18,6 +22,7 @@ import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Alert.AlertType;
|
import javafx.scene.control.Alert.AlertType;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.PasswordField;
|
import javafx.scene.control.PasswordField;
|
||||||
import javafx.scene.control.RadioButton;
|
import javafx.scene.control.RadioButton;
|
||||||
|
@ -60,6 +65,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
private ToggleGroup recordLocation;
|
private ToggleGroup recordLocation;
|
||||||
private ProxySettingsPane proxySettingsPane;
|
private ProxySettingsPane proxySettingsPane;
|
||||||
private TitledPane ctb;
|
private TitledPane ctb;
|
||||||
|
private ComboBox<SplitAfterOption> splitAfter;
|
||||||
|
|
||||||
public SettingsTab() {
|
public SettingsTab() {
|
||||||
setText("Settings");
|
setText("Settings");
|
||||||
|
@ -75,6 +81,49 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
setContent(mainLayout);
|
setContent(mainLayout);
|
||||||
|
|
||||||
GridPane layout = createGridLayout();
|
GridPane layout = createGridLayout();
|
||||||
|
Label l = new Label("Display stream resolution in overview");
|
||||||
|
layout.add(l, 0, 0);
|
||||||
|
loadResolution = new CheckBox();
|
||||||
|
loadResolution.setSelected(Config.getInstance().getSettings().determineResolution);
|
||||||
|
loadResolution.setOnAction((e) -> {
|
||||||
|
Config.getInstance().getSettings().determineResolution = loadResolution.isSelected();
|
||||||
|
if(!loadResolution.isSelected()) {
|
||||||
|
ThumbOverviewTab.queue.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
||||||
|
GridPane.setMargin(loadResolution, new Insets(0, 0, 0, CHECKBOX_MARGIN));
|
||||||
|
layout.add(loadResolution, 1, 0);
|
||||||
|
|
||||||
|
l = new Label("Manually select stream quality");
|
||||||
|
layout.add(l, 0, 1);
|
||||||
|
chooseStreamQuality.setSelected(Config.getInstance().getSettings().chooseStreamQuality);
|
||||||
|
chooseStreamQuality.setOnAction((e) -> Config.getInstance().getSettings().chooseStreamQuality = chooseStreamQuality.isSelected());
|
||||||
|
GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
||||||
|
GridPane.setMargin(chooseStreamQuality, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN));
|
||||||
|
layout.add(chooseStreamQuality, 1, 1);
|
||||||
|
|
||||||
|
l = new Label("Split recordings after (minutes)");
|
||||||
|
layout.add(l, 0, 2);
|
||||||
|
List<SplitAfterOption> options = new ArrayList<>();
|
||||||
|
options.add(new SplitAfterOption("disabled", 0));
|
||||||
|
options.add(new SplitAfterOption("10 min", 10 * 60));
|
||||||
|
options.add(new SplitAfterOption("15 min", 15 * 60));
|
||||||
|
options.add(new SplitAfterOption("20 min", 20 * 60));
|
||||||
|
options.add(new SplitAfterOption("30 min", 30 * 60));
|
||||||
|
options.add(new SplitAfterOption("60 min", 60 * 60));
|
||||||
|
splitAfter = new ComboBox<>(new ObservableListWrapper<>(options));
|
||||||
|
layout.add(splitAfter, 1, 2);
|
||||||
|
setSplitAfterValue();
|
||||||
|
splitAfter.setOnAction((e) -> Config.getInstance().getSettings().splitRecordings = splitAfter.getSelectionModel().getSelectedItem().getValue());
|
||||||
|
GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
||||||
|
GridPane.setMargin(splitAfter, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN));
|
||||||
|
|
||||||
|
TitledPane general = new TitledPane("General", layout);
|
||||||
|
general.setCollapsible(false);
|
||||||
|
mainLayout.add(general, 0, 0);
|
||||||
|
|
||||||
|
layout = createGridLayout();
|
||||||
layout.add(new Label("Recordings Directory"), 0, 0);
|
layout.add(new Label("Recordings Directory"), 0, 0);
|
||||||
recordingsDirectory = new TextField(Config.getInstance().getSettings().recordingsDir);
|
recordingsDirectory = new TextField(Config.getInstance().getSettings().recordingsDir);
|
||||||
recordingsDirectory.focusedProperty().addListener(createRecordingsDirectoryFocusListener());
|
recordingsDirectory.focusedProperty().addListener(createRecordingsDirectoryFocusListener());
|
||||||
|
@ -95,7 +144,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
layout.add(mediaPlayer, 1, 1);
|
layout.add(mediaPlayer, 1, 1);
|
||||||
layout.add(createMpvBrowseButton(), 3, 1);
|
layout.add(createMpvBrowseButton(), 3, 1);
|
||||||
|
|
||||||
Label l = new Label("Allow multiple players");
|
l = new Label("Allow multiple players");
|
||||||
layout.add(l, 0, 2);
|
layout.add(l, 0, 2);
|
||||||
multiplePlayers.setSelected(!Config.getInstance().getSettings().singlePlayer);
|
multiplePlayers.setSelected(!Config.getInstance().getSettings().singlePlayer);
|
||||||
multiplePlayers.setOnAction((e) -> Config.getInstance().getSettings().singlePlayer = !multiplePlayers.isSelected());
|
multiplePlayers.setOnAction((e) -> Config.getInstance().getSettings().singlePlayer = !multiplePlayers.isSelected());
|
||||||
|
@ -107,7 +156,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
|
|
||||||
TitledPane locations = new TitledPane("Locations", layout);
|
TitledPane locations = new TitledPane("Locations", layout);
|
||||||
locations.setCollapsible(false);
|
locations.setCollapsible(false);
|
||||||
mainLayout.add(locations, 0, 0);
|
mainLayout.add(locations, 0, 1);
|
||||||
|
|
||||||
proxySettingsPane = new ProxySettingsPane();
|
proxySettingsPane = new ProxySettingsPane();
|
||||||
mainLayout.add(proxySettingsPane, 1, 0);
|
mainLayout.add(proxySettingsPane, 1, 0);
|
||||||
|
@ -158,33 +207,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
|
|
||||||
ctb = new TitledPane("Chaturbate", layout);
|
ctb = new TitledPane("Chaturbate", layout);
|
||||||
ctb.setCollapsible(false);
|
ctb.setCollapsible(false);
|
||||||
mainLayout.add(ctb, 0, 1);
|
mainLayout.add(ctb, 0, 2);
|
||||||
|
|
||||||
layout = createGridLayout();
|
|
||||||
l = new Label("Display stream resolution in overview");
|
|
||||||
layout.add(l, 0, 0);
|
|
||||||
loadResolution = new CheckBox();
|
|
||||||
loadResolution.setSelected(Config.getInstance().getSettings().determineResolution);
|
|
||||||
loadResolution.setOnAction((e) -> {
|
|
||||||
Config.getInstance().getSettings().determineResolution = loadResolution.isSelected();
|
|
||||||
if(!loadResolution.isSelected()) {
|
|
||||||
ThumbOverviewTab.queue.clear();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
|
||||||
GridPane.setMargin(loadResolution, new Insets(0, 0, 0, CHECKBOX_MARGIN));
|
|
||||||
layout.add(loadResolution, 1, 0);
|
|
||||||
|
|
||||||
l = new Label("Manually select stream quality");
|
|
||||||
layout.add(l, 0, 1);
|
|
||||||
chooseStreamQuality.setSelected(Config.getInstance().getSettings().chooseStreamQuality);
|
|
||||||
chooseStreamQuality.setOnAction((e) -> Config.getInstance().getSettings().chooseStreamQuality = chooseStreamQuality.isSelected());
|
|
||||||
GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
|
||||||
GridPane.setMargin(chooseStreamQuality, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN));
|
|
||||||
layout.add(chooseStreamQuality, 1, 1);
|
|
||||||
TitledPane quality = new TitledPane("Stream Quality", layout);
|
|
||||||
quality.setCollapsible(false);
|
|
||||||
mainLayout.add(quality, 0, 2);
|
|
||||||
|
|
||||||
layout = createGridLayout();
|
layout = createGridLayout();
|
||||||
l = new Label("Record Location");
|
l = new Label("Record Location");
|
||||||
|
@ -272,6 +295,15 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
setRecordingMode(recordLocal.isSelected());
|
setRecordingMode(recordLocal.isSelected());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setSplitAfterValue() {
|
||||||
|
int value = Config.getInstance().getSettings().splitRecordings;
|
||||||
|
for (SplitAfterOption option : splitAfter.getItems()) {
|
||||||
|
if(option.getValue() == value) {
|
||||||
|
splitAfter.getSelectionModel().select(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void showRestartRequired() {
|
static void showRestartRequired() {
|
||||||
Alert restart = new AutosizeAlert(AlertType.INFORMATION);
|
Alert restart = new AutosizeAlert(AlertType.INFORMATION);
|
||||||
restart.setTitle("Restart required");
|
restart.setTitle("Restart required");
|
||||||
|
@ -295,6 +327,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
ctb.setDisable(!local);
|
ctb.setDisable(!local);
|
||||||
recordingsDirectory.setDisable(!local);
|
recordingsDirectory.setDisable(!local);
|
||||||
recordingsDirectoryButton.setDisable(!local);
|
recordingsDirectoryButton.setDisable(!local);
|
||||||
|
splitAfter.setDisable(!local);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChangeListener<? super Boolean> createRecordingsDirectoryFocusListener() {
|
private ChangeListener<? super Boolean> createRecordingsDirectoryFocusListener() {
|
||||||
|
@ -422,4 +455,24 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
public void saveConfig() {
|
public void saveConfig() {
|
||||||
proxySettingsPane.saveConfig();
|
proxySettingsPane.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SplitAfterOption {
|
||||||
|
private String label;
|
||||||
|
private int value;
|
||||||
|
|
||||||
|
public SplitAfterOption(String label, int value) {
|
||||||
|
super();
|
||||||
|
this.label = label;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue