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.
This commit is contained in:
parent
ea3715134e
commit
4c4f885844
|
@ -15,4 +15,5 @@ public class Settings {
|
||||||
public String password = "";
|
public String password = "";
|
||||||
public String lastDownloadDir = "";
|
public String lastDownloadDir = "";
|
||||||
public List<Model> models = new ArrayList<Model>();
|
public List<Model> models = new ArrayList<Model>();
|
||||||
|
public boolean determineResolution = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
package ctbrec.recorder;
|
package ctbrec.recorder;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.JsonAdapter;
|
||||||
import com.squareup.moshi.Moshi;
|
import com.squareup.moshi.Moshi;
|
||||||
|
|
||||||
|
@ -13,6 +23,7 @@ import ctbrec.Model;
|
||||||
import okhttp3.FormBody;
|
import okhttp3.FormBody;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
public class Chaturbate {
|
public class Chaturbate {
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(Chaturbate.class);
|
private static final transient Logger LOG = LoggerFactory.getLogger(Chaturbate.class);
|
||||||
|
@ -27,11 +38,44 @@ public class Chaturbate {
|
||||||
.post(body)
|
.post(body)
|
||||||
.addHeader("X-Requested-With", "XMLHttpRequest")
|
.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||||
.build();
|
.build();
|
||||||
String content = client.execute(req).body().string();
|
Response response = client.execute(req);
|
||||||
LOG.debug("Raw stream info: {}", content);
|
if(response.isSuccessful()) {
|
||||||
Moshi moshi = new Moshi.Builder().build();
|
String content = response.body().string();
|
||||||
JsonAdapter<StreamInfo> adapter = moshi.adapter(StreamInfo.class);
|
LOG.debug("Raw stream info: {}", content);
|
||||||
StreamInfo streamInfo = adapter.fromJson(content);
|
Moshi moshi = new Moshi.Builder().build();
|
||||||
return streamInfo;
|
JsonAdapter<StreamInfo> 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import javafx.scene.Node;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Alert.AlertType;
|
import javafx.scene.control.Alert.AlertType;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.PasswordField;
|
import javafx.scene.control.PasswordField;
|
||||||
import javafx.scene.control.RadioButton;
|
import javafx.scene.control.RadioButton;
|
||||||
|
@ -41,6 +42,7 @@ public class SettingsTab extends Tab {
|
||||||
private TextField username;
|
private TextField username;
|
||||||
private TextField server;
|
private TextField server;
|
||||||
private TextField port;
|
private TextField port;
|
||||||
|
private CheckBox loadResolution;
|
||||||
private PasswordField password;
|
private PasswordField password;
|
||||||
private RadioButton recordLocal;
|
private RadioButton recordLocal;
|
||||||
private RadioButton recordRemote;
|
private RadioButton recordRemote;
|
||||||
|
@ -101,6 +103,12 @@ public class SettingsTab extends Tab {
|
||||||
GridPane.setColumnSpan(password, 2);
|
GridPane.setColumnSpan(password, 2);
|
||||||
layout.add(password, 1, row);
|
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(), 0, ++row);
|
||||||
|
|
||||||
layout.add(new Label("Record Location"), 0, ++row);
|
layout.add(new Label("Record Location"), 0, ++row);
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
package ctbrec.ui;
|
package ctbrec.ui;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.iheartradio.m3u8.ParseException;
|
||||||
|
import com.iheartradio.m3u8.PlaylistException;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
import ctbrec.HttpClient;
|
import ctbrec.HttpClient;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.recorder.Chaturbate;
|
import ctbrec.recorder.Chaturbate;
|
||||||
|
@ -54,12 +60,17 @@ public class ThumbCell extends StackPane {
|
||||||
private static final int HEIGHT = 135;
|
private static final int HEIGHT = 135;
|
||||||
private static final Duration ANIMATION_DURATION = new Duration(250);
|
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<String, int[]> resolutions = new HashMap<>();
|
||||||
|
|
||||||
private Model model;
|
private Model model;
|
||||||
private ImageView iv;
|
private ImageView iv;
|
||||||
|
private Rectangle resolutionBackground;
|
||||||
private Rectangle nameBackground;
|
private Rectangle nameBackground;
|
||||||
private Rectangle topicBackground;
|
private Rectangle topicBackground;
|
||||||
private Text name;
|
private Text name;
|
||||||
private Text topic;
|
private Text topic;
|
||||||
|
private Text resolutionTag;
|
||||||
private Recorder recorder;
|
private Recorder recorder;
|
||||||
private Circle recordingIndicator;
|
private Circle recordingIndicator;
|
||||||
private FadeTransition recordingAnimation;
|
private FadeTransition recordingAnimation;
|
||||||
|
@ -102,6 +113,15 @@ public class ThumbCell extends StackPane {
|
||||||
StackPane.setAlignment(topicBackground, Pos.TOP_LEFT);
|
StackPane.setAlignment(topicBackground, Pos.TOP_LEFT);
|
||||||
getChildren().add(topicBackground);
|
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 = new Text(model.getName());
|
||||||
name.setFill(Color.WHITE);
|
name.setFill(Color.WHITE);
|
||||||
name.setFont(new Font("Sansserif", 16));
|
name.setFont(new Font("Sansserif", 16));
|
||||||
|
@ -125,10 +145,17 @@ public class ThumbCell extends StackPane {
|
||||||
StackPane.setAlignment(topic, Pos.TOP_CENTER);
|
StackPane.setAlignment(topic, Pos.TOP_CENTER);
|
||||||
getChildren().add(topic);
|
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 = new Circle(8);
|
||||||
recordingIndicator.setFill(colorRecording);
|
recordingIndicator.setFill(colorRecording);
|
||||||
StackPane.setMargin(recordingIndicator, new Insets(3));
|
StackPane.setMargin(recordingIndicator, new Insets(3));
|
||||||
StackPane.setAlignment(recordingIndicator, Pos.TOP_RIGHT);
|
StackPane.setAlignment(recordingIndicator, Pos.TOP_LEFT);
|
||||||
getChildren().add(recordingIndicator);
|
getChildren().add(recordingIndicator);
|
||||||
recordingAnimation = new FadeTransition(Duration.millis(1000), recordingIndicator);
|
recordingAnimation = new FadeTransition(Duration.millis(1000), recordingIndicator);
|
||||||
recordingAnimation.setInterpolator(Interpolator.EASE_BOTH);
|
recordingAnimation.setInterpolator(Interpolator.EASE_BOTH);
|
||||||
|
@ -163,6 +190,50 @@ public class ThumbCell extends StackPane {
|
||||||
setPrefSize(WIDTH, HEIGHT);
|
setPrefSize(WIDTH, HEIGHT);
|
||||||
|
|
||||||
setRecording(recording);
|
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) {
|
private void setImage(String url) {
|
||||||
|
@ -369,6 +440,9 @@ public class ThumbCell extends StackPane {
|
||||||
topic.setText(model.getDescription());
|
topic.setText(model.getDescription());
|
||||||
setRecording(recorder.isRecording(model));
|
setRecording(recorder.isRecording(model));
|
||||||
requestLayout();
|
requestLayout();
|
||||||
|
if(Config.getInstance().getSettings().determineResolution) {
|
||||||
|
determineResolution();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -5,11 +5,16 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
@ -42,6 +47,10 @@ import okhttp3.Response;
|
||||||
public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(ThumbOverviewTab.class);
|
private static final transient Logger LOG = LoggerFactory.getLogger(ThumbOverviewTab.class);
|
||||||
|
|
||||||
|
static Set<Model> resolutionProcessing = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
static BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
||||||
|
static ExecutorService threadPool = new ThreadPoolExecutor(2, 2, 10, TimeUnit.MINUTES, queue);
|
||||||
|
|
||||||
ScheduledService<List<Model>> updateService;
|
ScheduledService<List<Model>> updateService;
|
||||||
Recorder recorder;
|
Recorder recorder;
|
||||||
List<ThumbCell> filteredThumbCells = Collections.synchronizedList(new ArrayList<>());
|
List<ThumbCell> filteredThumbCells = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
Loading…
Reference in New Issue