385 lines
20 KiB
Java
385 lines
20 KiB
Java
package ctbrec.ui.settings;
|
|
|
|
import static ctbrec.Settings.DirectoryStructure.*;
|
|
import static ctbrec.Settings.ProxyType.*;
|
|
import static java.util.Optional.*;
|
|
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.stream.Collectors;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import ctbrec.Config;
|
|
import ctbrec.Hmac;
|
|
import ctbrec.Settings;
|
|
import ctbrec.Settings.DirectoryStructure;
|
|
import ctbrec.Settings.ProxyType;
|
|
import ctbrec.recorder.Recorder;
|
|
import ctbrec.sites.Site;
|
|
import ctbrec.ui.SiteUI;
|
|
import ctbrec.ui.SiteUiFactory;
|
|
import ctbrec.ui.controls.range.DiscreteRange;
|
|
import ctbrec.ui.settings.api.Category;
|
|
import ctbrec.ui.settings.api.ExclusiveSelectionProperty;
|
|
import ctbrec.ui.settings.api.GigabytesConverter;
|
|
import ctbrec.ui.settings.api.Group;
|
|
import ctbrec.ui.settings.api.Preferences;
|
|
import ctbrec.ui.settings.api.Setting;
|
|
import ctbrec.ui.settings.api.SimpleDirectoryProperty;
|
|
import ctbrec.ui.settings.api.SimpleFileProperty;
|
|
import ctbrec.ui.settings.api.SimpleRangeProperty;
|
|
import ctbrec.ui.settings.api.ValueConverter;
|
|
import ctbrec.ui.sites.ConfigUI;
|
|
import ctbrec.ui.tabs.TabSelectionListener;
|
|
import javafx.beans.binding.BooleanExpression;
|
|
import javafx.beans.property.SimpleBooleanProperty;
|
|
import javafx.beans.property.SimpleIntegerProperty;
|
|
import javafx.beans.property.SimpleListProperty;
|
|
import javafx.beans.property.SimpleLongProperty;
|
|
import javafx.beans.property.SimpleStringProperty;
|
|
import javafx.beans.value.ObservableValue;
|
|
import javafx.collections.FXCollections;
|
|
import javafx.geometry.Insets;
|
|
import javafx.scene.control.Tab;
|
|
import javafx.scene.control.TextInputDialog;
|
|
import javafx.scene.layout.GridPane;
|
|
|
|
public class SettingsTab extends Tab implements TabSelectionListener {
|
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(SettingsTab.class);
|
|
public static final int CHECKBOX_MARGIN = 6;
|
|
|
|
private List<Site> sites;
|
|
private Recorder recorder;
|
|
private boolean initialized = false;
|
|
private Config config;
|
|
private Settings settings;
|
|
|
|
private SimpleStringProperty httpUserAgent;
|
|
private SimpleStringProperty httpUserAgentMobile;
|
|
private SimpleIntegerProperty overviewUpdateIntervalInSecs;
|
|
private SimpleBooleanProperty updateThumbnails;
|
|
private SimpleBooleanProperty determineResolution;
|
|
private SimpleBooleanProperty chooseStreamQuality;
|
|
private SimpleBooleanProperty livePreviews;
|
|
private SimpleListProperty<String> startTab;
|
|
private SimpleFileProperty mediaPlayer;
|
|
private SimpleStringProperty mediaPlayerParams;
|
|
private SimpleIntegerProperty maximumResolutionPlayer;
|
|
private SimpleBooleanProperty showPlayerStarting;
|
|
private SimpleBooleanProperty singlePlayer;
|
|
private SimpleListProperty<ProxyType> proxyType;
|
|
private SimpleStringProperty proxyHost;
|
|
private SimpleStringProperty proxyPort;
|
|
private SimpleStringProperty proxyUser;
|
|
private SimpleStringProperty proxyPassword;
|
|
private SimpleDirectoryProperty recordingsDir;
|
|
private SimpleListProperty<DirectoryStructure> directoryStructure;
|
|
private SimpleListProperty<SplitAfterOption> splitAfter;
|
|
private SimpleRangeProperty<Integer> resolutionRange;
|
|
private List<Integer> labels = Arrays.asList(0, 240, 360, 480, 600, 720, 960, 1080, 1440, 2160, 4320, 8640);
|
|
private List<Integer> values = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
|
|
private DiscreteRange<Integer> rangeValues = new DiscreteRange<>(values, labels);
|
|
private SimpleIntegerProperty concurrentRecordings;
|
|
private SimpleIntegerProperty onlineCheckIntervalInSecs;
|
|
private SimpleBooleanProperty onlineCheckSkipsPausedModels;
|
|
private SimpleLongProperty leaveSpaceOnDevice;
|
|
private SimpleStringProperty ffmpegParameters;
|
|
private SimpleStringProperty fileExtension;
|
|
private SimpleStringProperty server;
|
|
private SimpleIntegerProperty port;
|
|
private SimpleStringProperty path;
|
|
private SimpleBooleanProperty requireAuthentication;
|
|
private SimpleBooleanProperty transportLayerSecurity;
|
|
private ExclusiveSelectionProperty recordLocal;
|
|
private SimpleIntegerProperty postProcessingThreads;
|
|
private IgnoreList ignoreList;
|
|
|
|
public SettingsTab(List<Site> sites, Recorder recorder) {
|
|
this.sites = sites;
|
|
this.recorder = recorder;
|
|
setText("Settings");
|
|
setClosable(false);
|
|
config = Config.getInstance();
|
|
settings = config.getSettings();
|
|
}
|
|
|
|
private void initializeProperties() {
|
|
httpUserAgent = new SimpleStringProperty(null, "httpUserAgent", settings.httpUserAgent);
|
|
httpUserAgentMobile = new SimpleStringProperty(null, "httpUserAgentMobile", settings.httpUserAgentMobile);
|
|
overviewUpdateIntervalInSecs = new SimpleIntegerProperty(null, "overviewUpdateIntervalInSecs", settings.overviewUpdateIntervalInSecs);
|
|
updateThumbnails = new SimpleBooleanProperty(null, "updateThumbnails", settings.updateThumbnails);
|
|
determineResolution = new SimpleBooleanProperty(null, "determineResolution", settings.determineResolution);
|
|
chooseStreamQuality = new SimpleBooleanProperty(null, "chooseStreamQuality", settings.chooseStreamQuality);
|
|
livePreviews = new SimpleBooleanProperty(null, "livePreviews", settings.livePreviews);
|
|
startTab = new SimpleListProperty<>(null, "startTab", FXCollections.observableList(getTabNames()));
|
|
mediaPlayer = new SimpleFileProperty(null, "mediaPlayer", settings.mediaPlayer);
|
|
mediaPlayerParams = new SimpleStringProperty(null, "mediaPlayerParams", settings.mediaPlayerParams);
|
|
maximumResolutionPlayer = new SimpleIntegerProperty(null, "maximumResolutionPlayer", settings.maximumResolutionPlayer);
|
|
showPlayerStarting = new SimpleBooleanProperty(null, "showPlayerStarting", settings.showPlayerStarting);
|
|
singlePlayer = new SimpleBooleanProperty(null, "singlePlayer", settings.singlePlayer);
|
|
proxyType = new SimpleListProperty<>(null, "proxyType", FXCollections.observableList(List.of(DIRECT, HTTP, SOCKS4, SOCKS5)));
|
|
proxyHost = new SimpleStringProperty(null, "proxyHost", settings.proxyHost);
|
|
proxyPort = new SimpleStringProperty(null, "proxyPort", settings.proxyPort);
|
|
proxyUser = new SimpleStringProperty(null, "proxyUser", settings.proxyUser);
|
|
proxyPassword = new SimpleStringProperty(null, "proxyPassword", settings.proxyPassword);
|
|
recordingsDir = new SimpleDirectoryProperty(null, "recordingsDir", settings.recordingsDir);
|
|
directoryStructure = new SimpleListProperty<>(null, "recordingsDirStructure", FXCollections.observableList(List.of(FLAT, ONE_PER_MODEL, ONE_PER_RECORDING)));
|
|
splitAfter = new SimpleListProperty<>(null, "splitRecordings", FXCollections.observableList(getSplitOptions()));
|
|
resolutionRange = new SimpleRangeProperty<>(rangeValues, "minimumResolution", "maximumResolution", settings.minimumResolution, settings.maximumResolution);
|
|
concurrentRecordings = new SimpleIntegerProperty(null, "concurrentRecordings", settings.concurrentRecordings);
|
|
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);
|
|
fileExtension = new SimpleStringProperty(null, "ffmpegFileSuffix", settings.ffmpegFileSuffix);
|
|
server = new SimpleStringProperty(null, "httpServer", settings.httpServer);
|
|
port = new SimpleIntegerProperty(null, "httpPort", settings.httpPort);
|
|
path = new SimpleStringProperty(null, "servletContext", settings.servletContext);
|
|
requireAuthentication = new SimpleBooleanProperty(null, "requireAuthentication", settings.requireAuthentication);
|
|
requireAuthentication.addListener(this::requireAuthenticationChanged);
|
|
transportLayerSecurity = new SimpleBooleanProperty(null, "transportLayerSecurity", settings.transportLayerSecurity);
|
|
recordLocal = new ExclusiveSelectionProperty(null, "localRecording", settings.localRecording, "Local", "Remote");
|
|
postProcessingThreads = new SimpleIntegerProperty(null, "postProcessingThreads", settings.postProcessingThreads);
|
|
onlineCheckSkipsPausedModels = new SimpleBooleanProperty(null, "onlineCheckSkipsPausedModels", settings.onlineCheckSkipsPausedModels);
|
|
}
|
|
|
|
private void createGui() {
|
|
ignoreList = new IgnoreList(sites);
|
|
List<Category> siteCategories = new ArrayList<>();
|
|
for (Site site : sites) {
|
|
ofNullable(SiteUiFactory.getUi(site)).map(SiteUI::getConfigUI).map(ConfigUI::createConfigPanel)
|
|
.ifPresent(configPanel -> siteCategories.add(Category.of(site.getName(), configPanel)));
|
|
}
|
|
|
|
Preferences prefs = Preferences.of(new CtbrecPreferencesStorage(config),
|
|
Category.of("General",
|
|
Group.of("General",
|
|
Setting.of("User-Agent", httpUserAgent),
|
|
Setting.of("User-Agent mobile", httpUserAgentMobile),
|
|
Setting.of("Update overview interval (seconds)", overviewUpdateIntervalInSecs, "Update the thumbnail overviews every x seconds"),
|
|
Setting.of("Update thumbnails", updateThumbnails, "The overviews will still be updated, but the thumbnails won't be changed. This is useful for less powerful systems."),
|
|
Setting.of("Display stream resolution in overview", determineResolution),
|
|
Setting.of("Manually select stream quality", chooseStreamQuality, "Opens a dialog to select the video resolution before recording"),
|
|
Setting.of("Enable live previews (experimental)", livePreviews),
|
|
Setting.of("Start Tab", startTab),
|
|
Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance()))
|
|
),
|
|
Group.of("Player",
|
|
Setting.of("Player", mediaPlayer),
|
|
Setting.of("Start parameters", mediaPlayerParams),
|
|
Setting.of("Maximum resolution (0 = unlimited)", maximumResolutionPlayer, "video height, e.g. 720 or 1080"),
|
|
Setting.of("Show \"Player Starting\" Message", showPlayerStarting),
|
|
Setting.of("Start only one player at a time", singlePlayer)
|
|
)
|
|
),
|
|
Category.of("Recorder",
|
|
Group.of("Settings",
|
|
Setting.of("Recordings Directory", recordingsDir),
|
|
Setting.of("Directory Structure", directoryStructure),
|
|
Setting.of("Split recordings after (minutes)", splitAfter).converter(SplitAfterOption.converter()),
|
|
Setting.of("Restrict Resolution", resolutionRange, "Only record streams with resolution within the given range"),
|
|
Setting.of("Concurrent Recordings (0 = unlimited)", concurrentRecordings),
|
|
Setting.of("Leave space on device (GiB)", leaveSpaceOnDevice, "Stop recording, if the free space on the device gets below this threshold").converter(new GigabytesConverter()),
|
|
Setting.of("FFmpeg parameters", ffmpegParameters, "FFmpeg parameters to use when merging stream segments"),
|
|
Setting.of("File Extension", fileExtension, "File extension to use for recordings"),
|
|
Setting.of("Check online state every (seconds)", onlineCheckIntervalInSecs, "Check every x seconds, if a model came online"),
|
|
Setting.of("Skip online check for paused models", onlineCheckSkipsPausedModels, "Skip online check for paused models")
|
|
),
|
|
Group.of("Location",
|
|
Setting.of("Record Location", recordLocal),
|
|
Setting.of("Server", server),
|
|
Setting.of("Port", port),
|
|
Setting.of("Path", path, "Leave empty, if you didn't change the servletContext in the server config"),
|
|
Setting.of("Require authentication", requireAuthentication),
|
|
Setting.of("Use Secure Communication (TLS)", transportLayerSecurity)
|
|
)
|
|
),
|
|
Category.of("Post-Processing",
|
|
Group.of("Post-Processing",
|
|
Setting.of("Threads", postProcessingThreads),
|
|
Setting.of("Steps", new PostProcessingStepPanel(config))
|
|
)
|
|
),
|
|
Category.of("Events & Actions", new ActionSettingsPanel(recorder)),
|
|
Category.of("Ignore List", ignoreList),
|
|
Category.of("Sites", siteCategories.toArray(new Category[0])),
|
|
Category.of("Proxy",
|
|
Group.of("Proxy",
|
|
Setting.of("Type", proxyType),
|
|
Setting.of("Host", proxyHost),
|
|
Setting.of("Port", proxyPort),
|
|
Setting.of("Username", proxyUser),
|
|
Setting.of("Password", proxyPassword)
|
|
)
|
|
)
|
|
);
|
|
setContent(prefs.getView());
|
|
prefs.expandTree();
|
|
|
|
|
|
prefs.getSetting("httpServer").ifPresent(s -> bindEnabledProperty(s, recordLocal));
|
|
prefs.getSetting("httpPort").ifPresent(s -> bindEnabledProperty(s, recordLocal));
|
|
prefs.getSetting("servletContext").ifPresent(s -> bindEnabledProperty(s, recordLocal));
|
|
prefs.getSetting("requireAuthentication").ifPresent(s -> bindEnabledProperty(s, recordLocal));
|
|
prefs.getSetting("transportLayerSecurity").ifPresent(s -> bindEnabledProperty(s, recordLocal));
|
|
prefs.getSetting("recordingsDir").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
prefs.getSetting("splitRecordings").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
prefs.getSetting("minimumResolution").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
prefs.getSetting("recordingsDirStructure").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
prefs.getSetting("onlineCheckIntervalInSecs").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
prefs.getSetting("onlineCheckSkipsPausedModels").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
prefs.getSetting("minimumSpaceLeftInBytes").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
prefs.getSetting("postProcessing").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
prefs.getSetting("postProcessingThreads").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
prefs.getSetting("removeRecordingAfterPostProcessing").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
prefs.getSetting("minimumLengthInSeconds").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
prefs.getSetting("concurrentRecordings").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
|
|
}
|
|
|
|
private void bindEnabledProperty(Setting s, BooleanExpression bindTo) {
|
|
try {
|
|
s.getGui().disableProperty().bind(bindTo);
|
|
} catch (Exception e) {
|
|
LOG.error("Couldn't bind disableProperty of {}", s.getName(), e);
|
|
}
|
|
}
|
|
|
|
private List<String> getTabNames() {
|
|
return getTabPane().getTabs().stream()
|
|
.map(Tab::getText)
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
private List<SplitAfterOption> getSplitOptions() {
|
|
List<SplitAfterOption> splitOptions = new ArrayList<>();
|
|
splitOptions.add(new SplitAfterOption("disabled", 0));
|
|
if (Config.isDevMode()) {
|
|
splitOptions.add(new SplitAfterOption("1 min", 1 * 60));
|
|
splitOptions.add(new SplitAfterOption("3 min", 3 * 60));
|
|
}
|
|
splitOptions.add(new SplitAfterOption("5 min", 5 * 60));
|
|
splitOptions.add(new SplitAfterOption("10 min", 10 * 60));
|
|
splitOptions.add(new SplitAfterOption("15 min", 15 * 60));
|
|
splitOptions.add(new SplitAfterOption("20 min", 20 * 60));
|
|
splitOptions.add(new SplitAfterOption("30 min", 30 * 60));
|
|
splitOptions.add(new SplitAfterOption("60 min", 60 * 60));
|
|
return splitOptions;
|
|
}
|
|
|
|
private void requireAuthenticationChanged(ObservableValue<?> obs, Boolean oldV, Boolean newV) { // NOSONAR
|
|
boolean requiresAuthentication = newV;
|
|
Config.getInstance().getSettings().requireAuthentication = requiresAuthentication;
|
|
if (requiresAuthentication) {
|
|
byte[] key = Config.getInstance().getSettings().key;
|
|
if (key == null) {
|
|
key = Hmac.generateKey();
|
|
Config.getInstance().getSettings().key = key;
|
|
saveConfig();
|
|
}
|
|
TextInputDialog keyDialog = new TextInputDialog();
|
|
keyDialog.setResizable(true);
|
|
keyDialog.setTitle("Server Authentication");
|
|
keyDialog.setHeaderText("A key has been generated");
|
|
keyDialog.setContentText("Add this setting to your server's config.json:\n");
|
|
keyDialog.getEditor().setText("\"key\": " + Arrays.toString(key));
|
|
keyDialog.getEditor().setEditable(false);
|
|
keyDialog.setWidth(800);
|
|
keyDialog.setHeight(200);
|
|
keyDialog.show();
|
|
}
|
|
}
|
|
|
|
public void saveConfig() {
|
|
try {
|
|
Config.getInstance().save();
|
|
} catch (IOException e) {
|
|
LOG.error("Couldn't save config", e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void selected() {
|
|
if (!initialized) {
|
|
initializeProperties();
|
|
createGui();
|
|
initialized = true;
|
|
}
|
|
ignoreList.refresh();
|
|
}
|
|
|
|
@Override
|
|
public void deselected() {
|
|
saveConfig();
|
|
}
|
|
|
|
public static GridPane createGridLayout() {
|
|
GridPane layout = new GridPane();
|
|
layout.setPadding(new Insets(10));
|
|
layout.setHgap(5);
|
|
layout.setVgap(5);
|
|
return layout;
|
|
}
|
|
|
|
void showRestartRequired() {
|
|
// TODO restartLabel.setVisible(true);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
final int prime = 31;
|
|
int result = 1;
|
|
result = prime * result + value;
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj)
|
|
return true;
|
|
if (obj == null)
|
|
return false;
|
|
if (getClass() != obj.getClass())
|
|
return false;
|
|
SplitAfterOption other = (SplitAfterOption) obj;
|
|
return value == other.value;
|
|
}
|
|
|
|
public static ValueConverter converter() {
|
|
return new ValueConverter() {
|
|
@Override
|
|
public Integer convertFrom(Object splitAfterOption) {
|
|
return ((SplitAfterOption) splitAfterOption).getValue();
|
|
}
|
|
|
|
@Override
|
|
public SplitAfterOption convertTo(Object integer) {
|
|
return new SplitAfterOption(integer.toString(), (Integer) integer);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|