From 2dbc6015d233495ab935860b944206bfbfaeb61d Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 31 Oct 2020 15:09:34 +0100
Subject: [PATCH 01/80] Make sure to actually start the stream redirect threads
---
client/src/main/java/ctbrec/ui/ExternalBrowser.java | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/ExternalBrowser.java b/client/src/main/java/ctbrec/ui/ExternalBrowser.java
index 80d56b1d..8b8864cd 100644
--- a/client/src/main/java/ctbrec/ui/ExternalBrowser.java
+++ b/client/src/main/java/ctbrec/ui/ExternalBrowser.java
@@ -47,14 +47,13 @@ public class ExternalBrowser implements AutoCloseable {
addProxyConfig(jsonConfig.getJSONObject("config"));
+ p = new ProcessBuilder(OS.getBrowserCommand()).start();
if (LOG.isTraceEnabled()) {
- p = new ProcessBuilder(OS.getBrowserCommand("--enable-logging")).start();
- new StreamRedirectThread(p.getInputStream(), System.err);
- new StreamRedirectThread(p.getErrorStream(), System.err);
+ new Thread(new StreamRedirectThread(p.getInputStream(), System.out)).start();
+ new Thread(new StreamRedirectThread(p.getErrorStream(), System.err)).start();
} else {
- p = new ProcessBuilder(OS.getBrowserCommand()).start();
- new StreamRedirectThread(p.getInputStream(), OutputStream.nullOutputStream());
- new StreamRedirectThread(p.getErrorStream(), OutputStream.nullOutputStream());
+ new Thread(new StreamRedirectThread(p.getInputStream(), OutputStream.nullOutputStream())).start();
+ new Thread(new StreamRedirectThread(p.getErrorStream(), OutputStream.nullOutputStream())).start();
}
LOG.debug("Browser started");
@@ -95,6 +94,7 @@ public class ExternalBrowser implements AutoCloseable {
out = socket.getOutputStream();
reader = new Thread(this::readBrowserOutput);
reader.start();
+ LOG.debug("Connected to control socket");
return;
} catch (IOException e) {
if(i == 19) {
From 808f96c04f8e6c07c1c61216cda09a8f1860ccc5 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 31 Oct 2020 15:10:44 +0100
Subject: [PATCH 02/80] Rename StreamRedirectThread to StreamRedirector
... since it isn't a thread, but a Runnable
---
client/src/main/java/ctbrec/ui/DesktopIntegration.java | 6 +++---
client/src/main/java/ctbrec/ui/ExternalBrowser.java | 10 +++++-----
client/src/main/java/ctbrec/ui/Player.java | 6 +++---
common/src/main/java/ctbrec/event/ExecuteProgram.java | 6 +++---
...StreamRedirectThread.java => StreamRedirector.java} | 6 +++---
.../ctbrec/recorder/download/VideoLengthDetector.java | 6 +++---
.../ctbrec/recorder/download/dash/FfmpegMuxer.java | 6 +++---
.../ctbrec/recorder/download/hls/FFmpegDownload.java | 6 +++---
.../recorder/download/hls/MergedFfmpegHlsDownload.java | 6 +++---
.../recorder/postprocessing/CreateContactSheet.java | 6 +++---
.../java/ctbrec/recorder/postprocessing/Remux.java | 6 +++---
.../java/ctbrec/recorder/postprocessing/Script.java | 6 +++---
12 files changed, 38 insertions(+), 38 deletions(-)
rename common/src/main/java/ctbrec/io/{StreamRedirectThread.java => StreamRedirector.java} (83%)
diff --git a/client/src/main/java/ctbrec/ui/DesktopIntegration.java b/client/src/main/java/ctbrec/ui/DesktopIntegration.java
index 98bd0304..965a094d 100644
--- a/client/src/main/java/ctbrec/ui/DesktopIntegration.java
+++ b/client/src/main/java/ctbrec/ui/DesktopIntegration.java
@@ -15,7 +15,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.OS;
-import ctbrec.io.StreamRedirectThread;
+import ctbrec.io.StreamRedirector;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
@@ -133,8 +133,8 @@ public class DesktopIntegration {
msg.replace("-", "\\\\-").replace("\\s", "\\\\ "),
"--icon=dialog-information"
});
- new Thread(new StreamRedirectThread(p.getInputStream(), System.out)).start(); // NOSONAR
- new Thread(new StreamRedirectThread(p.getErrorStream(), System.err)).start(); // NOSONAR
+ new Thread(new StreamRedirector(p.getInputStream(), System.out)).start(); // NOSONAR
+ new Thread(new StreamRedirector(p.getErrorStream(), System.err)).start(); // NOSONAR
} catch (IOException e1) {
LOG.error("Notification failed", e1);
}
diff --git a/client/src/main/java/ctbrec/ui/ExternalBrowser.java b/client/src/main/java/ctbrec/ui/ExternalBrowser.java
index 8b8864cd..51a036df 100644
--- a/client/src/main/java/ctbrec/ui/ExternalBrowser.java
+++ b/client/src/main/java/ctbrec/ui/ExternalBrowser.java
@@ -17,7 +17,7 @@ import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.OS;
import ctbrec.Settings.ProxyType;
-import ctbrec.io.StreamRedirectThread;
+import ctbrec.io.StreamRedirector;
public class ExternalBrowser implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(ExternalBrowser.class);
@@ -49,11 +49,11 @@ public class ExternalBrowser implements AutoCloseable {
p = new ProcessBuilder(OS.getBrowserCommand()).start();
if (LOG.isTraceEnabled()) {
- new Thread(new StreamRedirectThread(p.getInputStream(), System.out)).start();
- new Thread(new StreamRedirectThread(p.getErrorStream(), System.err)).start();
+ new Thread(new StreamRedirector(p.getInputStream(), System.out)).start();
+ new Thread(new StreamRedirector(p.getErrorStream(), System.err)).start();
} else {
- new Thread(new StreamRedirectThread(p.getInputStream(), OutputStream.nullOutputStream())).start();
- new Thread(new StreamRedirectThread(p.getErrorStream(), OutputStream.nullOutputStream())).start();
+ new Thread(new StreamRedirector(p.getInputStream(), OutputStream.nullOutputStream())).start();
+ new Thread(new StreamRedirector(p.getErrorStream(), OutputStream.nullOutputStream())).start();
}
LOG.debug("Browser started");
diff --git a/client/src/main/java/ctbrec/ui/Player.java b/client/src/main/java/ctbrec/ui/Player.java
index abf5b024..d6653a9b 100644
--- a/client/src/main/java/ctbrec/ui/Player.java
+++ b/client/src/main/java/ctbrec/ui/Player.java
@@ -25,7 +25,7 @@ import ctbrec.Config;
import ctbrec.Model;
import ctbrec.OS;
import ctbrec.Recording;
-import ctbrec.io.StreamRedirectThread;
+import ctbrec.io.StreamRedirector;
import ctbrec.io.UrlUtil;
import ctbrec.recorder.download.StreamSource;
import ctbrec.ui.controls.Dialogs;
@@ -165,12 +165,12 @@ public class Player {
// create threads, which read stdout and stderr of the player process. these are needed,
// because otherwise the internal buffer for these streams fill up and block the process
- Thread std = new Thread(new StreamRedirectThread(playerProcess.getInputStream(), OutputStream.nullOutputStream()));
+ Thread std = new Thread(new StreamRedirector(playerProcess.getInputStream(), OutputStream.nullOutputStream()));
//Thread std = new Thread(new StreamRedirectThread(playerProcess.getInputStream(), System.out));
std.setName("Player stdout pipe");
std.setDaemon(true);
std.start();
- Thread err = new Thread(new StreamRedirectThread(playerProcess.getErrorStream(), OutputStream.nullOutputStream()));
+ Thread err = new Thread(new StreamRedirector(playerProcess.getErrorStream(), OutputStream.nullOutputStream()));
//Thread err = new Thread(new StreamRedirectThread(playerProcess.getErrorStream(), System.err));
err.setName("Player stderr pipe");
err.setDaemon(true);
diff --git a/common/src/main/java/ctbrec/event/ExecuteProgram.java b/common/src/main/java/ctbrec/event/ExecuteProgram.java
index 5bb2b321..4c9caebc 100644
--- a/common/src/main/java/ctbrec/event/ExecuteProgram.java
+++ b/common/src/main/java/ctbrec/event/ExecuteProgram.java
@@ -7,7 +7,7 @@ import org.slf4j.LoggerFactory;
import ctbrec.OS;
import ctbrec.event.EventHandlerConfiguration.ActionConfiguration;
-import ctbrec.io.StreamRedirectThread;
+import ctbrec.io.StreamRedirector;
public class ExecuteProgram extends Action {
@@ -38,11 +38,11 @@ public class ExecuteProgram extends Action {
// create threads, which read stdout and stderr of the player process. these are needed,
// because otherwise the internal buffer for these streams fill up and block the process
- Thread std = new Thread(new StreamRedirectThread(process.getInputStream(), System.out));
+ Thread std = new Thread(new StreamRedirector(process.getInputStream(), System.out));
std.setName("Player stdout pipe");
std.setDaemon(true);
std.start();
- Thread err = new Thread(new StreamRedirectThread(process.getErrorStream(), System.err));
+ Thread err = new Thread(new StreamRedirector(process.getErrorStream(), System.err));
err.setName("Player stderr pipe");
err.setDaemon(true);
err.start();
diff --git a/common/src/main/java/ctbrec/io/StreamRedirectThread.java b/common/src/main/java/ctbrec/io/StreamRedirector.java
similarity index 83%
rename from common/src/main/java/ctbrec/io/StreamRedirectThread.java
rename to common/src/main/java/ctbrec/io/StreamRedirector.java
index 64983e1b..7e4cdcf4 100644
--- a/common/src/main/java/ctbrec/io/StreamRedirectThread.java
+++ b/common/src/main/java/ctbrec/io/StreamRedirector.java
@@ -6,13 +6,13 @@ import java.io.OutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class StreamRedirectThread implements Runnable {
- private static final Logger LOG = LoggerFactory.getLogger(StreamRedirectThread.class);
+public class StreamRedirector implements Runnable {
+ private static final Logger LOG = LoggerFactory.getLogger(StreamRedirector.class);
private InputStream in;
private OutputStream out;
- public StreamRedirectThread(InputStream in, OutputStream out) {
+ public StreamRedirector(InputStream in, OutputStream out) {
super();
this.in = in;
this.out = out;
diff --git a/common/src/main/java/ctbrec/recorder/download/VideoLengthDetector.java b/common/src/main/java/ctbrec/recorder/download/VideoLengthDetector.java
index c06f0adc..450c3574 100644
--- a/common/src/main/java/ctbrec/recorder/download/VideoLengthDetector.java
+++ b/common/src/main/java/ctbrec/recorder/download/VideoLengthDetector.java
@@ -13,7 +13,7 @@ import org.slf4j.LoggerFactory;
import ctbrec.OS;
import ctbrec.io.DevNull;
-import ctbrec.io.StreamRedirectThread;
+import ctbrec.io.StreamRedirector;
public class VideoLengthDetector {
private static final Logger LOG = LoggerFactory.getLogger(VideoLengthDetector.class);
@@ -39,8 +39,8 @@ public class VideoLengthDetector {
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));
+ Thread stdout = new Thread(new StreamRedirector(ffmpeg.getInputStream(), new DevNull()));
+ Thread stderr = new Thread(new StreamRedirector(ffmpeg.getErrorStream(), stdErrBuffer));
stdout.start();
stderr.start();
exitCode = ffmpeg.waitFor();
diff --git a/common/src/main/java/ctbrec/recorder/download/dash/FfmpegMuxer.java b/common/src/main/java/ctbrec/recorder/download/dash/FfmpegMuxer.java
index 47723943..7b23b6ac 100644
--- a/common/src/main/java/ctbrec/recorder/download/dash/FfmpegMuxer.java
+++ b/common/src/main/java/ctbrec/recorder/download/dash/FfmpegMuxer.java
@@ -12,7 +12,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.OS;
-import ctbrec.io.StreamRedirectThread;
+import ctbrec.io.StreamRedirector;
import ctbrec.recorder.download.ProcessExitedUncleanException;
public class FfmpegMuxer {
@@ -99,8 +99,8 @@ public class FfmpegMuxer {
// @formatter:on
LOG.debug("Command line: {}", Arrays.toString(cmdline));
Process ffmpeg = Runtime.getRuntime().exec(cmdline);
- Thread stdout = new Thread(new StreamRedirectThread(ffmpeg.getInputStream(), muxLogStream));
- Thread stderr = new Thread(new StreamRedirectThread(ffmpeg.getErrorStream(), muxLogStream));
+ Thread stdout = new Thread(new StreamRedirector(ffmpeg.getInputStream(), muxLogStream));
+ Thread stderr = new Thread(new StreamRedirector(ffmpeg.getErrorStream(), muxLogStream));
stdout.start();
stderr.start();
int exitCode = ffmpeg.waitFor();
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/FFmpegDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/FFmpegDownload.java
index 1febf103..6ef507a2 100644
--- a/common/src/main/java/ctbrec/recorder/download/hls/FFmpegDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/hls/FFmpegDownload.java
@@ -24,7 +24,7 @@ import ctbrec.Model;
import ctbrec.OS;
import ctbrec.Recording;
import ctbrec.io.HttpClient;
-import ctbrec.io.StreamRedirectThread;
+import ctbrec.io.StreamRedirector;
import ctbrec.recorder.download.ProcessExitedUncleanException;
/**
@@ -73,8 +73,8 @@ public class FFmpegDownload extends AbstractHlsDownload {
File ffmpegLog = File.createTempFile(targetFile.getName(), ".log");
ffmpegLog.deleteOnExit();
try (FileOutputStream mergeLogStream = new FileOutputStream(ffmpegLog)) {
- Thread stdout = new Thread(new StreamRedirectThread(ffmpeg.getInputStream(), mergeLogStream));
- Thread stderr = new Thread(new StreamRedirectThread(ffmpeg.getErrorStream(), mergeLogStream));
+ 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();
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java
index ed8f9d70..d5a0767d 100644
--- a/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java
@@ -40,7 +40,7 @@ import ctbrec.Recording;
import ctbrec.io.BandwidthMeter;
import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
-import ctbrec.io.StreamRedirectThread;
+import ctbrec.io.StreamRedirector;
import ctbrec.recorder.ProgressListener;
import ctbrec.recorder.download.HttpHeaderFactory;
import ctbrec.recorder.download.ProcessExitedUncleanException;
@@ -153,8 +153,8 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
File ffmpegLog = File.createTempFile(target.getName(), ".log");
ffmpegLog.deleteOnExit();
try (FileOutputStream mergeLogStream = new FileOutputStream(ffmpegLog)) {
- Thread stdout = new Thread(new StreamRedirectThread(ffmpeg.getInputStream(), mergeLogStream));
- Thread stderr = new Thread(new StreamRedirectThread(ffmpeg.getErrorStream(), mergeLogStream));
+ 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();
diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/CreateContactSheet.java b/common/src/main/java/ctbrec/recorder/postprocessing/CreateContactSheet.java
index 392f9872..7db00d4c 100644
--- a/common/src/main/java/ctbrec/recorder/postprocessing/CreateContactSheet.java
+++ b/common/src/main/java/ctbrec/recorder/postprocessing/CreateContactSheet.java
@@ -14,7 +14,7 @@ import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.OS;
import ctbrec.Recording;
-import ctbrec.io.StreamRedirectThread;
+import ctbrec.io.StreamRedirector;
import ctbrec.recorder.RecordingManager;
public class CreateContactSheet extends AbstractPlaceholderAwarePostProcessor {
@@ -85,8 +85,8 @@ public class CreateContactSheet extends AbstractPlaceholderAwarePostProcessor {
File ffmpegLog = File.createTempFile("create_contact_sheet_" + rec.getId() + '_', ".log");
ffmpegLog.deleteOnExit();
try (FileOutputStream mergeLogStream = new FileOutputStream(ffmpegLog)) {
- Thread stdout = new Thread(new StreamRedirectThread(ffmpeg.getInputStream(), mergeLogStream));
- Thread stderr = new Thread(new StreamRedirectThread(ffmpeg.getErrorStream(), mergeLogStream));
+ 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();
diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java b/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java
index bc811a54..941989bb 100644
--- a/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java
+++ b/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java
@@ -14,7 +14,7 @@ import ctbrec.Config;
import ctbrec.OS;
import ctbrec.Recording;
import ctbrec.io.IoUtils;
-import ctbrec.io.StreamRedirectThread;
+import ctbrec.io.StreamRedirector;
import ctbrec.recorder.RecordingManager;
import ctbrec.recorder.download.ProcessExitedUncleanException;
@@ -76,8 +76,8 @@ public class Remux extends AbstractPostProcessor {
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 StreamRedirectThread(ffmpeg.getInputStream(), mergeLogStream));
- Thread stderr = new Thread(new StreamRedirectThread(ffmpeg.getErrorStream(), mergeLogStream));
+ 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();
diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Script.java b/common/src/main/java/ctbrec/recorder/postprocessing/Script.java
index 69cfb779..ec5c8eb7 100644
--- a/common/src/main/java/ctbrec/recorder/postprocessing/Script.java
+++ b/common/src/main/java/ctbrec/recorder/postprocessing/Script.java
@@ -13,7 +13,7 @@ import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.OS;
import ctbrec.Recording;
-import ctbrec.io.StreamRedirectThread;
+import ctbrec.io.StreamRedirector;
import ctbrec.recorder.RecordingManager;
import ctbrec.recorder.download.ProcessExitedUncleanException;
@@ -61,11 +61,11 @@ public class Script extends AbstractPlaceholderAwarePostProcessor {
}
private void startLogging(Process process, FileOutputStream logStream) {
- Thread std = new Thread(new StreamRedirectThread(process.getInputStream(), logStream));
+ Thread std = new Thread(new StreamRedirector(process.getInputStream(), logStream));
std.setName("Process stdout pipe");
std.setDaemon(true);
std.start();
- Thread err = new Thread(new StreamRedirectThread(process.getErrorStream(), logStream));
+ Thread err = new Thread(new StreamRedirector(process.getErrorStream(), logStream));
err.setName("Process stderr pipe");
err.setDaemon(true);
err.start();
From f6a0a5dcaa5d446c79aca81bdc6402129e5fd227 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 31 Oct 2020 22:10:31 +0100
Subject: [PATCH 03/80] Update http user agent string
---
CHANGELOG.md | 4 ++++
common/src/main/java/ctbrec/Settings.java | 4 ++--
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 158fa26d..ce23cf51 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+3.10.4
+========================
+* Fix: Bongacams login
+
3.10.3
========================
* Fix: Recordings couldn't be found in client server setup, if the client was
diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java
index 09b5acc2..fc67ce73 100644
--- a/common/src/main/java/ctbrec/Settings.java
+++ b/common/src/main/java/ctbrec/Settings.java
@@ -61,8 +61,8 @@ public class Settings {
public int httpSecurePort = 8443;
public String httpServer = "localhost";
public int httpTimeout = 10000;
- public String httpUserAgent = "Mozilla/5.0 Gecko/20100101 Firefox/73.0";
- public String httpUserAgentMobile = "Mozilla/5.0 (Android 9.0; Mobile; rv:73.0) Gecko/63.0 Firefox/73.0";
+ public String httpUserAgent = "Mozilla/5.0 (X11; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0";
+ public String httpUserAgentMobile = "Mozilla/5.0 (Android 9.0; Mobile; rv:82.0) Gecko/82.0 Firefox/82.0";
public byte[] key = null;
public String lastDownloadDir = "";
public String livejasminBaseUrl = "https://www.livejasmin.com";
From 57efe8b186bc5995be32727ec005e9fd82eaeaef Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 31 Oct 2020 22:14:23 +0100
Subject: [PATCH 04/80] Update changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce23cf51..7d35f63b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
3.10.4
========================
* Fix: Bongacams login
+* Update minimal browser to Electron 10.1.5
3.10.3
========================
From 17b0a51d0286456ee50ad2a159f7926e101118c2 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 31 Oct 2020 22:19:15 +0100
Subject: [PATCH 05/80] Set version to 3.10.4
---
client/pom.xml | 2 +-
common/pom.xml | 2 +-
master/pom.xml | 2 +-
server/pom.xml | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/client/pom.xml b/client/pom.xml
index ea93eea8..f51b964e 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.3
+ 3.10.4
../master
diff --git a/common/pom.xml b/common/pom.xml
index b606300a..c81ebb05 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.3
+ 3.10.4
../master
diff --git a/master/pom.xml b/master/pom.xml
index 1e2b9a5a..8e74add4 100644
--- a/master/pom.xml
+++ b/master/pom.xml
@@ -6,7 +6,7 @@
ctbrec
master
pom
- 3.10.3
+ 3.10.4
../common
diff --git a/server/pom.xml b/server/pom.xml
index a30cc623..7dc0af29 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.3
+ 3.10.4
../master
From 3cb618ae42db2e7c1a369ee321c5e772e27db04b Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 1 Nov 2020 11:25:45 +0100
Subject: [PATCH 06/80] Make resizing of filter and model input more equal
---
.../src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java b/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
index 005fb456..1bbbef4f 100644
--- a/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
@@ -269,7 +269,9 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
ObservableList suggestions = FXCollections.observableArrayList();
sites.forEach(site -> suggestions.add(site.getClass().getSimpleName()));
model = new AutoFillTextField(new ObservableListSuggester(suggestions));
- model.setPrefWidth(600);
+ model.minWidth(150);
+ model.prefWidth(600);
+ HBox.setHgrow(model, Priority.ALWAYS);
model.setPromptText("e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/");
model.onActionHandler(this::addModel);
model.setTooltip(new Tooltip("To add a model enter SiteName:ModelName\n" +
@@ -291,8 +293,12 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
filterContainer.setSpacing(0);
filterContainer.setPadding(new Insets(0));
filterContainer.setAlignment(Pos.CENTER_RIGHT);
+ filterContainer.minWidth(100);
+ filterContainer.prefWidth(150);
HBox.setHgrow(filterContainer, Priority.ALWAYS);
filter = new SearchBox(false);
+ filter.minWidth(100);
+ filter.prefWidth(150);
filter.setPromptText("Filter");
filter.textProperty().addListener( (observableValue, oldValue, newValue) -> {
String q = filter.getText();
From 80ea981644514074bdc9acfb74925e94243fa183 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 1 Nov 2020 16:50:46 +0100
Subject: [PATCH 07/80] Fix login page simplifaction manipulation
---
.../ui/sites/bonga/BongaCamsElectronLoginDialog.java | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsElectronLoginDialog.java b/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsElectronLoginDialog.java
index aa496ec9..40bff63c 100644
--- a/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsElectronLoginDialog.java
+++ b/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsElectronLoginDialog.java
@@ -67,11 +67,10 @@ public class BongaCamsElectronLoginDialog {
browser.executeJavaScript("document.getElementById('log_in_password').value = '" + password + "';");
}
String[] simplify = new String[] {
- "$('div#header').css('display','none');",
- "$('div.footer').css('display','none');",
- "$('div.footer_copy').css('display','none')",
- "$('div[class~=\"banner_top_index\"]').css('display','none');",
- "$('td.menu_container').css('display','none');",
+ "$('div[class~=\"page_header\"]').css('display','none');",
+ "$('div[class~=\"header_bar\"]').css('display','none')",
+ "$('footer').css('display','none');",
+ "$('div[class~=\"footer_copy\"]').css('display','none')",
"$('div[class~=\"fancybox-overlay\"]').css('display','none');"
};
for (String js : simplify) {
From 8df1ff72e7a4ac8ce2ddcbe8b3084d28bc8890c7 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 1 Nov 2020 19:12:47 +0100
Subject: [PATCH 08/80] Make recording indicator clickable
You can now click on the recording / pause indicator to pause or resume
recording in the thubmnail overview tabs
---
CHANGELOG.md | 7 +++++
.../main/java/ctbrec/ui/PauseIndicator.java | 26 +++++++++++--------
.../main/java/ctbrec/ui/tabs/ThumbCell.java | 4 +++
3 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d35f63b..fc5037db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,13 @@
+3.10.5
+========================
+* You can now click on the recording / pause indicator to pause or resume
+ recording in the thubmnail overview tabs
+* Some smaller UI tweaks
+
3.10.4
========================
* Fix: Bongacams login
+* Fix: Minimal browser would freeze on windows
* Update minimal browser to Electron 10.1.5
3.10.3
diff --git a/client/src/main/java/ctbrec/ui/PauseIndicator.java b/client/src/main/java/ctbrec/ui/PauseIndicator.java
index f716b871..b7834c3e 100644
--- a/client/src/main/java/ctbrec/ui/PauseIndicator.java
+++ b/client/src/main/java/ctbrec/ui/PauseIndicator.java
@@ -1,18 +1,22 @@
package ctbrec.ui;
-import javafx.scene.layout.HBox;
+
import javafx.scene.paint.Color;
-import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.Polygon;
-public class PauseIndicator extends HBox {
+public class PauseIndicator extends Polygon {
- public PauseIndicator(Color c, int size) {
- spacingProperty().setValue(size*1/5);
- Rectangle left = new Rectangle(size*2/5, size);
- left.setFill(c);
- Rectangle right = new Rectangle(size*2/5, size);
- right.setFill(c);
- getChildren().add(left);
- getChildren().add(right);
+ public PauseIndicator(Color color, int size) {
+ super(
+ 0, size,
+ 0, 0,
+ (size * 2.0 / 5.0), 0,
+ (size * 2.0 / 5.0), size,
+ (size * 3.0 / 5.0), size,
+ (size * 3.0 / 5.0), 0,
+ size, 0,
+ size, size
+ );
+ setFill(color);
}
}
diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
index 653678e0..e7fe9a60 100644
--- a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
+++ b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
@@ -177,12 +177,16 @@ public class ThumbCell extends StackPane {
recordingIndicator = new Circle(8);
recordingIndicator.setFill(colorRecording);
+ recordingIndicator.setCursor(Cursor.HAND);
+ recordingIndicator.setOnMouseClicked(e -> pauseResumeAction(true));
StackPane.setMargin(recordingIndicator, new Insets(3));
StackPane.setAlignment(recordingIndicator, Pos.TOP_LEFT);
getChildren().add(recordingIndicator);
pausedIndicator = new PauseIndicator(colorRecording, 16);
pausedIndicator.setVisible(false);
+ pausedIndicator.setCursor(Cursor.HAND);
+ pausedIndicator.setOnMouseClicked(e -> pauseResumeAction(false));
StackPane.setMargin(pausedIndicator, new Insets(3));
StackPane.setAlignment(pausedIndicator, Pos.TOP_LEFT);
getChildren().add(pausedIndicator);
From 20473f9a236f2cefd0037b9139ab05bbba25baef Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 8 Nov 2020 13:40:10 +0100
Subject: [PATCH 09/80] Change MFC websocket URL to the TLS one
---
common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java
index 5cc44079..0320396c 100644
--- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java
+++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java
@@ -105,7 +105,7 @@ public class MyFreeCamsClient {
}
String server = websocketServers.get(new Random().nextInt(websocketServers.size() - 1));
- String wsUrl = "ws://" + server + ".myfreecams.com:8080/fcsl";
+ String wsUrl = "wss://" + server + ".myfreecams.com/fcsl";
LOG.debug("Connecting to random websocket server {}", wsUrl);
Thread watchDog = new Thread(() -> {
From 13eef32ffb21e8268f2380916f9d28958de7ecf8 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 14 Nov 2020 19:25:12 +0100
Subject: [PATCH 10/80] Move ThumbCell.css to package of ThumbCell.java
---
client/src/main/java/ctbrec/ui/CamrecApplication.java | 2 +-
client/src/main/java/ctbrec/ui/{ => tabs}/ThumbCell.css | 0
2 files changed, 1 insertion(+), 1 deletion(-)
rename client/src/main/java/ctbrec/ui/{ => tabs}/ThumbCell.css (100%)
diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java
index d1f04896..c2337997 100644
--- a/client/src/main/java/ctbrec/ui/CamrecApplication.java
+++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java
@@ -206,10 +206,10 @@ public class CamrecApplication extends Application {
}
loadStyleSheet(primaryStage, "style.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/settings/ColorSettingsPane.css");
- primaryStage.getScene().getStylesheets().add("/ctbrec/ui/ThumbCell.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/SearchBox.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/Popover.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/settings/api/Preferences.css");
+ primaryStage.getScene().getStylesheets().add("/ctbrec/ui/tabs/ThumbCell.css");
primaryStage.getScene().widthProperty().addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowWidth = newVal.intValue());
primaryStage.getScene().heightProperty()
.addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowHeight = newVal.intValue());
diff --git a/client/src/main/java/ctbrec/ui/ThumbCell.css b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.css
similarity index 100%
rename from client/src/main/java/ctbrec/ui/ThumbCell.css
rename to client/src/main/java/ctbrec/ui/tabs/ThumbCell.css
From e96bdc1c7e72a613c947c88e70d2916a2f552d59 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 14 Nov 2020 19:25:51 +0100
Subject: [PATCH 11/80] Adjust mouse scroll speed to height of the scrollpane
content
---
.../FasterVerticalScrollPaneSkin.java | 31 +++++++++++++++++++
.../java/ctbrec/ui/tabs/ThumbOverviewTab.java | 3 ++
2 files changed, 34 insertions(+)
create mode 100644 client/src/main/java/ctbrec/ui/controls/FasterVerticalScrollPaneSkin.java
diff --git a/client/src/main/java/ctbrec/ui/controls/FasterVerticalScrollPaneSkin.java b/client/src/main/java/ctbrec/ui/controls/FasterVerticalScrollPaneSkin.java
new file mode 100644
index 00000000..fee2f219
--- /dev/null
+++ b/client/src/main/java/ctbrec/ui/controls/FasterVerticalScrollPaneSkin.java
@@ -0,0 +1,31 @@
+package ctbrec.ui.controls;
+
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.skin.ScrollPaneSkin;
+import javafx.scene.input.ScrollEvent;
+
+public class FasterVerticalScrollPaneSkin extends ScrollPaneSkin {
+
+ public FasterVerticalScrollPaneSkin(final ScrollPane scrollPane) {
+ super(scrollPane);
+
+ getSkinnable().addEventFilter(ScrollEvent.SCROLL, event -> {
+ double ratio = scrollPane.getViewportBounds().getHeight() / scrollPane.getContent().getBoundsInLocal().getHeight();
+ double baseUnitIncrement = 0.15;
+ double unitIncrement = baseUnitIncrement * ratio * 1.25;
+ getVerticalScrollBar().setUnitIncrement(unitIncrement);
+
+ if (event.getDeltaX() < 0) {
+ getHorizontalScrollBar().increment();
+ } else if (event.getDeltaX() > 0) {
+ getHorizontalScrollBar().decrement();
+ }
+ if (event.getDeltaY() < 0) {
+ getVerticalScrollBar().increment();
+ } else if (event.getDeltaY() > 0) {
+ getVerticalScrollBar().decrement();
+ }
+ event.consume();
+ });
+ }
+}
diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java b/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
index bdf13d8b..09ceac00 100644
--- a/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
@@ -42,6 +42,7 @@ import ctbrec.ui.action.OpenRecordingsDir;
import ctbrec.ui.controls.SearchBox;
import ctbrec.ui.controls.SearchPopover;
import ctbrec.ui.controls.SearchPopoverTreeList;
+import ctbrec.ui.controls.FasterVerticalScrollPaneSkin;
import javafx.animation.FadeTransition;
import javafx.animation.Interpolator;
import javafx.animation.ParallelTransition;
@@ -196,6 +197,8 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
scrollPane.setContent(grid);
scrollPane.setFitToHeight(true);
scrollPane.setFitToWidth(true);
+ FasterVerticalScrollPaneSkin scrollPaneSkin = new FasterVerticalScrollPaneSkin(scrollPane);
+ scrollPane.setSkin(scrollPaneSkin);
BorderPane.setMargin(scrollPane, new Insets(5));
pagination = new HBox(5);
From dc8d288b05fe47e50512d5044682cc9d2a0b1867 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 15 Nov 2020 14:47:07 +0100
Subject: [PATCH 12/80] Add shortcuts for pagination
---
.../java/ctbrec/ui/tabs/ThumbOverviewTab.java | 67 ++++++++++++-------
1 file changed, 43 insertions(+), 24 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java b/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
index 09ceac00..7f21296a 100644
--- a/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
@@ -39,10 +39,10 @@ import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.TipDialog;
import ctbrec.ui.TokenLabel;
import ctbrec.ui.action.OpenRecordingsDir;
+import ctbrec.ui.controls.FasterVerticalScrollPaneSkin;
import ctbrec.ui.controls.SearchBox;
import ctbrec.ui.controls.SearchPopover;
import ctbrec.ui.controls.SearchPopoverTreeList;
-import ctbrec.ui.controls.FasterVerticalScrollPaneSkin;
import javafx.animation.FadeTransition;
import javafx.animation.Interpolator;
import javafx.animation.ParallelTransition;
@@ -58,6 +58,7 @@ import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.Worker.State;
import javafx.concurrent.WorkerStateEvent;
+import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@@ -208,29 +209,13 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
pagination.getChildren().add(pageInput);
BorderPane.setMargin(pagination, new Insets(5));
pageInput.setPrefWidth(50);
- pageInput.setOnAction(e -> handlePageNumberInput());
+ pageInput.setOnAction(e -> handlePageNumberInput(e));
pageFirst.setTooltip(new Tooltip("First Page"));
- pageFirst.setOnAction(e -> {
- pageInput.setText(Integer.toString(1));
- updateService.setPage(1);
- restartUpdateService();
- });
+ pageFirst.setOnAction(e -> changePageTo(1));
pagePrev.setTooltip(new Tooltip("Previous Page"));
- pagePrev.setOnAction(e -> {
- int page = updateService.getPage();
- page = Math.max(1, --page);
- pageInput.setText(Integer.toString(page));
- updateService.setPage(page);
- restartUpdateService();
- });
+ pagePrev.setOnAction(e -> previousPage());
pageNext.setTooltip(new Tooltip("Next Page"));
- pageNext.setOnAction(e -> {
- int page = updateService.getPage();
- page++;
- pageInput.setText(Integer.toString(page));
- updateService.setPage(page);
- restartUpdateService();
- });
+ pageNext.setOnAction(e -> nextPage());
HBox thumbSizeSelector = new HBox(5);
Label l = new Label("Thumb Size");
@@ -266,6 +251,41 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
root.getChildren().add(borderPane);
root.getChildren().add(popover);
setContent(root);
+
+ scrollPane.setOnKeyReleased(new EventHandler() {
+ @Override
+ public void handle(KeyEvent event) {
+ if (event.getCode() == KeyCode.RIGHT) {
+ System.out.println(event.getSource());
+ nextPage();
+ } else if (event.getCode() == KeyCode.LEFT) {
+ System.out.println(event.getSource());
+ previousPage();
+ } else if (event.getCode().getCode() >= KeyCode.DIGIT1.getCode() && event.getCode().getCode() <= KeyCode.DIGIT9.getCode()) {
+ System.out.println(event.getSource());
+ changePageTo(event.getCode().getCode() - 48);
+ }
+ }
+ });
+ }
+
+ private void nextPage() {
+ int page = updateService.getPage();
+ page++;
+ changePageTo(page);
+ }
+
+
+ private void previousPage() {
+ int page = updateService.getPage();
+ page = Math.max(1, --page);
+ changePageTo(page);
+ }
+
+ private void changePageTo(int page) {
+ pageInput.setText(Integer.toString(page));
+ updateService.setPage(page);
+ restartUpdateService();
}
private ChangeListener super String> search() {
@@ -295,12 +315,11 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
}
}
- private void handlePageNumberInput() {
+ private void handlePageNumberInput(ActionEvent event) {
try {
int page = Integer.parseInt(pageInput.getText());
page = Math.max(1, page);
- updateService.setPage(page);
- restartUpdateService();
+ changePageTo(page);
} catch(NumberFormatException e) {
// noop
} finally {
From 0e7b5b5452edd5624e9ec7084f5bcebd2aa92fb1 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 21 Nov 2020 16:06:45 +0100
Subject: [PATCH 13/80] Fix: date placeholders with patterns with more than one
ocurrence are
---
CHANGELOG.md | 9 +++++++--
.../AbstractPlaceholderAwarePostProcessor.java | 11 +++++++----
.../AbstractPlaceholderAwarePostProcessorTest.java | 10 ++++++++++
3 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc5037db..c42600bc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,13 @@
3.10.5
========================
-* You can now click on the recording / pause indicator to pause or resume
- recording in the thubmnail overview tabs
+* MFC websocket now uses the TLS URL
+* Fix: date placeholders with patterns with more than one ocurrence are
+ replaced with the value of the first one
* Some smaller UI tweaks
+ - adjusted component sizes for small resolutions
+ - recording indicator can now be used to pause / resume the recording
+ - adjusted scroll speed in the thumbnail overviews
+ - added shortcuts for the thumbnail overviews (keys 1-9 and arrow keys)
3.10.4
========================
diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java b/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java
index 3cf306e5..bb2ae13f 100644
--- a/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java
+++ b/common/src/main/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessor.java
@@ -67,15 +67,18 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost
private String replaceDateTime(Recording rec, String filename, String placeHolder, ZoneId zone) {
String pattern = "yyyy-MM-dd_HH-mm-ss";
- Matcher m = Pattern.compile("\\$\\{" + placeHolder + "(?:\\((.*?)\\))?\\}").matcher(filename);
- if (m.find()) {
+ Pattern regex = Pattern.compile("\\$\\{" + placeHolder + "(?:\\((.*?)\\))?\\}");
+ Matcher m = regex.matcher(filename);
+ while (m.find()) {
String p = m.group(1);
if (p != null) {
pattern = p;
}
+ String formattedDate = getDateTime(rec, pattern, zone);
+ filename = m.replaceFirst(formattedDate);
+ m = regex.matcher(filename);
}
- String formattedDate = getDateTime(rec, pattern, zone);
- return m.replaceAll(formattedDate);
+ return filename;
}
private String getDateTime(Recording rec, String pattern, ZoneId zone) {
diff --git a/common/src/test/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessorTest.java b/common/src/test/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessorTest.java
index 04e4e13d..e3e6797d 100644
--- a/common/src/test/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessorTest.java
+++ b/common/src/test/java/ctbrec/recorder/postprocessing/AbstractPlaceholderAwarePostProcessorTest.java
@@ -54,6 +54,7 @@ public class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
@Test
public void testUtcTimeReplacement() {
+ // without user defined pattern
String date = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")
.withLocale(Locale.US)
.withZone(ZoneOffset.UTC)
@@ -61,12 +62,21 @@ public class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
String input = "asdf_${utcDateTime}_asdf";
assertEquals("asdf_" + date + "_asdf", placeHolderAwarePp.fillInPlaceHolders(input, rec, config));
+ // with user defined pattern
date = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")
.withLocale(Locale.US)
.withZone(ZoneOffset.UTC)
.format(rec.getStartDate());
input = "asdf_${utcDateTime(yyyyMMdd-HHmmss)}_asdf";
assertEquals("asdf_" + date + "_asdf", placeHolderAwarePp.fillInPlaceHolders(input, rec, config));
+
+ // multiple occurences with user defined patterns
+ date = DateTimeFormatter.ofPattern("yyyy-MM-dd/yyyy")
+ .withLocale(Locale.US)
+ .withZone(ZoneOffset.UTC)
+ .format(rec.getStartDate());
+ input = "asdf_${utcDateTime(yyyy)}-${utcDateTime(MM)}-${utcDateTime(dd)}/${utcDateTime(yyyy)}_asdf";
+ assertEquals("asdf_" + date + "_asdf", placeHolderAwarePp.fillInPlaceHolders(input, rec, config));
}
@Test
From eb1db87e8268a6a59e91a4ec1a9ab933d74163cf Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 21 Nov 2020 18:43:57 +0100
Subject: [PATCH 14/80] Improve recording PausedIndicator
Make the whole area clickable and not only the painted area
---
.../{PauseIndicator.java => PauseIcon.java} | 4 +--
.../ctbrec/ui/controls/PausedIndicator.java | 36 +++++++++++++++++++
.../main/java/ctbrec/ui/tabs/ThumbCell.java | 10 ++----
3 files changed, 41 insertions(+), 9 deletions(-)
rename client/src/main/java/ctbrec/ui/{PauseIndicator.java => PauseIcon.java} (81%)
create mode 100644 client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
diff --git a/client/src/main/java/ctbrec/ui/PauseIndicator.java b/client/src/main/java/ctbrec/ui/PauseIcon.java
similarity index 81%
rename from client/src/main/java/ctbrec/ui/PauseIndicator.java
rename to client/src/main/java/ctbrec/ui/PauseIcon.java
index b7834c3e..30a1ff24 100644
--- a/client/src/main/java/ctbrec/ui/PauseIndicator.java
+++ b/client/src/main/java/ctbrec/ui/PauseIcon.java
@@ -4,9 +4,9 @@ package ctbrec.ui;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
-public class PauseIndicator extends Polygon {
+public class PauseIcon extends Polygon {
- public PauseIndicator(Color color, int size) {
+ public PauseIcon(Color color, int size) {
super(
0, size,
0, 0,
diff --git a/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java b/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
new file mode 100644
index 00000000..8483e3d9
--- /dev/null
+++ b/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
@@ -0,0 +1,36 @@
+package ctbrec.ui.controls;
+
+import ctbrec.ui.PauseIcon;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Cursor;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Rectangle;
+
+public class PausedIndicator extends StackPane {
+
+ private PauseIcon pausedIcon;
+ private Rectangle clickPanel;
+
+ public PausedIndicator(int size, Color color) {
+ pausedIcon = new PauseIcon(color, size);
+ pausedIcon.setVisible(false);
+ clickPanel = new Rectangle(size, size);
+ clickPanel.setCursor(Cursor.HAND);
+ clickPanel.setFill(Paint.valueOf("#00000000"));
+ getChildren().add(pausedIcon);
+ getChildren().add(clickPanel);
+
+ StackPane.setMargin(pausedIcon, new Insets(3));
+ StackPane.setAlignment(pausedIcon, Pos.TOP_LEFT);
+ StackPane.setMargin(clickPanel, new Insets(3));
+ StackPane.setAlignment(clickPanel, Pos.TOP_LEFT);
+
+ pausedIcon.visibleProperty().bindBidirectional(visibleProperty());
+ pausedIcon.onMouseClickedProperty().bindBidirectional(onMouseClickedProperty());
+ clickPanel.onMouseClickedProperty().bindBidirectional(onMouseClickedProperty());
+ }
+}
+
diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
index e7fe9a60..fdb7cea8 100644
--- a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
+++ b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
@@ -27,10 +27,10 @@ import ctbrec.io.HttpException;
import ctbrec.recorder.Recorder;
import ctbrec.ui.AutosizeAlert;
import ctbrec.ui.CamrecApplication;
-import ctbrec.ui.PauseIndicator;
import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.StreamSourceSelectionDialog;
import ctbrec.ui.action.PlayAction;
+import ctbrec.ui.controls.PausedIndicator;
import ctbrec.ui.controls.StreamPreview;
import javafx.animation.FadeTransition;
import javafx.animation.FillTransition;
@@ -85,7 +85,7 @@ public class ThumbCell extends StackPane {
private Text resolutionTag;
private Recorder recorder;
private Circle recordingIndicator;
- private PauseIndicator pausedIndicator;
+ private PausedIndicator pausedIndicator;
private int index = 0;
ContextMenu popup;
private static final Color colorNormal = Color.BLACK;
@@ -183,12 +183,8 @@ public class ThumbCell extends StackPane {
StackPane.setAlignment(recordingIndicator, Pos.TOP_LEFT);
getChildren().add(recordingIndicator);
- pausedIndicator = new PauseIndicator(colorRecording, 16);
- pausedIndicator.setVisible(false);
- pausedIndicator.setCursor(Cursor.HAND);
+ pausedIndicator = new PausedIndicator(16, colorRecording);
pausedIndicator.setOnMouseClicked(e -> pauseResumeAction(false));
- StackPane.setMargin(pausedIndicator, new Insets(3));
- StackPane.setAlignment(pausedIndicator, Pos.TOP_LEFT);
getChildren().add(pausedIndicator);
if (Config.getInstance().getSettings().livePreviews) {
From 3db5d16bb5c52df1368cf31404ba50d30d63fce4 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 21 Nov 2020 18:44:41 +0100
Subject: [PATCH 15/80] Disable follow button, if credentials are not available
---
.../src/main/java/ctbrec/ui/controls/SearchPopoverTreeList.java | 1 +
client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java | 1 +
2 files changed, 2 insertions(+)
diff --git a/client/src/main/java/ctbrec/ui/controls/SearchPopoverTreeList.java b/client/src/main/java/ctbrec/ui/controls/SearchPopoverTreeList.java
index 1e3fb025..3b469739 100644
--- a/client/src/main/java/ctbrec/ui/controls/SearchPopoverTreeList.java
+++ b/client/src/main/java/ctbrec/ui/controls/SearchPopoverTreeList.java
@@ -221,6 +221,7 @@ public class SearchPopoverTreeList extends PopoverTreeList implements Pop
this.model = null;
} else {
follow.setVisible(model.getSite().supportsFollow());
+ follow.setDisable(!model.getSite().credentialsAvailable());
title.setVisible(true);
title.setText(model.getDisplayName());
this.model = model;
diff --git a/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java b/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
index 1bbbef4f..c432ea3a 100644
--- a/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
@@ -651,6 +651,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
switchStreamSource.setOnAction(e -> switchStreamSource(selectedModels.get(0)));
MenuItem follow = new MenuItem("Follow");
follow.setOnAction(e -> follow(selectedModels));
+ follow.setDisable(!selectedModels.stream().allMatch(m -> m.getSite().supportsFollow() && m.getSite().credentialsAvailable()));
MenuItem ignore = new MenuItem("Ignore");
ignore.setOnAction(e -> ignore(selectedModels));
MenuItem notes = new MenuItem("Notes");
From 69e13a7f378b8342c1aab045b59f93d8db46326d Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 21 Nov 2020 18:45:30 +0100
Subject: [PATCH 16/80] Add stop, pause and follow actions to context menu
---
.../java/ctbrec/ui/tabs/RecordingsTab.java | 105 +++++++++++-------
1 file changed, 62 insertions(+), 43 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
index 1a1e9f06..668c4a3a 100644
--- a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
@@ -23,11 +23,13 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
+import ctbrec.Model;
import ctbrec.Recording;
import ctbrec.Recording.State;
import ctbrec.StringUtil;
@@ -45,6 +47,9 @@ import ctbrec.ui.DesktopIntegration;
import ctbrec.ui.FileDownload;
import ctbrec.ui.JavaFxRecording;
import ctbrec.ui.Player;
+import ctbrec.ui.action.FollowAction;
+import ctbrec.ui.action.PauseAction;
+import ctbrec.ui.action.StopRecordingAction;
import ctbrec.ui.controls.DateTimeCellFactory;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.controls.Toast;
@@ -191,25 +196,23 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
public boolean isDownloadRunning() {
- return observableRecordings.stream()
- .map(Recording::getStatus)
- .anyMatch(s -> s == DOWNLOADING);
+ return observableRecordings.stream().map(Recording::getStatus).anyMatch(s -> s == DOWNLOADING);
}
private TableCell createSizeCell() {
TableCell cell = new TableCell() {
@Override
protected void updateItem(Number sizeInByte, boolean empty) {
- if(empty || sizeInByte == null) {
+ if (empty || sizeInByte == null) {
setText(null);
setStyle(null);
} else {
setText(StringUtil.formatSize(sizeInByte));
setStyle("-fx-alignment: CENTER-RIGHT;");
- if(Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
+ if (Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
int row = this.getTableRow().getIndex();
JavaFxRecording rec = tableViewProperty().get().getItems().get(row);
- if(!rec.valueChanged() && rec.getStatus() == RECORDING) {
+ if (!rec.valueChanged() && rec.getStatus() == RECORDING) {
setStyle("-fx-alignment: CENTER-RIGHT; -fx-background-color: red");
}
}
@@ -231,26 +234,26 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
private void onMousePressed(MouseEvent event) {
- if(popup != null) {
+ if (popup != null) {
popup.hide();
}
}
private void onMouseClicked(MouseEvent event) {
- if(event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
+ if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
Recording recording = table.getSelectionModel().getSelectedItem();
- if(recording != null) {
+ if (recording != null) {
play(recording);
}
}
}
- private void onKeyPressed( KeyEvent event ) {
+ private void onKeyPressed(KeyEvent event) {
List recordings = table.getSelectionModel().getSelectedItems();
if (recordings != null && !recordings.isEmpty()) {
State status = recordings.get(0).getStatus();
if (event.getCode() == KeyCode.DELETE) {
- if(recordings.size() > 1 || status == FINISHED || status == FAILED || status == WAITING) {
+ if (recordings.size() > 1 || status == FINISHED || status == FAILED || status == WAITING) {
delete(recordings);
}
} else if (event.getCode() == KeyCode.ENTER && status == FINISHED) {
@@ -279,8 +282,8 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
private void updateFreeSpaceDisplay() {
- if(spaceTotal != -1 && spaceFree != -1) {
- double free = ((double)spaceFree) / spaceTotal;
+ if (spaceTotal != -1 && spaceFree != -1) {
+ double free = ((double) spaceFree) / spaceTotal;
spaceLeft.setProgress(free);
double totalGiB = ((double) spaceTotal) / 1024 / 1024 / 1024;
double freeGiB = ((double) spaceFree) / 1024 / 1024 / 1024;
@@ -324,7 +327,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
private ScheduledService> createUpdateService() {
- ScheduledService> service = new ScheduledService>() {
+ ScheduledService> service = new ScheduledService>() {
@Override
protected Task> createTask() {
return new Task>() {
@@ -388,7 +391,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
JavaFxRecording first = recordings.get(0);
MenuItem openInPlayer = new MenuItem("Open in Player");
openInPlayer.setOnAction(e -> play(first));
- if(first.getStatus() == FINISHED || Config.getInstance().getSettings().localRecording) {
+ if (first.getStatus() == FINISHED || Config.getInstance().getSettings().localRecording) {
contextMenu.getItems().add(openInPlayer);
}
@@ -397,30 +400,33 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
openContactSheet.setDisable(first.getContactSheet().isEmpty());
contextMenu.getItems().add(openContactSheet);
- // TODO find a way to reenable this
- // MenuItem stopRecording = new MenuItem("Stop recording");
- // stopRecording.setOnAction((e) -> {
- // Model m = site.createModel(recording.getModelName());
- // try {
- // recorder.stopRecording(m);
- // } catch (Exception e1) {
- // showErrorDialog("Stop recording", "Couldn't stop recording of model " + m.getName(), e1);
- // }
- // });
- // if(recording.getStatus() == STATUS.RECORDING) {
- // contextMenu.getItems().add(stopRecording);
- // }
+ MenuItem stopRecording = new MenuItem("Stop Recording");
+ stopRecording.setOnAction(e -> stopRecording(recordings.stream().map(r -> r.getModel()).collect(Collectors.toList())));
+ if (recordings.stream().anyMatch(r -> r.getStatus() == RECORDING)) {
+ contextMenu.getItems().add(stopRecording);
+ }
+
+ MenuItem pauseRecording = new MenuItem("Pause Recording");
+ pauseRecording.setOnAction(e -> pauseRecording(recordings.stream().map(r -> r.getModel()).collect(Collectors.toList())));
+ if (recordings.stream().anyMatch(r -> r.getStatus() == RECORDING)) {
+ contextMenu.getItems().add(pauseRecording);
+ }
MenuItem deleteRecording = new MenuItem("Delete");
deleteRecording.setOnAction(e -> delete(recordings));
- if(first.getStatus() == FINISHED || first.getStatus() == WAITING || first.getStatus() == FAILED || recordings.size() > 1) {
+ if (first.getStatus() == FINISHED || first.getStatus() == WAITING || first.getStatus() == FAILED || recordings.size() > 1) {
contextMenu.getItems().add(deleteRecording);
deleteRecording.setDisable(recordings.stream().allMatch(Recording::isPinned));
}
+ MenuItem followModels = new MenuItem("Follow Model");
+ followModels.setOnAction(e -> follow(recordings.stream().map(r -> r.getModel()).collect(Collectors.toList())));
+ followModels.setDisable(!recordings.stream().map(r -> r.getModel()).allMatch(m -> m.getSite().supportsFollow() && m.getSite().credentialsAvailable()));
+ contextMenu.getItems().add(followModels);
+
MenuItem openDir = new MenuItem("Open directory");
openDir.setOnAction(e -> onOpenDirectory(first));
- if(Config.getInstance().getSettings().localRecording) {
+ if (Config.getInstance().getSettings().localRecording) {
contextMenu.getItems().add(openDir);
}
@@ -449,7 +455,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
contextMenu.getItems().add(rerunPostProcessing);
rerunPostProcessing.setDisable(!recordings.stream().allMatch(Recording::canBePostProcessed));
- if(recordings.size() > 1) {
+ if (recordings.size() > 1) {
openInPlayer.setDisable(true);
openDir.setDisable(true);
}
@@ -457,6 +463,18 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
return contextMenu;
}
+ private void follow(List selectedModels) {
+ new FollowAction(getTabPane(), selectedModels).execute();
+ }
+
+ private void stopRecording(List selectedModels) {
+ new StopRecordingAction(getTabPane(), selectedModels, recorder).execute();
+ }
+
+ private void pauseRecording(List selectedModels) {
+ new PauseAction(getTabPane(), selectedModels, recorder).execute();
+ }
+
private void openContactSheet(JavaFxRecording recording) {
if (config.getSettings().localRecording) {
recording.getContactSheet().ifPresent(f -> new Thread(() -> DesktopIntegration.open(f)).start());
@@ -487,7 +505,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
Node source = getTabPane();
String notes = recording.getNote();
Optional newNote = Dialogs.showTextInput(source.getScene(), "Recording Notes", "", notes);
- if(newNote.isPresent()) {
+ if (newNote.isPresent()) {
table.setCursor(Cursor.WAIT);
Thread backgroundThread = new Thread(() -> {
List exceptions = new ArrayList<>();
@@ -630,7 +648,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
private String proposeTargetFilename(Recording recording) {
- if(recording.isSingleFile()) {
+ if (recording.isSingleFile()) {
return recording.getPostProcessedFile().getName();
} else {
String downloadFilename = config.getSettings().downloadFilename;
@@ -665,7 +683,8 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
Platform.runLater(() -> {
recording.setStatus(FINISHED);
recording.setProgress(-1);
- RecordingStateChangedEvent evt = new RecordingStateChangedEvent(target, recording.getStatus(), recording.getModel(), recording.getStartDate());
+ RecordingStateChangedEvent evt = new RecordingStateChangedEvent(target, recording.getStatus(), recording.getModel(),
+ recording.getStartDate());
EventBusHolder.BUS.post(evt);
});
}
@@ -711,7 +730,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
@Override
public void run() {
boolean started = Player.play(recording);
- if(started && Config.getInstance().getSettings().showPlayerStarting) {
+ if (started && Config.getInstance().getSettings().showPlayerStarting) {
Platform.runLater(() -> Toast.makeText(getTabPane().getScene(), "Starting Player", 2000, 500, 500));
}
}
@@ -721,7 +740,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
private void delete(List recordings) {
table.setCursor(Cursor.WAIT);
String msg;
- if(recordings.size() > 1) {
+ if (recordings.size() > 1) {
msg = "Delete " + recordings.size() + " recordings for good?";
} else {
Recording r = recordings.get(0);
@@ -746,7 +765,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
List deleted = new ArrayList<>();
List exceptions = new ArrayList<>();
for (Iterator iterator = recordings.iterator(); iterator.hasNext();) {
- JavaFxRecording r = iterator.next();
+ JavaFxRecording r = iterator.next();
if (r.getStatus() != FINISHED && r.getStatus() != FAILED && r.getStatus() != WAITING) {
continue;
}
@@ -771,7 +790,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
public void saveState() {
- if(!table.getSortOrder().isEmpty()) {
+ if (!table.getSortOrder().isEmpty()) {
TableColumn col = table.getSortOrder().get(0);
Config.getInstance().getSettings().recordingsSortColumn = col.getText();
Config.getInstance().getSettings().recordingsSortType = col.getSortType().toString();
@@ -795,9 +814,9 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
private void restoreSorting() {
String sortCol = Config.getInstance().getSettings().recordingsSortColumn;
- if(StringUtil.isNotBlank(sortCol)) {
+ if (StringUtil.isNotBlank(sortCol)) {
for (TableColumn col : table.getColumns()) {
- if(Objects.equals(sortCol, col.getText())) {
+ if (Objects.equals(sortCol, col.getText())) {
col.setSortType(SortType.valueOf(Config.getInstance().getSettings().recordingsSortType));
table.getSortOrder().clear();
table.getSortOrder().add(col);
@@ -809,10 +828,10 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
private void restoreColumnOrder() {
String[] columnIds = Config.getInstance().getSettings().recordingsColumnIds;
- ObservableList> columns = table.getColumns();
+ ObservableList> columns = table.getColumns();
for (int i = 0; i < columnIds.length; i++) {
for (int j = 0; j < table.getColumns().size(); j++) {
- if(Objects.equals(columnIds[i], columns.get(j).getId())) {
+ if (Objects.equals(columnIds[i], columns.get(j).getId())) {
TableColumn col = columns.get(j);
columns.remove(j); // NOSONAR
columns.add(i, col);
@@ -823,7 +842,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
private void restoreColumnWidths() {
double[] columnWidths = Config.getInstance().getSettings().recordingsColumnWidths;
- if(columnWidths != null && columnWidths.length == table.getColumns().size()) {
+ if (columnWidths != null && columnWidths.length == table.getColumns().size()) {
for (int i = 0; i < columnWidths.length; i++) {
table.getColumns().get(i).setPrefWidth(columnWidths[i]);
}
From e53cee3272ab970a3fece24a92237efea2286614 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 21 Nov 2020 18:45:41 +0100
Subject: [PATCH 17/80] Update changelog
---
CHANGELOG.md | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c42600bc..152936fa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,12 @@
* Fix: date placeholders with patterns with more than one ocurrence are
replaced with the value of the first one
* Some smaller UI tweaks
- - adjusted component sizes for small resolutions
- - recording indicator can now be used to pause / resume the recording
- - adjusted scroll speed in the thumbnail overviews
- - added shortcuts for the thumbnail overviews (keys 1-9 and arrow keys)
+ * adjusted component sizes for small resolutions
+ * recording indicator can now be used to pause / resume the recording
+ * adjusted scroll speed in the thumbnail overviews
+ * added shortcuts for the thumbnail overviews (keys 1-9 and arrow keys)
+ * added "stop" and "pause" to Recordings tab
+ * added "follow" to Recordings tab
3.10.4
========================
From cd840afe0d35576330ae54ed972b2e6d163e533f Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 21 Nov 2020 19:20:01 +0100
Subject: [PATCH 18/80] Add idea project files to .gitignore
---
client/.gitignore | 2 ++
common/.gitignore | 2 ++
master/.gitignore | 2 ++
server/.gitignore | 2 ++
4 files changed, 8 insertions(+)
create mode 100644 master/.gitignore
diff --git a/client/.gitignore b/client/.gitignore
index c97c8943..4694bd3e 100644
--- a/client/.gitignore
+++ b/client/.gitignore
@@ -8,3 +8,5 @@
/server-local.sh
/browser/
/ffmpeg/
+/client.iml
+/.idea/
diff --git a/common/.gitignore b/common/.gitignore
index 09e3bc9b..17f982a3 100644
--- a/common/.gitignore
+++ b/common/.gitignore
@@ -1,2 +1,4 @@
/bin/
/target/
+/common.iml
+/.idea/
diff --git a/master/.gitignore b/master/.gitignore
new file mode 100644
index 00000000..26fa2c6a
--- /dev/null
+++ b/master/.gitignore
@@ -0,0 +1,2 @@
+.idea
+master.iml
diff --git a/server/.gitignore b/server/.gitignore
index 6b69bd69..2bda3b99 100644
--- a/server/.gitignore
+++ b/server/.gitignore
@@ -7,3 +7,5 @@
/jre/
/server-local.sh
ctbrec.pid
+/.idea/
+*.iml
From 4f973dbeedec83b71acb5b7ce5022436daaacaa6 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 21 Nov 2020 20:11:46 +0100
Subject: [PATCH 19/80] Resolve maven dependency conflicts
---
client/pom.xml | 4 ----
master/pom.xml | 4 ++--
2 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/client/pom.xml b/client/pom.xml
index f51b964e..b82f968e 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -81,10 +81,6 @@
org.openjfx
javafx-media
-
- org.eclipse.jetty
- jetty-server
-
org.eclipse.jetty
jetty-servlet
diff --git a/master/pom.xml b/master/pom.xml
index 8e74add4..f2d5af82 100644
--- a/master/pom.xml
+++ b/master/pom.xml
@@ -63,7 +63,7 @@
com.squareup.moshi
moshi
- 1.5.0
+ 1.6.0
org.json
@@ -73,7 +73,7 @@
org.slf4j
slf4j-api
- 1.7.25
+ 1.7.30
ch.qos.logback
From 628c92cc67de87b6754f90ead868bac36f495309 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 21 Nov 2020 20:17:21 +0100
Subject: [PATCH 20/80] Little bit of code cleanup
---
.../java/ctbrec/ui/CamrecApplication.java | 146 ++++++++----------
.../java/ctbrec/ui/tabs/RecordingsTab.java | 141 ++++++-----------
2 files changed, 112 insertions(+), 175 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java
index c2337997..14cc0833 100644
--- a/client/src/main/java/ctbrec/ui/CamrecApplication.java
+++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java
@@ -1,28 +1,10 @@
package ctbrec.ui;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.lang.reflect.Type;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.google.common.eventbus.Subscribe;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.Types;
-
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.StringUtil;
@@ -56,25 +38,14 @@ import ctbrec.sites.stripchat.Stripchat;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.news.NewsTab;
import ctbrec.ui.settings.SettingsTab;
-import ctbrec.ui.tabs.DonateTabFx;
-import ctbrec.ui.tabs.HelpTab;
-import ctbrec.ui.tabs.RecordedModelsTab;
-import ctbrec.ui.tabs.RecordingsTab;
-import ctbrec.ui.tabs.SiteTab;
-import ctbrec.ui.tabs.TabSelectionListener;
-import ctbrec.ui.tabs.UpdateTab;
+import ctbrec.ui.tabs.*;
import ctbrec.ui.tabs.logging.LoggingTab;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.application.Platform;
-import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.scene.Scene;
-import javafx.scene.control.Alert;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.Label;
-import javafx.scene.control.Tab;
-import javafx.scene.control.TabPane;
+import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
@@ -83,6 +54,17 @@ import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import okhttp3.Request;
import okhttp3.Response;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
public class CamrecApplication extends Application {
@@ -92,11 +74,11 @@ public class CamrecApplication extends Application {
private Recorder recorder;
private OnlineMonitor onlineMonitor;
static HostServices hostServices;
- private BorderPane rootPane = new BorderPane();
- private HBox statusBar = new HBox();
- private Label statusLabel = new Label();
- private TabPane tabPane = new TabPane();
- private List sites = new ArrayList<>();
+ private final BorderPane rootPane = new BorderPane();
+ private final HBox statusBar = new HBox();
+ private final Label statusLabel = new Label();
+ private final TabPane tabPane = new TabPane();
+ private final List sites = new ArrayList<>();
public static HttpClient httpClient;
public static String title;
private Stage primaryStage;
@@ -180,9 +162,8 @@ public class CamrecApplication extends Application {
primaryStage.setScene(scene);
rootPane.setCenter(tabPane);
rootPane.setBottom(statusBar);
- for (Iterator iterator = sites.iterator(); iterator.hasNext();) {
- Site site = iterator.next();
- if(site.isEnabled()) {
+ for (Site site : sites) {
+ if (site.isEnabled()) {
SiteTab siteTab = new SiteTab(site, scene);
tabPane.getTabs().add(siteTab);
}
@@ -190,7 +171,7 @@ public class CamrecApplication extends Application {
modelsTab = new RecordedModelsTab("Recording", recorder, sites);
tabPane.getTabs().add(modelsTab);
- recordingsTab = new RecordingsTab("Recordings", recorder, config, sites);
+ recordingsTab = new RecordingsTab("Recordings", recorder, config);
tabPane.getTabs().add(recordingsTab);
tabPane.getTabs().add(new SettingsTab(sites, recorder));
tabPane.getTabs().add(new NewsTab());
@@ -215,7 +196,7 @@ public class CamrecApplication extends Application {
.addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowHeight = newVal.intValue());
primaryStage.setMaximized(Config.getInstance().getSettings().windowMaximized);
primaryStage.maximizedProperty()
- .addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowMaximized = newVal.booleanValue());
+ .addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowMaximized = newVal);
Player.scene = primaryStage.getScene();
primaryStage.setX(Config.getInstance().getSettings().windowX);
primaryStage.setY(Config.getInstance().getSettings().windowY);
@@ -225,7 +206,7 @@ public class CamrecApplication extends Application {
primaryStage.setOnCloseRequest(createShutdownHandler());
// register changelistener to activate / deactivate tabs, when the user switches between them
- tabPane.getSelectionModel().selectedItemProperty().addListener((ChangeListener) (ov, from, to) -> {
+ tabPane.getSelectionModel().selectedItemProperty().addListener((ov, from, to) -> {
if (from instanceof TabSelectionListener) {
((TabSelectionListener) from).deselected();
}
@@ -274,44 +255,41 @@ public class CamrecApplication extends Application {
shutdownInfo.show();
final boolean immediately = shutdownNow;
- new Thread() {
- @Override
- public void run() {
- modelsTab.saveState();
- recordingsTab.saveState();
- onlineMonitor.shutdown();
- recorder.shutdown(immediately);
- for (Site site : sites) {
- if(site.isEnabled()) {
- site.shutdown();
- }
- }
- try {
- Config.getInstance().save();
- LOG.info("Shutdown complete. Goodbye!");
- Platform.runLater(() -> {
- primaryStage.close();
- shutdownInfo.close();
- Platform.exit();
- // This is needed, because OkHttp?! seems to block the shutdown with its writer threads. They are not daemon threads :(
- System.exit(0);
- });
- } catch (IOException e1) {
- Platform.runLater(() -> {
- Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, primaryStage.getScene());
- alert.setTitle("Error saving settings");
- alert.setContentText("Couldn't save settings: " + e1.getLocalizedMessage());
- alert.showAndWait();
- System.exit(1);
- });
- }
- try {
- ExternalBrowser.getInstance().close();
- } catch (IOException e) {
- // noop
+ new Thread(() -> {
+ modelsTab.saveState();
+ recordingsTab.saveState();
+ onlineMonitor.shutdown();
+ recorder.shutdown(immediately);
+ for (Site site : sites) {
+ if (site.isEnabled()) {
+ site.shutdown();
}
}
- }.start();
+ try {
+ Config.getInstance().save();
+ LOG.info("Shutdown complete. Goodbye!");
+ Platform.runLater(() -> {
+ primaryStage.close();
+ shutdownInfo.close();
+ Platform.exit();
+ // This is needed, because OkHttp?! seems to block the shutdown with its writer threads. They are not daemon threads :(
+ System.exit(0);
+ });
+ } catch (IOException e1) {
+ Platform.runLater(() -> {
+ Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, primaryStage.getScene());
+ alert.setTitle("Error saving settings");
+ alert.setContentText("Couldn't save settings: " + e1.getLocalizedMessage());
+ alert.showAndWait();
+ System.exit(1);
+ });
+ }
+ try {
+ ExternalBrowser.getInstance().close();
+ } catch (IOException e12) {
+ // noop
+ }
+ }).start();
};
}
@@ -355,8 +333,8 @@ public class CamrecApplication extends Application {
if (activeRecordings == 0) {
bytesPerSecond = 0;
}
- String humanreadable = ByteUnitFormatter.format(bytesPerSecond);
- String status = String.format("Recording %s / %s models @ %s/s", activeRecordings, recorder.getModels().size(), humanreadable);
+ String humanReadable = ByteUnitFormatter.format(bytesPerSecond);
+ String status = String.format("Recording %s / %s models @ %s/s", activeRecordings, recorder.getModels().size(), humanReadable);
Platform.runLater(() -> statusLabel.setText(status));
}
@@ -370,7 +348,7 @@ public class CamrecApplication extends Application {
" -fx-focus-color: -fx-accent;\n" +
" -fx-control-inner-background-alt: derive(-fx-base, 95%);\n" +
"}";
- fos.write(content.getBytes("utf-8"));
+ fos.write(content.getBytes(StandardCharsets.UTF_8));
} catch(Exception e) {
LOG.error("Couldn't write stylesheet for user defined color theme");
}
@@ -400,7 +378,6 @@ public class CamrecApplication extends Application {
private void createRecorder() {
if (config.getSettings().localRecording) {
- //recorder = new LocalRecorder(config);
try {
recorder = new NextGenLocalRecorder(config, sites);
} catch (IOException e) {
@@ -431,7 +408,7 @@ public class CamrecApplication extends Application {
private void createHttpClient() {
httpClient = new HttpClient("camrec") {
@Override
- public boolean login() throws IOException {
+ public boolean login() {
return false;
}
};
@@ -481,8 +458,7 @@ public class CamrecApplication extends Application {
try (InputStream is = CamrecApplication.class.getClassLoader().getResourceAsStream("version")) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String versionString = reader.readLine();
- Version version = Version.of(versionString);
- return version;
+ return Version.of(versionString);
}
}
}
diff --git a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
index 668c4a3a..4f18c6ca 100644
--- a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
@@ -1,33 +1,5 @@
package ctbrec.ui.tabs;
-import static ctbrec.Recording.State.*;
-import static javafx.scene.control.ButtonType.*;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.net.URL;
-import java.nio.file.NoSuchFileException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.text.DecimalFormat;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Recording;
@@ -40,13 +12,7 @@ import ctbrec.recorder.ProgressListener;
import ctbrec.recorder.Recorder;
import ctbrec.recorder.RecordingPinnedException;
import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload;
-import ctbrec.sites.Site;
-import ctbrec.ui.AutosizeAlert;
-import ctbrec.ui.CamrecApplication;
-import ctbrec.ui.DesktopIntegration;
-import ctbrec.ui.FileDownload;
-import ctbrec.ui.JavaFxRecording;
-import ctbrec.ui.Player;
+import ctbrec.ui.*;
import ctbrec.ui.action.FollowAction;
import ctbrec.ui.action.PauseAction;
import ctbrec.ui.action.StopRecordingAction;
@@ -64,24 +30,9 @@ import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Alert.AlertType;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.ContextMenu;
-import javafx.scene.control.Label;
-import javafx.scene.control.MenuItem;
-import javafx.scene.control.ProgressBar;
-import javafx.scene.control.ScrollPane;
-import javafx.scene.control.SelectionMode;
-import javafx.scene.control.Tab;
-import javafx.scene.control.TableCell;
-import javafx.scene.control.TableColumn;
+import javafx.scene.control.*;
import javafx.scene.control.TableColumn.SortType;
-import javafx.scene.control.TableView;
-import javafx.scene.control.Tooltip;
-import javafx.scene.input.ContextMenuEvent;
-import javafx.scene.input.KeyCode;
-import javafx.scene.input.KeyEvent;
-import javafx.scene.input.MouseButton;
-import javafx.scene.input.MouseEvent;
+import javafx.scene.input.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
@@ -89,6 +40,29 @@ import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.stage.FileChooser;
import javafx.util.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.NoSuchFileException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.text.DecimalFormat;
+import java.time.Instant;
+import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+import static ctbrec.Recording.State.*;
+import static javafx.scene.control.ButtonType.NO;
+import static javafx.scene.control.ButtonType.YES;
public class RecordingsTab extends Tab implements TabSelectionListener {
private static final String ERROR_WHILE_DOWNLOADING_RECORDING = "Error while downloading recording";
@@ -96,10 +70,8 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
private static final Logger LOG = LoggerFactory.getLogger(RecordingsTab.class);
private ScheduledService> updateService;
- private Config config;
- private Recorder recorder;
- @SuppressWarnings("unused")
- private List sites;
+ private final Config config;
+ private final Recorder recorder;
private long spaceTotal = -1;
private long spaceFree = -1;
@@ -112,11 +84,10 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
Label spaceLabel;
Lock recordingsLock = new ReentrantLock();
- public RecordingsTab(String title, Recorder recorder, Config config, List sites) {
+ public RecordingsTab(String title, Recorder recorder, Config config) {
super(title);
this.recorder = recorder;
this.config = config;
- this.sites = sites;
createGui();
setClosable(false);
initializeUpdateService();
@@ -143,9 +114,9 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
date.setId("date");
date.setCellValueFactory(cdf -> {
Instant instant = cdf.getValue().getStartDate();
- return new SimpleObjectProperty(instant);
+ return new SimpleObjectProperty<>(instant);
});
- date.setCellFactory(new DateTimeCellFactory());
+ date.setCellFactory(new DateTimeCellFactory<>());
date.setPrefWidth(200);
TableColumn status = new TableColumn<>("Status");
status.setId("status");
@@ -200,7 +171,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
private TableCell createSizeCell() {
- TableCell cell = new TableCell() {
+ return new TableCell<>() {
@Override
protected void updateItem(Number sizeInByte, boolean empty) {
if (empty || sizeInByte == null) {
@@ -219,7 +190,6 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
}
};
- return cell;
}
private void onContextMenuRequested(ContextMenuEvent event) {
@@ -276,7 +246,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
AutosizeAlert autosizeAlert = new AutosizeAlert(AlertType.ERROR, getTabPane().getScene());
autosizeAlert.setTitle("Whoopsie!");
autosizeAlert.setHeaderText("Recordings not available");
- autosizeAlert.setContentText("An error occured while retrieving the list of recordings");
+ autosizeAlert.setContentText("An error occurred while retrieving the list of recordings");
autosizeAlert.showAndWait();
});
}
@@ -302,13 +272,9 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
recordingsLock.lock();
try {
- for (Iterator iterator = observableRecordings.iterator(); iterator.hasNext();) {
- JavaFxRecording old = iterator.next();
- if (!recordings.contains(old)) {
- // remove deleted recordings
- iterator.remove();
- }
- }
+ // remove deleted recordings
+ observableRecordings.removeIf(old -> !recordings.contains(old));
+
for (JavaFxRecording recording : recordings) {
if (!observableRecordings.contains(recording)) {
// add new recordings
@@ -327,10 +293,10 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
private ScheduledService> createUpdateService() {
- ScheduledService> service = new ScheduledService>() {
+ ScheduledService> service = new ScheduledService<>() {
@Override
protected Task> createTask() {
- return new Task>() {
+ return new Task<>() {
@Override
public List call() throws IOException, InvalidKeyException, NoSuchAlgorithmException {
updateSpace();
@@ -401,13 +367,13 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
contextMenu.getItems().add(openContactSheet);
MenuItem stopRecording = new MenuItem("Stop Recording");
- stopRecording.setOnAction(e -> stopRecording(recordings.stream().map(r -> r.getModel()).collect(Collectors.toList())));
+ stopRecording.setOnAction(e -> stopRecording(recordings.stream().map(JavaFxRecording::getModel).collect(Collectors.toList())));
if (recordings.stream().anyMatch(r -> r.getStatus() == RECORDING)) {
contextMenu.getItems().add(stopRecording);
}
MenuItem pauseRecording = new MenuItem("Pause Recording");
- pauseRecording.setOnAction(e -> pauseRecording(recordings.stream().map(r -> r.getModel()).collect(Collectors.toList())));
+ pauseRecording.setOnAction(e -> pauseRecording(recordings.stream().map(JavaFxRecording::getModel).collect(Collectors.toList())));
if (recordings.stream().anyMatch(r -> r.getStatus() == RECORDING)) {
contextMenu.getItems().add(pauseRecording);
}
@@ -420,8 +386,8 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
MenuItem followModels = new MenuItem("Follow Model");
- followModels.setOnAction(e -> follow(recordings.stream().map(r -> r.getModel()).collect(Collectors.toList())));
- followModels.setDisable(!recordings.stream().map(r -> r.getModel()).allMatch(m -> m.getSite().supportsFollow() && m.getSite().credentialsAvailable()));
+ followModels.setOnAction(e -> follow(recordings.stream().map(JavaFxRecording::getModel).collect(Collectors.toList())));
+ followModels.setDisable(!recordings.stream().map(JavaFxRecording::getModel).allMatch(m -> m.getSite().supportsFollow() && m.getSite().credentialsAvailable()));
contextMenu.getItems().add(followModels);
MenuItem openDir = new MenuItem("Open directory");
@@ -484,7 +450,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
try {
target = File.createTempFile("cs_", ".jpg");
target.deleteOnExit();
- FileDownload download = new FileDownload(CamrecApplication.httpClient, (p) -> {
+ FileDownload download = new FileDownload(CamrecApplication.httpClient, p -> {
if (p == 100) {
DesktopIntegration.open(target);
}
@@ -653,8 +619,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
} else {
String downloadFilename = config.getSettings().downloadFilename;
String fileSuffix = config.getSettings().ffmpegFileSuffix;
- String filename = new DownloadPostprocessor().fillInPlaceHolders(downloadFilename, recording, config) + '.' + fileSuffix;
- return filename;
+ return new DownloadPostprocessor().fillInPlaceHolders(downloadFilename, recording, config) + '.' + fileSuffix;
}
}
@@ -716,7 +681,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
AutosizeAlert autosizeAlert = new AutosizeAlert(AlertType.ERROR, getTabPane().getScene());
autosizeAlert.setTitle(title);
autosizeAlert.setHeaderText(msg);
- StringBuilder contentText = new StringBuilder("On or more error(s) occured:");
+ StringBuilder contentText = new StringBuilder("On or more error(s) occurred:");
for (Exception exception : exceptions) {
contentText.append("\n• ").append(exception.getLocalizedMessage());
}
@@ -726,15 +691,12 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
private void play(Recording recording) {
- new Thread() {
- @Override
- public void run() {
- boolean started = Player.play(recording);
- if (started && Config.getInstance().getSettings().showPlayerStarting) {
- Platform.runLater(() -> Toast.makeText(getTabPane().getScene(), "Starting Player", 2000, 500, 500));
- }
+ new Thread(() -> {
+ boolean started = Player.play(recording);
+ if (started && Config.getInstance().getSettings().showPlayerStarting) {
+ Platform.runLater(() -> Toast.makeText(getTabPane().getScene(), "Starting Player", 2000, 500, 500));
}
- }.start();
+ }).start();
}
private void delete(List recordings) {
@@ -764,8 +726,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
try {
List deleted = new ArrayList<>();
List exceptions = new ArrayList<>();
- for (Iterator iterator = recordings.iterator(); iterator.hasNext();) {
- JavaFxRecording r = iterator.next();
+ for (JavaFxRecording r : recordings) {
if (r.getStatus() != FINISHED && r.getStatus() != FAILED && r.getStatus() != WAITING) {
continue;
}
From 80ce269298a351801fb90576c55f42f8ecb5c132 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 21 Nov 2020 20:47:19 +0100
Subject: [PATCH 21/80] Little bit of code cleanup
---
.../java/ctbrec/ui/tabs/RecordingsTab.java | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
index 4f18c6ca..d0dcb5ab 100644
--- a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
@@ -546,7 +546,10 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
private void jumpToNextModel(KeyCode code) {
- if (!table.getItems().isEmpty() && (code.isLetterKey() || code.isDigitKey())) {
+ try {
+ ensureTableIsNotEmpty();
+ ensureKeyCodeIsLetterOrDigit(code);
+
// determine where to start looking for the next model
int startAt = 0;
if (table.getSelectionModel().getSelectedIndex() >= 0) {
@@ -571,6 +574,20 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
i = 0;
}
} while (i != startAt);
+ } catch (IllegalStateException | IllegalArgumentException e) {
+ // GUI was not in the state to process the user input
+ }
+ }
+
+ private void ensureKeyCodeIsLetterOrDigit(KeyCode code) {
+ if (!(code.isLetterKey() || code.isDigitKey())) {
+ throw new IllegalArgumentException("keycode not allowed");
+ }
+ }
+
+ private void ensureTableIsNotEmpty() {
+ if (table.getItems().isEmpty()) {
+ throw new IllegalStateException("table is empty");
}
}
From 678d7b01748065971bf98a56b8945856976a3d19 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 21 Nov 2020 20:51:23 +0100
Subject: [PATCH 22/80] Set version to 3.10.5
---
client/pom.xml | 2 +-
common/pom.xml | 2 +-
master/pom.xml | 6 +++---
server/pom.xml | 10 +++++-----
4 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/client/pom.xml b/client/pom.xml
index b82f968e..453bdce2 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.4
+ 3.10.5
../master
diff --git a/common/pom.xml b/common/pom.xml
index c81ebb05..98714b49 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.4
+ 3.10.5
../master
diff --git a/master/pom.xml b/master/pom.xml
index f2d5af82..11b93a17 100644
--- a/master/pom.xml
+++ b/master/pom.xml
@@ -1,12 +1,12 @@
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
ctbrec
master
pom
- 3.10.4
+ 3.10.5
../common
diff --git a/server/pom.xml b/server/pom.xml
index 7dc0af29..263e9735 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -6,11 +6,11 @@
server
- ctbrec
- master
- 3.10.4
- ../master
-
+ ctbrec
+ master
+ 3.10.5
+ ../master
+
1.8
From 4bdb7f5c211a01d7718ea026aeab2b75613e2675 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 22 Nov 2020 00:19:32 +0100
Subject: [PATCH 23/80] Fix pause indicator click area
---
.../ctbrec/ui/controls/PausedIndicator.java | 10 ++----
.../main/java/ctbrec/ui/tabs/ThumbCell.java | 31 +++++++++----------
2 files changed, 16 insertions(+), 25 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java b/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
index 8483e3d9..b5b4d916 100644
--- a/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
+++ b/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
@@ -1,8 +1,6 @@
package ctbrec.ui.controls;
import ctbrec.ui.PauseIcon;
-import javafx.geometry.Insets;
-import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
@@ -15,6 +13,8 @@ public class PausedIndicator extends StackPane {
private Rectangle clickPanel;
public PausedIndicator(int size, Color color) {
+ setMaxSize(size, size);
+
pausedIcon = new PauseIcon(color, size);
pausedIcon.setVisible(false);
clickPanel = new Rectangle(size, size);
@@ -23,13 +23,7 @@ public class PausedIndicator extends StackPane {
getChildren().add(pausedIcon);
getChildren().add(clickPanel);
- StackPane.setMargin(pausedIcon, new Insets(3));
- StackPane.setAlignment(pausedIcon, Pos.TOP_LEFT);
- StackPane.setMargin(clickPanel, new Insets(3));
- StackPane.setAlignment(clickPanel, Pos.TOP_LEFT);
-
pausedIcon.visibleProperty().bindBidirectional(visibleProperty());
- pausedIcon.onMouseClickedProperty().bindBidirectional(onMouseClickedProperty());
clickPanel.onMouseClickedProperty().bindBidirectional(onMouseClickedProperty());
}
}
diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
index fdb7cea8..05268a53 100644
--- a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
+++ b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
@@ -1,25 +1,8 @@
package ctbrec.ui.tabs;
-import static ctbrec.Model.State.*;
-import static ctbrec.io.HttpConstants.*;
-
-import java.io.IOException;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
-
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Model.State;
@@ -63,6 +46,18 @@ import javafx.scene.text.TextAlignment;
import javafx.util.Duration;
import okhttp3.Request;
import okhttp3.Response;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.*;
+import java.util.function.Function;
+
+import static ctbrec.Model.State.OFFLINE;
+import static ctbrec.Model.State.ONLINE;
+import static ctbrec.io.HttpConstants.*;
public class ThumbCell extends StackPane {
@@ -185,6 +180,8 @@ public class ThumbCell extends StackPane {
pausedIndicator = new PausedIndicator(16, colorRecording);
pausedIndicator.setOnMouseClicked(e -> pauseResumeAction(false));
+ StackPane.setMargin(pausedIndicator, new Insets(3));
+ StackPane.setAlignment(pausedIndicator, Pos.TOP_LEFT);
getChildren().add(pausedIndicator);
if (Config.getInstance().getSettings().livePreviews) {
From 69b75ae53180755779b1cb37ca93b8d2305eecba Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 22 Nov 2020 00:35:22 +0100
Subject: [PATCH 24/80] Add tooltips to recording / pause indicators
---
client/src/main/java/ctbrec/ui/controls/PausedIndicator.java | 4 ++++
client/src/main/java/ctbrec/ui/tabs/ThumbCell.java | 3 +++
2 files changed, 7 insertions(+)
diff --git a/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java b/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
index b5b4d916..482d8d08 100644
--- a/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
+++ b/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
@@ -2,6 +2,7 @@ package ctbrec.ui.controls;
import ctbrec.ui.PauseIcon;
import javafx.scene.Cursor;
+import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
@@ -25,6 +26,9 @@ public class PausedIndicator extends StackPane {
pausedIcon.visibleProperty().bindBidirectional(visibleProperty());
clickPanel.onMouseClickedProperty().bindBidirectional(onMouseClickedProperty());
+
+ Tooltip tooltip = new Tooltip("Resume Recording");
+ Tooltip.install(clickPanel, tooltip);
}
}
diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
index 05268a53..126b9816 100644
--- a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
+++ b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
@@ -31,6 +31,7 @@ import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.ContextMenu;
+import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
@@ -174,6 +175,8 @@ public class ThumbCell extends StackPane {
recordingIndicator.setFill(colorRecording);
recordingIndicator.setCursor(Cursor.HAND);
recordingIndicator.setOnMouseClicked(e -> pauseResumeAction(true));
+ Tooltip tooltip = new Tooltip("Pause Recording");
+ Tooltip.install(recordingIndicator, tooltip);
StackPane.setMargin(recordingIndicator, new Insets(3));
StackPane.setAlignment(recordingIndicator, Pos.TOP_LEFT);
getChildren().add(recordingIndicator);
From 5c3755dedcd690494307c6a397520a30d923baa8 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 28 Nov 2020 01:00:13 +0100
Subject: [PATCH 25/80] Fixed wrong playlist file extensions
m3u8a -> m3u8
---
common/src/main/java/ctbrec/Recording.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/src/main/java/ctbrec/Recording.java b/common/src/main/java/ctbrec/Recording.java
index d422c4fd..ad34b717 100644
--- a/common/src/main/java/ctbrec/Recording.java
+++ b/common/src/main/java/ctbrec/Recording.java
@@ -211,7 +211,7 @@ public class Recording implements Serializable {
public Duration getLength() {
File ppFile = getPostProcessedFile();
if (ppFile.isDirectory()) {
- File playlist = new File(ppFile, "playlist.m3u8a");
+ File playlist = new File(ppFile, "playlist.m3u8");
return VideoLengthDetector.getLength(playlist);
} else {
return VideoLengthDetector.getLength(ppFile);
From 267e3394b52733563a04f4a4b6725f4a96c58d50 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 28 Nov 2020 17:41:56 +0100
Subject: [PATCH 26/80] Code cleanup
---
.../main/java/ctbrec/ui/tabs/ThumbCell.java | 60 ++++++-------------
1 file changed, 18 insertions(+), 42 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
index 126b9816..1930253c 100644
--- a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
+++ b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
@@ -13,6 +13,7 @@ import ctbrec.ui.CamrecApplication;
import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.StreamSourceSelectionDialog;
import ctbrec.ui.action.PlayAction;
+import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.controls.PausedIndicator;
import ctbrec.ui.controls.StreamPreview;
import javafx.animation.FadeTransition;
@@ -22,7 +23,6 @@ import javafx.animation.Transition;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
-import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
@@ -236,7 +236,7 @@ public class ThumbCell extends StackPane {
play.setStyle("-fx-background-color: black;");
previewTrigger.getChildren().add(play);
- Circle clip = new Circle(s / 2);
+ Circle clip = new Circle(s / 2.0);
clip.setTranslateX(clip.getRadius());
clip.setTranslateY(clip.getRadius());
previewTrigger.setClip(clip);
@@ -287,8 +287,7 @@ public class ThumbCell extends StackPane {
resolution = resolutionCache.get(model);
resolutionBackgroundColor = resolutionOnlineColor;
final int w = resolution[1];
- String width = w != Integer.MAX_VALUE ? Integer.toString(w) : "HD";
- tagText = width;
+ tagText = w != Integer.MAX_VALUE ? Integer.toString(w) : "HD";
if (w == 0) {
State state = model.getOnlineState(false);
tagText = state.name();
@@ -364,7 +363,7 @@ public class ThumbCell extends StackPane {
setThumbWidth(Config.getInstance().getSettings().thumbWidth);
});
} else {
- img.progressProperty().addListener((ChangeListener) (observable, oldValue, newValue) -> {
+ img.progressProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.doubleValue() == 1.0) {
iv.setImage(img);
setThumbWidth(Config.getInstance().getSettings().thumbWidth);
@@ -403,13 +402,13 @@ public class ThumbCell extends StackPane {
private void setRecording(boolean recording) {
this.recording = recording;
+ Color c;
if (recording) {
- Color c = mouseHovering ? colorHighlight : colorRecording;
- nameBackground.setFill(c);
+ c = mouseHovering ? colorHighlight : colorRecording;
} else {
- Color c = mouseHovering ? colorHighlight : colorNormal;
- nameBackground.setFill(c);
+ c = mouseHovering ? colorHighlight : colorNormal;
}
+ nameBackground.setFill(c);
updateRecordingIndicator();
}
@@ -430,7 +429,7 @@ public class ThumbCell extends StackPane {
boolean selectSource = Config.getInstance().getSettings().chooseStreamQuality;
if (selectSource && start) {
Function onSuccess = modl -> {
- startStopActionAsync(modl, start);
+ startStopActionAsync(modl, true);
return null;
};
Function onFail = throwable -> {
@@ -484,13 +483,7 @@ public class ThumbCell extends StackPane {
}
} catch (Exception e1) {
LOG.error(COULDNT_START_STOP_RECORDING, e1);
- Platform.runLater(() -> {
- Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, getScene());
- alert.setTitle(ERROR);
- alert.setHeaderText(COULDNT_START_STOP_RECORDING);
- alert.setContentText("I/O error while starting/stopping the recording: " + e1.getLocalizedMessage());
- alert.showAndWait();
- });
+ Dialogs.showError(getScene(), COULDNT_START_STOP_RECORDING, "I/O error while starting/stopping the recording: ", e1);
} finally {
setCursor(Cursor.DEFAULT);
}
@@ -507,13 +500,7 @@ public class ThumbCell extends StackPane {
if (followed) {
return true;
} else {
- Platform.runLater(() -> {
- Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, getScene());
- alert.setTitle(ERROR);
- alert.setHeaderText("Couldn't follow model");
- alert.setContentText("");
- alert.showAndWait();
- });
+ Dialogs.showError(getScene(), "Couldn't follow model", "", null);
return false;
}
} else {
@@ -523,25 +510,14 @@ public class ThumbCell extends StackPane {
Platform.runLater(() -> thumbCellList.remove(ThumbCell.this));
return true;
} else {
- Platform.runLater(() -> {
- Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, getScene());
- alert.setTitle(ERROR);
- alert.setHeaderText("Couldn't unfollow model");
- alert.setContentText("");
- alert.showAndWait();
- });
+ Dialogs.showError(getScene(), "Couldn't unfollow model", "", null);
return false;
}
}
} catch (Exception e1) {
LOG.error("Couldn't follow/unfollow model {}", model.getName(), e1);
- Platform.runLater(() -> {
- Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, getScene());
- alert.setTitle(ERROR);
- alert.setHeaderText("Couldn't follow/unfollow model");
- alert.setContentText("I/O error while following/unfollowing model " + model.getName() + ": " + e1.getLocalizedMessage());
- alert.showAndWait();
- });
+ String msg = "I/O error while following/unfollowing model " + model.getName() + ": ";
+ Dialogs.showError(getScene(), "Couldn't follow/unfollow model", msg, e1);
return false;
} finally {
setCursor(Cursor.DEFAULT);
@@ -616,8 +592,8 @@ public class ThumbCell extends StackPane {
public void setThumbWidth(int width) {
int height = (int) (width * imgAspectRatio);
setSize(width, height);
- iv.prefHeight(height);
- iv.prefWidth(width);
+ iv.prefHeight(width);
+ iv.prefWidth(height);
}
private void setSize(int w, int h) {
@@ -638,8 +614,8 @@ public class ThumbCell extends StackPane {
topic.prefHeight(getHeight() - 25);
topic.maxHeight(getHeight() - 25);
int margin = 4;
- topic.maxWidth(w - margin * 2);
- topic.setWrappingWidth(w - margin * 2);
+ topic.maxWidth(w - margin * 2.0);
+ topic.setWrappingWidth(w - margin * 2.0);
streamPreview.resizeTo(w, h);
From cc277022f0fe609de21e1d991e24acca79e68bad Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 28 Nov 2020 19:12:18 +0100
Subject: [PATCH 27/80] Code cleanup
---
.../main/java/ctbrec/ui/controls/Dialogs.java | 18 +-
.../src/main/java/ctbrec/ui/news/NewsTab.java | 17 +-
.../settings/PostProcessingDialogFactory.java | 39 ++--
.../ui/settings/PostProcessingStepPanel.java | 82 ++++-----
.../ui/sites/bonga/BongaCamsSiteUi.java | 23 +--
.../ui/tabs/FollowTabBlinkTransition.java | 44 +++++
.../java/ctbrec/ui/tabs/ThumbOverviewTab.java | 170 +++++-------------
.../ui/tabs/ThumbOverviewTabSearchTask.java | 35 ++--
.../main/java/ctbrec/ui/tabs/UpdateTab.java | 9 +-
9 files changed, 187 insertions(+), 250 deletions(-)
create mode 100644 client/src/main/java/ctbrec/ui/tabs/FollowTabBlinkTransition.java
diff --git a/client/src/main/java/ctbrec/ui/controls/Dialogs.java b/client/src/main/java/ctbrec/ui/controls/Dialogs.java
index 886e33dd..a1ac9d76 100644
--- a/client/src/main/java/ctbrec/ui/controls/Dialogs.java
+++ b/client/src/main/java/ctbrec/ui/controls/Dialogs.java
@@ -1,30 +1,28 @@
package ctbrec.ui.controls;
-import static javafx.scene.control.ButtonType.*;
-
-import java.io.InputStream;
-import java.util.Optional;
-
import ctbrec.ui.AutosizeAlert;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
-import javafx.scene.control.Alert;
+import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
-import javafx.scene.control.Button;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.Dialog;
-import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.stage.Modality;
import javafx.stage.Stage;
+import java.io.InputStream;
+import java.util.Optional;
+
+import static javafx.scene.control.ButtonType.*;
+
public class Dialogs {
private Dialogs() {}
+
// TODO reduce calls to this method and use Dialogs.showError(Scene parent, String header, String text, Throwable t) instead
+ @Deprecated
public static void showError(String header, String text, Throwable t) {
showError(null, header, text, t);
}
diff --git a/client/src/main/java/ctbrec/ui/news/NewsTab.java b/client/src/main/java/ctbrec/ui/news/NewsTab.java
index fc0a2395..a5f11d8e 100644
--- a/client/src/main/java/ctbrec/ui/news/NewsTab.java
+++ b/client/src/main/java/ctbrec/ui/news/NewsTab.java
@@ -1,15 +1,7 @@
package ctbrec.ui.news;
-import static ctbrec.io.HttpConstants.*;
-
-import java.io.IOException;
-import java.util.Objects;
-
-import org.json.JSONObject;
-
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
-
import ctbrec.io.HttpException;
import ctbrec.ui.CamrecApplication;
import ctbrec.ui.controls.Dialogs;
@@ -22,10 +14,15 @@ import javafx.scene.control.Tab;
import javafx.scene.layout.VBox;
import okhttp3.Request;
import okhttp3.Response;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import static ctbrec.io.HttpConstants.USER_AGENT;
public class NewsTab extends Tab implements TabSelectionListener {
private static final String ACCESS_TOKEN = "a2804d73a89951a22e0f8483a6fcec8943afd88b7ba17c459c095aa9e6f94fd0";
- //private static final String URL = "https://mastodon.cloud/api/v1/timelines/home?limit=50";
private static final String URL = "https://mastodon.cloud/api/v1/accounts/480960/statuses?limit=20&exclude_replies=true";
private VBox layout = new VBox();
@@ -64,7 +61,7 @@ public class NewsTab extends Tab implements TabSelectionListener {
}
}
} catch (IOException e) {
- Dialogs.showError("News", "Couldn't load news from mastodon", e);
+ Dialogs.showError(getTabPane().getScene(), "News", "Couldn't load news from mastodon", e);
}
}
diff --git a/client/src/main/java/ctbrec/ui/settings/PostProcessingDialogFactory.java b/client/src/main/java/ctbrec/ui/settings/PostProcessingDialogFactory.java
index a0649f3f..dd6e8bb9 100644
--- a/client/src/main/java/ctbrec/ui/settings/PostProcessingDialogFactory.java
+++ b/client/src/main/java/ctbrec/ui/settings/PostProcessingDialogFactory.java
@@ -1,25 +1,18 @@
package ctbrec.ui.settings;
+import ctbrec.recorder.postprocessing.*;
+import ctbrec.ui.controls.Dialogs;
+import ctbrec.ui.settings.api.Preferences;
+import javafx.collections.ObservableList;
+import javafx.scene.Scene;
+import javafx.scene.layout.Region;
+
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
-import ctbrec.Config;
-import ctbrec.recorder.postprocessing.CreateContactSheet;
-import ctbrec.recorder.postprocessing.DeleteTooShort;
-import ctbrec.recorder.postprocessing.Move;
-import ctbrec.recorder.postprocessing.PostProcessor;
-import ctbrec.recorder.postprocessing.Remux;
-import ctbrec.recorder.postprocessing.Rename;
-import ctbrec.recorder.postprocessing.Script;
-import ctbrec.ui.controls.Dialogs;
-import ctbrec.ui.settings.api.Preferences;
-import javafx.collections.ObservableList;
-import javafx.scene.Scene;
-import javafx.scene.layout.Region;
-
public class PostProcessingDialogFactory {
static Map, Class>> ppToDialogMap = new HashMap<>();
@@ -35,19 +28,19 @@ public class PostProcessingDialogFactory {
private PostProcessingDialogFactory() {
}
- public static void openNewDialog(PostProcessor pp, Config config, Scene scene, ObservableList stepList) {
- openDialog(pp, config, scene, stepList, true);
+ public static void openNewDialog(PostProcessor pp, Scene scene, ObservableList stepList) {
+ openDialog(pp, scene, stepList, true);
}
- public static void openEditDialog(PostProcessor pp, Config config, Scene scene, ObservableList stepList) {
- openDialog(pp, config, scene, stepList, false);
+ public static void openEditDialog(PostProcessor pp, Scene scene, ObservableList stepList) {
+ openDialog(pp, scene, stepList, false);
}
- private static void openDialog(PostProcessor pp, Config config, Scene scene, ObservableList stepList, boolean newEntry) {
+ private static void openDialog(PostProcessor pp, Scene scene, ObservableList stepList, boolean newEntry) {
boolean ok;
try {
Optional preferences = createPreferences(pp);
- if(preferences.isPresent()) {
+ if (preferences.isPresent()) {
Region view = preferences.get().getView(false);
view.setMinWidth(600);
ok = Dialogs.showCustomInput(scene, "Configure " + pp.getName(), view);
@@ -62,12 +55,12 @@ public class PostProcessingDialogFactory {
}
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
| InstantiationException | IOException e) {
- Dialogs.showError("New post-processing step", "Couldn't create dialog for " + pp.getName(), e);
+ Dialogs.showError(scene, "New post-processing step", "Couldn't create dialog for " + pp.getName(), e);
}
}
- private static Optional createPreferences(PostProcessor pp) throws InstantiationException, IllegalAccessException, IllegalArgumentException,
- InvocationTargetException, NoSuchMethodException, SecurityException {
+ private static Optional createPreferences(PostProcessor pp) throws InstantiationException, IllegalAccessException,
+ InvocationTargetException, NoSuchMethodException {
Class> paneFactoryClass = ppToDialogMap.get(pp.getClass());
if (paneFactoryClass != null) {
AbstractPostProcessingPaneFactory factory = (AbstractPostProcessingPaneFactory) paneFactoryClass.getDeclaredConstructor().newInstance();
diff --git a/client/src/main/java/ctbrec/ui/settings/PostProcessingStepPanel.java b/client/src/main/java/ctbrec/ui/settings/PostProcessingStepPanel.java
index 6e75ccd3..e5cae35f 100644
--- a/client/src/main/java/ctbrec/ui/settings/PostProcessingStepPanel.java
+++ b/client/src/main/java/ctbrec/ui/settings/PostProcessingStepPanel.java
@@ -1,21 +1,7 @@
package ctbrec.ui.settings;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.util.Optional;
-
import ctbrec.Config;
-import ctbrec.recorder.postprocessing.Copy;
-import ctbrec.recorder.postprocessing.CreateContactSheet;
-import ctbrec.recorder.postprocessing.DeleteOriginal;
-import ctbrec.recorder.postprocessing.DeleteTooShort;
-import ctbrec.recorder.postprocessing.Move;
-import ctbrec.recorder.postprocessing.PostProcessor;
-import ctbrec.recorder.postprocessing.RemoveKeepFile;
-import ctbrec.recorder.postprocessing.Remux;
-import ctbrec.recorder.postprocessing.Rename;
-import ctbrec.recorder.postprocessing.Script;
+import ctbrec.recorder.postprocessing.*;
import ctbrec.ui.controls.Dialogs;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
@@ -30,21 +16,25 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Optional;
+
public class PostProcessingStepPanel extends GridPane {
- private Config config;
+ private final Config config;
-
- private static final Class>[] POST_PROCESSOR_CLASSES = new Class>[] { // @formatter: off
- Copy.class,
- Rename.class,
- Move.class,
- Remux.class,
- Script.class,
- DeleteOriginal.class,
- DeleteTooShort.class,
- RemoveKeepFile.class,
- CreateContactSheet.class
+ private static final Class>[] POST_PROCESSOR_CLASSES = new Class>[]{ // @formatter: off
+ Copy.class,
+ Rename.class,
+ Move.class,
+ Remux.class,
+ Script.class,
+ DeleteOriginal.class,
+ DeleteTooShort.class,
+ RemoveKeepFile.class,
+ CreateContactSheet.class
}; // @formatter: on
ListView stepListView;
@@ -98,33 +88,33 @@ public class PostProcessingStepPanel extends GridPane {
}
private Button createUpButton() {
- Button up = createButton("\u25B4", "Move step up");
- up.setOnAction(evt -> {
+ Button button = createButton("\u25B4", "Move step up");
+ button.setOnAction(evt -> {
int idx = stepListView.getSelectionModel().getSelectedIndex();
PostProcessor selectedItem = stepListView.getSelectionModel().getSelectedItem();
stepList.remove(idx);
stepList.add(idx - 1, selectedItem);
stepListView.getSelectionModel().select(idx - 1);
});
- return up;
+ return button;
}
private Button createDownButton() {
- Button down = createButton("\u25BE", "Move step down");
- down.setOnAction(evt -> {
+ Button button = createButton("\u25BE", "Move step down");
+ button.setOnAction(evt -> {
int idx = stepListView.getSelectionModel().getSelectedIndex();
PostProcessor selectedItem = stepListView.getSelectionModel().getSelectedItem();
stepList.remove(idx);
stepList.add(idx + 1, selectedItem);
stepListView.getSelectionModel().select(idx + 1);
});
- return down;
+ return button;
}
private Button createAddButton() {
- Button add = createButton("+", "Add a new step");
- add.setDisable(false);
- add.setOnAction(evt -> {
+ Button button = createButton("+", "Add a new step");
+ button.setDisable(false);
+ button.setOnAction(evt -> {
PostProcessor[] options = createOptions();
ChoiceDialog choice = new ChoiceDialog<>(options[0], options);
choice.setTitle("New Post-Processing Step");
@@ -138,17 +128,17 @@ public class PostProcessingStepPanel extends GridPane {
stage.getIcons().add(new Image(icon));
Optional result = choice.showAndWait();
- result.ifPresent(pp -> PostProcessingDialogFactory.openNewDialog(pp, config, getScene(), stepList));
+ result.ifPresent(pp -> PostProcessingDialogFactory.openNewDialog(pp, getScene(), stepList));
saveConfig();
});
- return add;
+ return button;
}
private void saveConfig() {
try {
config.save();
} catch (IOException e) {
- Dialogs.showError("Post-Processing", "Couldn't save post-processing step", e);
+ Dialogs.showError(getScene(), "Post-Processing", "Couldn't save post-processing step", e);
}
}
@@ -170,25 +160,25 @@ public class PostProcessingStepPanel extends GridPane {
}
private Button createRemoveButton() {
- Button remove = createButton("-", "Remove selected step");
- remove.setOnAction(evt -> {
+ Button button = createButton("-", "Remove selected step");
+ button.setOnAction(evt -> {
PostProcessor selectedItem = stepListView.getSelectionModel().getSelectedItem();
if (selectedItem != null) {
stepList.remove(selectedItem);
}
});
- return remove;
+ return button;
}
private Button createEditButton() {
- Button edit = createButton("\u270E", "Edit selected step");
- edit.setOnAction(evt -> {
+ Button button = createButton("\u270E", "Edit selected step");
+ button.setOnAction(evt -> {
PostProcessor selectedItem = stepListView.getSelectionModel().getSelectedItem();
- PostProcessingDialogFactory.openEditDialog(selectedItem, config, getScene(), stepList);
+ PostProcessingDialogFactory.openEditDialog(selectedItem, getScene(), stepList);
stepListView.refresh();
saveConfig();
});
- return edit;
+ return button;
}
private Button createButton(String text, String tooltip) {
diff --git a/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsSiteUi.java b/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsSiteUi.java
index b2406e07..a75ab316 100644
--- a/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsSiteUi.java
+++ b/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsSiteUi.java
@@ -1,25 +1,24 @@
package ctbrec.ui.sites.bonga;
-import java.io.IOException;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import ctbrec.sites.bonga.BongaCams;
import ctbrec.sites.bonga.BongaCamsHttpClient;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.sites.AbstractSiteUi;
import ctbrec.ui.sites.ConfigUI;
import ctbrec.ui.tabs.TabProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
public class BongaCamsSiteUi extends AbstractSiteUi {
- private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsSiteUi.class);
- private BongaCamsTabProvider tabProvider;
- private BongaCamsConfigUI configUi;
- private BongaCams bongaCams;
+ private static final Logger LOG = LoggerFactory.getLogger(BongaCamsSiteUi.class);
+ private final BongaCamsTabProvider tabProvider;
+ private final BongaCamsConfigUI configUi;
+ private final BongaCams bongaCams;
public BongaCamsSiteUi(BongaCams bongaCams) {
this.bongaCams = bongaCams;
@@ -57,11 +56,13 @@ public class BongaCamsSiteUi extends AbstractSiteUi {
try {
queue.put(true);
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
LOG.error("Error while signaling termination", e);
}
}).start();
queue.take();
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
LOG.error("Error while waiting for login dialog to close", e);
throw new IOException(e);
}
diff --git a/client/src/main/java/ctbrec/ui/tabs/FollowTabBlinkTransition.java b/client/src/main/java/ctbrec/ui/tabs/FollowTabBlinkTransition.java
new file mode 100644
index 00000000..a33f332a
--- /dev/null
+++ b/client/src/main/java/ctbrec/ui/tabs/FollowTabBlinkTransition.java
@@ -0,0 +1,44 @@
+package ctbrec.ui.tabs;
+
+import javafx.animation.Transition;
+import javafx.scene.control.Tab;
+import javafx.scene.paint.Color;
+import javafx.util.Duration;
+
+public class FollowTabBlinkTransition extends Transition {
+
+ private final String normalStyle;
+ private final Tab followedTab;
+ private final Color normal;
+ private final Color highlight;
+
+ FollowTabBlinkTransition(Tab followedTab) {
+ this.followedTab = followedTab;
+ normalStyle = followedTab.getStyle();
+ normal = Color.web("#f4f4f4");
+ highlight = Color.web("#2b8513");
+
+ setCycleDuration(Duration.millis(500));
+ setCycleCount(6);
+ setAutoReverse(true);
+ setOnFinished(evt -> followedTab.setStyle(normalStyle));
+ }
+
+ @Override
+ protected void interpolate(double fraction) {
+ double rh = highlight.getRed();
+ double rn = normal.getRed();
+ double diff = rh - rn;
+ double r = (rn + diff * fraction) * 255;
+ double gh = highlight.getGreen();
+ double gn = normal.getGreen();
+ diff = gh - gn;
+ double g = (gn + diff * fraction) * 255;
+ double bh = highlight.getBlue();
+ double bn = normal.getBlue();
+ diff = bh - bn;
+ double b = (bn + diff * fraction) * 255;
+ String style = "-fx-background-color: rgb(" + r + "," + g + "," + b + ")";
+ followedTab.setStyle(style);
+ }
+}
diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java b/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
index 7f21296a..90d2e163 100644
--- a/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
@@ -1,31 +1,5 @@
package ctbrec.ui.tabs;
-import static ctbrec.ui.controls.Dialogs.*;
-
-import java.io.IOException;
-import java.net.SocketTimeoutException;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.event.EventBusHolder;
@@ -33,22 +7,13 @@ import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;
import ctbrec.sites.mfc.MyFreeCamsClient;
import ctbrec.sites.mfc.MyFreeCamsModel;
-import ctbrec.ui.AutosizeAlert;
-import ctbrec.ui.DesktopIntegration;
-import ctbrec.ui.SiteUiFactory;
-import ctbrec.ui.TipDialog;
-import ctbrec.ui.TokenLabel;
+import ctbrec.ui.*;
import ctbrec.ui.action.OpenRecordingsDir;
import ctbrec.ui.controls.FasterVerticalScrollPaneSkin;
import ctbrec.ui.controls.SearchBox;
import ctbrec.ui.controls.SearchPopover;
import ctbrec.ui.controls.SearchPopoverTreeList;
-import javafx.animation.FadeTransition;
-import javafx.animation.Interpolator;
-import javafx.animation.ParallelTransition;
-import javafx.animation.ScaleTransition;
-import javafx.animation.Transition;
-import javafx.animation.TranslateTransition;
+import javafx.animation.*;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@@ -64,33 +29,24 @@ import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
-import javafx.scene.control.Alert;
-import javafx.scene.control.Button;
-import javafx.scene.control.ComboBox;
-import javafx.scene.control.ContextMenu;
-import javafx.scene.control.Label;
-import javafx.scene.control.MenuItem;
-import javafx.scene.control.ScrollPane;
-import javafx.scene.control.Tab;
-import javafx.scene.control.TabPane;
-import javafx.scene.control.TextField;
-import javafx.scene.control.Tooltip;
+import javafx.scene.control.*;
import javafx.scene.image.ImageView;
-import javafx.scene.input.Clipboard;
-import javafx.scene.input.ClipboardContent;
-import javafx.scene.input.ContextMenuEvent;
-import javafx.scene.input.KeyCode;
-import javafx.scene.input.KeyEvent;
-import javafx.scene.input.MouseButton;
-import javafx.scene.input.MouseEvent;
-import javafx.scene.layout.BorderPane;
-import javafx.scene.layout.FlowPane;
-import javafx.scene.layout.HBox;
-import javafx.scene.layout.Priority;
-import javafx.scene.layout.StackPane;
-import javafx.scene.paint.Color;
+import javafx.scene.input.*;
+import javafx.scene.layout.*;
import javafx.scene.transform.Transform;
import javafx.util.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.text.DecimalFormat;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+import static ctbrec.ui.controls.Dialogs.showError;
public class ThumbOverviewTab extends Tab implements TabSelectionListener {
private static final Logger LOG = LoggerFactory.getLogger(ThumbOverviewTab.class);
@@ -108,7 +64,6 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
private String filter;
ReentrantLock gridLock = new ReentrantLock();
ScrollPane scrollPane = new ScrollPane();
- boolean loginRequired;
TextField pageInput = new TextField(Integer.toString(1));
Button pageFirst = new Button("1");
Button pagePrev = new Button("â—€");
@@ -119,9 +74,9 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
StackPane root = new StackPane();
Task> searchTask;
SearchPopover popover;
- SearchPopoverTreeList popoverTreelist = new SearchPopoverTreeList();
+ SearchPopoverTreeList popoverTreeList = new SearchPopoverTreeList();
double imageAspectRatio = 3.0 / 4.0;
- private SimpleBooleanProperty preserveAspectRatio = new SimpleBooleanProperty(true);
+ private final SimpleBooleanProperty preserveAspectRatio = new SimpleBooleanProperty(true);
private ComboBox thumbWidth;
@@ -174,7 +129,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
popover.maxHeightProperty().bind(popover.minHeightProperty());
popover.prefHeightProperty().bind(popover.minHeightProperty());
popover.setMinHeight(450);
- popover.pushPage(popoverTreelist);
+ popover.pushPage(popoverTreeList);
StackPane.setAlignment(popover, Pos.TOP_RIGHT);
StackPane.setMargin(popover, new Insets(35, 50, 0, 0));
@@ -209,7 +164,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
pagination.getChildren().add(pageInput);
BorderPane.setMargin(pagination, new Insets(5));
pageInput.setPrefWidth(50);
- pageInput.setOnAction(e -> handlePageNumberInput(e));
+ pageInput.setOnAction(this::handlePageNumberInput);
pageFirst.setTooltip(new Tooltip("First Page"));
pageFirst.setOnAction(e -> changePageTo(1));
pagePrev.setTooltip(new Tooltip("Previous Page"));
@@ -230,8 +185,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
thumbWidth = new ComboBox<>(FXCollections.observableList(thumbWidths));
thumbWidth.getSelectionModel().select(Integer.valueOf(Config.getInstance().getSettings().thumbWidth));
thumbWidth.setOnAction(e -> {
- int width = thumbWidth.getSelectionModel().getSelectedItem();
- Config.getInstance().getSettings().thumbWidth = width;
+ Config.getInstance().getSettings().thumbWidth = thumbWidth.getSelectionModel().getSelectedItem();
updateThumbSize();
});
thumbSizeSelector.getChildren().add(thumbWidth);
@@ -252,19 +206,13 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
root.getChildren().add(popover);
setContent(root);
- scrollPane.setOnKeyReleased(new EventHandler() {
- @Override
- public void handle(KeyEvent event) {
- if (event.getCode() == KeyCode.RIGHT) {
- System.out.println(event.getSource());
- nextPage();
- } else if (event.getCode() == KeyCode.LEFT) {
- System.out.println(event.getSource());
- previousPage();
- } else if (event.getCode().getCode() >= KeyCode.DIGIT1.getCode() && event.getCode().getCode() <= KeyCode.DIGIT9.getCode()) {
- System.out.println(event.getSource());
- changePageTo(event.getCode().getCode() - 48);
- }
+ scrollPane.setOnKeyReleased(event -> {
+ if (event.getCode() == KeyCode.RIGHT) {
+ nextPage();
+ } else if (event.getCode() == KeyCode.LEFT) {
+ previousPage();
+ } else if (event.getCode().getCode() >= KeyCode.DIGIT1.getCode() && event.getCode().getCode() <= KeyCode.DIGIT9.getCode()) {
+ changePageTo(event.getCode().getCode() - 48);
}
});
}
@@ -296,7 +244,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
if(newValue.length() < 2) {
return;
}
- searchTask = new ThumbOverviewTabSearchTask(site, popover, popoverTreelist, newValue);
+ searchTask = new ThumbOverviewTabSearchTask(site, popover, popoverTreeList, newValue);
new Thread(searchTask).start();
};
}
@@ -448,11 +396,10 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
if(popup != null) {
popup.hide();
popup = null;
- return;
}
});
newCell.selectionProperty().addListener((obs, oldValue, newValue) -> {
- if(newValue.booleanValue()) {
+ if (Boolean.TRUE.equals(newValue)) {
selectedThumbCells.add(newCell);
} else {
selectedThumbCells.remove(newCell);
@@ -577,10 +524,10 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
event.put("amount", tokens.doubleValue());
EventBusHolder.BUS.post(event);
} catch (IOException ex) {
- LOG.error("An error occured while sending tip", ex);
- showError("Couldn't send tip", "An error occured while sending tip:", ex);
+ LOG.error("An error occurred while sending tip", ex);
+ showError(getTabPane().getScene(), "Couldn't send tip", "An error occurred while sending tip:", ex);
} catch (Exception ex) {
- showError("Couldn't send tip", "You entered an invalid amount of tokens", ex);
+ showError(getTabPane().getScene(), "Couldn't send tip", "You entered an invalid amount of tokens", ex);
}
}
});
@@ -599,7 +546,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
protected void follow(List selection, boolean follow) {
for (ThumbCell thumbCell : selection) {
thumbCell.follow(follow).thenAccept(success -> {
- if(follow && success.booleanValue()) {
+ if (follow && Boolean.TRUE.equals(success)) {
showAddToFollowedAnimation(thumbCell);
}
});
@@ -647,35 +594,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
ParallelTransition pt = new ParallelTransition(translate, scale);
pt.play();
pt.setOnFinished(evt -> root.getChildren().remove(iv));
-
- String normalStyle = followedTab.getStyle();
- Color normal = Color.web("#f4f4f4");
- Color highlight = Color.web("#2b8513");
- Transition blink = new Transition() {
- {
- setCycleDuration(Duration.millis(500));
- }
- @Override
- protected void interpolate(double frac) {
- double rh = highlight.getRed();
- double rn = normal.getRed();
- double diff = rh - rn;
- double r = (rn + diff * frac) * 255;
- double gh = highlight.getGreen();
- double gn = normal.getGreen();
- diff = gh - gn;
- double g = (gn + diff * frac) * 255;
- double bh = highlight.getBlue();
- double bn = normal.getBlue();
- diff = bh - bn;
- double b = (bn + diff * frac) * 255;
- String style = "-fx-background-color: rgb(" + r + "," + g + "," + b + ")";
- followedTab.setStyle(style);
- }
- };
- blink.setCycleCount(6);
- blink.setAutoReverse(true);
- blink.setOnFinished(evt -> followedTab.setStyle(normalStyle));
+ FollowTabBlinkTransition blink = new FollowTabBlinkTransition(followedTab);
blink.play();
});
}
@@ -714,13 +633,13 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
}
}
- private EventHandler mouseClickListener = e -> {
+ private final EventHandler mouseClickListener = e -> {
ThumbCell cell = (ThumbCell) e.getSource();
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
cell.setSelected(false);
cell.startPlayer();
} else if (e.getButton() == MouseButton.PRIMARY && e.isControlDown()) {
- if(popup == null) {
+ if (popup == null) {
cell.setSelected(!cell.isSelected());
}
} else if (e.getButton() == MouseButton.PRIMARY) {
@@ -750,12 +669,9 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
}
void filter() {
- Collections.sort(filteredThumbCells, (o1, o2) -> {
- ThumbCell c1 = o1;
- ThumbCell c2 = o2;
-
- if(c1.getIndex() < c2.getIndex()) return -1;
- if(c1.getIndex() > c2.getIndex()) return 1;
+ filteredThumbCells.sort((c1, c2) -> {
+ if (c1.getIndex() < c2.getIndex()) return -1;
+ if (c1.getIndex() > c2.getIndex()) return 1;
return c1.getModel().getName().compareTo(c2.getModel().getName());
});
@@ -861,7 +777,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
return !tokensMissing;
}
- private String createSearchText(Model m) throws ExecutionException {
+ private String createSearchText(Model m) {
StringBuilder searchTextBuilder = new StringBuilder(m.getName());
searchTextBuilder.append(' ');
searchTextBuilder.append(m.getDisplayName());
@@ -878,7 +794,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
public void setRecorder(Recorder recorder) {
this.recorder = recorder;
- popoverTreelist.setRecorder(recorder);
+ popoverTreeList.setRecorder(recorder);
}
@Override
diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTabSearchTask.java b/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTabSearchTask.java
index 4a0d159f..25892349 100644
--- a/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTabSearchTask.java
+++ b/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTabSearchTask.java
@@ -1,13 +1,5 @@
package ctbrec.ui.tabs;
-import static ctbrec.ui.controls.Dialogs.*;
-
-import java.io.IOException;
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import ctbrec.Model;
import ctbrec.sites.Site;
import ctbrec.ui.SiteUiFactory;
@@ -15,20 +7,27 @@ import ctbrec.ui.controls.SearchPopover;
import ctbrec.ui.controls.SearchPopoverTreeList;
import javafx.application.Platform;
import javafx.concurrent.Task;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+import static ctbrec.ui.controls.Dialogs.showError;
public class ThumbOverviewTabSearchTask extends Task> {
private static final Logger LOG = LoggerFactory.getLogger(ThumbOverviewTabSearchTask.class);
- private Site site;
- private SearchPopover popover;
- private SearchPopoverTreeList popoverTreelist;
- private String query;
+ private final Site site;
+ private final SearchPopover popover;
+ private final SearchPopoverTreeList popoverTreeList;
+ private final String query;
- public ThumbOverviewTabSearchTask(Site site, SearchPopover popover, SearchPopoverTreeList popoverTreelist, String query) {
+ public ThumbOverviewTabSearchTask(Site site, SearchPopover popover, SearchPopoverTreeList popoverTreeList, String query) {
this.site = site;
this.popover = popover;
- this.popoverTreelist = popoverTreelist;
+ this.popoverTreeList = popoverTreeList;
this.query = query;
}
@@ -39,10 +38,10 @@ public class ThumbOverviewTabSearchTask extends Task> {
try {
loggedin = SiteUiFactory.getUi(site).login();
} catch (IOException e) {
- loggedin = false;
+ // nothing to do
}
if(!loggedin) {
- showError("Login failed", "Search won't work correctly without login", null);
+ showError(popover.getScene(), "Login failed", "Search won't work correctly without login", null);
}
}
return site.search(query);
@@ -61,9 +60,9 @@ public class ThumbOverviewTabSearchTask extends Task> {
if(models.isEmpty()) {
popover.hide();
} else {
- popoverTreelist.getItems().clear();
+ popoverTreeList.getItems().clear();
for (Model model : getValue()) {
- popoverTreelist.getItems().add(model);
+ popoverTreeList.getItems().add(model);
}
popover.show();
}
diff --git a/client/src/main/java/ctbrec/ui/tabs/UpdateTab.java b/client/src/main/java/ctbrec/ui/tabs/UpdateTab.java
index 494dbf6d..51af2896 100644
--- a/client/src/main/java/ctbrec/ui/tabs/UpdateTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/UpdateTab.java
@@ -1,8 +1,5 @@
package ctbrec.ui.tabs;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import ctbrec.io.HttpException;
import ctbrec.ui.CamrecApplication;
import ctbrec.ui.CamrecApplication.Release;
@@ -18,12 +15,14 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import okhttp3.Request;
import okhttp3.Response;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class UpdateTab extends Tab {
private static final Logger LOG = LoggerFactory.getLogger(UpdateTab.class);
- private TextArea changelog;
+ private final TextArea changelog;
public UpdateTab(Release latest) {
setText("Update Available");
@@ -52,7 +51,7 @@ public class UpdateTab extends Tab {
}
} catch (Exception e1) {
LOG.error("Couldn't download the changelog", e1);
- Dialogs.showError("Communication error", "Couldn't download the changelog", e1);
+ Dialogs.showError(getTabPane().getScene(), "Communication error", "Couldn't download the changelog", e1);
}
}).start();
}
From 75131cd325186455a9469d54a92f3cc7b808b89b Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 29 Nov 2020 16:36:39 +0100
Subject: [PATCH 28/80] Fix MV Live downloads and search
---
CHANGELOG.md | 5 +-
.../java/ctbrec/sites/manyvids/MVLive.java | 36 +++++----
.../ctbrec/sites/manyvids/MVLiveClient.java | 75 ++++++-------------
.../manyvids/MVLiveMergedHlsDownload.java | 15 ++--
.../ctbrec/sites/manyvids/MVLiveModel.java | 56 +++++++-------
5 files changed, 74 insertions(+), 113 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 152936fa..a33decb9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,8 @@
3.10.5
========================
-* MFC websocket now uses the TLS URL
-* Fix: date placeholders with patterns with more than one ocurrence are
+* Fixed MV Live downloads
+* MFC web socket now uses the TLS URL
+* Fix: date placeholders with patterns with more than one occurrence are
replaced with the value of the first one
* Some smaller UI tweaks
* adjusted component sizes for small resolutions
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
index bf7b0cfc..a1bbbfc1 100644
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
+++ b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
@@ -1,19 +1,5 @@
package ctbrec.sites.manyvids;
-import static ctbrec.io.HttpConstants.*;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.jsoup.nodes.Element;
-import org.jsoup.select.Elements;
-
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Model.State;
@@ -25,11 +11,23 @@ import okhttp3.FormBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static ctbrec.io.HttpConstants.*;
public class MVLive extends AbstractSite {
- public static final String WS_URL = "wss://live.manyvids.com";
- //public static final String WS_URL = "http://localhost:8080";
+ public static final String WS_URL = "wss://app-v2.live.manyvids.com";
public static final String WS_ORIGIN = "https://live.manyvids.com";
public static final String BASE_URL = "https://www.manyvids.com/MVLive/";
@@ -111,7 +109,8 @@ public class MVLive extends AbstractSite {
}
@Override
- public void init() throws IOException {
+ public void init() {
+ // nothing special to do for manyvids
}
public List getModels() throws IOException {
@@ -175,9 +174,8 @@ public class MVLive extends AbstractSite {
String getMvToken() throws IOException {
if (mvtoken == null) {
Request request = new Request.Builder()
- .url(getBaseUrl())
+ .url("https://www.manyvids.com/")
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(REFERER, MVLive.BASE_URL)
.build();
try (Response response = getHttpClient().execute(request)) {
if (response.isSuccessful()) {
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java
index 370c00b0..1793154f 100644
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java
+++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java
@@ -1,56 +1,43 @@
package ctbrec.sites.manyvids;
-import static ctbrec.StringUtil.*;
-import static ctbrec.io.HttpConstants.*;
-import static ctbrec.sites.manyvids.MVLive.*;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Random;
-import java.util.UUID;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
+import com.google.common.base.Objects;
+import ctbrec.Config;
+import ctbrec.sites.manyvids.wsmsg.*;
+import okhttp3.Response;
+import okhttp3.*;
+import okio.ByteString;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.base.Objects;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
-import ctbrec.Config;
-import ctbrec.io.HttpException;
-import ctbrec.sites.manyvids.wsmsg.GetBroadcastHealth;
-import ctbrec.sites.manyvids.wsmsg.Message;
-import ctbrec.sites.manyvids.wsmsg.Ping;
-import ctbrec.sites.manyvids.wsmsg.RegisterMessage;
-import ctbrec.sites.manyvids.wsmsg.SendMessage;
-import okhttp3.Cookie;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.WebSocket;
-import okhttp3.WebSocketListener;
-import okio.ByteString;
+import static ctbrec.StringUtil.isNotBlank;
+import static ctbrec.io.HttpConstants.*;
+import static ctbrec.sites.manyvids.MVLive.WS_ORIGIN;
+import static ctbrec.sites.manyvids.MVLive.WS_URL;
public class MVLiveClient {
private static final Logger LOG = LoggerFactory.getLogger(MVLiveClient.class);
+ private final Map futureResponses = new HashMap<>();
+ private final MVLiveHttpClient httpClient;
+ private final Object streamUrlMonitor = new Object();
+ private final Random rng = new Random();
+
private WebSocket ws;
- private Random rng = new Random();
private volatile boolean running = false;
private volatile boolean connecting = false;
- private Object streamUrlMonitor = new Object();
private String masterPlaylist = null;
private String roomNumber;
private String roomId;
private ScheduledExecutorService scheduler;
- private Map futureResponses = new HashMap<>();
- private MVLiveHttpClient httpClient;
public MVLiveClient(MVLiveHttpClient httpClient) {
this.httpClient = httpClient;
@@ -61,7 +48,7 @@ public class MVLiveClient {
if (ws == null && !connecting) {
httpClient.fetchAuthenticationCookies();
- JSONObject response = getRoomLocation(model);
+ JSONObject response = model.getRoomLocation();
roomNumber = response.optString("floorId");
roomId = response.optString("roomId");
int randomNumber = 100 + rng.nextInt(800);
@@ -73,22 +60,6 @@ public class MVLiveClient {
}
}
- private JSONObject getRoomLocation(MVLiveModel model) throws IOException {
- Request req = new Request.Builder()
- .url(WS_ORIGIN + "/api/roomlocation/" + model.getDisplayName() + "?private=false")
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(COOKIE, getPhpSessionIdCookie())
- .build();
- try (Response response = httpClient.execute(req)) {
- if (response.isSuccessful()) {
- return new JSONObject(response.body().string());
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
private String getPhpSessionIdCookie() {
List cookies = httpClient.getCookiesByName("PHPSESSID");
return cookies.stream().map(c -> c.name() + "=" + c.value()).findFirst().orElse("");
@@ -109,7 +80,7 @@ public class MVLiveClient {
.header(ORIGIN, WS_ORIGIN)
.header(COOKIE, getPhpSessionIdCookie())
.build();
- WebSocket websocket = httpClient.newWebSocket(req, new WebSocketListener() {
+ return httpClient.newWebSocket(req, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
@@ -157,7 +128,6 @@ public class MVLiveClient {
@Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
- //msgBuffer.append(text);
LOG.trace("Message: {}", text);
text = Optional.ofNullable(text).orElse("");
if (Objects.equal("o", text)) {
@@ -202,7 +172,6 @@ public class MVLiveClient {
LOG.debug("Binary Message: {}", bytes.hex());
}
});
- return websocket;
}
void sendMessages(Message... messages) {
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java
index 6db9f879..4a7d2327 100644
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java
+++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java
@@ -1,21 +1,20 @@
package ctbrec.sites.manyvids;
+import ctbrec.io.HttpClient;
+import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.IOException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ctbrec.io.HttpClient;
-import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload;
-
public class MVLiveMergedHlsDownload extends MergedFfmpegHlsDownload {
private static final Logger LOG = LoggerFactory.getLogger(MVLiveMergedHlsDownload.class);
- private ScheduledExecutorService scheduler;
+ private transient ScheduledExecutorService scheduler;
public MVLiveMergedHlsDownload(HttpClient client) {
super(client);
@@ -31,7 +30,7 @@ public class MVLiveMergedHlsDownload extends MergedFfmpegHlsDownload {
t.setPriority(Thread.MIN_PRIORITY);
return t;
});
- scheduler.scheduleAtFixedRate(() -> updateCloudFlareCookies(), 2, 2, TimeUnit.MINUTES);
+ scheduler.scheduleAtFixedRate(this::updateCloudFlareCookies, 2, 2, TimeUnit.MINUTES);
updateCloudFlareCookies();
super.start();
} finally {
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java
index 681ddab0..f5b2de30 100644
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java
+++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java
@@ -1,8 +1,21 @@
package ctbrec.sites.manyvids;
-import static ctbrec.Model.State.*;
-import static ctbrec.io.HttpConstants.*;
-import static java.nio.charset.StandardCharsets.*;
+import com.iheartradio.m3u8.*;
+import com.iheartradio.m3u8.data.MasterPlaylist;
+import com.iheartradio.m3u8.data.Playlist;
+import com.iheartradio.m3u8.data.PlaylistData;
+import ctbrec.AbstractModel;
+import ctbrec.Config;
+import ctbrec.Model;
+import ctbrec.StringUtil;
+import ctbrec.io.HttpException;
+import ctbrec.recorder.download.Download;
+import ctbrec.recorder.download.StreamSource;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -13,29 +26,9 @@ import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.iheartradio.m3u8.Encoding;
-import com.iheartradio.m3u8.Format;
-import com.iheartradio.m3u8.ParseException;
-import com.iheartradio.m3u8.ParsingMode;
-import com.iheartradio.m3u8.PlaylistException;
-import com.iheartradio.m3u8.PlaylistParser;
-import com.iheartradio.m3u8.data.MasterPlaylist;
-import com.iheartradio.m3u8.data.Playlist;
-import com.iheartradio.m3u8.data.PlaylistData;
-
-import ctbrec.AbstractModel;
-import ctbrec.Config;
-import ctbrec.Model;
-import ctbrec.StringUtil;
-import ctbrec.io.HttpException;
-import ctbrec.recorder.download.Download;
-import ctbrec.recorder.download.StreamSource;
-import okhttp3.Request;
-import okhttp3.Response;
+import static ctbrec.Model.State.ONLINE;
+import static ctbrec.io.HttpConstants.*;
+import static java.nio.charset.StandardCharsets.UTF_8;
public class MVLiveModel extends AbstractModel {
@@ -116,7 +109,7 @@ public class MVLiveModel extends AbstractModel {
}
public void updateCloudFlareCookies() throws IOException, InterruptedException {
- String url = MVLive.WS_ORIGIN + "/api/" + getRoomNumber() + "/player-settings/" + getDisplayName();
+ String url = "https://app-v2.live.manyvids.com/api/" + getRoomNumber() + "/player-settings/" + getDisplayName();
LOG.trace("Getting CF cookies: {}", url);
Request req = new Request.Builder()
.url(url)
@@ -158,8 +151,8 @@ public class MVLiveModel extends AbstractModel {
public JSONObject getRoomLocation() throws IOException {
fetchGeneralCookies();
httpClient.fetchAuthenticationCookies();
- String url = MVLive.WS_ORIGIN + "/api/roomlocation/" + getDisplayName() + "?private=false";
- LOG.trace("Fetching room location from {}", url);
+ String url = "https://roompool.live.manyvids.com/roompool/" + getDisplayName() + "?private=false";
+ LOG.debug("Fetching room location from {}", url);
Request req = new Request.Builder()
.url(url)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
@@ -169,8 +162,9 @@ public class MVLiveModel extends AbstractModel {
.build();
try (Response response = getHttpClient().execute(req)) {
if (response.isSuccessful()) {
- JSONObject json = new JSONObject(response.body().string());
- LOG.trace("Room location response: {}", json.toString(2));
+ String body = response.body().string();
+ JSONObject json = new JSONObject(body);
+ LOG.trace("Room location response: {}", json);
return json;
} else {
throw new HttpException(response.code(), response.message());
From 9e2074ba5905fb262ad0ea70f22dea2ba0b4d991 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Fri, 4 Dec 2020 21:04:11 +0100
Subject: [PATCH 29/80] Fix Cam4 model details link
---
common/src/main/java/ctbrec/sites/cam4/Cam4Model.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
index 63cf1afc..239b5869 100644
--- a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
+++ b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
@@ -58,7 +58,7 @@ public class Cam4Model extends AbstractModel {
}
private void loadModelDetails() throws IOException, ModelDetailsEmptyException {
- String url = site.getBaseUrl() + "/getBroadcasting?usernames=" + getName();
+ String url = site.getBaseUrl() + "/directoryCams?directoryJson=true&online=true&username=" + getName();
LOG.trace("Loading model details {}", url);
Request req = new Request.Builder().url(url).build();
try (Response response = site.getHttpClient().execute(req)) {
From ab911baeaefae742fbc2245393196d051641a305 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Fri, 4 Dec 2020 21:05:18 +0100
Subject: [PATCH 30/80] Bump version to 3.10.6
---
client/pom.xml | 2 +-
common/pom.xml | 2 +-
master/pom.xml | 2 +-
server/pom.xml | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/client/pom.xml b/client/pom.xml
index 453bdce2..fff10beb 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.5
+ 3.10.6
../master
diff --git a/common/pom.xml b/common/pom.xml
index 98714b49..0f67d0c1 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.5
+ 3.10.6
../master
diff --git a/master/pom.xml b/master/pom.xml
index 11b93a17..6df4c6d2 100644
--- a/master/pom.xml
+++ b/master/pom.xml
@@ -6,7 +6,7 @@
ctbrec
master
pom
- 3.10.5
+ 3.10.6
../common
diff --git a/server/pom.xml b/server/pom.xml
index 263e9735..f82f7441 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.5
+ 3.10.6
../master
From dc95b431dcb309ac76276aa90944c6b1cc89bf2d Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Fri, 4 Dec 2020 21:05:54 +0100
Subject: [PATCH 31/80] Update changelog
---
CHANGELOG.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a33decb9..6bae03aa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+3.10.6
+========================
+* Fixed Cam4 downloads
+
3.10.5
========================
* Fixed MV Live downloads
From f79441d4146bbc3cbc9ffdc42adf80ae25ab5375 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 5 Dec 2020 12:53:44 +0100
Subject: [PATCH 32/80] Remove stray java file
---
.../ctbrec/sites/bonga/BongaCamsModel.java | 193 ------------------
1 file changed, 193 deletions(-)
delete mode 100644 src/main/java/ctbrec/sites/bonga/BongaCamsModel.java
diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java
deleted file mode 100644
index dba742fe..00000000
--- a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java
+++ /dev/null
@@ -1,193 +0,0 @@
-package ctbrec.sites.bonga;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.iheartradio.m3u8.Encoding;
-import com.iheartradio.m3u8.Format;
-import com.iheartradio.m3u8.ParseException;
-import com.iheartradio.m3u8.PlaylistException;
-import com.iheartradio.m3u8.PlaylistParser;
-import com.iheartradio.m3u8.data.MasterPlaylist;
-import com.iheartradio.m3u8.data.Playlist;
-import com.iheartradio.m3u8.data.PlaylistData;
-import com.iheartradio.m3u8.data.StreamInfo;
-
-import ctbrec.AbstractModel;
-import ctbrec.recorder.download.StreamSource;
-import ctbrec.sites.Site;
-import okhttp3.FormBody;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-
-public class BongaCamsModel extends AbstractModel {
-
- private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsModel.class);
-
- private BongaCams site;
- private int userId;
- private String onlineState = "n/a";
- private boolean online = false;
- private List streamSources = new ArrayList<>();
- private int[] resolution;
-
- @Override
- public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
- return online;
- }
-
- public void setOnline(boolean online) {
- this.online = online;
- }
-
- @Override
- public String getOnlineState(boolean failFast) throws IOException, ExecutionException {
- return onlineState;
- }
-
- public void setOnlineState(String onlineState) {
- this.onlineState = onlineState;
- }
-
- @Override
- public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
- String streamUrl = getStreamUrl();
- if (streamUrl == null) {
- return Collections.emptyList();
- }
- Request req = new Request.Builder().url(streamUrl).build();
- Response response = site.getHttpClient().execute(req);
- try {
- InputStream inputStream = response.body().byteStream();
- PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
- Playlist playlist = parser.parse();
- MasterPlaylist master = playlist.getMasterPlaylist();
- for (PlaylistData playlistData : master.getPlaylists()) {
-
- StreamSource streamsource = new StreamSource();
- streamsource.mediaPlaylistUrl = streamUrl.replace("playlist.m3u8", playlistData.getUri());
- if (playlistData.hasStreamInfo()) {
- StreamInfo info = playlistData.getStreamInfo();
- streamsource.bandwidth = info.getBandwidth();
- streamsource.width = info.hasResolution() ? info.getResolution().width : 0;
- streamsource.height = info.hasResolution() ? info.getResolution().height : 0;
- } else {
- streamsource.bandwidth = 0;
- streamsource.width = 0;
- streamsource.height = 0;
- }
- streamSources.add(streamsource);
- }
- } finally {
- response.close();
- }
- return streamSources;
- }
-
- private String getStreamUrl() throws IOException {
- String url = BongaCams.BASE_URL + "/tools/amf.php";
- RequestBody body = new FormBody.Builder()
- .add("method", "getRoomData")
- .add("args[]", getName())
- .add("args[]", "false")
- .build();
- Request request = new Request.Builder()
- .url(url)
- .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0")
- .addHeader("Accept", "application/json, text/javascript, */*")
- .addHeader("Accept-Language", "en")
- .addHeader("Referer", BongaCams.BASE_URL)
- .addHeader("X-Requested-With", "XMLHttpRequest")
- .post(body)
- .build();
- try(Response response = site.getHttpClient().execute(request)) {
- if(response.isSuccessful()) {
- JSONObject json = new JSONObject(response.body().string());
- if(json.optString("status").equals("success")) {
- JSONObject localData = json.getJSONObject("localData");
- String server = localData.getString("videoServerUrl");
- return "https:" + server + "/hls/stream_" + getName() + "/playlist.m3u8";
- } else {
- throw new IOException("Request was not successful: " + json.toString(2));
- }
- } else {
- throw new IOException(response.code() + " " + response.message());
- }
- }
- }
-
- @Override
- public void invalidateCacheEntries() {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void receiveTip(int tokens) throws IOException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public int[] getStreamResolution(boolean failFast) throws ExecutionException {
- if(resolution == null) {
- if(failFast) {
- return new int[2];
- }
- try {
- List streamSources = getStreamSources();
- Collections.sort(streamSources);
- StreamSource best = streamSources.get(streamSources.size()-1);
- resolution = new int[] {best.width, best.height};
- } catch (ExecutionException | IOException | ParseException | PlaylistException e) {
- LOG.error("Couldn't determine stream resolution", e);
- }
- return resolution;
- } else {
- return resolution;
- }
- }
-
- @Override
- public boolean follow() throws IOException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean unfollow() throws IOException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void setSite(Site site) {
- if(site instanceof BongaCams) {
- this.site = (BongaCams) site;
- } else {
- throw new IllegalArgumentException("Site has to be an instance of BongaCams");
- }
- }
-
- @Override
- public Site getSite() {
- return site;
- }
-
- public int getUserId() {
- return userId;
- }
-
- public void setUserId(int userId) {
- this.userId = userId;
- }
-}
From c6dd2bb06cba85da62efa57cf4e856c9ed1b3905 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 5 Dec 2020 16:10:41 +0100
Subject: [PATCH 33/80] Fix bug in credentialsAvailable for Streamate
It was using the username for Chaturbate. Whoopsy!
---
common/src/main/java/ctbrec/sites/streamate/Streamate.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/src/main/java/ctbrec/sites/streamate/Streamate.java b/common/src/main/java/ctbrec/sites/streamate/Streamate.java
index 2bed9f70..8f845622 100644
--- a/common/src/main/java/ctbrec/sites/streamate/Streamate.java
+++ b/common/src/main/java/ctbrec/sites/streamate/Streamate.java
@@ -179,7 +179,7 @@ public class Streamate extends AbstractSite {
@Override
public boolean credentialsAvailable() {
- String username = Config.getInstance().getSettings().username;
+ String username = Config.getInstance().getSettings().streamateUsername;
return StringUtil.isNotBlank(username);
}
From f2d3026d0a2f19aaba53d5b6ccfbae64ec2b0999 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 5 Dec 2020 16:13:33 +0100
Subject: [PATCH 34/80] Code cleanup - Remove compiler warnings
---
.../src/main/java/ctbrec/recorder/postprocessing/Webhook.java | 4 ----
common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java | 1 +
.../src/main/java/ctbrec/sites/showup/ShowupHttpClient.java | 3 ---
3 files changed, 1 insertion(+), 7 deletions(-)
diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Webhook.java b/common/src/main/java/ctbrec/recorder/postprocessing/Webhook.java
index 4765736a..cee86c2d 100644
--- a/common/src/main/java/ctbrec/recorder/postprocessing/Webhook.java
+++ b/common/src/main/java/ctbrec/recorder/postprocessing/Webhook.java
@@ -2,9 +2,6 @@ package ctbrec.recorder.postprocessing;
import java.io.IOException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import ctbrec.Config;
import ctbrec.NotImplementedExcetion;
import ctbrec.Recording;
@@ -12,7 +9,6 @@ import ctbrec.recorder.RecordingManager;
public class Webhook extends AbstractPlaceholderAwarePostProcessor {
- private static final Logger LOG = LoggerFactory.getLogger(Webhook.class);
public static final String URL = "webhook.url";
public static final String HEADERS = "webhook.headers";
public static final String METHOD = "webhook.method";
diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java
index 0320396c..4834b078 100644
--- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java
+++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java
@@ -664,6 +664,7 @@ public class MyFreeCamsClient {
return camservString;
}
+ @SuppressWarnings("unused")
private boolean isBroadcasterOnWebRTC(SessionState state) {
return (Optional.ofNullable(state).map(SessionState::getM).map(Model::getFlags).orElse(0) & 524288) == 524288;
}
diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupHttpClient.java b/common/src/main/java/ctbrec/sites/showup/ShowupHttpClient.java
index be0eb313..a2d7e7bd 100644
--- a/common/src/main/java/ctbrec/sites/showup/ShowupHttpClient.java
+++ b/common/src/main/java/ctbrec/sites/showup/ShowupHttpClient.java
@@ -11,8 +11,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
@@ -27,7 +25,6 @@ import okhttp3.Response;
public class ShowupHttpClient extends HttpClient {
- private static final Logger LOG = LoggerFactory.getLogger(ShowupHttpClient.class);
private String csrfToken;
protected ShowupHttpClient() {
From 8b66dce61af02abe70c7c10afd630df49d220a2c Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 5 Dec 2020 16:13:56 +0100
Subject: [PATCH 35/80] Rename chaturbate username and password setting
---
.../ui/sites/chaturbate/ChaturbateConfigUi.java | 12 ++++++------
.../ui/sites/chaturbate/ChaturbateUpdateService.java | 3 +--
common/src/main/java/ctbrec/Config.java | 11 +++++++++++
common/src/main/java/ctbrec/Settings.java | 9 +++++++--
.../java/ctbrec/sites/chaturbate/Chaturbate.java | 4 ++--
.../sites/chaturbate/ChaturbateHttpClient.java | 6 +++---
6 files changed, 30 insertions(+), 15 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateConfigUi.java b/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateConfigUi.java
index 791b1fd1..445d3a77 100644
--- a/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateConfigUi.java
+++ b/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateConfigUi.java
@@ -45,10 +45,10 @@ public class ChaturbateConfigUi extends AbstractConfigUI {
layout.add(enabled, 1, row++);
layout.add(new Label("Chaturbate User"), 0, row);
- TextField username = new TextField(Config.getInstance().getSettings().username);
+ TextField username = new TextField(Config.getInstance().getSettings().chaturbateUsername);
username.textProperty().addListener((ob, o, n) -> {
- if(!n.equals(Config.getInstance().getSettings().username)) {
- Config.getInstance().getSettings().username = n;
+ if(!n.equals(Config.getInstance().getSettings().chaturbateUsername)) {
+ Config.getInstance().getSettings().chaturbateUsername = n;
chaturbate.getHttpClient().logout();
save();
}
@@ -60,10 +60,10 @@ public class ChaturbateConfigUi extends AbstractConfigUI {
layout.add(new Label("Chaturbate Password"), 0, row);
PasswordField password = new PasswordField();
- password.setText(Config.getInstance().getSettings().password);
+ password.setText(Config.getInstance().getSettings().chaturbatePassword);
password.textProperty().addListener((ob, o, n) -> {
- if(!n.equals(Config.getInstance().getSettings().password)) {
- Config.getInstance().getSettings().password = n;
+ if(!n.equals(Config.getInstance().getSettings().chaturbatePassword)) {
+ Config.getInstance().getSettings().chaturbatePassword = n;
chaturbate.getHttpClient().logout();
save();
}
diff --git a/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateUpdateService.java b/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateUpdateService.java
index cddd8d17..5fbcd63f 100644
--- a/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateUpdateService.java
+++ b/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateUpdateService.java
@@ -11,7 +11,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Model;
-import ctbrec.StringUtil;
import ctbrec.sites.chaturbate.Chaturbate;
import ctbrec.sites.chaturbate.ChaturbateModelParser;
import ctbrec.ui.SiteUiFactory;
@@ -49,7 +48,7 @@ public class ChaturbateUpdateService extends PaginatedScheduledService {
return new Task>() {
@Override
public List call() throws IOException {
- if(loginRequired && StringUtil.isBlank(ctbrec.Config.getInstance().getSettings().username)) {
+ if(loginRequired && !chaturbate.credentialsAvailable()) {
return Collections.emptyList();
} else {
String url = ChaturbateUpdateService.this.url + "?page="+page+"&keywords=&_=" + System.currentTimeMillis();
diff --git a/common/src/main/java/ctbrec/Config.java b/common/src/main/java/ctbrec/Config.java
index 2cbc8ce5..d4709ded 100644
--- a/common/src/main/java/ctbrec/Config.java
+++ b/common/src/main/java/ctbrec/Config.java
@@ -102,6 +102,7 @@ public class Config {
migrateOldSettings();
}
+ @SuppressWarnings("deprecation")
private void migrateOldSettings() {
// 3.8.0 from maxResolution only to resolution range
if(settings.minimumResolution == settings.maximumResolution && settings.minimumResolution == 0) {
@@ -128,6 +129,16 @@ public class Config {
settings.postProcessors.add(removeKeepFile);
settings.removeRecordingAfterPostProcessing = false;
}
+
+ // 3.10.7
+ if (StringUtil.isNotBlank(settings.username)) {
+ settings.chaturbateUsername = settings.username;
+ settings.username = null;
+ }
+ if (StringUtil.isNotBlank(settings.password)) {
+ settings.chaturbatePassword = settings.password;
+ settings.password = null;
+ }
}
private void makeBackup(File source) {
diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java
index fc67ce73..355a75c5 100644
--- a/common/src/main/java/ctbrec/Settings.java
+++ b/common/src/main/java/ctbrec/Settings.java
@@ -41,6 +41,8 @@ public class Settings {
public String cam4Username = "";
public String camsodaPassword = "";
public String camsodaUsername = "";
+ public String chaturbatePassword = "";
+ public String chaturbateUsername = "";
public String chaturbateBaseUrl = "https://chaturbate.com";
public boolean chooseStreamQuality = false;
public String colorAccent = "#FFFFFF";
@@ -93,7 +95,9 @@ public class Settings {
public int onlineCheckIntervalInSecs = 60;
public boolean onlineCheckSkipsPausedModels = false;
public int overviewUpdateIntervalInSecs = 10;
- public String password = ""; // chaturbate password TODO maybe rename this onetime
+ @Deprecated
+ public String password = "";
+ @Deprecated
public String postProcessing = "";
public int postProcessingThreads = 2;
public List postProcessors = new ArrayList<>();
@@ -130,7 +134,8 @@ public class Settings {
public boolean transportLayerSecurity = true;
public int thumbWidth = 180;
public boolean updateThumbnails = true;
- public String username = ""; // chaturbate username TODO maybe rename this onetime
+ @Deprecated
+ public String username = "";
public int windowHeight = 800;
public boolean windowMaximized = false;
public int windowWidth = 1340;
diff --git a/common/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java b/common/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java
index 8b2f087a..197c03a8 100644
--- a/common/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java
+++ b/common/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java
@@ -56,7 +56,7 @@ public class Chaturbate extends AbstractSite {
@Override
public Double getTokenBalance() throws IOException {
- String username = Config.getInstance().getSettings().username;
+ String username = Config.getInstance().getSettings().chaturbateUsername;
if (username == null || username.trim().isEmpty()) {
throw new IOException("Not logged in");
}
@@ -157,7 +157,7 @@ public class Chaturbate extends AbstractSite {
@Override
public boolean credentialsAvailable() {
- String username = Config.getInstance().getSettings().username;
+ String username = Config.getInstance().getSettings().chaturbateUsername;
return username != null && !username.trim().isEmpty();
}
diff --git a/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java b/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java
index e48671f1..9084018a 100644
--- a/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java
+++ b/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java
@@ -67,8 +67,8 @@ public class ChaturbateHttpClient extends HttpClient {
LOG.debug("csrf token is {}", token);
RequestBody body = new FormBody.Builder()
- .add("username", Config.getInstance().getSettings().username)
- .add("password", Config.getInstance().getSettings().password)
+ .add("username", Config.getInstance().getSettings().chaturbateUsername)
+ .add("password", Config.getInstance().getSettings().chaturbatePassword)
.add("next", "")
.add("csrfmiddlewaretoken", token)
.build();
@@ -103,7 +103,7 @@ public class ChaturbateHttpClient extends HttpClient {
}
private boolean checkLogin() throws IOException {
- String url = "https://chaturbate.com/p/" + Config.getInstance().getSettings().username + "/";
+ String url = "https://chaturbate.com/p/" + Config.getInstance().getSettings().chaturbateUsername + "/";
Request req = new Request.Builder()
.url(url)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
From 0430cc07a3a7a2a44b34c3ab42158f9bd50cd60b Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 5 Dec 2020 16:26:02 +0100
Subject: [PATCH 36/80] Fix use the stripchat user name instead of the
chaturbate user name for the credential check
---
.../java/ctbrec/ui/sites/stripchat/StripchatUpdateService.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatUpdateService.java b/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatUpdateService.java
index f44f2b4b..ff441d76 100644
--- a/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatUpdateService.java
+++ b/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatUpdateService.java
@@ -15,7 +15,6 @@ import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Model;
-import ctbrec.StringUtil;
import ctbrec.io.HttpException;
import ctbrec.sites.stripchat.Stripchat;
import ctbrec.sites.stripchat.StripchatModel;
@@ -46,7 +45,7 @@ public class StripchatUpdateService extends PaginatedScheduledService {
@Override
public List call() throws IOException {
List models = new ArrayList<>();
- if(loginRequired && StringUtil.isBlank(ctbrec.Config.getInstance().getSettings().username)) {
+ if(loginRequired && !stripchat.credentialsAvailable()) {
return Collections.emptyList();
} else {
int offset = (getPage() - 1) * modelsPerPage;
From a31debcdea607d0f1feef2ad5f600b22859de3b0 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 5 Dec 2020 21:30:54 +0100
Subject: [PATCH 37/80] Add possibility to split recordings with different
strategies
---
.../resources/html/docs/ConfigurationFile.md | 13 +-
common/src/main/java/ctbrec/Config.java | 6 +
common/src/main/java/ctbrec/Recording.java | 41 +---
common/src/main/java/ctbrec/Settings.java | 11 ++
common/src/main/java/ctbrec/io/IoUtils.java | 29 +++
.../ctbrec/recorder/NextGenLocalRecorder.java | 1 +
.../ctbrec/recorder/download/Download.java | 2 +
.../recorder/download/SplittingStrategy.java | 9 +
.../recorder/download/dash/DashDownload.java | 6 +
.../download/hls/AbstractHlsDownload.java | 26 +++
.../hls/CombinedSplittingStrategy.java | 31 +++
.../recorder/download/hls/FFmpegDownload.java | 5 +
.../recorder/download/hls/HlsDownload.java | 182 ++++++++++--------
.../download/hls/MergedFfmpegHlsDownload.java | 37 ++--
.../download/hls/NoopSplittingStrategy.java | 19 ++
.../download/hls/SizeSplittingStrategy.java | 22 +++
.../download/hls/TimeSplittingStrategy.java | 28 +++
.../sites/showup/ShowupMergedDownload.java | 4 +-
18 files changed, 322 insertions(+), 150 deletions(-)
create mode 100644 common/src/main/java/ctbrec/recorder/download/SplittingStrategy.java
create mode 100644 common/src/main/java/ctbrec/recorder/download/hls/CombinedSplittingStrategy.java
create mode 100644 common/src/main/java/ctbrec/recorder/download/hls/NoopSplittingStrategy.java
create mode 100644 common/src/main/java/ctbrec/recorder/download/hls/SizeSplittingStrategy.java
create mode 100644 common/src/main/java/ctbrec/recorder/download/hls/TimeSplittingStrategy.java
diff --git a/client/src/main/resources/html/docs/ConfigurationFile.md b/client/src/main/resources/html/docs/ConfigurationFile.md
index e3ea4bf1..0222f242 100644
--- a/client/src/main/resources/html/docs/ConfigurationFile.md
+++ b/client/src/main/resources/html/docs/ConfigurationFile.md
@@ -53,16 +53,21 @@ the port ctbrec tries to connect to, if it is run in remote mode.
- **onlineCheckSkipsPausedModels** - [`true`,`false`] Skip the online check for paused models. If you have many models in the recording list, this can reduce the delay when a recording starts after a model came online.
-- **postProcessing** - Absolute path to a script, which is executed once a recording is finished. See [Post-Processing](/docs/PostProcessing.md).
+- **postProcessing** - **Deprecated. See [Post-Processing](/docs/PostProcessing.md)** Absolute path to a script, which is executed once a recording is finished.
- **recordingsDir** - Where ctbrec saves the recordings.
- **recordingsDirStructure** (server only) - [`FLAT`, `ONE_PER_MODEL`, `ONE_PER_RECORDING`] How recordings are stored in the file system. `FLAT` - all recordings in one directory; `ONE_PER_MODEL` - one directory per model; `ONE_PER_RECORDING` - each recordings ends up in its own directory. Change this only, if you have `recordSingleFile` set to `true`
-- **recordSingleFile** (server only) - [`true`,`false`] - How recordings are stored in the file system. `true` means, each recording is saved in one large file. `false` means, ctbrec just downloads the stream segments.
+- **recordSingleFile** (server only) - [`true`,`false`] - How recordings are stored in the file system. `true` means, each recording is saved in one large file. `false` means, ctbrec just downloads the stream segments.
-- **splitRecordings** - [0 - 2147483647] in seconds. Split recordings after this amount of seconds. The recordings are split up into several individual recordings,
-which have the defined length (roughly). 0 means no splitting. The server does not support splitRecordings.
+- **splitStrategy** - [`DONT`, `TIME`, `SIZE`, `TIME_OR_SIZE`] Defines if and how to split recordings. Also see `splitRecordingsAfterSecs` and `splitRecordingsBiggerThanBytes`
+
+- **splitRecordingsAfterSecs** - [0 - 2147483647] in seconds. Split recordings after this amount of seconds. The recordings are split up into several individual recordings,
+which have the defined length (roughly). Has to be activated with `splitStrategy`.
+
+- **splitRecordingsBiggerThanBytes** - [0 - 9223372036854775807] in bytes. Split recordings, if the size on disk exceeds this value. The recordings are split up into several individual recordings,
+which have the defined size (roughly). Has to be activated with `splitStrategy`.
- **webinterface** (server only) - [`true`,`false`] Enables the webinterface for the server. You can access it with http://host:port/static/index.html Don't activate this on
a machine, which can be accessed from the internet, because this is totally unprotected at the moment.
diff --git a/common/src/main/java/ctbrec/Config.java b/common/src/main/java/ctbrec/Config.java
index d4709ded..c943ab92 100644
--- a/common/src/main/java/ctbrec/Config.java
+++ b/common/src/main/java/ctbrec/Config.java
@@ -23,6 +23,7 @@ import org.slf4j.LoggerFactory;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
+import ctbrec.Settings.SplitStrategy;
import ctbrec.io.FileJsonAdapter;
import ctbrec.io.ModelJsonAdapter;
import ctbrec.io.PostProcessorJsonAdapter;
@@ -139,6 +140,11 @@ public class Config {
settings.chaturbatePassword = settings.password;
settings.password = null;
}
+ if (settings.splitRecordings > 0) {
+ settings.splitStrategy = SplitStrategy.TIME;
+ settings.splitRecordingsAfterSecs = settings.splitRecordings;
+ settings.splitRecordings = 0;
+ }
}
private void makeBackup(File source) {
diff --git a/common/src/main/java/ctbrec/Recording.java b/common/src/main/java/ctbrec/Recording.java
index ad34b717..f2ccc264 100644
--- a/common/src/main/java/ctbrec/Recording.java
+++ b/common/src/main/java/ctbrec/Recording.java
@@ -3,35 +3,23 @@ package ctbrec;
import static ctbrec.Recording.State.*;
import java.io.File;
-import java.io.IOException;
import java.io.Serializable;
-import java.nio.file.FileVisitOption;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
-import java.util.EnumSet;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import ctbrec.event.EventBusHolder;
import ctbrec.event.RecordingStateChangedEvent;
+import ctbrec.io.IoUtils;
import ctbrec.recorder.download.Download;
import ctbrec.recorder.download.VideoLengthDetector;
public class Recording implements Serializable {
- private static final transient Logger LOG = LoggerFactory.getLogger(Recording.class);
-
private String id;
private Model model;
private transient Download download;
@@ -253,11 +241,11 @@ public class Recording implements Serializable {
private long getSize() {
File rec = getAbsoluteFile();
if (rec.isDirectory()) {
- return getDirectorySize(rec);
+ return IoUtils.getDirectorySize(rec);
} else {
if (!rec.exists()) {
if (rec.getName().endsWith(".m3u8")) {
- return getDirectorySize(rec.getParentFile());
+ return IoUtils.getDirectorySize(rec.getParentFile());
} else {
return -1;
}
@@ -267,29 +255,6 @@ public class Recording implements Serializable {
}
}
- private long getDirectorySize(File dir) {
- final long[] size = { 0 };
- int maxDepth = 1; // Don't expect subdirs, so don't even try
- try {
- Files.walkFileTree(dir.toPath(), EnumSet.noneOf(FileVisitOption.class), maxDepth, new SimpleFileVisitor() {
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
- size[0] += attrs.size();
- return FileVisitResult.CONTINUE;
- }
-
- @Override
- public FileVisitResult visitFileFailed(Path file, IOException exc) {
- // Ignore file access issues
- return FileVisitResult.CONTINUE;
- }
- });
- } catch (IOException e) {
- LOG.error("Couldn't determine size of recording {}", this, e);
- }
- return size[0];
- }
-
public void refresh() {
sizeInByte = getSize();
}
diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java
index 355a75c5..ed233308 100644
--- a/common/src/main/java/ctbrec/Settings.java
+++ b/common/src/main/java/ctbrec/Settings.java
@@ -34,6 +34,13 @@ public class Settings {
SOCKS5
}
+ public enum SplitStrategy {
+ DONT,
+ TIME,
+ SIZE,
+ TIME_OR_SIZE
+ }
+
public String bongacamsBaseUrl = "https://bongacams.com";
public String bongaPassword = "";
public String bongaUsername = "";
@@ -124,7 +131,11 @@ public class Settings {
public String showupUsername = "";
public String showupPassword = "";
public boolean singlePlayer = true;
+ @Deprecated
public int splitRecordings = 0;
+ public SplitStrategy splitStrategy = SplitStrategy.DONT;
+ public int splitRecordingsAfterSecs = 0;
+ public long splitRecordingsBiggerThanBytes = 0;
public String startTab = "Settings";
public String streamatePassword = "";
public String streamateUsername = "";
diff --git a/common/src/main/java/ctbrec/io/IoUtils.java b/common/src/main/java/ctbrec/io/IoUtils.java
index 644e540b..a76d6879 100644
--- a/common/src/main/java/ctbrec/io/IoUtils.java
+++ b/common/src/main/java/ctbrec/io/IoUtils.java
@@ -2,7 +2,13 @@ package ctbrec.io;
import java.io.File;
import java.io.IOException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitResult;
import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.EnumSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,4 +56,27 @@ public class IoUtils {
throw new IOException("Couldn't delete all files in " + directory);
}
}
+
+ public static long getDirectorySize(File dir) {
+ final long[] size = { 0 };
+ int maxDepth = 1; // Don't expect subdirs, so don't even try
+ try {
+ Files.walkFileTree(dir.toPath(), EnumSet.noneOf(FileVisitOption.class), maxDepth, new SimpleFileVisitor() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ size[0] += attrs.size();
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFileFailed(Path file, IOException exc) {
+ // Ignore file access issues
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (IOException e) {
+ LOG.error("Couldn't determine size of directory {}", dir, e);
+ }
+ return size[0];
+ }
}
diff --git a/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java b/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java
index 90a38395..7e1a3c96 100644
--- a/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java
+++ b/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java
@@ -159,6 +159,7 @@ public class NextGenLocalRecorder implements Recorder {
ppPool.submit(() -> {
try {
setRecordingStatus(recording, State.POST_PROCESSING);
+ recording.refresh();
recordingManager.saveRecording(recording);
recording.postprocess();
List postProcessors = config.getSettings().postProcessors;
diff --git a/common/src/main/java/ctbrec/recorder/download/Download.java b/common/src/main/java/ctbrec/recorder/download/Download.java
index 171a8d98..b5497a1d 100644
--- a/common/src/main/java/ctbrec/recorder/download/Download.java
+++ b/common/src/main/java/ctbrec/recorder/download/Download.java
@@ -38,4 +38,6 @@ public interface Download extends Serializable {
* @return true, if the recording is only a single file
*/
public boolean isSingleFile();
+
+ public long getSizeInByte();
}
diff --git a/common/src/main/java/ctbrec/recorder/download/SplittingStrategy.java b/common/src/main/java/ctbrec/recorder/download/SplittingStrategy.java
new file mode 100644
index 00000000..aba7eaab
--- /dev/null
+++ b/common/src/main/java/ctbrec/recorder/download/SplittingStrategy.java
@@ -0,0 +1,9 @@
+package ctbrec.recorder.download;
+
+import ctbrec.Settings;
+
+public interface SplittingStrategy {
+
+ void init(Settings settings);
+ boolean splitNecessary(Download download);
+}
diff --git a/common/src/main/java/ctbrec/recorder/download/dash/DashDownload.java b/common/src/main/java/ctbrec/recorder/download/dash/DashDownload.java
index 9f57d93a..e28a6ba6 100644
--- a/common/src/main/java/ctbrec/recorder/download/dash/DashDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/dash/DashDownload.java
@@ -37,6 +37,7 @@ import ctbrec.Recording;
import ctbrec.io.BandwidthMeter;
import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
+import ctbrec.io.IoUtils;
import ctbrec.recorder.download.AbstractDownload;
import ctbrec.recorder.download.dash.SegmentTimelineType.S;
import ctbrec.recorder.download.hls.PostProcessingException;
@@ -416,4 +417,9 @@ public class DashDownload extends AbstractDownload {
return false;
}
+ @Override
+ public long getSizeInByte() {
+ return IoUtils.getDirectorySize(downloadDir.toFile());
+ }
+
}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java
index 4af8d12d..9cd2101e 100644
--- a/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java
@@ -44,6 +44,7 @@ import com.iheartradio.m3u8.data.TrackData;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Recording.State;
+import ctbrec.Settings;
import ctbrec.UnknownModel;
import ctbrec.io.BandwidthMeter;
import ctbrec.io.HttpClient;
@@ -51,6 +52,7 @@ import ctbrec.io.HttpException;
import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException;
import ctbrec.recorder.download.AbstractDownload;
import ctbrec.recorder.download.HttpHeaderFactory;
+import ctbrec.recorder.download.SplittingStrategy;
import ctbrec.recorder.download.StreamSource;
import ctbrec.sites.Site;
import okhttp3.Request;
@@ -67,6 +69,7 @@ public abstract class AbstractHlsDownload extends AbstractDownload {
protected Model model = new UnknownModel();
protected transient LinkedBlockingQueue downloadQueue = new LinkedBlockingQueue<>(50);
protected transient ExecutorService downloadThreadPool = new ThreadPoolExecutor(0, 5, 20, TimeUnit.SECONDS, downloadQueue, createThreadFactory());
+ protected transient SplittingStrategy splittingStrategy;
protected State state = State.UNKNOWN;
private int playlistEmptyCount = 0;
@@ -235,4 +238,27 @@ public abstract class AbstractHlsDownload extends AbstractDownload {
this.url = url;
}
}
+
+ protected SplittingStrategy initSplittingStrategy(Settings settings) {
+ SplittingStrategy strategy;
+ switch (settings.splitStrategy) {
+ case TIME:
+ strategy = new TimeSplittingStrategy();
+ break;
+ case SIZE:
+ strategy = new SizeSplittingStrategy();
+ break;
+ case TIME_OR_SIZE:
+ SplittingStrategy timeSplittingStrategy = new TimeSplittingStrategy();
+ SplittingStrategy sizeSplittingStrategy = new SizeSplittingStrategy();
+ strategy = new CombinedSplittingStrategy(timeSplittingStrategy, sizeSplittingStrategy);
+ break;
+ case DONT:
+ default:
+ strategy = new NoopSplittingStrategy();
+ break;
+ }
+ strategy.init(settings);
+ return strategy;
+ }
}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/CombinedSplittingStrategy.java b/common/src/main/java/ctbrec/recorder/download/hls/CombinedSplittingStrategy.java
new file mode 100644
index 00000000..a4f9284c
--- /dev/null
+++ b/common/src/main/java/ctbrec/recorder/download/hls/CombinedSplittingStrategy.java
@@ -0,0 +1,31 @@
+package ctbrec.recorder.download.hls;
+
+import ctbrec.Settings;
+import ctbrec.recorder.download.Download;
+import ctbrec.recorder.download.SplittingStrategy;
+
+public class CombinedSplittingStrategy implements SplittingStrategy {
+
+ private SplittingStrategy[] splittingStrategies;
+
+ public CombinedSplittingStrategy(SplittingStrategy... splittingStrategies) {
+ this.splittingStrategies = splittingStrategies;
+ }
+
+ @Override
+ public void init(Settings settings) {
+ for (SplittingStrategy splittingStrategy : splittingStrategies) {
+ splittingStrategy.init(settings);
+ }
+ }
+
+ @Override
+ public boolean splitNecessary(Download download) {
+ for (SplittingStrategy splittingStrategy : splittingStrategies) {
+ if (splittingStrategy.splitNecessary(download)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/FFmpegDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/FFmpegDownload.java
index 6ef507a2..c58239d6 100644
--- a/common/src/main/java/ctbrec/recorder/download/hls/FFmpegDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/hls/FFmpegDownload.java
@@ -145,4 +145,9 @@ public class FFmpegDownload extends AbstractHlsDownload {
return true;
}
+ @Override
+ public long getSizeInByte() {
+ return getTarget().length();
+ }
+
}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java
index 8491971a..8130178b 100644
--- a/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java
@@ -13,7 +13,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DecimalFormat;
import java.text.NumberFormat;
-import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@@ -40,6 +39,7 @@ import ctbrec.Recording.State;
import ctbrec.io.BandwidthMeter;
import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
+import ctbrec.io.IoUtils;
import ctbrec.recorder.PlaylistGenerator;
import ctbrec.recorder.download.HttpHeaderFactory;
import okhttp3.Request;
@@ -48,6 +48,8 @@ import okhttp3.Response;
public class HlsDownload extends AbstractHlsDownload {
+ private static final int TEN_SECONDS = 10_000;
+
private static final Logger LOG = LoggerFactory.getLogger(HlsDownload.class);
protected transient Path downloadDir;
@@ -55,8 +57,8 @@ public class HlsDownload extends AbstractHlsDownload {
private int segmentCounter = 1;
private NumberFormat nf = new DecimalFormat("000000");
private transient AtomicBoolean downloadFinished = new AtomicBoolean(false);
- private ZonedDateTime splitRecStartTime;
protected transient Config config;
+ private transient int waitFactor = 1;
public HlsDownload(HttpClient client) {
super(client);
@@ -71,6 +73,7 @@ public class HlsDownload extends AbstractHlsDownload {
String formattedStartTime = formatter.format(ZonedDateTime.ofInstant(this.startTime, ZoneId.systemDefault()));
Path modelDir = FileSystems.getDefault().getPath(config.getSettings().recordingsDir, model.getSanitizedNamed());
downloadDir = FileSystems.getDefault().getPath(modelDir.toString(), formattedStartTime);
+ splittingStrategy = initSplittingStrategy(config.getSettings());
}
@Override
@@ -78,7 +81,6 @@ public class HlsDownload extends AbstractHlsDownload {
try {
running = true;
Thread.currentThread().setName("Download " + model.getName());
- splitRecStartTime = ZonedDateTime.now();
String segments = getSegmentPlaylistUrl(model);
if (segments != null) {
if (!downloadDir.toFile().exists()) {
@@ -86,45 +88,13 @@ public class HlsDownload extends AbstractHlsDownload {
}
int lastSegmentNumber = 0;
int nextSegmentNumber = 0;
- int waitFactor = 1;
while (running) {
SegmentPlaylist playlist = getNextSegments(segments);
emptyPlaylistCheck(playlist);
- if (nextSegmentNumber > 0 && playlist.seq > nextSegmentNumber) {
- waitFactor *= 2;
- LOG.warn("Missed segments {} < {} in download for {} - setting wait factor to 1/{}", nextSegmentNumber, playlist.seq, model,
- waitFactor);
- }
- int skip = nextSegmentNumber - playlist.seq;
- for (String segment : playlist.segments) {
- if (skip > 0) {
- skip--;
- } else {
- URL segmentUrl = new URL(segment);
- String prefix = nf.format(segmentCounter++);
- SegmentDownload segmentDownload = new SegmentDownload(playlist, segmentUrl, downloadDir, client, prefix);
- enqueueDownload(segmentDownload, prefix, segmentUrl);
- }
- }
-
- // split recordings
- boolean split = splitRecording();
- if (split) {
- break;
- }
-
- long waitForMillis = 0;
- if (lastSegmentNumber == playlist.seq) {
- // playlist didn't change -> wait for at least half the target duration
- waitForMillis = (long) playlist.targetDuration * 1000 / waitFactor;
- LOG.trace("Playlist didn't change... waiting for {}ms", waitForMillis);
- } else {
- // playlist did change -> wait for at least last segment duration
- waitForMillis = 1;
- LOG.trace("Playlist changed... waiting for {}ms", waitForMillis);
- }
-
- waitSomeTime(waitForMillis);
+ logMissedSegments(playlist, nextSegmentNumber);
+ enqueueNewSegments(playlist, nextSegmentNumber);
+ splitRecordingIfNecessary();
+ waitSomeTime(playlist, lastSegmentNumber, waitFactor);
// this if check makes sure, that we don't decrease nextSegment. for some reason
// streamate playlists sometimes jump back. e.g. max sequence = 79 -> 80 -> 79
@@ -144,45 +114,96 @@ public class HlsDownload extends AbstractHlsDownload {
// end of playlist reached
LOG.debug("Reached end of playlist for model {}", model);
} catch (HttpException e) {
- if (e.getResponseCode() == 404) {
- ctbrec.Model.State modelState;
- try {
- modelState = model.getOnlineState(false);
- } catch (ExecutionException e1) {
- modelState = ctbrec.Model.State.UNKNOWN;
- }
- LOG.info("Playlist not found (404). Model {} probably went offline. Model state: {}", model, modelState);
- waitSomeTime(10_000);
- } else if (e.getResponseCode() == 403) {
- ctbrec.Model.State modelState;
- try {
- modelState = model.getOnlineState(false);
- } catch (ExecutionException e1) {
- modelState = ctbrec.Model.State.UNKNOWN;
- }
- LOG.info("Playlist access forbidden (403). Model {} probably went private or offline. Model state: {}", model, modelState);
- waitSomeTime(10_000);
- } else {
- throw e;
- }
+ handleHttpException(e);
} catch (Exception e) {
throw new IOException("Couldn't download segment", e);
} finally {
- downloadThreadPool.shutdown();
- try {
- LOG.debug("Waiting for last segments for {}", model);
- downloadThreadPool.awaitTermination(60, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- downloadFinished.set(true);
- synchronized (downloadFinished) {
- downloadFinished.notifyAll();
- }
- LOG.debug("Download for {} terminated", model);
+ finalizeDownload();
}
}
+ private void finalizeDownload() {
+ downloadThreadPool.shutdown();
+ try {
+ LOG.debug("Waiting for last segments for {}", model);
+ downloadThreadPool.awaitTermination(60, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ downloadFinished.set(true);
+ synchronized (downloadFinished) {
+ downloadFinished.notifyAll();
+ }
+ LOG.debug("Download for {} terminated", model);
+ }
+
+ private void handleHttpException(HttpException e) throws IOException {
+ if (e.getResponseCode() == 404) {
+ ctbrec.Model.State modelState;
+ try {
+ modelState = model.getOnlineState(false);
+ } catch (ExecutionException e1) {
+ modelState = ctbrec.Model.State.UNKNOWN;
+ }
+ LOG.info("Playlist not found (404). Model {} probably went offline. Model state: {}", model, modelState);
+ waitSomeTime(TEN_SECONDS);
+ } else if (e.getResponseCode() == 403) {
+ ctbrec.Model.State modelState;
+ try {
+ modelState = model.getOnlineState(false);
+ } catch (ExecutionException e1) {
+ modelState = ctbrec.Model.State.UNKNOWN;
+ }
+ LOG.info("Playlist access forbidden (403). Model {} probably went private or offline. Model state: {}", model, modelState);
+ waitSomeTime(TEN_SECONDS);
+ } else {
+ throw e;
+ }
+ }
+
+ private void splitRecordingIfNecessary() {
+ if (splittingStrategy.splitNecessary(this)) {
+ internalStop();
+ }
+ }
+
+ private void enqueueNewSegments(SegmentPlaylist playlist, int nextSegmentNumber) throws IOException, ExecutionException, InterruptedException {
+ int skip = nextSegmentNumber - playlist.seq;
+ for (String segment : playlist.segments) {
+ if (skip > 0) {
+ skip--;
+ } else {
+ URL segmentUrl = new URL(segment);
+ String prefix = nf.format(segmentCounter++);
+ SegmentDownload segmentDownload = new SegmentDownload(playlist, segmentUrl, downloadDir, client, prefix);
+ enqueueDownload(segmentDownload, prefix, segmentUrl);
+ }
+ }
+ }
+
+ private void logMissedSegments(SegmentPlaylist playlist, int nextSegmentNumber) {
+ if (nextSegmentNumber > 0 && playlist.seq > nextSegmentNumber) {
+ waitFactor *= 2;
+ LOG.warn("Missed segments {} < {} in download for {} - setting wait factor to 1/{}", nextSegmentNumber, playlist.seq, model,
+ waitFactor);
+ }
+ }
+
+ private void waitSomeTime(SegmentPlaylist playlist, int lastSegmentNumber, int waitFactor) {
+ long waitForMillis = 0;
+ if (lastSegmentNumber == playlist.seq) {
+ // playlist didn't change -> wait for at least half the target duration
+ waitForMillis = (long) playlist.targetDuration * 1000 / waitFactor;
+ LOG.trace("Playlist didn't change... waiting for {}ms", waitForMillis);
+ } else {
+ // playlist did change -> wait for at least last segment duration
+ waitForMillis = 1;
+ LOG.trace("Playlist changed... waiting for {}ms", waitForMillis);
+ }
+
+ waitSomeTime(waitForMillis);
+ }
+
private void enqueueDownload(SegmentDownload segmentDownload, String prefix, URL segmentUrl) throws IOException, ExecutionException, InterruptedException {
try {
downloadThreadPool.submit(segmentDownload);
@@ -228,18 +249,6 @@ public class HlsDownload extends AbstractHlsDownload {
}
- private boolean splitRecording() {
- if (config.getSettings().splitRecordings > 0) {
- Duration recordingDuration = Duration.between(splitRecStartTime, ZonedDateTime.now());
- long seconds = recordingDuration.getSeconds();
- if (seconds >= config.getSettings().splitRecordings) {
- internalStop();
- return true;
- }
- }
- return false;
- }
-
@Override
public void stop() {
if (running) {
@@ -334,4 +343,9 @@ public class HlsDownload extends AbstractHlsDownload {
public boolean isSingleFile() {
return false;
}
+
+ @Override
+ public long getSizeInByte() {
+ return IoUtils.getDirectorySize(getTarget());
+ }
}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java
index d5a0767d..136a159f 100644
--- a/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java
@@ -11,9 +11,7 @@ import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
-import java.time.Duration;
import java.time.Instant;
-import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
@@ -53,14 +51,13 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
private static final long serialVersionUID = 1L;
private static final Logger LOG = LoggerFactory.getLogger(MergedFfmpegHlsDownload.class);
private static final boolean IGNORE_CACHE = true;
- private ZonedDateTime splitRecStartTime;
private File targetFile;
private transient Config config;
private transient Process ffmpeg;
private transient OutputStream ffmpegStdIn;
protected transient Thread ffmpegThread;
private transient Object ffmpegStartMonitor = new Object();
- private Queue> downloads = new LinkedList<>();
+ private transient Queue> downloads = new LinkedList<>();
public MergedFfmpegHlsDownload(HttpClient client) {
super(client);
@@ -73,6 +70,7 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
this.model = model;
String fileSuffix = config.getSettings().ffmpegFileSuffix;
targetFile = config.getFileForRecording(model, fileSuffix, startTime);
+ splittingStrategy = initSplittingStrategy(config.getSettings());
}
@Override
@@ -86,7 +84,6 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
running = true;
Thread.currentThread().setName("Download " + model.getName());
super.startTime = Instant.now();
- splitRecStartTime = ZonedDateTime.now();
String segments = getSegmentPlaylistUrl(model);
@@ -211,11 +208,7 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
}
if (livestreamDownload) {
- // split up the recording, if configured
- boolean split = splitRecording();
- if (split) {
- break;
- }
+ splitRecordingIfNecessary();
// wait some time until requesting the segment playlist again to not hammer the server
waitForNewSegments(lsp, lastSegment, downloadTookMillis);
@@ -245,6 +238,12 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
ffmpegThread.interrupt();
}
+ protected void splitRecordingIfNecessary() {
+ if (splittingStrategy.splitNecessary(this)) {
+ internalStop();
+ }
+ }
+
private void downloadRecording(SegmentPlaylist lsp) throws IOException {
for (String segment : lsp.segments) {
URL segmentUrl = new URL(segment);
@@ -337,18 +336,6 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
writeSegment(segmentData, 0, segmentData.length);
}
- protected boolean splitRecording() {
- if (config.getSettings().splitRecordings > 0) {
- Duration recordingDuration = Duration.between(splitRecStartTime, ZonedDateTime.now());
- long seconds = recordingDuration.getSeconds();
- if (seconds >= config.getSettings().splitRecordings) {
- internalStop();
- return true;
- }
- }
- return false;
- }
-
private void waitForNewSegments(SegmentPlaylist lsp, int lastSegment, long downloadTookMillis) {
try {
long wait = 0;
@@ -493,6 +480,7 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
@Override
public void postprocess(Recording recording) {
+ // nothing to do
}
public void downloadFinishedRecording(String segmentPlaylistUri, File target, ProgressListener progressListener, long sizeInBytes) throws Exception {
@@ -549,4 +537,9 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
public boolean isSingleFile() {
return true;
}
+
+ @Override
+ public long getSizeInByte() {
+ return getTarget().length();
+ }
}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/NoopSplittingStrategy.java b/common/src/main/java/ctbrec/recorder/download/hls/NoopSplittingStrategy.java
new file mode 100644
index 00000000..6af9404b
--- /dev/null
+++ b/common/src/main/java/ctbrec/recorder/download/hls/NoopSplittingStrategy.java
@@ -0,0 +1,19 @@
+package ctbrec.recorder.download.hls;
+
+import ctbrec.Settings;
+import ctbrec.recorder.download.Download;
+import ctbrec.recorder.download.SplittingStrategy;
+
+public class NoopSplittingStrategy implements SplittingStrategy {
+
+ @Override
+ public void init(Settings settings) {
+ // settings not needed
+ }
+
+ @Override
+ public boolean splitNecessary(Download download) {
+ return false;
+ }
+
+}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/SizeSplittingStrategy.java b/common/src/main/java/ctbrec/recorder/download/hls/SizeSplittingStrategy.java
new file mode 100644
index 00000000..e37eadb0
--- /dev/null
+++ b/common/src/main/java/ctbrec/recorder/download/hls/SizeSplittingStrategy.java
@@ -0,0 +1,22 @@
+package ctbrec.recorder.download.hls;
+
+import ctbrec.Settings;
+import ctbrec.recorder.download.Download;
+import ctbrec.recorder.download.SplittingStrategy;
+
+public class SizeSplittingStrategy implements SplittingStrategy {
+
+ private Settings settings;
+
+ @Override
+ public void init(Settings settings) {
+ this.settings = settings;
+ }
+
+ @Override
+ public boolean splitNecessary(Download download) {
+ long sizeInByte = download.getSizeInByte();
+ return sizeInByte >= settings.splitRecordingsBiggerThanBytes;
+ }
+
+}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/TimeSplittingStrategy.java b/common/src/main/java/ctbrec/recorder/download/hls/TimeSplittingStrategy.java
new file mode 100644
index 00000000..8fb5a119
--- /dev/null
+++ b/common/src/main/java/ctbrec/recorder/download/hls/TimeSplittingStrategy.java
@@ -0,0 +1,28 @@
+package ctbrec.recorder.download.hls;
+
+import java.time.Duration;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import ctbrec.Settings;
+import ctbrec.recorder.download.Download;
+import ctbrec.recorder.download.SplittingStrategy;
+
+public class TimeSplittingStrategy implements SplittingStrategy {
+
+ private Settings settings;
+
+ @Override
+ public void init(Settings settings) {
+ this.settings = settings;
+ }
+
+ @Override
+ public boolean splitNecessary(Download download) {
+ ZonedDateTime startTime = download.getStartTime().atZone(ZoneId.systemDefault());
+ Duration recordingDuration = Duration.between(startTime, ZonedDateTime.now());
+ long seconds = recordingDuration.getSeconds();
+ return seconds >= settings.splitRecordingsAfterSecs;
+ }
+
+}
diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupMergedDownload.java b/common/src/main/java/ctbrec/sites/showup/ShowupMergedDownload.java
index b9516ad0..78a33af3 100644
--- a/common/src/main/java/ctbrec/sites/showup/ShowupMergedDownload.java
+++ b/common/src/main/java/ctbrec/sites/showup/ShowupMergedDownload.java
@@ -48,8 +48,8 @@ public class ShowupMergedDownload extends MergedFfmpegHlsDownload {
BandwidthMeter.add(length);
writeSegment(buffer, 0, length);
keepGoing = running && !Thread.interrupted() && model.isOnline(true);
- if (livestreamDownload && splitRecording()) {
- break;
+ if (livestreamDownload) {
+ splitRecordingIfNecessary();
}
}
} else {
From d88e5ab960be237b1fe49c28335ff26fd4d4126f Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 5 Dec 2020 22:39:03 +0100
Subject: [PATCH 38/80] Add GUI for setting splitRecordingsBiggerThanBytes
---
CHANGELOG.md | 7 ++
.../ui/settings/CtbrecPreferencesStorage.java | 4 +
.../java/ctbrec/ui/settings/SettingsTab.java | 117 ++++++++++++++++--
.../java/ctbrec/ui/settings/api/Setting.java | 11 ++
4 files changed, 131 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6bae03aa..c9802796 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+3.10.7
+========================
+* Fixed credentials related bugs for Streamate and Stripchat
+ They used the user name from Chaturbate for some requests. Whoopsie!
+* Renamed settings for Chaturbate's user name and password
+* Add setting to split recordings by size
+
3.10.6
========================
* Fixed Cam4 downloads
diff --git a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java
index b3bb8180..246559e7 100644
--- a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java
+++ b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java
@@ -28,6 +28,7 @@ import javafx.beans.property.ListProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.Property;
import javafx.beans.property.StringProperty;
+import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
@@ -267,6 +268,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
}
config.save();
}));
+ if(setting.getChangeListener() != null) {
+ comboBox.valueProperty().addListener((ChangeListener super Object>) setting.getChangeListener());
+ }
return comboBox;
}
diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
index 4d2c2e51..c01ed574 100644
--- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
+++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
@@ -2,6 +2,7 @@ package ctbrec.ui.settings;
import static ctbrec.Settings.DirectoryStructure.*;
import static ctbrec.Settings.ProxyType.*;
+import static ctbrec.Settings.SplitStrategy.*;
import static java.util.Optional.*;
import java.io.IOException;
@@ -57,6 +58,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private static final Logger LOG = LoggerFactory.getLogger(SettingsTab.class);
public static final int CHECKBOX_MARGIN = 6;
+ private static final long MiB = 1024 * 1024L;
+ private static final long GiB = 1024 * MiB;
private List sites;
private Recorder recorder;
@@ -85,6 +88,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private SimpleDirectoryProperty recordingsDir;
private SimpleListProperty directoryStructure;
private SimpleListProperty splitAfter;
+ private SimpleListProperty splitBiggerThan;
private SimpleRangeProperty resolutionRange;
private List labels = Arrays.asList(0, 240, 360, 480, 600, 720, 960, 1080, 1440, 2160, 4320, 8640);
private List values = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
@@ -104,8 +108,6 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private ExclusiveSelectionProperty recordLocal;
private SimpleIntegerProperty postProcessingThreads;
private IgnoreList ignoreList;
- private PostProcessingStepPanel postProcessingStepPanel;
- private Button variablesHelpButton;
public SettingsTab(List sites, Recorder recorder) {
this.sites = sites;
@@ -137,7 +139,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
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()));
+ splitAfter = new SimpleListProperty<>(null, "splitRecordingsAfterSecs", FXCollections.observableList(getSplitAfterSecsOptions()));
+ splitBiggerThan = new SimpleListProperty<>(null, "splitRecordingsBiggerThanBytes", FXCollections.observableList(getSplitBiggerThanOptions()));
resolutionRange = new SimpleRangeProperty<>(rangeValues, "minimumResolution", "maximumResolution", settings.minimumResolution, settings.maximumResolution);
concurrentRecordings = new SimpleIntegerProperty(null, "concurrentRecordings", settings.concurrentRecordings);
onlineCheckIntervalInSecs = new SimpleIntegerProperty(null, "onlineCheckIntervalInSecs", settings.onlineCheckIntervalInSecs);
@@ -157,8 +160,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
}
private void createGui() {
- postProcessingStepPanel = new PostProcessingStepPanel(config);
- variablesHelpButton = createHelpButton("Variables", "http://localhost:5689/docs/PostProcessing.md#variables");
+ PostProcessingStepPanel postProcessingStepPanel = new PostProcessingStepPanel(config);
+ Button variablesHelpButton = createHelpButton("Variables", "http://localhost:5689/docs/PostProcessing.md#variables");
ignoreList = new IgnoreList(sites);
List siteCategories = new ArrayList<>();
for (Site site : sites) {
@@ -191,7 +194,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
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("Split recordings after", splitAfter).converter(SplitAfterOption.converter()).onChange(this::splitValuesChanged),
+ Setting.of("Split recordings bigger than", splitBiggerThan).converter(SplitBiggerThanOption.converter()).onChange(this::splitValuesChanged),
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()),
@@ -251,7 +255,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
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("splitRecordingsAfterSecs").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
+ prefs.getSetting("splitRecordingsBiggerThanBytes").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()));
@@ -266,6 +271,23 @@ public class SettingsTab extends Tab implements TabSelectionListener {
prefs.getSetting("downloadFilename").ifPresent(s -> bindEnabledProperty(s, recordLocal));
postProcessingStepPanel.disableProperty().bind(recordLocal.not());
variablesHelpButton.disableProperty().bind(recordLocal);
+
+
+ }
+
+ private void splitValuesChanged(ObservableValue> value, Object oldV, Object newV) {
+ boolean splitAfterSet = settings.splitRecordingsAfterSecs > 0;
+ boolean splitBiggerThanSet = settings.splitRecordingsBiggerThanBytes > 0;
+ if (splitAfterSet && splitBiggerThanSet) {
+ settings.splitStrategy = TIME_OR_SIZE;
+ } else if (splitAfterSet) {
+ settings.splitStrategy = TIME;
+ } else if (splitBiggerThanSet) {
+ settings.splitStrategy = SIZE;
+ } else {
+ settings.splitStrategy = DONT;
+ }
+ saveConfig();
}
private Button createHelpButton(String text, String url) {
@@ -288,7 +310,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
.collect(Collectors.toList());
}
- private List getSplitOptions() {
+ private List getSplitAfterSecsOptions() {
List splitOptions = new ArrayList<>();
splitOptions.add(new SplitAfterOption("disabled", 0));
if (Config.isDevMode()) {
@@ -304,6 +326,29 @@ public class SettingsTab extends Tab implements TabSelectionListener {
return splitOptions;
}
+ private List getSplitBiggerThanOptions() {
+ List splitOptions = new ArrayList<>();
+ splitOptions.add(new SplitBiggerThanOption("disabled", 0));
+ if (Config.isDevMode()) {
+ splitOptions.add(new SplitBiggerThanOption("10 MiB", 10 * MiB));
+ splitOptions.add(new SplitBiggerThanOption("20 MiB", 20 * MiB));
+ }
+ splitOptions.add(new SplitBiggerThanOption("100 MiB", 100 * MiB));
+ splitOptions.add(new SplitBiggerThanOption("250 MiB", 250 * MiB));
+ splitOptions.add(new SplitBiggerThanOption("500 MiB", 500 * MiB));
+ splitOptions.add(new SplitBiggerThanOption("1 GiB", 1 * GiB));
+ splitOptions.add(new SplitBiggerThanOption("2 GiB", 2 * GiB));
+ splitOptions.add(new SplitBiggerThanOption("3 GiB", 3 * GiB));
+ splitOptions.add(new SplitBiggerThanOption("4 GiB", 4 * GiB));
+ splitOptions.add(new SplitBiggerThanOption("5 GiB", 5 * GiB));
+ splitOptions.add(new SplitBiggerThanOption("6 GiB", 6 * GiB));
+ splitOptions.add(new SplitBiggerThanOption("7 GiB", 7 * GiB));
+ splitOptions.add(new SplitBiggerThanOption("8 GiB", 8 * GiB));
+ splitOptions.add(new SplitBiggerThanOption("9 GiB", 9 * GiB));
+ splitOptions.add(new SplitBiggerThanOption("10 GiB", 10 * GiB));
+ return splitOptions;
+ }
+
private void requireAuthenticationChanged(ObservableValue> obs, Boolean oldV, Boolean newV) { // NOSONAR
boolean requiresAuthentication = newV;
Config.getInstance().getSettings().requireAuthentication = requiresAuthentication;
@@ -415,4 +460,60 @@ public class SettingsTab extends Tab implements TabSelectionListener {
};
}
}
+
+ public static class SplitBiggerThanOption {
+ private String label;
+ private long value;
+
+ public SplitBiggerThanOption(String label, long value) {
+ super();
+ this.label = label;
+ this.value = value;
+ }
+
+ public long getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return label;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (value ^ (value >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SplitBiggerThanOption other = (SplitBiggerThanOption) obj;
+ if (value != other.value)
+ return false;
+ return true;
+ }
+
+ public static ValueConverter converter() {
+ return new ValueConverter() {
+ @Override
+ public Long convertFrom(Object splitBiggerThanOption) {
+ return ((SplitBiggerThanOption) splitBiggerThanOption).getValue();
+ }
+
+ @Override
+ public SplitBiggerThanOption convertTo(Object value) {
+ return new SplitBiggerThanOption(value.toString(), (Long) value);
+ }
+ };
+ }
+ }
}
diff --git a/client/src/main/java/ctbrec/ui/settings/api/Setting.java b/client/src/main/java/ctbrec/ui/settings/api/Setting.java
index 98acb0a6..d89e9de3 100644
--- a/client/src/main/java/ctbrec/ui/settings/api/Setting.java
+++ b/client/src/main/java/ctbrec/ui/settings/api/Setting.java
@@ -4,6 +4,7 @@ import static java.util.Optional.*;
import ctbrec.StringUtil;
import javafx.beans.property.Property;
+import javafx.beans.value.ChangeListener;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Tooltip;
@@ -17,6 +18,7 @@ public class Setting {
private PreferencesStorage preferencesStorage;
private boolean needsRestart = false;
private ValueConverter converter;
+ private ChangeListener> changeListener;
protected Setting(String name, Property> property) {
this.name = name;
@@ -107,4 +109,13 @@ public class Setting {
public ValueConverter getConverter() {
return converter;
}
+
+ public Setting onChange(ChangeListener> changeListener) {
+ this.changeListener = changeListener;
+ return this;
+ }
+
+ public ChangeListener> getChangeListener() {
+ return changeListener;
+ }
}
From 7884e602d76762a04519d30cbf04dc7441751157 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 5 Dec 2020 23:18:17 +0100
Subject: [PATCH 39/80] Set scene in Dialogs
---
.../java/ctbrec/ui/CamrecApplication.java | 46 +++++++++++++------
.../main/java/ctbrec/ui/controls/Dialogs.java | 26 +++++++----
2 files changed, 49 insertions(+), 23 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java
index 14cc0833..9628b12c 100644
--- a/client/src/main/java/ctbrec/ui/CamrecApplication.java
+++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java
@@ -1,10 +1,28 @@
package ctbrec.ui;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.google.common.eventbus.Subscribe;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.Types;
+
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.StringUtil;
@@ -38,14 +56,24 @@ import ctbrec.sites.stripchat.Stripchat;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.news.NewsTab;
import ctbrec.ui.settings.SettingsTab;
-import ctbrec.ui.tabs.*;
+import ctbrec.ui.tabs.DonateTabFx;
+import ctbrec.ui.tabs.HelpTab;
+import ctbrec.ui.tabs.RecordedModelsTab;
+import ctbrec.ui.tabs.RecordingsTab;
+import ctbrec.ui.tabs.SiteTab;
+import ctbrec.ui.tabs.TabSelectionListener;
+import ctbrec.ui.tabs.UpdateTab;
import ctbrec.ui.tabs.logging.LoggingTab;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
-import javafx.scene.control.*;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Label;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
@@ -54,17 +82,6 @@ import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import okhttp3.Request;
import okhttp3.Response;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-import java.lang.reflect.Type;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
public class CamrecApplication extends Application {
@@ -160,6 +177,7 @@ public class CamrecApplication extends Application {
Scene scene = new Scene(rootPane, windowWidth, windowHeight);
primaryStage.setScene(scene);
+ Dialogs.setScene(scene);
rootPane.setCenter(tabPane);
rootPane.setBottom(statusBar);
for (Site site : sites) {
@@ -196,7 +214,7 @@ public class CamrecApplication extends Application {
.addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowHeight = newVal.intValue());
primaryStage.setMaximized(Config.getInstance().getSettings().windowMaximized);
primaryStage.maximizedProperty()
- .addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowMaximized = newVal);
+ .addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowMaximized = newVal);
Player.scene = primaryStage.getScene();
primaryStage.setX(Config.getInstance().getSettings().windowX);
primaryStage.setY(Config.getInstance().getSettings().windowY);
diff --git a/client/src/main/java/ctbrec/ui/controls/Dialogs.java b/client/src/main/java/ctbrec/ui/controls/Dialogs.java
index a1ac9d76..f9e3d78f 100644
--- a/client/src/main/java/ctbrec/ui/controls/Dialogs.java
+++ b/client/src/main/java/ctbrec/ui/controls/Dialogs.java
@@ -1,30 +1,38 @@
package ctbrec.ui.controls;
+import static javafx.scene.control.ButtonType.*;
+
+import java.io.InputStream;
+import java.util.Optional;
+
import ctbrec.ui.AutosizeAlert;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
-import javafx.scene.control.*;
+import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
+import javafx.scene.control.Button;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.stage.Modality;
import javafx.stage.Stage;
-import java.io.InputStream;
-import java.util.Optional;
-
-import static javafx.scene.control.ButtonType.*;
-
public class Dialogs {
private Dialogs() {}
- // TODO reduce calls to this method and use Dialogs.showError(Scene parent, String header, String text, Throwable t) instead
- @Deprecated
+ private static Scene scene;
+
+ public static void setScene(Scene scene) {
+ Dialogs.scene = scene;
+ }
+
public static void showError(String header, String text, Throwable t) {
- showError(null, header, text, t);
+ showError(scene, header, text, t);
}
public static void showError(Scene parent, String header, String text, Throwable t) {
From 5135e10c19713e5dd95843f57e1b8b78fdb92404 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 5 Dec 2020 23:18:45 +0100
Subject: [PATCH 40/80] Change config servlet for the new split options
---
.../main/java/ctbrec/recorder/server/ConfigServlet.java | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/server/src/main/java/ctbrec/recorder/server/ConfigServlet.java b/server/src/main/java/ctbrec/recorder/server/ConfigServlet.java
index fb894d48..44a0041e 100644
--- a/server/src/main/java/ctbrec/recorder/server/ConfigServlet.java
+++ b/server/src/main/java/ctbrec/recorder/server/ConfigServlet.java
@@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Settings;
+import ctbrec.Settings.SplitStrategy;
public class ConfigServlet extends AbstractCtbrecServlet {
@@ -27,7 +28,7 @@ public class ConfigServlet extends AbstractCtbrecServlet {
private Settings settings;
public enum DataType {
- STRING, BOOLEAN, INTEGER, LONG, DOUBLE
+ STRING, BOOLEAN, INTEGER, LONG, DOUBLE, SPLIT_STRATEGY
}
public ConfigServlet(Config config) {
@@ -62,7 +63,9 @@ public class ConfigServlet extends AbstractCtbrecServlet {
addParameter("postProcessingThreads", "Post-Processing Threads", DataType.INTEGER, settings.postProcessingThreads, json);
addParameter("recordingsDir", "Recordings Directory", DataType.STRING, settings.recordingsDir, json);
addParameter("recordSingleFile", "Record Single File", DataType.BOOLEAN, settings.recordSingleFile, json);
- addParameter("splitRecordings", "Split Recordings (secs)", DataType.INTEGER, settings.splitRecordings, json);
+ addParameter("splitStrategy", "Split Strategy", DataType.SPLIT_STRATEGY, settings.splitStrategy, json);
+ addParameter("splitRecordingsAfterSecs", "Split Recordings After (secs)", DataType.INTEGER, settings.splitRecordingsAfterSecs, json);
+ addParameter("splitRecordingsBiggerThanBytes", "Split Recordings Bigger Than (bytes)", DataType.LONG, settings.splitRecordingsBiggerThanBytes, json);
addParameter("transportLayerSecurity", "Transport Layer Security (TLS)", DataType.BOOLEAN, settings.transportLayerSecurity, json);
addParameter("webinterface", "Web-Interface", DataType.BOOLEAN, settings.webinterface, json);
addParameter("webinterfaceUsername", "Web-Interface User", DataType.STRING, settings.webinterfaceUsername, json);
@@ -153,6 +156,8 @@ public class ConfigServlet extends AbstractCtbrecServlet {
case DOUBLE:
corrected = Double.parseDouble(value.toString());
break;
+ case SPLIT_STRATEGY:
+ corrected = SplitStrategy.valueOf(value.toString());
default:
break;
}
From d76ee3994b567c70e778a5dd9dcd0309a0ba338c Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 5 Dec 2020 23:21:06 +0100
Subject: [PATCH 41/80] Fix bug, which caused the HLS servlet to double up the
recordings path
---
server/src/main/java/ctbrec/recorder/server/HlsServlet.java | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/server/src/main/java/ctbrec/recorder/server/HlsServlet.java b/server/src/main/java/ctbrec/recorder/server/HlsServlet.java
index 43c61233..d2837413 100644
--- a/server/src/main/java/ctbrec/recorder/server/HlsServlet.java
+++ b/server/src/main/java/ctbrec/recorder/server/HlsServlet.java
@@ -50,7 +50,8 @@ public class HlsServlet extends AbstractCtbrecServlet {
boolean idOnly = request.indexOf('/') < 0;
if (idOnly) {
requestFile = rec.get().getPostProcessedFile();
- requestedFilePath = requestFile.getCanonicalPath();
+ serveSegment(req, resp, requestFile);
+ return;
} else {
requestedFilePath = request.substring(request.indexOf('/'));
requestFile = new File(requestedFilePath);
@@ -73,7 +74,6 @@ public class HlsServlet extends AbstractCtbrecServlet {
}
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
writeResponse(resp, SC_UNAUTHORIZED, "{\"status\": \"error\", \"msg\": \"Authentication failed\"}");
- return;
}
}
@@ -99,7 +99,6 @@ public class HlsServlet extends AbstractCtbrecServlet {
private void serveSegment(HttpServletRequest req, HttpServletResponse resp, File requestedFile) throws IOException {
MimetypesFileTypeMap map = new MimetypesFileTypeMap();
- //String mimetype = requestedFile.getName().endsWith(".mp4") ? "video/mp4" : "application/octet-stream";
String mimetype = map.getContentType(requestedFile);
LOG.debug("Serving {} as {}", requestedFile.getName(), mimetype);
serveFile(req, resp, requestedFile, mimetype);
From dafde88721721c88da99267ed49108b55a7e1a29 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 6 Dec 2020 14:22:28 +0100
Subject: [PATCH 42/80] Fix moving of segmented downloads
---
CHANGELOG.md | 3 +++
.../main/java/ctbrec/recorder/postprocessing/Move.java | 9 +++++++--
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9802796..cca931c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,12 @@
3.10.7
========================
+* Fixed streaming of recordings from the server (the file path was duplicated
+ if single file was used)
* Fixed credentials related bugs for Streamate and Stripchat
They used the user name from Chaturbate for some requests. Whoopsie!
* Renamed settings for Chaturbate's user name and password
* Add setting to split recordings by size
+* Fixed moving of segment recordings on the server (post-processing)
3.10.6
========================
diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Move.java b/common/src/main/java/ctbrec/recorder/postprocessing/Move.java
index 8070a080..3b33b736 100644
--- a/common/src/main/java/ctbrec/recorder/postprocessing/Move.java
+++ b/common/src/main/java/ctbrec/recorder/postprocessing/Move.java
@@ -6,6 +6,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.util.Objects;
+import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,7 +16,7 @@ import ctbrec.recorder.RecordingManager;
public class Move extends AbstractPlaceholderAwarePostProcessor {
- private static final Logger LOG = LoggerFactory.getLogger(Rename.class);
+ private static final Logger LOG = LoggerFactory.getLogger(Move.class);
public static final String PATH_TEMPLATE = "path.template";
public static final String DEFAULT = "${modelSanitizedName}" + File.separatorChar + "${localDateTime}";
@@ -36,7 +37,11 @@ public class Move extends AbstractPlaceholderAwarePostProcessor {
}
LOG.info("Moving {} to {}", src.getName(), target.getParentFile().getCanonicalPath());
Files.createDirectories(target.getParentFile().toPath());
- Files.move(rec.getPostProcessedFile().toPath(), target.toPath());
+ if (rec.getPostProcessedFile().isDirectory()) {
+ FileUtils.moveDirectory(rec.getPostProcessedFile(), target);
+ } else {
+ FileUtils.moveFile(rec.getPostProcessedFile(), target);
+ }
rec.setPostProcessedFile(target);
if (Objects.equals(src, rec.getAbsoluteFile())) {
rec.setAbsoluteFile(target);
From 8791b5b1f8b3a56b9a2de6b788e25eb49b31846d Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 6 Dec 2020 15:34:12 +0100
Subject: [PATCH 43/80] Rename local variable
---
.../main/java/ctbrec/ui/settings/api/GigabytesConverter.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/settings/api/GigabytesConverter.java b/client/src/main/java/ctbrec/ui/settings/api/GigabytesConverter.java
index f007e20d..52b87310 100644
--- a/client/src/main/java/ctbrec/ui/settings/api/GigabytesConverter.java
+++ b/client/src/main/java/ctbrec/ui/settings/api/GigabytesConverter.java
@@ -12,8 +12,8 @@ public class GigabytesConverter implements ValueConverter {
@Override
public Object convertFrom(Object b) {
- long spaceLeftInGiB = (long) b;
- return spaceLeftInGiB * ONE_GIB_IN_BYTES;
+ long gibiBytes = (long) b;
+ return gibiBytes * ONE_GIB_IN_BYTES;
}
}
From 5be7fcc2ae1339633eb53a911d23b1c4cee049a8 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 6 Dec 2020 15:34:48 +0100
Subject: [PATCH 44/80] Add clipboard monitoring for model URLs
---
.../java/ctbrec/ui/CamrecApplication.java | 48 ++++++++++----
.../java/ctbrec/ui/ClipboardListener.java | 66 +++++++++++++++++++
.../java/ctbrec/ui/settings/SettingsTab.java | 3 +
common/src/main/java/ctbrec/Settings.java | 1 +
4 files changed, 106 insertions(+), 12 deletions(-)
create mode 100644 client/src/main/java/ctbrec/ui/ClipboardListener.java
diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java
index 9628b12c..03c539a9 100644
--- a/client/src/main/java/ctbrec/ui/CamrecApplication.java
+++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java
@@ -14,6 +14,9 @@ import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -101,14 +104,38 @@ public class CamrecApplication extends Application {
private Stage primaryStage;
private RecordedModelsTab modelsTab;
private RecordingsTab recordingsTab;
-
+ private ScheduledExecutorService scheduler;
private int activeRecordings = 0;
private double bytesPerSecond = 0;
+
@Override
public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage;
+ scheduler = Executors.newScheduledThreadPool(1, r -> {
+ Thread t = new Thread(r);
+ t.setDaemon(true);
+ t.setName("Scheduler");
+ return t;
+ });
+
logEnvironment();
+ initSites();
+ loadConfig();
+ registerAlertSystem();
+ registerActiveRecordingsCounter();
+ registerBandwidthMeterListener();
+ createHttpClient();
+ hostServices = getHostServices();
+ createRecorder();
+ startOnlineMonitor();
+ createGui(primaryStage);
+ checkForUpdates();
+ startHelpServer();
+ registerClipboardListener();
+ }
+
+ private void initSites() {
sites.add(new BongaCams());
sites.add(new Cam4());
sites.add(new Camsoda());
@@ -121,17 +148,13 @@ public class CamrecApplication extends Application {
sites.add(new Showup());
sites.add(new Streamate());
sites.add(new Stripchat());
- loadConfig();
- registerAlertSystem();
- registerActiveRecordingsCounter();
- registerBandwidthMeterListener();
- createHttpClient();
- hostServices = getHostServices();
- createRecorder();
- startOnlineMonitor();
- createGui(primaryStage);
- checkForUpdates();
- startHelpServer();
+ }
+
+ private void registerClipboardListener() {
+ if(config.getSettings().monitorClipboard) {
+ ClipboardListener clipboardListener = new ClipboardListener(recorder, sites);
+ scheduler.scheduleAtFixedRate(clipboardListener, 0, 1, TimeUnit.SECONDS);
+ }
}
private void startHelpServer() {
@@ -307,6 +330,7 @@ public class CamrecApplication extends Application {
} catch (IOException e12) {
// noop
}
+ scheduler.shutdownNow();
}).start();
};
}
diff --git a/client/src/main/java/ctbrec/ui/ClipboardListener.java b/client/src/main/java/ctbrec/ui/ClipboardListener.java
new file mode 100644
index 00000000..dc83cb96
--- /dev/null
+++ b/client/src/main/java/ctbrec/ui/ClipboardListener.java
@@ -0,0 +1,66 @@
+package ctbrec.ui;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.Objects;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ctbrec.Model;
+import ctbrec.recorder.Recorder;
+import ctbrec.sites.Site;
+import javafx.application.Platform;
+import javafx.scene.input.Clipboard;
+
+public class ClipboardListener implements Runnable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ClipboardListener.class);
+ private Recorder recorder;
+ private List sites;
+ private Clipboard systemClipboard;
+ private String lastUrl = null;
+
+ public ClipboardListener(Recorder recorder, List sites) {
+ this.recorder = recorder;
+ this.sites = sites;
+ systemClipboard = Clipboard.getSystemClipboard();
+ }
+
+ @Override
+ public void run() {
+ Platform.runLater(() -> {
+ try {
+ String url = null;
+ if (systemClipboard.hasUrl()) {
+ url = systemClipboard.getUrl();
+ } else if (systemClipboard.hasString()) {
+ url = systemClipboard.getString();
+ }
+ if (!Objects.equals(url, lastUrl)) {
+ lastUrl = url;
+ addModelIfUrlMatches(url);
+ }
+ } catch (Exception e) {
+ LOG.error("Error in clipboard polling loop", e);
+ }
+ });
+ }
+
+ private void addModelIfUrlMatches(String url) {
+ for (Site site : sites) {
+ Model m = site.createModelFromUrl(url);
+ if (m != null) {
+ try {
+ recorder.startRecording(m);
+ DesktopIntegration.notification("Add from clipboard", "Model added", "Model " + m.getDisplayName() + " added");
+ } catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
+ DesktopIntegration.notification("Add from clipboard", "Error", "Couldn't add URL from clipboard: " + e.getLocalizedMessage());
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
index c01ed574..39e30bce 100644
--- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
+++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
@@ -74,6 +74,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private SimpleBooleanProperty determineResolution;
private SimpleBooleanProperty chooseStreamQuality;
private SimpleBooleanProperty livePreviews;
+ private SimpleBooleanProperty monitorClipboard;
private SimpleListProperty startTab;
private SimpleFileProperty mediaPlayer;
private SimpleStringProperty mediaPlayerParams;
@@ -126,6 +127,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
determineResolution = new SimpleBooleanProperty(null, "determineResolution", settings.determineResolution);
chooseStreamQuality = new SimpleBooleanProperty(null, "chooseStreamQuality", settings.chooseStreamQuality);
livePreviews = new SimpleBooleanProperty(null, "livePreviews", settings.livePreviews);
+ monitorClipboard = new SimpleBooleanProperty(null, "monitorClipboard", settings.monitorClipboard);
startTab = new SimpleListProperty<>(null, "startTab", FXCollections.observableList(getTabNames()));
mediaPlayer = new SimpleFileProperty(null, "mediaPlayer", settings.mediaPlayer);
mediaPlayerParams = new SimpleStringProperty(null, "mediaPlayerParams", settings.mediaPlayerParams);
@@ -179,6 +181,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
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("Add models from clipboard", monitorClipboard, "Monitor clipboard for model URLs and automatically add them to the recorder").needsRestart(),
Setting.of("Start Tab", startTab),
Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance()))
),
diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java
index ed233308..49cfa237 100644
--- a/common/src/main/java/ctbrec/Settings.java
+++ b/common/src/main/java/ctbrec/Settings.java
@@ -99,6 +99,7 @@ public class Settings {
public Map modelNotes = new HashMap<>();
public List models = new ArrayList<>();
public List modelsIgnored = new ArrayList<>();
+ public boolean monitorClipboard = false;
public int onlineCheckIntervalInSecs = 60;
public boolean onlineCheckSkipsPausedModels = false;
public int overviewUpdateIntervalInSecs = 10;
From 793f6d371fde2341929c025bd598612a7f2f9f62 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 6 Dec 2020 17:52:32 +0100
Subject: [PATCH 45/80] Reduced rescan period
---
client/src/main/resources/logback.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/src/main/resources/logback.xml b/client/src/main/resources/logback.xml
index fa146bf1..e83c831b 100644
--- a/client/src/main/resources/logback.xml
+++ b/client/src/main/resources/logback.xml
@@ -1,4 +1,4 @@
-
+
{}", oldLocation, newLocation);
+ FileUtils.moveDirectory(oldLocation, newLocation);
+ } catch (IOException e) {
+ LOG.error("Couldn't migrate minimal browser config location", e);
+ }
+ }
}
private void makeBackup(File source) {
From 7d3e65fc14712b612c79178a1ad3d1bfd6d37f6c Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 6 Dec 2020 18:40:12 +0100
Subject: [PATCH 47/80] Add parameters to the macOS browser command
Add parameters to the open command, so that it waits for the process to
end. Otherwise the process returns immediately and causes ctbrec to go
on even though the login process didn't run in the browser.
Furthermore we now pass on the config dir for the minimal browser as a
command line argument
---
common/src/main/java/ctbrec/OS.java | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/common/src/main/java/ctbrec/OS.java b/common/src/main/java/ctbrec/OS.java
index 76f759cc..b97ebb61 100644
--- a/common/src/main/java/ctbrec/OS.java
+++ b/common/src/main/java/ctbrec/OS.java
@@ -86,10 +86,16 @@ public class OS {
System.arraycopy(args, 0, cmd, 1, args.length);
break;
case MAC:
- cmd = new String[args.length + 2];
- cmd[0] = "open";
- cmd[1] = new File(browserDir, "ctbrec-minimal-browser.app").getAbsolutePath();
- System.arraycopy(args, 0, cmd, 2, args.length);
+ cmd = new String[args.length + 5];
+ int index = 0;
+ cmd[index++] = "open";
+ cmd[index++] = "-W";
+ cmd[index++] = "-a";
+ cmd[index++] = new File(browserDir, "ctbrec-minimal-browser.app").getAbsolutePath();
+ if (args.length > 0) {
+ cmd[index] = "--args";
+ System.arraycopy(args, 0, cmd, 5, args.length);
+ }
break;
default:
throw new UnsupportedOperatingSystemException(System.getProperty(OS_NAME));
From 7fe589480863a6b292a2b2b7c92959a4a109c825 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 6 Dec 2020 18:40:47 +0100
Subject: [PATCH 48/80] Update changelog
---
CHANGELOG.md | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 351a561a..0d462a96 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,11 +2,14 @@
========================
* Fixed streaming of recordings from the server (the file path was duplicated
if single file was used)
-* Fixed credentials related bugs for Streamate and Stripchat
+* Fixed credentials related bugs for Streamate and Stripchat.
They used the user name from Chaturbate for some requests. Whoopsie!
* Renamed settings for Chaturbate's user name and password
-* Add setting to split recordings by size
+* Added setting to split recordings by size
+* Added setting to monitor the clipboard for model URLs and automatically add
+ them to the recorder
* Fixed moving of segment recordings on the server (post-processing)
+* Fixed minimal browser on macOS
* Minimal browser config is now stored in ctbrec's config directory
3.10.6
From 5020f9f06d8855caeef77b5d6cc00b0a2904ef5c Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 6 Dec 2020 20:49:59 +0100
Subject: [PATCH 49/80] Update version to 3.10.7
---
client/pom.xml | 2 +-
common/pom.xml | 2 +-
master/pom.xml | 2 +-
server/pom.xml | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/client/pom.xml b/client/pom.xml
index fff10beb..56a45659 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.6
+ 3.10.7
../master
diff --git a/common/pom.xml b/common/pom.xml
index 0f67d0c1..01597e1c 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.6
+ 3.10.7
../master
diff --git a/master/pom.xml b/master/pom.xml
index 6df4c6d2..39ca712d 100644
--- a/master/pom.xml
+++ b/master/pom.xml
@@ -6,7 +6,7 @@
ctbrec
master
pom
- 3.10.6
+ 3.10.7
../common
diff --git a/server/pom.xml b/server/pom.xml
index f82f7441..d7538fa9 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.6
+ 3.10.7
../master
From 68371876b5d749d2556033aa978873f2d74b00db Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Mon, 7 Dec 2020 20:26:23 +0100
Subject: [PATCH 50/80] Remove ProxySettingsPanel
---
.../ctbrec/ui/settings/ProxySettingsPane.java | 110 ------------------
1 file changed, 110 deletions(-)
delete mode 100644 client/src/main/java/ctbrec/ui/settings/ProxySettingsPane.java
diff --git a/client/src/main/java/ctbrec/ui/settings/ProxySettingsPane.java b/client/src/main/java/ctbrec/ui/settings/ProxySettingsPane.java
deleted file mode 100644
index c7de4c8e..00000000
--- a/client/src/main/java/ctbrec/ui/settings/ProxySettingsPane.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package ctbrec.ui.settings;
-import static ctbrec.Settings.ProxyType.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import ctbrec.Config;
-import ctbrec.Settings.ProxyType;
-import javafx.collections.FXCollections;
-import javafx.event.ActionEvent;
-import javafx.event.EventHandler;
-import javafx.scene.control.ComboBox;
-import javafx.scene.control.Label;
-import javafx.scene.control.PasswordField;
-import javafx.scene.control.TextField;
-import javafx.scene.control.TitledPane;
-import javafx.scene.layout.GridPane;
-
-public class ProxySettingsPane extends TitledPane implements EventHandler {
-
- private ComboBox proxyType;
- private TextField proxyHost = new TextField();
- private TextField proxyPort = new TextField();
- private TextField proxyUser = new TextField();
- private PasswordField proxyPassword = new PasswordField();
- private SettingsTab settingsTab;
-
- public ProxySettingsPane(SettingsTab settingsTab) {
- this.settingsTab = settingsTab;
- createGui();
- loadConfig();
- }
-
- private void createGui() {
- setText("Proxy");
- setCollapsible(false);
- GridPane layout = SettingsTab.createGridLayout();
- setContent(layout);
-
- Label l = new Label("Type");
- layout.add(l, 0, 0);
- List proxyTypes = new ArrayList<>();
- proxyTypes.add(DIRECT);
- proxyTypes.add(HTTP);
- proxyTypes.add(SOCKS4);
- proxyTypes.add(SOCKS5);
- proxyType = new ComboBox<>(FXCollections.observableList(proxyTypes));
- proxyType.setOnAction(this);
- layout.add(proxyType, 1, 0);
-
- l = new Label("Host");
- layout.add(l, 0, 1);
- layout.add(proxyHost, 1, 1);
- proxyHost.textProperty().addListener((ob, o, n) -> settingsTab.saveConfig());
-
- l = new Label("Port");
- layout.add(l, 0, 2);
- layout.add(proxyPort, 1, 2);
- proxyPort.textProperty().addListener((ob, o, n) -> settingsTab.saveConfig());
-
- l = new Label("Username");
- layout.add(l, 0, 3);
- layout.add(proxyUser, 1, 3);
- proxyUser.textProperty().addListener((ob, o, n) -> settingsTab.saveConfig());
-
-
- l = new Label("Password");
- layout.add(l, 0, 4);
- layout.add(proxyPassword, 1, 4);
- proxyPassword.textProperty().addListener((ob, o, n) -> settingsTab.saveConfig());
- }
-
- private void loadConfig() {
- proxyType.valueProperty().set(Config.getInstance().getSettings().proxyType);
- proxyHost.setText(Config.getInstance().getSettings().proxyHost);
- proxyPort.setText(Config.getInstance().getSettings().proxyPort);
- proxyUser.setText(Config.getInstance().getSettings().proxyUser);
- proxyPassword.setText(Config.getInstance().getSettings().proxyPassword);
- setComponentDisableState();
- }
-
- void saveConfig() {
- Config.getInstance().getSettings().proxyType = proxyType.getValue();
- Config.getInstance().getSettings().proxyHost = proxyHost.getText();
- Config.getInstance().getSettings().proxyPort = proxyPort.getText();
- Config.getInstance().getSettings().proxyUser = proxyUser.getText();
- Config.getInstance().getSettings().proxyPassword = proxyPassword.getText();
- }
-
- @Override
- public void handle(ActionEvent event) {
- setComponentDisableState();
- settingsTab.showRestartRequired();
- settingsTab.saveConfig();
- }
-
- private void setComponentDisableState() {
- if(proxyType.getValue() == DIRECT) {
- proxyHost.setDisable(true);
- proxyPort.setDisable(true);
- proxyUser.setDisable(true);
- proxyPassword.setDisable(true);
- } else {
- proxyHost.setDisable(false);
- proxyPort.setDisable(false);
- proxyUser.setDisable(proxyType.getValue() == SOCKS4);
- proxyPassword.setDisable(proxyType.getValue() == SOCKS4);
- }
- }
-}
From 9c440e3750ddcc89ecde8395d3c4bdfe032c790c Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Mon, 7 Dec 2020 20:52:12 +0100
Subject: [PATCH 51/80] Add "restart required" notification for some settings
---
.../ui/settings/CtbrecPreferencesStorage.java | 33 ++++++++-
.../java/ctbrec/ui/settings/SettingsTab.java | 74 ++++++++++++++++---
.../ctbrec/ui/settings/api/Preferences.java | 10 +++
3 files changed, 104 insertions(+), 13 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java
index 246559e7..99193fc0 100644
--- a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java
+++ b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java
@@ -48,12 +48,17 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
private Config config;
private Settings settings;
+ private Preferences prefs;
public CtbrecPreferencesStorage(Config config) {
this.config = config;
this.settings = config.getSettings();
}
+ public void setPreferences(Preferences prefs) {
+ this.prefs = prefs;
+ }
+
@Override
public void save(Preferences preferences) throws IOException {
throw new RuntimeException("not implemented");
@@ -103,6 +108,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
prop.addListener((obs, oldV, newV) -> saveValue(() -> {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, newV);
+ if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) {
+ prefs.getRestartRequiredCallback().run();
+ }
config.save();
}));
HBox row = new HBox();
@@ -143,7 +151,7 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
private int getRangeSliderValue(List values, List labels, int value) {
for (int i = 0; i < labels.size(); i++) {
int label = labels.get(i).intValue();
- if(label == value) {
+ if (label == value) {
return values.get(i);
}
}
@@ -158,6 +166,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
String oldValue = (String) field.get(settings);
if (!Objects.equals(path, oldValue)) {
field.set(settings, path);
+ if (setting.doesNeedRestart()) {
+ prefs.getRestartRequiredCallback().run();
+ }
config.save();
}
}));
@@ -175,6 +186,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
String oldValue = (String) field.get(settings);
if (!Objects.equals(path, oldValue)) {
field.set(settings, path);
+ if (setting.doesNeedRestart()) {
+ prefs.getRestartRequiredCallback().run();
+ }
config.save();
}
}));
@@ -188,6 +202,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, newV);
+ if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) {
+ prefs.getRestartRequiredCallback().run();
+ }
config.save();
}));
StringProperty prop = (StringProperty) setting.getProperty();
@@ -205,6 +222,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
if (!ctrl.getText().isEmpty()) {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, Integer.parseInt(ctrl.getText()));
+ if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) {
+ prefs.getRestartRequiredCallback().run();
+ }
config.save();
}
}));
@@ -227,6 +247,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
}
Field field = Settings.class.getField(setting.getKey());
field.set(settings, value);
+ if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) {
+ prefs.getRestartRequiredCallback().run();
+ }
config.save();
}
}));
@@ -240,6 +263,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
ctrl.selectedProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, newV);
+ if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) {
+ prefs.getRestartRequiredCallback().run();
+ }
config.save();
}));
BooleanProperty prop = (BooleanProperty) setting.getProperty();
@@ -266,9 +292,12 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
} else {
field.set(settings, newV);
}
+ if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) {
+ prefs.getRestartRequiredCallback().run();
+ }
config.save();
}));
- if(setting.getChangeListener() != null) {
+ if (setting.getChangeListener() != null) {
comboBox.valueProperty().addListener((ChangeListener super Object>) setting.getChangeListener());
}
return comboBox;
diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
index 39e30bce..40084cde 100644
--- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
+++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
@@ -37,6 +37,9 @@ import ctbrec.ui.settings.api.SimpleRangeProperty;
import ctbrec.ui.settings.api.ValueConverter;
import ctbrec.ui.sites.ConfigUI;
import ctbrec.ui.tabs.TabSelectionListener;
+import javafx.animation.FadeTransition;
+import javafx.animation.PauseTransition;
+import javafx.animation.Transition;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
@@ -46,13 +49,26 @@ import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
import javafx.scene.control.Button;
+import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TextInputDialog;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.Border;
+import javafx.scene.layout.BorderStroke;
+import javafx.scene.layout.BorderStrokeStyle;
+import javafx.scene.layout.BorderWidths;
+import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.util.Duration;
public class SettingsTab extends Tab implements TabSelectionListener {
@@ -109,6 +125,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private ExclusiveSelectionProperty recordLocal;
private SimpleIntegerProperty postProcessingThreads;
private IgnoreList ignoreList;
+ private Label restartNotification;
public SettingsTab(List sites, Recorder recorder) {
this.sites = sites;
@@ -171,19 +188,20 @@ public class SettingsTab extends Tab implements TabSelectionListener {
.ifPresent(configPanel -> siteCategories.add(Category.of(site.getName(), configPanel)));
}
- Preferences prefs = Preferences.of(new CtbrecPreferencesStorage(config),
+ CtbrecPreferencesStorage storage = new CtbrecPreferencesStorage(config);
+ Preferences prefs = Preferences.of(storage,
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 overview interval (seconds)", overviewUpdateIntervalInSecs, "Update the thumbnail overviews every x seconds").needsRestart(),
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("Add models from clipboard", monitorClipboard, "Monitor clipboard for model URLs and automatically add them to the recorder").needsRestart(),
Setting.of("Start Tab", startTab),
- Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance()))
+ Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance())).needsRestart()
),
Group.of("Player",
Setting.of("Player", mediaPlayer),
@@ -208,7 +226,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
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("Record Location", recordLocal).needsRestart(),
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"),
@@ -230,15 +248,17 @@ public class SettingsTab extends Tab implements TabSelectionListener {
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)
+ Setting.of("Type", proxyType).needsRestart(),
+ Setting.of("Host", proxyHost).needsRestart(),
+ Setting.of("Port", proxyPort).needsRestart(),
+ Setting.of("Username", proxyUser).needsRestart(),
+ Setting.of("Password", proxyPassword).needsRestart()
)
)
);
Region preferencesView = prefs.getView();
+ prefs.onRestartRequired(this::showRestartRequired);
+ storage.setPreferences(prefs);
preferencesView.setMinSize(800, 400);
preferencesView.setPrefSize(1280, 960);
ScrollPane scrollPane = new ScrollPane(preferencesView);
@@ -248,7 +268,20 @@ public class SettingsTab extends Tab implements TabSelectionListener {
GridPane.setFillHeight(scrollPane, true);
GridPane.setHgrow(scrollPane, Priority.ALWAYS);
GridPane.setVgrow(scrollPane, Priority.ALWAYS);
- setContent(container);
+
+ StackPane stackPane = new StackPane();
+ stackPane.getChildren().add(container);
+ restartNotification = new Label("Restart Required");
+ restartNotification.setVisible(false);
+ restartNotification.setOpacity(0);
+ restartNotification.setStyle("-fx-font-size: 28; -fx-padding: .3em");
+ restartNotification.setBorder(new Border(new BorderStroke(Color.web(settings.colorAccent), BorderStrokeStyle.SOLID, new CornerRadii(5), new BorderWidths(2))));
+ restartNotification.setBackground(new Background(new BackgroundFill(Color.web(settings.colorBase), new CornerRadii(5), Insets.EMPTY)));
+ stackPane.getChildren().add(restartNotification);
+ StackPane.setAlignment(restartNotification, Pos.TOP_RIGHT);
+ StackPane.setMargin(restartNotification, new Insets(10, 40, 0, 0));
+
+ setContent(stackPane);
prefs.expandTree();
@@ -407,7 +440,26 @@ public class SettingsTab extends Tab implements TabSelectionListener {
}
void showRestartRequired() {
- // TODO restartLabel.setVisible(true);
+ if (!restartNotification.isVisible()) {
+ restartNotification.setVisible(true);
+ Transition fadeIn = changeOpacity(restartNotification, 1);
+ fadeIn.play();
+ fadeIn.setOnFinished(e -> {
+ Transition fadeOut = changeOpacity(restartNotification, 0);
+ fadeOut.setOnFinished(e2 -> restartNotification.setVisible(false));
+ PauseTransition pauseTransition = new PauseTransition(Duration.seconds(5));
+ pauseTransition.setOnFinished(evt -> fadeOut.play());
+ pauseTransition.play();
+ });
+ }
+ }
+
+ private static final Duration ANIMATION_DURATION = new Duration(500);
+ private Transition changeOpacity(Node node, double opacity) {
+ FadeTransition transition = new FadeTransition(ANIMATION_DURATION, node);
+ transition.setFromValue(node.getOpacity());
+ transition.setToValue(opacity);
+ return transition;
}
public static class SplitAfterOption {
diff --git a/client/src/main/java/ctbrec/ui/settings/api/Preferences.java b/client/src/main/java/ctbrec/ui/settings/api/Preferences.java
index d8c9bb7d..f5a72a66 100644
--- a/client/src/main/java/ctbrec/ui/settings/api/Preferences.java
+++ b/client/src/main/java/ctbrec/ui/settings/api/Preferences.java
@@ -35,6 +35,8 @@ public class Preferences {
private PreferencesStorage preferencesStorage;
+ private Runnable restartRequiredCallback = () -> {};
+
private Preferences(PreferencesStorage preferencesStorage, Category...categories) {
this.preferencesStorage = preferencesStorage;
this.categories = categories;
@@ -248,4 +250,12 @@ public class Preferences {
return result;
}
}
+
+ public void onRestartRequired(Runnable callback) {
+ this.restartRequiredCallback = callback;
+ }
+
+ public Runnable getRestartRequiredCallback() {
+ return restartRequiredCallback;
+ }
}
From 7d343db450e7d60c04714d002b47f146b75a502d Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Fri, 11 Dec 2020 18:24:22 +0100
Subject: [PATCH 52/80] Fix Bongacams New tab URL
---
CHANGELOG.md | 4 ++++
.../main/java/ctbrec/ui/sites/bonga/BongaCamsTabProvider.java | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d462a96..3d8d68ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+3.10.8
+========================
+* Fixed Bongacams "New" tab
+
3.10.7
========================
* Fixed streaming of recordings from the server (the file path was duplicated
diff --git a/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsTabProvider.java b/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsTabProvider.java
index 399a1efe..8feacf7c 100644
--- a/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsTabProvider.java
+++ b/client/src/main/java/ctbrec/ui/sites/bonga/BongaCamsTabProvider.java
@@ -47,7 +47,7 @@ public class BongaCamsTabProvider extends TabProvider {
tabs.add(createTab("Transsexual", updateService));
// new
- url = BongaCams.baseUrl + "/tools/listing_v3.php?livetab=new-models&online_only=true&is_mobile=true&offset=";
+ url = BongaCams.baseUrl + "/tools/listing_v3.php?livetab=new&online_only=true&is_mobile=true&offset=";
updateService = new BongaCamsUpdateService(bongaCams, url);
tabs.add(createTab("New", updateService));
From ae14844170ac4307a940bc67fdfd480e5b37b8ce Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 12 Dec 2020 15:19:58 +0100
Subject: [PATCH 53/80] Fixed possible NPE in CtbrecPreferencesStorage
---
.../main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java
index 99193fc0..3c84aa54 100644
--- a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java
+++ b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java
@@ -222,7 +222,7 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
if (!ctrl.getText().isEmpty()) {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, Integer.parseInt(ctrl.getText()));
- if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) {
+ if (setting.doesNeedRestart() && !Objects.equals(oldV, newV) && prefs != null) {
prefs.getRestartRequiredCallback().run();
}
config.save();
From 605269b4a06969c6c726ab700963b5dbe567e45e Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 12 Dec 2020 15:20:45 +0100
Subject: [PATCH 54/80] Add setting to switch FFmpeg logging on / off
---
CHANGELOG.md | 1 +
.../java/ctbrec/ui/settings/SettingsTab.java | 7 +
common/src/main/java/ctbrec/Config.java | 8 +-
common/src/main/java/ctbrec/Settings.java | 1 +
.../src/main/java/ctbrec/recorder/FFmpeg.java | 135 ++++++++++
.../download/hls/MergedFfmpegHlsDownload.java | 237 +++++++++---------
.../postprocessing/CreateContactSheet.java | 33 +--
.../ctbrec/recorder/postprocessing/Remux.java | 110 ++++----
.../sites/showup/ShowupMergedDownload.java | 2 +-
9 files changed, 328 insertions(+), 206 deletions(-)
create mode 100644 common/src/main/java/ctbrec/recorder/FFmpeg.java
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d8d68ce..ce1a5a76 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
========================
diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
index 40084cde..a7566dfd 100644
--- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
+++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
@@ -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();
diff --git a/common/src/main/java/ctbrec/Config.java b/common/src/main/java/ctbrec/Config.java
index 76eec655..48560ab0 100644
--- a/common/src/main/java/ctbrec/Config.java
+++ b/common/src/main/java/ctbrec/Config.java
@@ -151,8 +151,12 @@ public class Config {
if (oldLocation.exists()) {
File newLocation = new File(getConfigDir(), oldLocation.getName());
try {
- LOG.debug("Moving minimal browser config {} --> {}", oldLocation, newLocation);
- FileUtils.moveDirectory(oldLocation, newLocation);
+ 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);
}
diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java
index 49cfa237..804d759b 100644
--- a/common/src/main/java/ctbrec/Settings.java
+++ b/common/src/main/java/ctbrec/Settings.java
@@ -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;
diff --git a/common/src/main/java/ctbrec/recorder/FFmpeg.java b/common/src/main/java/ctbrec/recorder/FFmpeg.java
new file mode 100644
index 00000000..fdfa3bb1
--- /dev/null
+++ b/common/src/main/java/ctbrec/recorder/FFmpeg.java
@@ -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 startCallback;
+ private Consumer 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 startCallback;
+ private Consumer 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 callback) {
+ this.startCallback = callback;
+ return this;
+ }
+
+ public Builder onExit(Consumer 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;
+ }
+ }
+
+}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java
index 136a159f..00713d63 100644
--- a/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java
@@ -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> 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());
- 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);
- }
- }
+ String[] cmdline = prepareCommandLine(target);
+ FFmpeg ffmpeg = new FFmpeg.Builder()
+ .logOutput(config.getSettings().logFFmpegOutput)
+ .onStarted(p -> {
+ ffmpegProcess = p;
+ ffmpegStdIn = ffmpegProcess.getOutputStream();
+ synchronized (ffmpegStartMonitor) {
+ ffmpegStartMonitor.notifyAll();
+ }
+ })
+ .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,48 +157,23 @@ 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 {
- SegmentPlaylist lsp = getNextSegments(segmentPlaylistUri);
- emptyPlaylistCheck(lsp);
-
- // download new segments
- long downloadStart = System.currentTimeMillis();
- if (livestreamDownload) {
- downloadNewSegments(lsp, nextSegment);
- } else {
- downloadRecording(lsp);
- }
- long downloadTookMillis = System.currentTimeMillis() - downloadStart;
-
- // download segments, which might have been skipped
- if (nextSegment > 0 && lsp.seq > nextSegment) {
- LOG.warn("Missed segments {} < {} in download for {}. Download took {}ms. Playlist is {}sec", nextSegment, lsp.seq, lsp.url,
- downloadTookMillis, lsp.totalDuration);
- }
-
- if (livestreamDownload) {
- splitRecordingIfNecessary();
-
- // wait some time until requesting the segment playlist again to not hammer the server
- waitForNewSegments(lsp, lastSegment, downloadTookMillis);
-
- lastSegment = lsp.seq;
- nextSegment = lastSegment + lsp.segments.size();
- } else {
- break;
- }
+ downloadSegments(segmentPlaylistUri, livestreamDownload);
} catch (HttpException e) {
- if (e.getResponseCode() == 404) {
- LOG.debug("Playlist not found (404). Model {} probably went offline", model);
- } else if (e.getResponseCode() == 403) {
- LOG.debug("Playlist access forbidden (403). Model {} probably went private or offline", model);
- } else {
- LOG.info("Unexpected error while downloading {}", model, e);
- }
+ logHttpException(e);
running = false;
} catch (MalformedURLException e) {
LOG.info("Malformed URL {} - {}", model, segmentPlaylistUri, e);
@@ -238,6 +186,48 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
ffmpegThread.interrupt();
}
+ private void downloadSegments(String segmentPlaylistUri, boolean livestreamDownload) throws IOException, ParseException, PlaylistException, ExecutionException {
+ SegmentPlaylist lsp = getNextSegments(segmentPlaylistUri);
+ emptyPlaylistCheck(lsp);
+
+ // download new segments
+ long downloadStart = System.currentTimeMillis();
+ if (livestreamDownload) {
+ downloadNewSegments(lsp, nextSegment);
+ } else {
+ downloadRecording(lsp);
+ }
+ long downloadTookMillis = System.currentTimeMillis() - downloadStart;
+
+ // download segments, which might have been skipped
+ if (nextSegment > 0 && lsp.seq > nextSegment) {
+ LOG.warn("Missed segments {} < {} in download for {}. Download took {}ms. Playlist is {}sec", nextSegment, lsp.seq, lsp.url,
+ downloadTookMillis, lsp.totalDuration);
+ }
+
+ if (livestreamDownload) {
+ splitRecordingIfNecessary();
+
+ // wait some time until requesting the segment playlist again to not hammer the server
+ waitForNewSegments(lsp, lastSegment, downloadTookMillis);
+
+ lastSegment = lsp.seq;
+ nextSegment = lastSegment + lsp.segments.size();
+ } else {
+ running = false;
+ }
+ }
+
+ 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) {
+ LOG.debug("Playlist access forbidden (403). Model {} probably went private or offline", model);
+ } else {
+ LOG.info("Unexpected error while downloading {}", model, e);
+ }
+ }
+
protected void splitRecordingIfNecessary() {
if (splittingStrategy.splitNecessary(this)) {
internalStop();
@@ -291,33 +281,40 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
} catch (CancellationException e) {
LOG.info("Segment download cancelled");
} catch (ExecutionException e) {
- Throwable cause = e.getCause();
- if (cause instanceof MissingSegmentException) {
- if (model != null && !isModelOnline()) {
- LOG.debug("Error while downloading segment, because model {} is offline. Stopping now", model.getName());
- running = false;
- } else {
- 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;
- if (model != null && !isModelOnline()) {
- LOG.debug("Error {} while downloading segment, because model {} is offline. Stopping now", he.getResponseCode(), model.getName());
- running = false;
- } else {
- if (he.getResponseCode() == 404) {
- LOG.info("Playlist for {} not found [HTTP 404]. Stopping now", ofNullable(model).map(Model::getName).orElse("n/a"));
- running = false;
- } else if (he.getResponseCode() == 403) {
- LOG.info("Playlist for {} not accessible [HTTP 403]. Stopping now", ofNullable(model).map(Model::getName).orElse("n/a"));
- running = false;
- } else {
- throw he;
- }
- }
- } else {
- throw e;
- }
+ handleExecutionExceptione(e);
+ }
+ }
+ }
+
+ private void handleExecutionExceptione(ExecutionException e) throws HttpException, ExecutionException {
+ Throwable cause = e.getCause();
+ if (cause instanceof MissingSegmentException) {
+ if (model != null && !isModelOnline()) {
+ LOG.debug("Error while downloading segment, because model {} is offline. Stopping now", model.getName());
+ running = false;
+ } else {
+ LOG.debug("Segment not available, but model {} still online. Going on", ofNullable(model).map(Model::getName).orElse("n/a"));
+ }
+ } else if (cause instanceof HttpException) {
+ 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;
+ } else {
+ if (he.getResponseCode() == 404) {
+ LOG.info("Playlist for {} not found [HTTP 404]. Stopping now", ofNullable(model).map(Model::getName).orElse("n/a"));
+ running = false;
+ } else if (he.getResponseCode() == 403) {
+ LOG.info("Playlist for {} not accessible [HTTP 403]. Stopping now", ofNullable(model).map(Model::getName).orElse("n/a"));
+ running = false;
+ } else {
+ throw he;
}
}
}
@@ -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) {
diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/CreateContactSheet.java b/common/src/main/java/ctbrec/recorder/postprocessing/CreateContactSheet.java
index 7db00d4c..f8a8bace 100644
--- a/common/src/main/java/ctbrec/recorder/postprocessing/CreateContactSheet.java
+++ b/common/src/main/java/ctbrec/recorder/postprocessing/CreateContactSheet.java
@@ -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) {
diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java b/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java
index 941989bb..fa3f3bc2 100644
--- a/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java
+++ b/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java
@@ -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,70 +32,62 @@ 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");
}
+ 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);
+
+ 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());
+ if (Objects.equals(inputFile.getParentFile(), rec.getAbsoluteFile())) {
+ rec.setAbsoluteFile(remuxedFile);
+ }
+ } else {
+ Files.deleteIfExists(inputFile.toPath());
+ if (Objects.equals(inputFile, rec.getAbsoluteFile())) {
+ rec.setAbsoluteFile(remuxedFile);
+ }
+ }
+ rec.setSingleFile(true);
+ rec.setSizeInByte(remuxedFile.length());
+ IoUtils.deleteEmptyParents(inputFile.getParentFile());
+ rec.getAssociatedFiles().remove(inputFile.getCanonicalPath());
+ rec.getAssociatedFiles().add(remuxedFile.getCanonicalPath());
+ } catch (IOException e) {
+ LOG.error("Couldn't finalize remux post-processing step", e);
+ }
+ }
+ }
+
+ 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);
- File remuxedFile = new File(rec.getPostProcessedFile().getAbsolutePath() + '.' + fileExt);
argsPlusFile[argsPlusFile.length - 1] = remuxedFile.getAbsolutePath();
- String[] cmdline = OS.getFFmpegCommand(argsPlusFile);
- 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);
- rec.setPostProcessedFile(remuxedFile);
- if (inputFile.getName().equals("playlist.m3u8")) {
- IoUtils.deleteDirectory(inputFile.getParentFile());
- if (Objects.equals(inputFile.getParentFile(), rec.getAbsoluteFile())) {
- rec.setAbsoluteFile(remuxedFile);
- }
- } else {
- Files.deleteIfExists(inputFile.toPath());
- if (Objects.equals(inputFile, rec.getAbsoluteFile())) {
- rec.setAbsoluteFile(remuxedFile);
- }
- }
- rec.setSingleFile(true);
- rec.setSizeInByte(remuxedFile.length());
- IoUtils.deleteEmptyParents(inputFile.getParentFile());
- rec.getAssociatedFiles().remove(inputFile.getCanonicalPath());
- rec.getAssociatedFiles().add(remuxedFile.getCanonicalPath());
- return true;
- }
-
- 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);
- }
+ return OS.getFFmpegCommand(argsPlusFile);
}
@Override
diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupMergedDownload.java b/common/src/main/java/ctbrec/sites/showup/ShowupMergedDownload.java
index 78a33af3..28537c45 100644
--- a/common/src/main/java/ctbrec/sites/showup/ShowupMergedDownload.java
+++ b/common/src/main/java/ctbrec/sites/showup/ShowupMergedDownload.java
@@ -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);
From cdaeaa746bc9ee152e34422a43791cde569c74fe Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 12 Dec 2020 16:31:36 +0100
Subject: [PATCH 55/80] Remove state from Remux post-processor
Post-processors have to be thread safe and can't have any state
---
.../ctbrec/recorder/postprocessing/Remux.java | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java b/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java
index fa3f3bc2..c18eb389 100644
--- a/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java
+++ b/common/src/main/java/ctbrec/recorder/postprocessing/Remux.java
@@ -22,8 +22,6 @@ 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,12 +30,14 @@ public class Remux extends AbstractPostProcessor {
@Override
public boolean postprocess(Recording rec, RecordingManager recordingManager, Config config) throws IOException, InterruptedException {
- inputFile = rec.getPostProcessedFile();
- if (inputFile.isDirectory()) {
- inputFile = new File(inputFile, "playlist.m3u8");
+ final File inputFile;
+ if (rec.getPostProcessedFile().isDirectory()) {
+ inputFile = new File(rec.getPostProcessedFile(), "playlist.m3u8");
+ } else {
+ inputFile = rec.getPostProcessedFile();
}
String fileExt = getConfig().get(FILE_EXT);
- remuxedFile = new File(rec.getPostProcessedFile().getAbsolutePath() + '.' + fileExt);
+ File 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);
@@ -46,14 +46,14 @@ public class Remux extends AbstractPostProcessor {
FFmpeg ffmpeg = new FFmpeg.Builder()
.logOutput(config.getSettings().logFFmpegOutput)
.logFile(ffmpegLog)
- .onExit(exitCode -> finalizeStep(exitCode, rec))
+ .onExit(exitCode -> finalizeStep(exitCode, rec, inputFile, remuxedFile))
.build();
ffmpeg.exec(cmdline, new String[0], executionDir);
int exitCode = ffmpeg.waitFor();
return exitCode != 1;
}
- private void finalizeStep(int exitCode, Recording rec) {
+ private void finalizeStep(int exitCode, Recording rec, File inputFile, File remuxedFile) {
if (exitCode != 1) {
try {
rec.setPostProcessedFile(remuxedFile);
From 2eacbae228726dffae43a469d6fc089e167003fe Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 12 Dec 2020 21:06:38 +0100
Subject: [PATCH 56/80] Fix Stripchat recordings
For some models the recording didn't start, even if they were online and
publicly visible in the browser. We now use a different JSON object to
determine, which resolutions are available
---
CHANGELOG.md | 4 +-
.../sites/stripchat/StripchatModel.java | 48 ++++++++++++++-----
2 files changed, 39 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce1a5a76..c77574fa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
3.10.8
========================
-* Fixed Bongacams "New" tab
+* Fixed Stripchat recordings. For some models the recording didn't start,
+ even if they were online and publicly visible in the browser
+* Fixed Bongacams "New" tab. It didn't show new models.
* Added setting to switch FFmpeg logging on/off (category Advanced/Devtools)
3.10.7
diff --git a/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java b/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java
index 2705e68f..f5e7b78b 100644
--- a/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java
+++ b/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java
@@ -7,14 +7,16 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
-import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import javax.xml.bind.JAXBException;
import org.json.JSONArray;
+import org.json.JSONException;
import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.iheartradio.m3u8.ParseException;
import com.iheartradio.m3u8.PlaylistException;
@@ -29,6 +31,7 @@ import okhttp3.RequestBody;
import okhttp3.Response;
public class StripchatModel extends AbstractModel {
+ private static final transient Logger LOG = LoggerFactory.getLogger(StripchatModel.class);
private String status = null;
private int[] resolution = new int[] {0, 0};
@@ -39,13 +42,28 @@ public class StripchatModel extends AbstractModel {
if (jsonResponse.has("user")) {
JSONObject user = jsonResponse.getJSONObject("user");
status = user.optString("status");
+ mapOnlineState(status);
}
}
- boolean online = Objects.equals(status, "public");
- if (online) {
+ return onlineState == ONLINE;
+ }
+
+ private void mapOnlineState(String status) {
+ switch (status) {
+ case "public":
setOnlineState(ONLINE);
+ break;
+ case "idle":
+ setOnlineState(AWAY);
+ break;
+ case "private":
+ setOnlineState(PRIVATE);
+ break;
+ default:
+ LOG.debug("Unknown online state {} for model {}", status, getName());
+ setOnlineState(OFFLINE);
+ break;
}
- return online;
}
private JSONObject loadModelInfo() throws IOException {
@@ -84,7 +102,7 @@ public class StripchatModel extends AbstractModel {
try (Response response = site.getHttpClient().execute(req)) {
if (response.isSuccessful()) {
JSONObject jsonResponse = new JSONObject(response.body().string());
- String streamName = jsonResponse.optString("streamName");
+ String streamName = jsonResponse.optString("streamName", jsonResponse.optString(""));
JSONObject viewServers = jsonResponse.getJSONObject("viewServers");
String serverName = viewServers.optString("flashphoner-hls");
JSONObject broadcastSettings = jsonResponse.getJSONObject("broadcastSettings");
@@ -92,18 +110,20 @@ public class StripchatModel extends AbstractModel {
StreamSource best = new StreamSource();
best.height = broadcastSettings.optInt("height");
best.width = broadcastSettings.optInt("width");
- best.mediaPlaylistUrl = "https://b-" + serverName + ".stripst.com/hls/" + streamName + "/" + streamName + ".m3u8";
+ best.mediaPlaylistUrl = "https://b-" + serverName + ".stripst.com/hls/" + streamName + '/' + streamName + ".m3u8";
sources.add(best);
- JSONObject resolutions = broadcastSettings.optJSONObject("resolutions");
- if (resolutions instanceof JSONObject) {
- JSONArray heights = resolutions.names();
+ JSONObject presets = broadcastSettings.optJSONObject("presets");
+ Object defaultObject = presets.get("testing");
+ if (defaultObject instanceof JSONObject) {
+ JSONObject defaults = (JSONObject) defaultObject;
+ JSONArray heights = defaults.names();
for (int i = 0; i < heights.length(); i++) {
String h = heights.getString(i);
StreamSource streamSource = new StreamSource();
- streamSource.height = Integer.parseInt(h.replace("p", ""));
+ streamSource.height = Integer.parseInt(h.replaceAll("[^\\d]", ""));
streamSource.width = streamSource.height * best.getWidth() / best.getHeight();
- String source = streamName + "-" + streamSource.height + "p";
- streamSource.mediaPlaylistUrl = "https://b-" + serverName + ".stripst.com/hls/" + source + "/" + source + ".m3u8";
+ String source = streamName + '_' + streamSource.height + 'p';
+ streamSource.mediaPlaylistUrl = "https://b-" + serverName + ".stripst.com/hls/" + source + '/' + source + ".m3u8";
sources.add(streamSource);
}
}
@@ -111,9 +131,13 @@ public class StripchatModel extends AbstractModel {
} else {
throw new HttpException(response.code(), response.message());
}
+ } catch(JSONException e) {
+ System.err.println(getName());
+ throw e;
}
}
+
@Override
public void invalidateCacheEntries() {
status = null;
From 1f07cea34342f3edba81f7db7b308331d687dd18 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 12 Dec 2020 21:45:32 +0100
Subject: [PATCH 57/80] Add more online states to mapOnlineState()
---
common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java b/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java
index f5e7b78b..3d81a5e7 100644
--- a/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java
+++ b/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java
@@ -57,6 +57,8 @@ public class StripchatModel extends AbstractModel {
setOnlineState(AWAY);
break;
case "private":
+ case "p2p":
+ case "groupShow":
setOnlineState(PRIVATE);
break;
default:
From ff273efedc7e1e87c003f4305eb41e57113b0022 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 12 Dec 2020 22:27:10 +0100
Subject: [PATCH 58/80] Set version to 3.10.8
---
client/pom.xml | 2 +-
common/pom.xml | 2 +-
master/pom.xml | 2 +-
server/pom.xml | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/client/pom.xml b/client/pom.xml
index 56a45659..43018917 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.7
+ 3.10.8
../master
diff --git a/common/pom.xml b/common/pom.xml
index 01597e1c..1a761180 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.7
+ 3.10.8
../master
diff --git a/master/pom.xml b/master/pom.xml
index 39ca712d..39fb3903 100644
--- a/master/pom.xml
+++ b/master/pom.xml
@@ -6,7 +6,7 @@
ctbrec
master
pom
- 3.10.7
+ 3.10.8
../common
diff --git a/server/pom.xml b/server/pom.xml
index d7538fa9..5afe6b5e 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.7
+ 3.10.8
../master
From d50ca023741bff3ac44540c883dbeabe49443710 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 13 Dec 2020 02:54:01 +0100
Subject: [PATCH 59/80] Add online state "off" to mapOnlineState()
---
.../src/main/java/ctbrec/sites/stripchat/StripchatModel.java | 3 +++
1 file changed, 3 insertions(+)
diff --git a/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java b/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java
index 3d81a5e7..4e657af3 100644
--- a/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java
+++ b/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java
@@ -61,6 +61,9 @@ public class StripchatModel extends AbstractModel {
case "groupShow":
setOnlineState(PRIVATE);
break;
+ case "off":
+ setOnlineState(OFFLINE);
+ break;
default:
LOG.debug("Unknown online state {} for model {}", status, getName());
setOnlineState(OFFLINE);
From 43d93e3590476bbbec70c268c263aa4177da606e Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 13 Dec 2020 16:59:10 +0100
Subject: [PATCH 60/80] Add button to check model URLs
Add button and action, which goes over all model URLs and checks, if the
account still exists.
---
CHANGELOG.md | 5 ++
.../ui/action/CheckModelAccountAction.java | 82 +++++++++++++++++++
.../ctbrec/ui/tabs/RecordedModelsTab.java | 8 +-
3 files changed, 94 insertions(+), 1 deletion(-)
create mode 100644 client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c77574fa..af417890 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+3.10.9
+========================
+* Added button to the "Recording" tab to go over all model URLs and check, if
+ the account still exists
+
3.10.8
========================
* Fixed Stripchat recordings. For some models the recording didn't start,
diff --git a/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java b/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
new file mode 100644
index 00000000..6dfd9c4b
--- /dev/null
+++ b/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
@@ -0,0 +1,82 @@
+package ctbrec.ui.action;
+
+import static ctbrec.io.HttpConstants.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ctbrec.Config;
+import ctbrec.Model;
+import ctbrec.recorder.Recorder;
+import ctbrec.ui.controls.Dialogs;
+import javafx.application.Platform;
+import javafx.scene.control.Button;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class CheckModelAccountAction {
+ private static final Logger LOG = LoggerFactory.getLogger(CheckModelAccountAction.class);
+
+ private Button b;
+
+ private Recorder recorder;
+
+ public CheckModelAccountAction(Button b, Recorder recorder) {
+ this.b = b;
+ this.recorder = recorder;
+
+ }
+
+ public void execute() {
+ String buttonText = b.getText();
+ b.setDisable(true);
+ Runnable checker = (() -> {
+ List deletedAccounts = new ArrayList<>();
+ try {
+ List models = recorder.getModels();
+ int total = models.size();
+ for (int i = 0; i < total; i++) {
+ final int counter = i+1;
+ Platform.runLater(() -> b.setText(buttonText + ' ' + counter + '/' + total));
+ Model modelToCheck = models.get(i);
+ Request req = new Request.Builder()
+ .url(modelToCheck.getUrl())
+ .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
+ .build();
+ try(Response response = modelToCheck.getSite().getHttpClient().execute(req)) {
+ if(!response.isSuccessful() && response.code() == 404) {
+ deletedAccounts.add(modelToCheck);
+ }
+ } catch (IOException e) {
+ LOG.warn("Couldn't check, if model account still exists", e);
+ }
+ }
+ } finally {
+ Platform.runLater(() -> {
+ b.setDisable(false);
+ b.setText(buttonText);
+ StringBuilder sb = new StringBuilder();
+ for (Model deletedModel : deletedAccounts) {
+ String name = deletedModel.getDisplayName() + " ".repeat(30);
+ name = name.substring(0, 30);
+ sb.append(name)
+ .append(' ').append('(')
+ .append(deletedModel.getUrl())
+ .append(')').append('\n');
+ }
+
+ boolean remove = Dialogs.showConfirmDialog("Deleted Accounts", sb.toString(),
+ "The following accounts seem to have been deleted. Do you want to remove them?", b.getScene());
+ if (remove) {
+ new StopRecordingAction(b, deletedAccounts, recorder).execute();
+ }
+ });
+ }
+ });
+ new Thread(checker).start();
+ }
+}
diff --git a/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java b/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
index c432ea3a..90eba303 100644
--- a/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
@@ -38,6 +38,7 @@ import ctbrec.ui.DesktopIntegration;
import ctbrec.ui.JavaFxModel;
import ctbrec.ui.PreviewPopupHandler;
import ctbrec.ui.StreamSourceSelectionDialog;
+import ctbrec.ui.action.CheckModelAccountAction;
import ctbrec.ui.action.EditNotesAction;
import ctbrec.ui.action.FollowAction;
import ctbrec.ui.action.OpenRecordingsDir;
@@ -125,6 +126,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
Button pauseAll = new Button("Pause All");
Button resumeAll = new Button("Resume All");
ToggleButton toggleRecording = new ToggleButton("Pause Recording");
+ Button checkModelAccountExistance = new Button("Check URLs");
TextField filter;
public RecordedModelsTab(String title, Recorder recorder, List sites) {
@@ -279,7 +281,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
BorderPane.setMargin(addModelBox, new Insets(5));
addModelButton.setOnAction(this::addModel);
addModelButton.setPadding(new Insets(5));
- addModelBox.getChildren().addAll(modelLabel, model, addModelButton, pauseAll, resumeAll, toggleRecording);
+ addModelBox.getChildren().addAll(modelLabel, model, addModelButton, pauseAll, resumeAll, toggleRecording, checkModelAccountExistance);
HBox.setMargin(pauseAll, new Insets(0, 0, 0, 20));
pauseAll.setOnAction(this::pauseAll);
resumeAll.setOnAction(this::resumeAll);
@@ -288,6 +290,10 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
toggleRecording.setPadding(new Insets(5));
toggleRecording.setOnAction(this::toggleRecording);
HBox.setMargin(toggleRecording, new Insets(0, 0, 0, 20));
+ checkModelAccountExistance.setPadding(new Insets(5));
+ checkModelAccountExistance.setTooltip(new Tooltip("Go over all model URLs and check, if the account still exists"));
+ HBox.setMargin(checkModelAccountExistance, new Insets(0, 0, 0, 20));
+ checkModelAccountExistance.setOnAction(evt -> new CheckModelAccountAction(checkModelAccountExistance, recorder).execute());
HBox filterContainer = new HBox();
filterContainer.setSpacing(0);
From 2f0ef47acf297f442924ce61d39a1a8c60df3b80 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Mon, 14 Dec 2020 21:28:10 +0100
Subject: [PATCH 61/80] Add more category tabs for CamSoda
---
CHANGELOG.md | 1 +
.../ui/sites/camsoda/CamsodaTabProvider.java | 11 ++-
.../sites/camsoda/CamsodaUpdateService.java | 98 +++++++++----------
.../ctbrec/sites/camsoda/CamsodaModel.java | 9 ++
4 files changed, 67 insertions(+), 52 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index af417890..226f2eee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
3.10.9
========================
+* Added more category tabs for CamSoda
* Added button to the "Recording" tab to go over all model URLs and check, if
the account still exists
diff --git a/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaTabProvider.java b/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaTabProvider.java
index d90ba6e7..8a460a08 100644
--- a/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaTabProvider.java
+++ b/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaTabProvider.java
@@ -4,6 +4,7 @@ import static ctbrec.sites.camsoda.Camsoda.*;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.function.Predicate;
import ctbrec.recorder.Recorder;
@@ -16,6 +17,7 @@ import javafx.scene.control.Tab;
public class CamsodaTabProvider extends TabProvider {
+ private static final String API_URL = BASE_URI + "/api/v1/browse/online";
private Camsoda camsoda;
private Recorder recorder;
CamsodaFollowedTab followedTab;
@@ -29,8 +31,13 @@ public class CamsodaTabProvider extends TabProvider {
@Override
public List getTabs(Scene scene) {
List tabs = new ArrayList<>();
- tabs.add(createTab("Online", BASE_URI + "/api/v1/browse/online", m -> true));
- //tabs.add(createTab("New", BASE_URI + "/api/v1/browse/online", CamsodaModel::isNew));
+ tabs.add(createTab("All", API_URL, m -> true));
+ tabs.add(createTab("New", API_URL, CamsodaModel::isNew));
+ tabs.add(createTab("Female", API_URL, m -> Objects.equals("f", m.getGender())));
+ tabs.add(createTab("Male", API_URL, m -> Objects.equals("m", m.getGender())));
+ tabs.add(createTab("Couples", API_URL, m -> Objects.equals("c", m.getGender())));
+ tabs.add(createTab("Trans", API_URL, m -> Objects.equals("t", m.getGender())));
+ //tabs.add(createTab("#petite", BASE_URI + "/api/v1/browse/tag-petite", m -> true));
followedTab.setRecorder(recorder);
followedTab.setScene(scene);
tabs.add(followedTab);
diff --git a/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java b/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java
index 1b8510d4..27e90dd6 100644
--- a/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java
+++ b/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java
@@ -74,60 +74,58 @@ public class CamsodaUpdateService extends PaginatedScheduledService {
if (response.isSuccessful()) {
String body = response.body().string();
JSONObject json = new JSONObject(body);
- if (json.optBoolean("status")) {
- JSONArray template = json.getJSONArray("template");
- JSONArray results = json.getJSONArray("results");
- for (int i = 0; i < results.length(); i++) {
- Object result = results.getJSONObject(i).get("tpl");
- CamsodaModel model;
- try {
- if (result instanceof JSONObject) {
- JSONObject tpl = (JSONObject) result;
- String name = tpl.getString(Integer.toString(getTemplateIndex(template, "username")));
- model = (CamsodaModel) camsoda.createModel(name);
- model.setDescription(tpl.getString(Integer.toString(getTemplateIndex(template, "subject_html"))));
- model.setSortOrder(tpl.getFloat(Integer.toString(getTemplateIndex(template, "sort_value"))));
- String preview = "https:" + tpl.getString(Integer.toString(getTemplateIndex(template, "thumb")));
- model.setPreview(preview);
- String displayName = tpl.getString(Integer.toString(getTemplateIndex(template, "display_name")));
- model.setDisplayName(displayName.replaceAll("[^a-zA-Z0-9]", ""));
- if (model.getDisplayName().isBlank()) {
- model.setDisplayName(name);
- }
- model.setOnlineState(tpl.getString(Integer.toString(getTemplateIndex(template, "stream_name"))).isEmpty() ? OFFLINE : ONLINE);
- try {
- String statusIndex = Integer.toString(getTemplateIndex(template, "status"));
- if (tpl.has(statusIndex)) {
- model.setOnlineStateByStatus(tpl.getString(statusIndex));
- }
- } catch (NoSuchElementException e) {
- }
- models.add(model);
- } else if (result instanceof JSONArray) {
- JSONArray tpl = (JSONArray) result;
- String name = tpl.getString(getTemplateIndex(template, "username"));
- model = (CamsodaModel) camsoda.createModel(name);
- model.setSortOrder(tpl.getFloat(getTemplateIndex(template, "sort_value")));
- model.setDescription(tpl.getString(getTemplateIndex(template, "subject_html")));
- String preview = "https:" + tpl.getString(getTemplateIndex(template, "thumb"));
- model.setPreview(preview);
- model.setOnlineStateByStatus(tpl.getString(getTemplateIndex(template, "status")));
- String displayName = tpl.getString(getTemplateIndex(template, "display_name"));
- model.setDisplayName(displayName.replaceAll("[^a-zA-Z0-9]", ""));
- if (model.getDisplayName().isBlank()) {
- model.setDisplayName(name);
- }
- models.add(model);
+ JSONArray template = json.getJSONArray("template");
+ JSONArray results = json.getJSONArray("results");
+ for (int i = 0; i < results.length(); i++) {
+ JSONObject result = results.getJSONObject(i);
+ Object templateObject = result.get("tpl");
+ CamsodaModel model;
+ try {
+ if (templateObject instanceof JSONObject) {
+ JSONObject tpl = (JSONObject) templateObject;
+ String name = tpl.getString(Integer.toString(getTemplateIndex(template, "username")));
+ model = (CamsodaModel) camsoda.createModel(name);
+ model.setDescription(tpl.getString(Integer.toString(getTemplateIndex(template, "subject_html"))));
+ model.setSortOrder(tpl.getFloat(Integer.toString(getTemplateIndex(template, "sort_value"))));
+ model.setNew(result.optBoolean("new"));
+ model.setGender(tpl.getString(Integer.toString(getTemplateIndex(template, "gender"))));
+ String preview = "https:" + tpl.getString(Integer.toString(getTemplateIndex(template, "thumb")));
+ model.setPreview(preview);
+ String displayName = tpl.getString(Integer.toString(getTemplateIndex(template, "display_name")));
+ model.setDisplayName(displayName.replaceAll("[^a-zA-Z0-9]", ""));
+ if (model.getDisplayName().isBlank()) {
+ model.setDisplayName(name);
}
- } catch (Exception e) {
- LOG.warn("Couldn't parse one of the models: {}", result, e);
+ model.setOnlineState(tpl.getString(Integer.toString(getTemplateIndex(template, "stream_name"))).isEmpty() ? OFFLINE : ONLINE);
+ try {
+ String statusIndex = Integer.toString(getTemplateIndex(template, "status"));
+ if (tpl.has(statusIndex)) {
+ model.setOnlineStateByStatus(tpl.getString(statusIndex));
+ }
+ } catch (NoSuchElementException e) {
+ }
+ models.add(model);
+ } else if (templateObject instanceof JSONArray) {
+ JSONArray tpl = (JSONArray) templateObject;
+ String name = tpl.getString(getTemplateIndex(template, "username"));
+ model = (CamsodaModel) camsoda.createModel(name);
+ model.setSortOrder(tpl.getFloat(getTemplateIndex(template, "sort_value")));
+ model.setDescription(tpl.getString(getTemplateIndex(template, "subject_html")));
+ String preview = "https:" + tpl.getString(getTemplateIndex(template, "thumb"));
+ model.setPreview(preview);
+ model.setOnlineStateByStatus(tpl.getString(getTemplateIndex(template, "status")));
+ String displayName = tpl.getString(getTemplateIndex(template, "display_name"));
+ model.setDisplayName(displayName.replaceAll("[^a-zA-Z0-9]", ""));
+ if (model.getDisplayName().isBlank()) {
+ model.setDisplayName(name);
+ }
+ models.add(model);
}
+ } catch (Exception e) {
+ LOG.warn("Couldn't parse one of the models: {}", result, e);
}
- return models;
- } else {
- LOG.debug("Response was not successful: {}", json);
- return Collections.emptyList();
}
+ return models;
} else {
throw new HttpException(response.code(), response.message());
}
diff --git a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java
index e980eb78..6266f8e1 100644
--- a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java
+++ b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java
@@ -47,6 +47,7 @@ public class CamsodaModel extends AbstractModel {
private static final Logger LOG = LoggerFactory.getLogger(CamsodaModel.class);
private transient List streamSources = null;
private transient boolean isNew;
+ private transient String gender;
private float sortOrder = 0;
private Random random = new Random();
@@ -344,4 +345,12 @@ public class CamsodaModel extends AbstractModel {
public void setNew(boolean isNew) {
this.isNew = isNew;
}
+
+ public String getGender() {
+ return gender;
+ }
+
+ public void setGender(String gender) {
+ this.gender = gender;
+ }
}
From 372fcd0d135567297c5f32a906f701bda2e87d09 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Tue, 15 Dec 2020 18:37:04 +0100
Subject: [PATCH 62/80] Don't show dialog, if no model account has been deleted
---
.../ui/action/CheckModelAccountAction.java | 39 ++++++++++---------
1 file changed, 20 insertions(+), 19 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java b/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
index 6dfd9c4b..7bdf7cea 100644
--- a/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
+++ b/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
@@ -56,25 +56,26 @@ public class CheckModelAccountAction {
}
}
} finally {
- Platform.runLater(() -> {
- b.setDisable(false);
- b.setText(buttonText);
- StringBuilder sb = new StringBuilder();
- for (Model deletedModel : deletedAccounts) {
- String name = deletedModel.getDisplayName() + " ".repeat(30);
- name = name.substring(0, 30);
- sb.append(name)
- .append(' ').append('(')
- .append(deletedModel.getUrl())
- .append(')').append('\n');
- }
-
- boolean remove = Dialogs.showConfirmDialog("Deleted Accounts", sb.toString(),
- "The following accounts seem to have been deleted. Do you want to remove them?", b.getScene());
- if (remove) {
- new StopRecordingAction(b, deletedAccounts, recorder).execute();
- }
- });
+ if (!deletedAccounts.isEmpty()) {
+ Platform.runLater(() -> {
+ b.setDisable(false);
+ b.setText(buttonText);
+ StringBuilder sb = new StringBuilder();
+ for (Model deletedModel : deletedAccounts) {
+ String name = deletedModel.getDisplayName() + " ".repeat(30);
+ name = name.substring(0, 30);
+ sb.append(name)
+ .append(' ').append('(')
+ .append(deletedModel.getUrl())
+ .append(')').append('\n');
+ }
+ boolean remove = Dialogs.showConfirmDialog("Deleted Accounts", sb.toString(),
+ "The following accounts seem to have been deleted. Do you want to remove them?", b.getScene());
+ if (remove) {
+ new StopRecordingAction(b, deletedAccounts, recorder).execute();
+ }
+ });
+ }
}
});
new Thread(checker).start();
From 04ee553c1a62d7efef1d74924385b99b6d6d4fba Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Tue, 15 Dec 2020 19:31:29 +0100
Subject: [PATCH 63/80] Set version to 3.10.9
---
client/pom.xml | 2 +-
common/pom.xml | 2 +-
master/pom.xml | 2 +-
server/pom.xml | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/client/pom.xml b/client/pom.xml
index 43018917..80ea9c06 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.8
+ 3.10.9
../master
diff --git a/common/pom.xml b/common/pom.xml
index 1a761180..b1e2900b 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.8
+ 3.10.9
../master
diff --git a/master/pom.xml b/master/pom.xml
index 39fb3903..98370118 100644
--- a/master/pom.xml
+++ b/master/pom.xml
@@ -6,7 +6,7 @@
ctbrec
master
pom
- 3.10.8
+ 3.10.9
../common
diff --git a/server/pom.xml b/server/pom.xml
index 5afe6b5e..e1da2b71 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.8
+ 3.10.9
../master
From 809d86f5a2bafaa432843b65304e213a40ff5d1b Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Thu, 17 Dec 2020 18:49:08 +0100
Subject: [PATCH 64/80] Extend regex to support more model URLs
---
.../java/ctbrec/sites/manyvids/MVLive.java | 29 ++++++------
.../ctbrec/sites/manyvids/MVLiveClient.java | 45 ++++++++++++-------
2 files changed, 44 insertions(+), 30 deletions(-)
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
index a1bbbfc1..0532d0ed 100644
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
+++ b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
@@ -1,5 +1,19 @@
package ctbrec.sites.manyvids;
+import static ctbrec.io.HttpConstants.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Model.State;
@@ -11,19 +25,6 @@ import okhttp3.FormBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.jsoup.nodes.Element;
-import org.jsoup.select.Elements;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static ctbrec.io.HttpConstants.*;
public class MVLive extends AbstractSite {
@@ -243,7 +244,7 @@ public class MVLive extends AbstractSite {
@Override
public Model createModelFromUrl(String url) {
- Matcher m = Pattern.compile("https://live.manyvids.com/stream/(.*?)(?:/.*?)?").matcher(url.trim());
+ Matcher m = Pattern.compile("https://live.manyvids.com/(?:stream/)?(.*?)(?:/.*?)?").matcher(url.trim());
if(m.matches()) {
return createModel(m.group(1));
}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java
index 1793154f..06cb5dc5 100644
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java
+++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java
@@ -1,26 +1,39 @@
package ctbrec.sites.manyvids;
-import com.google.common.base.Objects;
-import ctbrec.Config;
-import ctbrec.sites.manyvids.wsmsg.*;
-import okhttp3.Response;
-import okhttp3.*;
-import okio.ByteString;
+import static ctbrec.StringUtil.*;
+import static ctbrec.io.HttpConstants.*;
+import static ctbrec.sites.manyvids.MVLive.*;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
-import java.util.*;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
+import com.google.common.base.Objects;
-import static ctbrec.StringUtil.isNotBlank;
-import static ctbrec.io.HttpConstants.*;
-import static ctbrec.sites.manyvids.MVLive.WS_ORIGIN;
-import static ctbrec.sites.manyvids.MVLive.WS_URL;
+import ctbrec.Config;
+import ctbrec.sites.manyvids.wsmsg.GetBroadcastHealth;
+import ctbrec.sites.manyvids.wsmsg.Message;
+import ctbrec.sites.manyvids.wsmsg.Ping;
+import ctbrec.sites.manyvids.wsmsg.RegisterMessage;
+import ctbrec.sites.manyvids.wsmsg.SendMessage;
+import okhttp3.Cookie;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.WebSocket;
+import okhttp3.WebSocketListener;
+import okio.ByteString;
public class MVLiveClient {
@@ -67,7 +80,7 @@ public class MVLiveClient {
public void stop() {
running = false;
- scheduler.shutdown();
+ Optional.ofNullable(scheduler).ifPresent(ScheduledExecutorService::shutdown);
ws.close(1000, "Good Bye"); // terminate normally (1000)
ws = null;
}
From 98d9ae53776c247aa4ccba47a4787e852140c78f Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Thu, 17 Dec 2020 18:53:31 +0100
Subject: [PATCH 65/80] Add MVLive to autocomplete list in web interface
---
server/src/main/resources/html/static/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/src/main/resources/html/static/index.html b/server/src/main/resources/html/static/index.html
index e9cc978b..c94a29f9 100644
--- a/server/src/main/resources/html/static/index.html
+++ b/server/src/main/resources/html/static/index.html
@@ -273,7 +273,7 @@
});
} else {
$('#addModelByUrl').autocomplete({
- source: ["BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "Fc2Live:", "Flirt4Free:", "LiveJasmin:", "MyFreeCams:", "Showup:", "Streamate:", "Stripchat:"]
+ source: ["BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "Fc2Live:", "Flirt4Free:", "LiveJasmin:", "MyFreeCams:", "MVLive:", "Showup:", "Streamate:", "Stripchat:"]
});
}
}
From f730f950263282bb559a6f79babd782d784bec5a Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Thu, 17 Dec 2020 20:37:09 +0100
Subject: [PATCH 66/80] Also use the model page to detect the online state
---
CHANGELOG.md | 1 +
.../java/ctbrec/sites/cam4/Cam4Model.java | 48 +++++++++++++++----
2 files changed, 41 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 226f2eee..f6a96470 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
* Added more category tabs for CamSoda
* Added button to the "Recording" tab to go over all model URLs and check, if
the account still exists
+* Fix: some Cam4 models were not detected as online
3.10.8
========================
diff --git a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
index 239b5869..174cbc53 100644
--- a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
+++ b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
@@ -2,6 +2,7 @@ package ctbrec.sites.cam4;
import static ctbrec.Model.State.*;
import static ctbrec.io.HttpConstants.*;
+import static java.util.regex.Pattern.*;
import java.io.IOException;
import java.io.InputStream;
@@ -10,6 +11,8 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -51,7 +54,15 @@ public class Cam4Model extends AbstractModel {
try {
loadModelDetails();
} catch (ModelDetailsEmptyException e) {
- return false;
+ // nothing to do, keep going
+ }
+ if (playlistUrl == null || onlineState == OFFLINE) {
+ try {
+ getPlaylistUrl();
+ onlineState = ONLINE;
+ } catch (IOException e) {
+ return false;
+ }
}
}
return onlineState == ONLINE && !privateRoom && playlistUrl != null && !playlistUrl.isEmpty();
@@ -126,19 +137,40 @@ public class Cam4Model extends AbstractModel {
}
private String getPlaylistUrl() throws IOException {
- if(playlistUrl == null || playlistUrl.trim().isEmpty()) {
- try {
- loadModelDetails();
- if (playlistUrl == null) {
- throw new IOException("Couldn't determine playlist url");
+ if (playlistUrl == null || playlistUrl.trim().isEmpty()) {
+ String page = loadModelPage();
+ Matcher m = Pattern.compile("hlsUrl\\s*:\\s*'(.*?)'", DOTALL | MULTILINE).matcher(page);
+ if (m.find()) {
+ playlistUrl = m.group(1);
+ } else {
+ m = Pattern.compile("\"videoPlayUrl\"\\s*:\\s*\"(.*?)\"", DOTALL | MULTILINE).matcher(page);
+ if (m.find()) {
+ String streamName = m.group(1);
+ m = Pattern.compile(".*?-(\\d{3,})-.*?").matcher(streamName);
+ if (m.find()) {
+ String number = m.group(1);
+ playlistUrl = "https://cam4-hls.xcdnpro.com/" + number + "/cam4-origin-live/ngrp:" + streamName + "_all/playlist.m3u8";
+ }
}
- } catch (ModelDetailsEmptyException e) {
- throw new IOException(e);
+ }
+ if (playlistUrl == null) {
+ throw new IOException("Couldn't determine playlist url");
}
}
return playlistUrl;
}
+ private String loadModelPage() throws IOException {
+ Request req = new Request.Builder().url(getUrl()).build();
+ try (Response response = site.getHttpClient().execute(req)) {
+ if (response.isSuccessful()) {
+ return response.body().string();
+ } else {
+ throw new HttpException(response.code(), response.message());
+ }
+ }
+ }
+
@Override
public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
MasterPlaylist masterPlaylist = getMasterPlaylist();
From e0537650c913402350f23fc684775d13c6ff4701 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Fri, 18 Dec 2020 19:46:41 +0100
Subject: [PATCH 67/80] Fix: button for model account check stays inactive
---
.../ui/action/CheckModelAccountAction.java | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java b/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
index 7bdf7cea..f778a30e 100644
--- a/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
+++ b/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
@@ -56,26 +56,23 @@ public class CheckModelAccountAction {
}
}
} finally {
- if (!deletedAccounts.isEmpty()) {
- Platform.runLater(() -> {
- b.setDisable(false);
- b.setText(buttonText);
+ Platform.runLater(() -> {
+ b.setDisable(false);
+ b.setText(buttonText);
+ if (!deletedAccounts.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (Model deletedModel : deletedAccounts) {
String name = deletedModel.getDisplayName() + " ".repeat(30);
name = name.substring(0, 30);
- sb.append(name)
- .append(' ').append('(')
- .append(deletedModel.getUrl())
- .append(')').append('\n');
+ sb.append(name).append(' ').append('(').append(deletedModel.getUrl()).append(')').append('\n');
}
boolean remove = Dialogs.showConfirmDialog("Deleted Accounts", sb.toString(),
"The following accounts seem to have been deleted. Do you want to remove them?", b.getScene());
if (remove) {
new StopRecordingAction(b, deletedAccounts, recorder).execute();
}
- });
- }
+ }
+ });
}
});
new Thread(checker).start();
From dbc4e1eae4e16052c8dbe0e922e8ff55bce36e26 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 15:47:20 +0100
Subject: [PATCH 68/80] Switch order of submodules
---
master/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/master/pom.xml b/master/pom.xml
index 98370118..7f948ea0 100644
--- a/master/pom.xml
+++ b/master/pom.xml
@@ -10,8 +10,8 @@
../common
- ../client
../server
+ ../client
From 49469d8987fa811894e60437a478c8fa80f50559 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 15:47:44 +0100
Subject: [PATCH 69/80] Determine online state of Cam4 models through the chat
websocket
---
.../ctbrec/sites/ModelOfflineException.java | 10 +
.../java/ctbrec/sites/cam4/Cam4Model.java | 116 +++++-----
.../java/ctbrec/sites/cam4/Cam4WsClient.java | 202 ++++++++++++++++++
3 files changed, 268 insertions(+), 60 deletions(-)
create mode 100644 common/src/main/java/ctbrec/sites/ModelOfflineException.java
create mode 100644 common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java
diff --git a/common/src/main/java/ctbrec/sites/ModelOfflineException.java b/common/src/main/java/ctbrec/sites/ModelOfflineException.java
new file mode 100644
index 00000000..f7539caf
--- /dev/null
+++ b/common/src/main/java/ctbrec/sites/ModelOfflineException.java
@@ -0,0 +1,10 @@
+package ctbrec.sites;
+
+import ctbrec.Model;
+
+public class ModelOfflineException extends RuntimeException {
+
+ public ModelOfflineException(Model model) {
+ super("Model " + model + " is offline");
+ }
+}
diff --git a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
index 174cbc53..953cb2ac 100644
--- a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
+++ b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
@@ -6,15 +6,18 @@ import static java.util.regex.Pattern.*;
import java.io.IOException;
import java.io.InputStream;
+import java.time.Duration;
+import java.time.Instant;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.json.JSONArray;
import org.json.JSONObject;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
@@ -44,6 +47,7 @@ import okhttp3.Response;
public class Cam4Model extends AbstractModel {
private static final Logger LOG = LoggerFactory.getLogger(Cam4Model.class);
+ private transient Instant playlistRequestTimestamp = Instant.EPOCH;
private String playlistUrl;
private int[] resolution = null;
private boolean privateRoom = false;
@@ -53,58 +57,34 @@ public class Cam4Model extends AbstractModel {
if (ignoreCache || onlineState == UNKNOWN) {
try {
loadModelDetails();
- } catch (ModelDetailsEmptyException e) {
- // nothing to do, keep going
- }
- if (playlistUrl == null || onlineState == OFFLINE) {
- try {
- getPlaylistUrl();
- onlineState = ONLINE;
- } catch (IOException e) {
- return false;
- }
+ getPlaylistUrl();
+ } catch (Exception e) {
+ onlineState = OFFLINE;
}
}
return onlineState == ONLINE && !privateRoom && playlistUrl != null && !playlistUrl.isEmpty();
}
- private void loadModelDetails() throws IOException, ModelDetailsEmptyException {
- String url = site.getBaseUrl() + "/directoryCams?directoryJson=true&online=true&username=" + getName();
- LOG.trace("Loading model details {}", url);
- Request req = new Request.Builder().url(url).build();
- try (Response response = site.getHttpClient().execute(req)) {
- if (response.isSuccessful()) {
- JSONArray json = new JSONArray(response.body().string());
- if (json.length() == 0) {
- onlineState = OFFLINE;
- throw new ModelDetailsEmptyException("Model details are empty");
- }
- JSONObject details = json.getJSONObject(0);
- String showType = details.getString("showType");
- setOnlineStateByShowType(showType);
- playlistUrl = details.getString("hlsPreviewUrl");
- privateRoom = details.getBoolean("privateRoom");
- if (privateRoom) {
- onlineState = PRIVATE;
- }
- if (details.has("resolution")) {
- String res = details.getString("resolution");
- String[] tokens = res.split(":");
- resolution = new int[] { Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]) };
- }
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
+ private void loadModelDetails() throws IOException {
+ JSONObject roomState = new Cam4WsClient(Config.getInstance(), (Cam4)getSite(), this).getRoomState();
+ if(LOG.isTraceEnabled()) LOG.trace(roomState.toString(2));
+ String state = roomState.optString("newShowsState");
+ setOnlineStateByShowType(state);
+ privateRoom = roomState.optBoolean("privateRoom");
+ setDescription(roomState.optString("status"));
}
public void setOnlineStateByShowType(String showType) {
switch(showType) {
case "NORMAL":
+ case "ACCEPTING":
case "GROUP_SHOW_SELLING_TICKETS":
+ case "GS_SELLING_TICKETS":
+ case "GS_SELLING_TICKETS_UNSUCCESSFUL":
onlineState = ONLINE;
break;
case "PRIVATE_SHOW":
+ case "INSIDE_PS":
onlineState = PRIVATE;
break;
case "GROUP_SHOW":
@@ -114,7 +94,7 @@ public class Cam4Model extends AbstractModel {
onlineState = OFFLINE;
break;
default:
- LOG.debug("Unknown show type [{}]", showType);
+ LOG.debug("############################## Unknown show type [{}]", showType);
onlineState = UNKNOWN;
}
@@ -128,7 +108,7 @@ public class Cam4Model extends AbstractModel {
if(onlineState == UNKNOWN) {
try {
loadModelDetails();
- } catch (ModelDetailsEmptyException e) {
+ } catch (Exception e) {
LOG.warn("Couldn't load model details {}", e.getMessage());
}
}
@@ -137,7 +117,7 @@ public class Cam4Model extends AbstractModel {
}
private String getPlaylistUrl() throws IOException {
- if (playlistUrl == null || playlistUrl.trim().isEmpty()) {
+ if (playlistUrl == null || playlistUrl.trim().isEmpty() || playlistIsOutdated()) {
String page = loadModelPage();
Matcher m = Pattern.compile("hlsUrl\\s*:\\s*'(.*?)'", DOTALL | MULTILINE).matcher(page);
if (m.find()) {
@@ -145,21 +125,29 @@ public class Cam4Model extends AbstractModel {
} else {
m = Pattern.compile("\"videoPlayUrl\"\\s*:\\s*\"(.*?)\"", DOTALL | MULTILINE).matcher(page);
if (m.find()) {
- String streamName = m.group(1);
- m = Pattern.compile(".*?-(\\d{3,})-.*?").matcher(streamName);
- if (m.find()) {
- String number = m.group(1);
- playlistUrl = "https://cam4-hls.xcdnpro.com/" + number + "/cam4-origin-live/ngrp:" + streamName + "_all/playlist.m3u8";
- }
+ generatePlaylistUrlFromStreamName(m.group(1));
}
}
if (playlistUrl == null) {
throw new IOException("Couldn't determine playlist url");
}
+ playlistRequestTimestamp = Instant.now();
}
return playlistUrl;
}
+ private boolean playlistIsOutdated() {
+ return Duration.between(playlistRequestTimestamp, Instant.now()).getSeconds() > TimeUnit.MINUTES.toSeconds(2);
+ }
+
+ private void generatePlaylistUrlFromStreamName(String streamName) {
+ Matcher m = Pattern.compile(".*?-(\\d{3,})-.*").matcher(streamName);
+ if (m.find()) {
+ String number = m.group(1);
+ playlistUrl = "https://cam4-hls.xcdnpro.com/" + number + "/cam4-origin-live/ngrp:" + streamName + "_all/playlist.m3u8";
+ }
+ }
+
private String loadModelPage() throws IOException {
Request req = new Request.Builder().url(getUrl()).build();
try (Response response = site.getHttpClient().execute(req)) {
@@ -192,7 +180,7 @@ public class Cam4Model extends AbstractModel {
}
private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException {
- LOG.debug("Loading master playlist [{}]", getPlaylistUrl());
+ LOG.trace("Loading master playlist [{}]", getPlaylistUrl());
Request req = new Request.Builder().url(getPlaylistUrl()).build();
try (Response response = site.getHttpClient().execute(req)) {
@@ -224,19 +212,27 @@ public class Cam4Model extends AbstractModel {
if(resolution == null) {
if(failFast) {
return new int[2];
- } else {
- try {
- if(onlineState != OFFLINE) {
- loadModelDetails();
- } else {
- resolution = new int[2];
- }
- } catch (Exception e) {
- throw new ExecutionException(e);
- }
}
+ try {
+ if(!isOnline()) {
+ return new int[2];
+ }
+ List sources = getStreamSources();
+ Collections.sort(sources);
+ StreamSource best = sources.get(sources.size()-1);
+ resolution = new int[] {best.width, best.height};
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ LOG.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage());
+ resolution = new int[2];
+ } catch (ExecutionException | IOException | ParseException | PlaylistException e) {
+ LOG.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage());
+ resolution = new int[2];
+ }
+ return resolution;
+ } else {
+ return resolution;
}
- return resolution;
}
@Override
diff --git a/common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java b/common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java
new file mode 100644
index 00000000..dbe854b5
--- /dev/null
+++ b/common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java
@@ -0,0 +1,202 @@
+package ctbrec.sites.cam4;
+
+import static ctbrec.io.HttpConstants.*;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ctbrec.Config;
+import ctbrec.io.HttpException;
+import ctbrec.sites.ModelOfflineException;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.WebSocket;
+import okhttp3.WebSocketListener;
+import okio.ByteString;
+
+public class Cam4WsClient {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Cam4WsClient.class);
+
+ private Cam4 site;
+ private Cam4Model model;
+ private Config config;
+ private String shard;
+ private String token;
+ private WebSocket websocket;
+ private int r = 1;
+ private Map> responseFutures = new HashMap<>();
+
+ public Cam4WsClient(Config config, Cam4 site, Cam4Model model) {
+ this.config = config;
+ this.site = site;
+ this.model = model;
+ }
+
+ public JSONObject getRoomState() throws IOException {
+ requestAccessToken();
+ if (connectAndAuthorize()) {
+ return requestRoomState();
+ } else {
+ throw new IOException("Connect or authorize failed");
+ }
+ }
+
+ private JSONObject requestRoomState() throws IOException {
+ String p = "chatRooms/" + model.getName() + "/roomState";
+ CompletableFuture roomStateFuture = send(p, "{\"t\":\"d\",\"d\":{\"r\":" + (r++) + ",\"a\":\"q\",\"b\":{\"p\":\"" + p + "\",\"h\":\"\"}}}");
+ try {
+ JSONObject roomState = parseRoomStateResponse(roomStateFuture.get(5000, TimeUnit.SECONDS));
+ websocket.close(1000, "");
+ return roomState;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IOException("Interrupted while getting room state with websocket");
+ } catch (TimeoutException | ExecutionException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private boolean connectAndAuthorize() throws IOException {
+ CompletableFuture connectedAndAuthorized = openWebsocketConnection();
+ try {
+ return connectedAndAuthorized.get(5000, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IOException("Interrupted while connecting with websocket");
+ } catch (TimeoutException | ExecutionException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private CompletableFuture send(String p, String msg) {
+ CompletableFuture future = new CompletableFuture<>();
+ LOG.trace("--> {}", msg);
+ boolean sent = websocket.send(msg);
+ if (!sent) {
+ future.completeExceptionally(new IOException("send() returned false"));
+ } else {
+ responseFutures.put(p, future);
+ }
+ return future;
+ }
+
+ private void requestAccessToken() throws IOException {
+ Request req = new Request.Builder() // @formatter:off
+ .url("https://webchat.cam4.com/requestAccess?roomname=" + model.getName())
+ .header(USER_AGENT, config.getSettings().httpUserAgent)
+ .header(REFERER, Cam4.BASE_URI + '/' + model.getName())
+ .header(ORIGIN, Cam4.BASE_URI)
+ .header(ACCEPT, "*/*")
+ .build(); // @formatter:on
+ try (Response response = site.getHttpClient().execute(req)) {
+ if (response.isSuccessful()) {
+ JSONObject body = new JSONObject(response.body().string());
+ if (body.optString("status").equals("success")) {
+ shard = body.getString("shard").replace("https", "wss");
+ token = body.getString("token");
+ } else {
+ throw new ModelOfflineException(model);
+ }
+ } else {
+ throw new HttpException(response.code(), response.message());
+ }
+ }
+ }
+
+ private JSONObject parseRoomStateResponse(String msg) {
+ JSONObject json = new JSONObject(msg);
+ JSONObject d = json.getJSONObject("d");
+ JSONObject b = d.getJSONObject("b");
+ return b.getJSONObject("d");
+ }
+
+ private CompletableFuture openWebsocketConnection() {
+ CompletableFuture connectedAndAuthorized = new CompletableFuture<>();
+
+ String url = shard + ".ws?v=5";
+ LOG.trace("Opening websocket {}", url);
+ Request req = new Request.Builder() // @formatter:off
+ .url(url)
+ .header(USER_AGENT, config.getSettings().httpUserAgent)
+ .header(REFERER, Cam4.BASE_URI + '/' + model.getName())
+ .header(ORIGIN, Cam4.BASE_URI)
+ .header(ACCEPT, "*/*")
+ .build(); // @formatter:on
+
+ websocket = site.getHttpClient().newWebSocket(req, new WebSocketListener() {
+ @Override
+ public void onOpen(WebSocket webSocket, Response response) {
+ super.onOpen(webSocket, response);
+ try {
+ LOG.trace("open: {}", response.body().string());
+ } catch (IOException e) {
+ LOG.error("Connection open, but couldn't get the response body", e);
+ }
+ send("", "{\"t\":\"d\",\"d\":{\"r\":" + (r++) + ",\"a\":\"s\",\"b\":{\"c\":{\"sdk.js.2-3-1\":1}}}}");
+ send("", "{\"t\":\"d\",\"d\":{\"r\":" + (r++) + ",\"a\":\"auth\",\"b\":{\"cred\":\"" + token + "\"}}}");
+ }
+
+ @Override
+ public void onClosed(WebSocket webSocket, int code, String reason) {
+ super.onClosed(webSocket, code, reason);
+ LOG.trace("closed: {} {}", code, reason);
+ connectedAndAuthorized.complete(false);
+ }
+
+ @Override
+ public void onFailure(WebSocket webSocket, Throwable t, Response response) {
+ super.onFailure(webSocket, t, response);
+ try {
+ if(response != null) {
+ LOG.error("failure: {}", response.body().string(), t);
+ } else {
+ LOG.error("failure:", t);
+ }
+ } catch (IOException e) {
+ LOG.error("Connection failure and couldn't get the response body", e);
+ }
+ connectedAndAuthorized.completeExceptionally(t);
+ }
+
+ @Override
+ public void onMessage(WebSocket webSocket, String text) {
+ super.onMessage(webSocket, text);
+ LOG.trace("msgt: {}", text);
+ JSONObject response = new JSONObject(text);
+ if (response.has("d")) {
+ JSONObject d = response.getJSONObject("d");
+ int responseSequence = d.optInt("r");
+ if (responseSequence == 2) {
+ JSONObject body = d.getJSONObject("b");
+ String status = body.optString("s");
+ connectedAndAuthorized.complete(status.equals("ok"));
+ } else if (d.has("b")) {
+ JSONObject body = d.getJSONObject("b");
+ String p = body.optString("p", "-");
+ if (responseFutures.containsKey(p)) {
+ CompletableFuture future = responseFutures.get(p);
+ future.complete(text);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onMessage(WebSocket webSocket, ByteString bytes) {
+ super.onMessage(webSocket, bytes);
+ LOG.trace("msgb: {}", bytes.hex());
+ }
+ });
+ return connectedAndAuthorized;
+ }
+}
From ae3726d90654ccb49fb67d36b3a6d8eb5d4e7bb5 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 15:50:24 +0100
Subject: [PATCH 70/80] Improve logging
---
common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java b/common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java
index dbe854b5..81a5c9a7 100644
--- a/common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java
+++ b/common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java
@@ -158,9 +158,9 @@ public class Cam4WsClient {
super.onFailure(webSocket, t, response);
try {
if(response != null) {
- LOG.error("failure: {}", response.body().string(), t);
+ LOG.error("failure {}: {}", model, response.body().string(), t);
} else {
- LOG.error("failure:", t);
+ LOG.error("failure {}:", model, t);
}
} catch (IOException e) {
LOG.error("Connection failure and couldn't get the response body", e);
From c081f95d439d40fbbb321fd79dcc9b6c5fc46d4a Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 17:40:53 +0100
Subject: [PATCH 71/80] Fix potential ArrayOutOfBoundsException
---
.../main/java/ctbrec/ui/tabs/logging/LoggingTab.java | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/tabs/logging/LoggingTab.java b/client/src/main/java/ctbrec/ui/tabs/logging/LoggingTab.java
index 932929b7..c52b9aa8 100644
--- a/client/src/main/java/ctbrec/ui/tabs/logging/LoggingTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/logging/LoggingTab.java
@@ -90,9 +90,13 @@ public class LoggingTab extends Tab {
TableColumn location = createTableColumn("Location", 250, idx++);
location.setCellValueFactory(cdf -> {
- StackTraceElement loc = cdf.getValue().getCallerData()[0];
- String l = loc.getFileName() + ":" + loc.getLineNumber();
- return new SimpleStringProperty(l);
+ if(cdf.getValue().getCallerData().length > 0) {
+ StackTraceElement loc = cdf.getValue().getCallerData()[0];
+ String l = loc.getFileName() + ":" + loc.getLineNumber();
+ return new SimpleStringProperty(l);
+ } else {
+ return new SimpleStringProperty("");
+ }
});
table.getColumns().add(location);
From 3d076cdde6ac60b640af5163a81a001b71846068 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 17:41:03 +0100
Subject: [PATCH 72/80] Improve logging
---
.../main/java/ctbrec/recorder/download/hls/HlsDownload.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java
index 8130178b..9a26ae68 100644
--- a/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java
@@ -107,9 +107,9 @@ public class HlsDownload extends AbstractHlsDownload {
throw new IOException("Couldn't determine segments uri");
}
} catch (ParseException e) {
- throw new IOException("Couldn't parse HLS playlist:\n" + e.getInput(), e);
+ throw new IOException("Couldn't parse HLS playlist for model " + model + "\n" + e.getInput(), e);
} catch (PlaylistException e) {
- throw new IOException("Couldn't parse HLS playlist", e);
+ throw new IOException("Couldn't parse HLS playlist for model " + model, e);
} catch (EOFException e) {
// end of playlist reached
LOG.debug("Reached end of playlist for model {}", model);
From a3ffa7a71e5128793c879f969ef2225d17acf93a Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 17:41:44 +0100
Subject: [PATCH 73/80] Improve error handling and set timeouts in the Cam4
websocket
---
.../java/ctbrec/sites/cam4/Cam4Model.java | 4 +++
.../java/ctbrec/sites/cam4/Cam4WsClient.java | 27 ++++++++++++++-----
2 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
index 953cb2ac..f84f242d 100644
--- a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
+++ b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
@@ -87,9 +87,13 @@ public class Cam4Model extends AbstractModel {
case "INSIDE_PS":
onlineState = PRIVATE;
break;
+ case "INSIDE_GS":
case "GROUP_SHOW":
onlineState = GROUP;
break;
+ case "PAUSED":
+ onlineState = AWAY;
+ break;
case "OFFLINE":
onlineState = OFFLINE;
break;
diff --git a/common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java b/common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java
index 81a5c9a7..e5a9557b 100644
--- a/common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java
+++ b/common/src/main/java/ctbrec/sites/cam4/Cam4WsClient.java
@@ -2,6 +2,7 @@ package ctbrec.sites.cam4;
import static ctbrec.io.HttpConstants.*;
+import java.io.EOFException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@@ -34,7 +35,8 @@ public class Cam4WsClient {
private String token;
private WebSocket websocket;
private int r = 1;
- private Map> responseFutures = new HashMap<>();
+ private Map> responseFuturesByPath = new HashMap<>();
+ private Map> responseFuturesBySequence = new HashMap<>();
public Cam4WsClient(Config config, Cam4 site, Cam4Model model) {
this.config = config;
@@ -55,7 +57,7 @@ public class Cam4WsClient {
String p = "chatRooms/" + model.getName() + "/roomState";
CompletableFuture roomStateFuture = send(p, "{\"t\":\"d\",\"d\":{\"r\":" + (r++) + ",\"a\":\"q\",\"b\":{\"p\":\"" + p + "\",\"h\":\"\"}}}");
try {
- JSONObject roomState = parseRoomStateResponse(roomStateFuture.get(5000, TimeUnit.SECONDS));
+ JSONObject roomState = parseRoomStateResponse(roomStateFuture.get(1, TimeUnit.SECONDS));
websocket.close(1000, "");
return roomState;
} catch (InterruptedException e) {
@@ -69,7 +71,7 @@ public class Cam4WsClient {
private boolean connectAndAuthorize() throws IOException {
CompletableFuture connectedAndAuthorized = openWebsocketConnection();
try {
- return connectedAndAuthorized.get(5000, TimeUnit.SECONDS);
+ return connectedAndAuthorized.get(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while connecting with websocket");
@@ -85,7 +87,7 @@ public class Cam4WsClient {
if (!sent) {
future.completeExceptionally(new IOException("send() returned false"));
} else {
- responseFutures.put(p, future);
+ responseFuturesByPath.put(p, future);
}
return future;
}
@@ -157,6 +159,9 @@ public class Cam4WsClient {
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
try {
+ if (t instanceof EOFException) {
+ return;
+ }
if(response != null) {
LOG.error("failure {}: {}", model, response.body().string(), t);
} else {
@@ -164,8 +169,9 @@ public class Cam4WsClient {
}
} catch (IOException e) {
LOG.error("Connection failure and couldn't get the response body", e);
+ } finally {
+ connectedAndAuthorized.completeExceptionally(t);
}
- connectedAndAuthorized.completeExceptionally(t);
}
@Override
@@ -180,11 +186,18 @@ public class Cam4WsClient {
JSONObject body = d.getJSONObject("b");
String status = body.optString("s");
connectedAndAuthorized.complete(status.equals("ok"));
+ } else if (responseFuturesBySequence.containsKey(responseSequence)) {
+ JSONObject body = d.getJSONObject("b");
+ String status = body.optString("s");
+ if (!status.equals("ok")) {
+ CompletableFuture future = responseFuturesBySequence.remove(responseSequence);
+ future.completeExceptionally(new IOException(status));
+ }
} else if (d.has("b")) {
JSONObject body = d.getJSONObject("b");
String p = body.optString("p", "-");
- if (responseFutures.containsKey(p)) {
- CompletableFuture future = responseFutures.get(p);
+ if (responseFuturesByPath.containsKey(p)) {
+ CompletableFuture future = responseFuturesByPath.remove(p);
future.complete(text);
}
}
From b5640d932fe217075297dce6809d8bd9ee9806cb Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 17:46:15 +0100
Subject: [PATCH 74/80] Set version to 3.10.10
---
client/pom.xml | 2 +-
common/pom.xml | 2 +-
master/pom.xml | 2 +-
server/pom.xml | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/client/pom.xml b/client/pom.xml
index 80ea9c06..a41574e1 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.9
+ 3.10.10
../master
diff --git a/common/pom.xml b/common/pom.xml
index b1e2900b..a9a87322 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.9
+ 3.10.10
../master
diff --git a/master/pom.xml b/master/pom.xml
index 7f948ea0..08bd71b8 100644
--- a/master/pom.xml
+++ b/master/pom.xml
@@ -6,7 +6,7 @@
ctbrec
master
pom
- 3.10.9
+ 3.10.10
../common
diff --git a/server/pom.xml b/server/pom.xml
index e1da2b71..e14eaa12 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.10.9
+ 3.10.10
../master
From fb58be47bba8feeb8c81fac72990c1fb1756f3b3 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 18:06:55 +0100
Subject: [PATCH 75/80] Normalize Cam4 model URLs in setUrl
---
common/src/main/java/ctbrec/sites/cam4/Cam4Model.java | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
index f84f242d..93355df9 100644
--- a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
+++ b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
@@ -308,4 +308,13 @@ public class Cam4Model extends AbstractModel {
super(msg);
}
}
+
+ @Override
+ public void setUrl(String url) {
+ String normalizedUrl = url.toLowerCase();
+ if (normalizedUrl.endsWith("/")) {
+ normalizedUrl = normalizedUrl.substring(0, normalizedUrl.length() - 1);
+ }
+ super.setUrl(normalizedUrl);
+ }
}
From c9cd6e825dad3cac47694d8944cedbf2b7ff8923 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 18:07:30 +0100
Subject: [PATCH 76/80] Add Model.exists to check, if a model account exists
---
.../src/main/java/ctbrec/ui/JavaFxModel.java | 5 +++++
.../ui/action/CheckModelAccountAction.java | 13 ++-----------
common/src/main/java/ctbrec/AbstractModel.java | 18 ++++++++++++++++++
common/src/main/java/ctbrec/Model.java | 7 +++++++
4 files changed, 32 insertions(+), 11 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/JavaFxModel.java b/client/src/main/java/ctbrec/ui/JavaFxModel.java
index 21885b73..3902f842 100644
--- a/client/src/main/java/ctbrec/ui/JavaFxModel.java
+++ b/client/src/main/java/ctbrec/ui/JavaFxModel.java
@@ -307,4 +307,9 @@ public class JavaFxModel implements Model {
public void setRecordUntilSubsequentAction(SubsequentAction action) {
delegate.setRecordUntilSubsequentAction(action);
}
+
+ @Override
+ public boolean exists() throws IOException {
+ return delegate.exists();
+ }
}
diff --git a/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java b/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
index f778a30e..e9f3b188 100644
--- a/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
+++ b/client/src/main/java/ctbrec/ui/action/CheckModelAccountAction.java
@@ -1,7 +1,5 @@
package ctbrec.ui.action;
-import static ctbrec.io.HttpConstants.*;
-
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -9,14 +7,11 @@ import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import ctbrec.Config;
import ctbrec.Model;
import ctbrec.recorder.Recorder;
import ctbrec.ui.controls.Dialogs;
import javafx.application.Platform;
import javafx.scene.control.Button;
-import okhttp3.Request;
-import okhttp3.Response;
public class CheckModelAccountAction {
private static final Logger LOG = LoggerFactory.getLogger(CheckModelAccountAction.class);
@@ -43,12 +38,8 @@ public class CheckModelAccountAction {
final int counter = i+1;
Platform.runLater(() -> b.setText(buttonText + ' ' + counter + '/' + total));
Model modelToCheck = models.get(i);
- Request req = new Request.Builder()
- .url(modelToCheck.getUrl())
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .build();
- try(Response response = modelToCheck.getSite().getHttpClient().execute(req)) {
- if(!response.isSuccessful() && response.code() == 404) {
+ try {
+ if (!modelToCheck.exists()) {
deletedAccounts.add(modelToCheck);
}
} catch (IOException e) {
diff --git a/common/src/main/java/ctbrec/AbstractModel.java b/common/src/main/java/ctbrec/AbstractModel.java
index 974e8380..8a3dac36 100644
--- a/common/src/main/java/ctbrec/AbstractModel.java
+++ b/common/src/main/java/ctbrec/AbstractModel.java
@@ -1,5 +1,7 @@
package ctbrec;
+import static ctbrec.io.HttpConstants.*;
+
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
@@ -17,6 +19,8 @@ import ctbrec.recorder.download.HttpHeaderFactoryImpl;
import ctbrec.recorder.download.hls.HlsDownload;
import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload;
import ctbrec.sites.Site;
+import okhttp3.Request;
+import okhttp3.Response;
public abstract class AbstractModel implements Model {
@@ -270,4 +274,18 @@ public abstract class AbstractModel implements Model {
fac.setSegmentHeaders(new HashMap<>());
return fac;
}
+
+ @Override
+ public boolean exists() throws IOException {
+ Request req = new Request.Builder() // @formatter:off
+ .url(getUrl())
+ .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
+ .build(); // @formatter:on
+ try (Response response = getSite().getHttpClient().execute(req)) {
+ if (!response.isSuccessful() && response.code() == 404) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/common/src/main/java/ctbrec/Model.java b/common/src/main/java/ctbrec/Model.java
index 42ccd10e..15b94c7f 100644
--- a/common/src/main/java/ctbrec/Model.java
+++ b/common/src/main/java/ctbrec/Model.java
@@ -136,4 +136,11 @@ public interface Model extends Comparable, Serializable {
public SubsequentAction getRecordUntilSubsequentAction();
public void setRecordUntilSubsequentAction(SubsequentAction action);
+ /**
+ * Check, if this model account exists
+ * @return true, if it exists, false otherwise
+ * @throws IOException
+ */
+ public boolean exists() throws IOException;
+
}
\ No newline at end of file
From 672d2a77d4e7e2ccf122cabc76447ac36f7e5db8 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 19:29:15 +0100
Subject: [PATCH 77/80] Add another playlist source for Cam4 models
---
.../java/ctbrec/sites/cam4/Cam4Model.java | 51 +++++++++++--------
1 file changed, 29 insertions(+), 22 deletions(-)
diff --git a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
index 93355df9..e0258478 100644
--- a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
+++ b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java
@@ -6,15 +6,12 @@ import static java.util.regex.Pattern.*;
import java.io.IOException;
import java.io.InputStream;
-import java.time.Duration;
-import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -47,7 +44,6 @@ import okhttp3.Response;
public class Cam4Model extends AbstractModel {
private static final Logger LOG = LoggerFactory.getLogger(Cam4Model.class);
- private transient Instant playlistRequestTimestamp = Instant.EPOCH;
private String playlistUrl;
private int[] resolution = null;
private boolean privateRoom = false;
@@ -121,34 +117,44 @@ public class Cam4Model extends AbstractModel {
}
private String getPlaylistUrl() throws IOException {
- if (playlistUrl == null || playlistUrl.trim().isEmpty() || playlistIsOutdated()) {
+ if (playlistUrl == null || playlistUrl.trim().isEmpty()) {
String page = loadModelPage();
Matcher m = Pattern.compile("hlsUrl\\s*:\\s*'(.*?)'", DOTALL | MULTILINE).matcher(page);
if (m.find()) {
playlistUrl = m.group(1);
} else {
- m = Pattern.compile("\"videoPlayUrl\"\\s*:\\s*\"(.*?)\"", DOTALL | MULTILINE).matcher(page);
- if (m.find()) {
- generatePlaylistUrlFromStreamName(m.group(1));
- }
+ getPlaylistUrlFromStreamUrl();
}
if (playlistUrl == null) {
throw new IOException("Couldn't determine playlist url");
}
- playlistRequestTimestamp = Instant.now();
}
return playlistUrl;
}
- private boolean playlistIsOutdated() {
- return Duration.between(playlistRequestTimestamp, Instant.now()).getSeconds() > TimeUnit.MINUTES.toSeconds(2);
- }
-
- private void generatePlaylistUrlFromStreamName(String streamName) {
- Matcher m = Pattern.compile(".*?-(\\d{3,})-.*").matcher(streamName);
- if (m.find()) {
- String number = m.group(1);
- playlistUrl = "https://cam4-hls.xcdnpro.com/" + number + "/cam4-origin-live/ngrp:" + streamName + "_all/playlist.m3u8";
+ private void getPlaylistUrlFromStreamUrl() throws IOException {
+ String url = getSite().getBaseUrl() + "/_profile/streamURL?username=" + getName();
+ Request req = new Request.Builder() // @formatter:off
+ .url(url)
+ .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
+ .header(ACCEPT, "*/*")
+ .header(ACCEPT_LANGUAGE, "*")
+ .header(REFERER, getUrl())
+ .build(); // @formatter:on
+ try (Response response = site.getHttpClient().execute(req)) {
+ if (response.isSuccessful()) {
+ JSONObject json = new JSONObject(response.body().string());
+ LOG.trace(json.toString(2));
+ if (json.has("canUseCDN")) {
+ if (json.getBoolean("canUseCDN")) {
+ playlistUrl = json.getString("cdnURL");
+ } else {
+ playlistUrl = json.getString("edgeURL");
+ }
+ }
+ } else {
+ throw new HttpException(response.code(), response.message());
+ }
}
}
@@ -184,8 +190,9 @@ public class Cam4Model extends AbstractModel {
}
private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException {
- LOG.trace("Loading master playlist [{}]", getPlaylistUrl());
- Request req = new Request.Builder().url(getPlaylistUrl()).build();
+ String playlistUrl = getPlaylistUrl();
+ LOG.trace("Loading master playlist [{}]", playlistUrl);
+ Request req = new Request.Builder().url(playlistUrl).build();
try (Response response = site.getHttpClient().execute(req)) {
if (response.isSuccessful()) {
@@ -195,7 +202,7 @@ public class Cam4Model extends AbstractModel {
MasterPlaylist master = playlist.getMasterPlaylist();
return master;
} else {
- throw new HttpException(response.code(), "Couldn't download HLS playlist");
+ throw new HttpException(response.code(), "Couldn't download HLS playlist " + playlistUrl);
}
}
}
From ae8e4acf7a93ffbc90c4c0b837a2254fab4b88ce Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 20:29:50 +0100
Subject: [PATCH 78/80] Disallow playback of running remote recordings
Instead allow to open the live stream
---
CHANGELOG.md | 5 ++
.../java/ctbrec/ui/tabs/RecordingsTab.java | 90 +++++++++++++------
2 files changed, 68 insertions(+), 27 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f6a96470..9d39e8b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+3.10.10
+========================
+* Fix: "Check URLs" button stays inactive
+* Fix: recordings for some Cam4 models still didn't start
+
3.10.9
========================
* Added more category tabs for CamSoda
diff --git a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
index d0dcb5ab..43728def 100644
--- a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java
@@ -1,5 +1,32 @@
package ctbrec.ui.tabs;
+import static ctbrec.Recording.State.*;
+import static javafx.scene.control.ButtonType.*;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.NoSuchFileException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.text.DecimalFormat;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Recording;
@@ -12,9 +39,15 @@ import ctbrec.recorder.ProgressListener;
import ctbrec.recorder.Recorder;
import ctbrec.recorder.RecordingPinnedException;
import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload;
-import ctbrec.ui.*;
+import ctbrec.ui.AutosizeAlert;
+import ctbrec.ui.CamrecApplication;
+import ctbrec.ui.DesktopIntegration;
+import ctbrec.ui.FileDownload;
+import ctbrec.ui.JavaFxRecording;
+import ctbrec.ui.Player;
import ctbrec.ui.action.FollowAction;
import ctbrec.ui.action.PauseAction;
+import ctbrec.ui.action.PlayAction;
import ctbrec.ui.action.StopRecordingAction;
import ctbrec.ui.controls.DateTimeCellFactory;
import ctbrec.ui.controls.Dialogs;
@@ -30,9 +63,24 @@ import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Alert.AlertType;
-import javafx.scene.control.*;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.Label;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.ProgressBar;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.SelectionMode;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.SortType;
-import javafx.scene.input.*;
+import javafx.scene.control.TableView;
+import javafx.scene.control.Tooltip;
+import javafx.scene.input.ContextMenuEvent;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
@@ -40,29 +88,6 @@ import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.stage.FileChooser;
import javafx.util.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.net.URL;
-import java.nio.file.NoSuchFileException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.text.DecimalFormat;
-import java.time.Instant;
-import java.util.*;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
-
-import static ctbrec.Recording.State.*;
-import static javafx.scene.control.ButtonType.NO;
-import static javafx.scene.control.ButtonType.YES;
public class RecordingsTab extends Tab implements TabSelectionListener {
private static final String ERROR_WHILE_DOWNLOADING_RECORDING = "Error while downloading recording";
@@ -213,7 +238,10 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
Recording recording = table.getSelectionModel().getSelectedItem();
if (recording != null) {
- play(recording);
+ State state = recording.getStatus();
+ if(state == FINISHED || state == RECORDING && config.getSettings().localRecording) {
+ play(recording);
+ }
}
}
}
@@ -359,6 +387,10 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
openInPlayer.setOnAction(e -> play(first));
if (first.getStatus() == FINISHED || Config.getInstance().getSettings().localRecording) {
contextMenu.getItems().add(openInPlayer);
+ } else if (first.getStatus() == RECORDING && !Config.getInstance().getSettings().localRecording) {
+ openInPlayer.setText("Open live stream");
+ openInPlayer.setOnAction(e -> play(first.getModel()));
+ contextMenu.getItems().add(openInPlayer);
}
MenuItem openContactSheet = new MenuItem("Open contact sheet");
@@ -716,6 +748,10 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}).start();
}
+ private void play(Model model) {
+ new PlayAction(table, model).execute();
+ }
+
private void delete(List recordings) {
table.setCursor(Cursor.WAIT);
String msg;
From eba3052885ce63203badb106aa4b68ad14d29561 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 19 Dec 2020 21:18:36 +0100
Subject: [PATCH 79/80] Add headlineMessage as description
... better than nothing
---
.../java/ctbrec/ui/sites/streamate/StreamateUpdateService.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/client/src/main/java/ctbrec/ui/sites/streamate/StreamateUpdateService.java b/client/src/main/java/ctbrec/ui/sites/streamate/StreamateUpdateService.java
index 377f3d9e..a38512ef 100644
--- a/client/src/main/java/ctbrec/ui/sites/streamate/StreamateUpdateService.java
+++ b/client/src/main/java/ctbrec/ui/sites/streamate/StreamateUpdateService.java
@@ -62,6 +62,7 @@ public class StreamateUpdateService extends PaginatedScheduledService {
model.setId(p.getLong("id"));
//model.setPreview(p.getString("thumbnail"));
model.setPreview("https://cdn.nsimg.net/snap/320x240/" + model.getId() + ".jpg");
+ model.setDescription(p.optString("headlineMessage"));
boolean online = p.optBoolean("online");
model.setOnline(online);
model.setOnlineState(online ? ONLINE : OFFLINE);
From 867e500fbdadc5384d5f8303d7ae5ff26f9a96bd Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 20 Dec 2020 00:08:07 +0100
Subject: [PATCH 80/80] Fix MVLive recordings
---
CHANGELOG.md | 4 +-
.../java/ctbrec/sites/manyvids/MVLive.java | 3 +-
.../ctbrec/sites/manyvids/MVLiveModel.java | 50 +++++++++++--------
3 files changed, 34 insertions(+), 23 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d39e8b9..7460318b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,9 @@
3.10.10
========================
-* Fix: "Check URLs" button stays inactive
+* Fixed MVLive recordings once again
+* Fix: "Check URLs" button stays inactive after the first run
* Fix: recordings for some Cam4 models still didn't start
+* Some smaller tweaks here and there
3.10.9
========================
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
index 0532d0ed..7b7b2a78 100644
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
+++ b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
@@ -28,7 +28,8 @@ import okhttp3.Response;
public class MVLive extends AbstractSite {
- public static final String WS_URL = "wss://app-v2.live.manyvids.com";
+ public static final String APP_HOST = "app-v1.live.manyvids.com";
+ public static final String WS_URL = "wss://" + APP_HOST;
public static final String WS_ORIGIN = "https://live.manyvids.com";
public static final String BASE_URL = "https://www.manyvids.com/MVLive/";
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java
index f5b2de30..bbca090a 100644
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java
+++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java
@@ -1,21 +1,9 @@
package ctbrec.sites.manyvids;
-import com.iheartradio.m3u8.*;
-import com.iheartradio.m3u8.data.MasterPlaylist;
-import com.iheartradio.m3u8.data.Playlist;
-import com.iheartradio.m3u8.data.PlaylistData;
-import ctbrec.AbstractModel;
-import ctbrec.Config;
-import ctbrec.Model;
-import ctbrec.StringUtil;
-import ctbrec.io.HttpException;
-import ctbrec.recorder.download.Download;
-import ctbrec.recorder.download.StreamSource;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import static ctbrec.Model.State.*;
+import static ctbrec.io.HttpConstants.*;
+import static ctbrec.sites.manyvids.MVLive.*;
+import static java.nio.charset.StandardCharsets.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -26,9 +14,29 @@ import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
-import static ctbrec.Model.State.ONLINE;
-import static ctbrec.io.HttpConstants.*;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.iheartradio.m3u8.Encoding;
+import com.iheartradio.m3u8.Format;
+import com.iheartradio.m3u8.ParseException;
+import com.iheartradio.m3u8.ParsingMode;
+import com.iheartradio.m3u8.PlaylistException;
+import com.iheartradio.m3u8.PlaylistParser;
+import com.iheartradio.m3u8.data.MasterPlaylist;
+import com.iheartradio.m3u8.data.Playlist;
+import com.iheartradio.m3u8.data.PlaylistData;
+
+import ctbrec.AbstractModel;
+import ctbrec.Config;
+import ctbrec.Model;
+import ctbrec.StringUtil;
+import ctbrec.io.HttpException;
+import ctbrec.recorder.download.Download;
+import ctbrec.recorder.download.StreamSource;
+import okhttp3.Request;
+import okhttp3.Response;
public class MVLiveModel extends AbstractModel {
@@ -109,8 +117,8 @@ public class MVLiveModel extends AbstractModel {
}
public void updateCloudFlareCookies() throws IOException, InterruptedException {
- String url = "https://app-v2.live.manyvids.com/api/" + getRoomNumber() + "/player-settings/" + getDisplayName();
- LOG.trace("Getting CF cookies: {}", url);
+ String url = "https://" + APP_HOST + "/api/" + getRoomNumber() + "/player-settings/" + getDisplayName();
+ LOG.debug("Getting CF cookies: {}", url);
Request req = new Request.Builder()
.url(url)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)