From 4c4f88584437f545c8668fd0444974059a702b0e Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Fri, 6 Jul 2018 13:05:42 +0200 Subject: [PATCH] Added resoltion tag to thumb cell Added the possibility to display the video resolution for each model in the top right corner of the thumb. Can be toggled in the settings. --- src/main/java/ctbrec/Settings.java | 1 + src/main/java/ctbrec/recorder/Chaturbate.java | 56 ++++++++++++-- src/main/java/ctbrec/ui/SettingsTab.java | 8 ++ src/main/java/ctbrec/ui/ThumbCell.java | 76 ++++++++++++++++++- src/main/java/ctbrec/ui/ThumbOverviewTab.java | 9 +++ 5 files changed, 143 insertions(+), 7 deletions(-) diff --git a/src/main/java/ctbrec/Settings.java b/src/main/java/ctbrec/Settings.java index f826adb5..4d55d2b4 100644 --- a/src/main/java/ctbrec/Settings.java +++ b/src/main/java/ctbrec/Settings.java @@ -15,4 +15,5 @@ public class Settings { public String password = ""; public String lastDownloadDir = ""; public List models = new ArrayList(); + public boolean determineResolution = false; } diff --git a/src/main/java/ctbrec/recorder/Chaturbate.java b/src/main/java/ctbrec/recorder/Chaturbate.java index 6c9ca18b..ec44e92c 100644 --- a/src/main/java/ctbrec/recorder/Chaturbate.java +++ b/src/main/java/ctbrec/recorder/Chaturbate.java @@ -1,10 +1,20 @@ package ctbrec.recorder; import java.io.IOException; +import java.io.InputStream; +import java.net.URL; 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.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; @@ -13,6 +23,7 @@ import ctbrec.Model; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; +import okhttp3.Response; public class Chaturbate { private static final transient Logger LOG = LoggerFactory.getLogger(Chaturbate.class); @@ -27,11 +38,44 @@ public class Chaturbate { .post(body) .addHeader("X-Requested-With", "XMLHttpRequest") .build(); - String content = client.execute(req).body().string(); - LOG.debug("Raw stream info: {}", content); - Moshi moshi = new Moshi.Builder().build(); - JsonAdapter adapter = moshi.adapter(StreamInfo.class); - StreamInfo streamInfo = adapter.fromJson(content); - return streamInfo; + Response response = client.execute(req); + if(response.isSuccessful()) { + String content = response.body().string(); + LOG.debug("Raw stream info: {}", content); + Moshi moshi = new Moshi.Builder().build(); + JsonAdapter adapter = moshi.adapter(StreamInfo.class); + StreamInfo streamInfo = adapter.fromJson(content); + return streamInfo; + } else { + int code = response.code(); + String message = response.message(); + response.close(); + throw new IOException("Server responded with " + code + " - " + message + " headers: [" + response.headers() + "]"); + } + } + + public static int[] getResolution(Model model, HttpClient client) throws IOException, ParseException, PlaylistException { + int[] res = new int[2]; + StreamInfo streamInfo = getStreamInfo(model, client); + if(!streamInfo.url.startsWith("http")) { + return res; + } + + URL masterUrl = new URL(streamInfo.url); + InputStream inputStream = masterUrl.openStream(); + PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8); + Playlist playlist = parser.parse(); + MasterPlaylist master = playlist.getMasterPlaylist(); + for (PlaylistData playlistData : master.getPlaylists()) { + if(playlistData.hasStreamInfo() && playlistData.getStreamInfo().hasResolution()) { + int h = playlistData.getStreamInfo().getResolution().height; + int w = playlistData.getStreamInfo().getResolution().width; + if(w > res[1]) { + res[0] = w; + res[1] = h; + } + } + } + return res; } } diff --git a/src/main/java/ctbrec/ui/SettingsTab.java b/src/main/java/ctbrec/ui/SettingsTab.java index 1ee69c25..b115b111 100644 --- a/src/main/java/ctbrec/ui/SettingsTab.java +++ b/src/main/java/ctbrec/ui/SettingsTab.java @@ -14,6 +14,7 @@ import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.RadioButton; @@ -41,6 +42,7 @@ public class SettingsTab extends Tab { private TextField username; private TextField server; private TextField port; + private CheckBox loadResolution; private PasswordField password; private RadioButton recordLocal; private RadioButton recordRemote; @@ -101,6 +103,12 @@ public class SettingsTab extends Tab { GridPane.setColumnSpan(password, 2); layout.add(password, 1, row); + layout.add(new Label("Display stream resolution in overview"), 0, ++row); + loadResolution = new CheckBox(); + loadResolution.setSelected(Config.getInstance().getSettings().determineResolution); + loadResolution.setOnAction((e) -> Config.getInstance().getSettings().determineResolution = loadResolution.isSelected()); + layout.add(loadResolution, 1, row); + layout.add(new Label(), 0, ++row); layout.add(new Label("Record Location"), 0, ++row); diff --git a/src/main/java/ctbrec/ui/ThumbCell.java b/src/main/java/ctbrec/ui/ThumbCell.java index be6f4faf..f83a8e21 100644 --- a/src/main/java/ctbrec/ui/ThumbCell.java +++ b/src/main/java/ctbrec/ui/ThumbCell.java @@ -1,11 +1,17 @@ package ctbrec.ui; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.iheartradio.m3u8.ParseException; +import com.iheartradio.m3u8.PlaylistException; + +import ctbrec.Config; import ctbrec.HttpClient; import ctbrec.Model; import ctbrec.recorder.Chaturbate; @@ -54,12 +60,17 @@ public class ThumbCell extends StackPane { private static final int HEIGHT = 135; private static final Duration ANIMATION_DURATION = new Duration(250); + // this acts like a cache, once the stream resolution for a model has been determined, we don't do it again (until ctbrec is restarted) + private static Map resolutions = new HashMap<>(); + private Model model; private ImageView iv; + private Rectangle resolutionBackground; private Rectangle nameBackground; private Rectangle topicBackground; private Text name; private Text topic; + private Text resolutionTag; private Recorder recorder; private Circle recordingIndicator; private FadeTransition recordingAnimation; @@ -102,6 +113,15 @@ public class ThumbCell extends StackPane { StackPane.setAlignment(topicBackground, Pos.TOP_LEFT); getChildren().add(topicBackground); + resolutionBackground = new Rectangle(34, 16); + resolutionBackground.setFill(new Color(0.22, 0.8, 0.29, 1)); + resolutionBackground.setVisible(false); + resolutionBackground.setArcHeight(5); + resolutionBackground.setArcWidth(resolutionBackground.getArcHeight()); + StackPane.setAlignment(resolutionBackground, Pos.TOP_RIGHT); + StackPane.setMargin(resolutionBackground, new Insets(2)); + getChildren().add(resolutionBackground); + name = new Text(model.getName()); name.setFill(Color.WHITE); name.setFont(new Font("Sansserif", 16)); @@ -125,10 +145,17 @@ public class ThumbCell extends StackPane { StackPane.setAlignment(topic, Pos.TOP_CENTER); getChildren().add(topic); + resolutionTag = new Text(); + resolutionTag.setFill(Color.WHITE); + resolutionTag.setVisible(false); + StackPane.setAlignment(resolutionTag, Pos.TOP_RIGHT); + StackPane.setMargin(resolutionTag, new Insets(2, 4, 2, 2)); + getChildren().add(resolutionTag); + recordingIndicator = new Circle(8); recordingIndicator.setFill(colorRecording); StackPane.setMargin(recordingIndicator, new Insets(3)); - StackPane.setAlignment(recordingIndicator, Pos.TOP_RIGHT); + StackPane.setAlignment(recordingIndicator, Pos.TOP_LEFT); getChildren().add(recordingIndicator); recordingAnimation = new FadeTransition(Duration.millis(1000), recordingIndicator); recordingAnimation.setInterpolator(Interpolator.EASE_BOTH); @@ -163,6 +190,50 @@ public class ThumbCell extends StackPane { setPrefSize(WIDTH, HEIGHT); setRecording(recording); + if(Config.getInstance().getSettings().determineResolution) { + determineResolution(); + } + } + + private void determineResolution() { + if(ThumbOverviewTab.resolutionProcessing.contains(model)) { + return; + } + + ThumbOverviewTab.resolutionProcessing.add(model); + int[] res = resolutions.get(model.getName()); + if(res == null) { + ThumbOverviewTab.threadPool.submit(() -> { + try { + Thread.sleep(500); // throttle down, so that we don't do too many requests + int[] resolution = Chaturbate.getResolution(model, client); + resolutions.put(model.getName(), resolution); + if (resolution[1] > 0) { + LOG.trace("Model resolution {} {}x{}", model.getName(), resolution[0], resolution[1]); + LOG.trace("Resolution queue size: {}", ThumbOverviewTab.queue.size()); + final int w = resolution[1]; + Platform.runLater(() -> { + resolutionTag.setText(Integer.toString(w)); + resolutionTag.setVisible(true); + resolutionBackground.setVisible(true); + resolutionBackground.setWidth(resolutionTag.getBoundsInLocal().getWidth() + 4); + }); + } + } catch (IOException | ParseException | PlaylistException | InterruptedException e) { + LOG.error("Coulnd't get resolution for model {}", model, e); + } finally { + ThumbOverviewTab.resolutionProcessing.remove(model); + } + }); + } else { + ThumbOverviewTab.resolutionProcessing.remove(model); + Platform.runLater(() -> { + resolutionTag.setText(Integer.toString(res[1])); + resolutionTag.setVisible(true); + resolutionBackground.setVisible(true); + resolutionBackground.setWidth(resolutionTag.getBoundsInLocal().getWidth() + 4); + }); + } } private void setImage(String url) { @@ -369,6 +440,9 @@ public class ThumbCell extends StackPane { topic.setText(model.getDescription()); setRecording(recorder.isRecording(model)); requestLayout(); + if(Config.getInstance().getSettings().determineResolution) { + determineResolution(); + } } @Override diff --git a/src/main/java/ctbrec/ui/ThumbOverviewTab.java b/src/main/java/ctbrec/ui/ThumbOverviewTab.java index 5b013e73..13860416 100644 --- a/src/main/java/ctbrec/ui/ThumbOverviewTab.java +++ b/src/main/java/ctbrec/ui/ThumbOverviewTab.java @@ -5,11 +5,16 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +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; @@ -42,6 +47,10 @@ import okhttp3.Response; public class ThumbOverviewTab extends Tab implements TabSelectionListener { private static final transient Logger LOG = LoggerFactory.getLogger(ThumbOverviewTab.class); + static Set resolutionProcessing = Collections.synchronizedSet(new HashSet<>()); + static BlockingQueue queue = new LinkedBlockingQueue<>(); + static ExecutorService threadPool = new ThreadPoolExecutor(2, 2, 10, TimeUnit.MINUTES, queue); + ScheduledService> updateService; Recorder recorder; List filteredThumbCells = Collections.synchronizedList(new ArrayList<>());