From 9da580c6e808f941fbc0cf39a34b96cd8605b0ff Mon Sep 17 00:00:00 2001
From: 0xboobface <0xboobface@gmail.com>
Date: Sun, 4 Nov 2018 13:37:27 +0100
Subject: [PATCH] Add setting to define a maximum resolution for recordings

---
 src/main/java/ctbrec/Settings.java            |  1 +
 .../download/AbstractHlsDownload.java         | 24 ++++++-
 src/main/java/ctbrec/ui/SettingsTab.java      | 68 +++++++++++++------
 3 files changed, 72 insertions(+), 21 deletions(-)

diff --git a/src/main/java/ctbrec/Settings.java b/src/main/java/ctbrec/Settings.java
index db2cbf0c..a8809ddf 100644
--- a/src/main/java/ctbrec/Settings.java
+++ b/src/main/java/ctbrec/Settings.java
@@ -34,6 +34,7 @@ public class Settings {
     public boolean determineResolution = false;
     public boolean requireAuthentication = false;
     public boolean chooseStreamQuality = false;
+    public int maximumResolution = 0;
     public byte[] key = null;
     public ProxyType proxyType = ProxyType.DIRECT;
     public String proxyHost;
diff --git a/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java b/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java
index 0bf3d61e..97bc1bdd 100644
--- a/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java
+++ b/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java
@@ -7,11 +7,15 @@ import java.net.URL;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.iheartradio.m3u8.Encoding;
 import com.iheartradio.m3u8.Format;
 import com.iheartradio.m3u8.ParseException;
@@ -22,6 +26,7 @@ import com.iheartradio.m3u8.data.MediaPlaylist;
 import com.iheartradio.m3u8.data.Playlist;
 import com.iheartradio.m3u8.data.TrackData;
 
+import ctbrec.Config;
 import ctbrec.Model;
 import ctbrec.io.HttpClient;
 import okhttp3.Request;
@@ -29,6 +34,8 @@ import okhttp3.Response;
 
 public abstract class AbstractHlsDownload implements Download {
 
+    private static final transient Logger LOG = LoggerFactory.getLogger(AbstractHlsDownload.class);
+
     ExecutorService downloadThreadPool = Executors.newFixedThreadPool(5);
     HttpClient client;
     volatile boolean running = false;
@@ -80,7 +87,22 @@ public abstract class AbstractHlsDownload implements Download {
             url = streamSources.get(model.getStreamUrlIndex()).getMediaPlaylistUrl();
         } else {
             Collections.sort(streamSources);
-            url = streamSources.get(streamSources.size()-1).getMediaPlaylistUrl();
+            // filter out stream resolutions, which are too high
+            int maxRes = Config.getInstance().getSettings().maximumResolution;
+            if(maxRes > 0) {
+                for (Iterator<StreamSource> iterator = streamSources.iterator(); iterator.hasNext();) {
+                    StreamSource streamSource = iterator.next();
+                    if(streamSource.height > 0 && maxRes < streamSource.height) {
+                        LOG.debug("Res too high {} > {}", streamSource.height, maxRes);
+                        iterator.remove();
+                    }
+                }
+            }
+            if(streamSources.isEmpty()) {
+                throw new ExecutionException(new RuntimeException("No stream left in playlist"));
+            } else {
+                url = streamSources.get(streamSources.size()-1).getMediaPlaylistUrl();
+            }
         }
         return url;
     }
diff --git a/src/main/java/ctbrec/ui/SettingsTab.java b/src/main/java/ctbrec/ui/SettingsTab.java
index 693427d6..f0066613 100644
--- a/src/main/java/ctbrec/ui/SettingsTab.java
+++ b/src/main/java/ctbrec/ui/SettingsTab.java
@@ -65,6 +65,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
     private RadioButton recordRemote;
     private ToggleGroup recordLocation;
     private ProxySettingsPane proxySettingsPane;
+    private ComboBox<Integer> maxResolution;
     private ComboBox<SplitAfterOption> splitAfter;
     private List<Site> sites;
     private Label restartLabel;
@@ -231,8 +232,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
                 keyDialog.show();
             }
         });
-        GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, CHECKBOX_MARGIN, 0, 0));
-        GridPane.setMargin(secureCommunication, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
+        GridPane.setMargin(l, new Insets(4, CHECKBOX_MARGIN, 0, 0));
+        GridPane.setMargin(secureCommunication, new Insets(4, 0, 0, 0));
         layout.add(secureCommunication, 1, 3);
 
         TitledPane recordLocation = new TitledPane("Record Location", layout);
@@ -249,6 +250,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
         GridPane.setFillWidth(recordingsDirectory, true);
         GridPane.setHgrow(recordingsDirectory, Priority.ALWAYS);
         GridPane.setColumnSpan(recordingsDirectory, 2);
+        GridPane.setMargin(recordingsDirectory, new Insets(0, 0, 0, CHECKBOX_MARGIN));
         layout.add(recordingsDirectory, 1, 0);
         recordingsDirectoryButton = createRecordingsBrowseButton();
         layout.add(recordingsDirectoryButton, 3, 0);
@@ -259,19 +261,10 @@ public class SettingsTab extends Tab implements TabSelectionListener {
         GridPane.setFillWidth(mediaPlayer, true);
         GridPane.setHgrow(mediaPlayer, Priority.ALWAYS);
         GridPane.setColumnSpan(mediaPlayer, 2);
+        GridPane.setMargin(mediaPlayer, new Insets(0, 0, 0, CHECKBOX_MARGIN));
         layout.add(mediaPlayer, 1, 1);
         layout.add(createMpvBrowseButton(), 3, 1);
 
-        Label l = new Label("Allow multiple players");
-        layout.add(l, 0, 2);
-        multiplePlayers.setSelected(!Config.getInstance().getSettings().singlePlayer);
-        multiplePlayers.setOnAction((e) -> Config.getInstance().getSettings().singlePlayer = !multiplePlayers.isSelected());
-        GridPane.setMargin(recordingsDirectory, new Insets(0, 0, 0, CHECKBOX_MARGIN));
-        GridPane.setMargin(mediaPlayer, new Insets(0, 0, 0, CHECKBOX_MARGIN));
-        GridPane.setMargin(l, new Insets(3, 0, 0, 0));
-        GridPane.setMargin(multiplePlayers, new Insets(3, 0, 0, CHECKBOX_MARGIN));
-        layout.add(multiplePlayers, 1, 2);
-
         TitledPane locations = new TitledPane("Locations", layout);
         locations.setCollapsible(false);
         return locations;
@@ -279,8 +272,9 @@ public class SettingsTab extends Tab implements TabSelectionListener {
 
     private Node createGeneralPanel() {
         GridPane layout = createGridLayout();
+        int row = 0;
         Label l = new Label("Display stream resolution in overview");
-        layout.add(l, 0, 0);
+        layout.add(l, 0, row);
         loadResolution = new CheckBox();
         loadResolution.setSelected(Config.getInstance().getSettings().determineResolution);
         loadResolution.setOnAction((e) -> {
@@ -291,18 +285,41 @@ public class SettingsTab extends Tab implements TabSelectionListener {
         });
         //GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
         GridPane.setMargin(loadResolution, new Insets(0, 0, 0, CHECKBOX_MARGIN));
-        layout.add(loadResolution, 1, 0);
+        layout.add(loadResolution, 1, row++);
+
+        l = new Label("Allow multiple players");
+        layout.add(l, 0, row);
+        multiplePlayers.setSelected(!Config.getInstance().getSettings().singlePlayer);
+        multiplePlayers.setOnAction((e) -> Config.getInstance().getSettings().singlePlayer = !multiplePlayers.isSelected());
+        GridPane.setMargin(l, new Insets(3, 0, 0, 0));
+        GridPane.setMargin(multiplePlayers, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN));
+        layout.add(multiplePlayers, 1, row++);
 
         l = new Label("Manually select stream quality");
-        layout.add(l, 0, 1);
+        layout.add(l, 0, row);
         chooseStreamQuality.setSelected(Config.getInstance().getSettings().chooseStreamQuality);
         chooseStreamQuality.setOnAction((e) -> Config.getInstance().getSettings().chooseStreamQuality = chooseStreamQuality.isSelected());
         GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
         GridPane.setMargin(chooseStreamQuality, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN));
-        layout.add(chooseStreamQuality, 1, 1);
+        layout.add(chooseStreamQuality, 1, row++);
+
+        l = new Label("Maximum resolution (0 = unlimited)");
+        layout.add(l, 0, row);
+        List<Integer> resolutionOptions = new ArrayList<>();
+        resolutionOptions.add(1080);
+        resolutionOptions.add(720);
+        resolutionOptions.add(600);
+        resolutionOptions.add(480);
+        resolutionOptions.add(0);
+        maxResolution = new ComboBox<>(new ObservableListWrapper<>(resolutionOptions));
+        setMaxResolutionValue();
+        maxResolution.setOnAction((e) -> Config.getInstance().getSettings().maximumResolution = maxResolution.getSelectionModel().getSelectedItem());
+        layout.add(maxResolution, 1, row++);
+        GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
+        GridPane.setMargin(maxResolution, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN));
 
         l = new Label("Split recordings after (minutes)");
-        layout.add(l, 0, 2);
+        layout.add(l, 0, row);
         List<SplitAfterOption> options = new ArrayList<>();
         options.add(new SplitAfterOption("disabled", 0));
         options.add(new SplitAfterOption("10 min", 10 * 60));
@@ -311,11 +328,12 @@ public class SettingsTab extends Tab implements TabSelectionListener {
         options.add(new SplitAfterOption("30 min", 30 * 60));
         options.add(new SplitAfterOption("60 min", 60 * 60));
         splitAfter = new ComboBox<>(new ObservableListWrapper<>(options));
-        layout.add(splitAfter, 1, 2);
+        layout.add(splitAfter, 1, row++);
         setSplitAfterValue();
         splitAfter.setOnAction((e) -> Config.getInstance().getSettings().splitRecordings = splitAfter.getSelectionModel().getSelectedItem().getValue());
-        GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
-        GridPane.setMargin(splitAfter, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN));
+        GridPane.setMargin(l, new Insets(0, 0, 0, 0));
+        GridPane.setMargin(splitAfter, new Insets(0, 0, 0, CHECKBOX_MARGIN));
+        maxResolution.prefWidthProperty().bind(splitAfter.widthProperty());
 
         TitledPane general = new TitledPane("General", layout);
         general.setCollapsible(false);
@@ -331,6 +349,15 @@ public class SettingsTab extends Tab implements TabSelectionListener {
         }
     }
 
+    private void setMaxResolutionValue() {
+        int value = Config.getInstance().getSettings().maximumResolution;
+        for (Integer option : maxResolution.getItems()) {
+            if(option == value) {
+                maxResolution.getSelectionModel().select(option);
+            }
+        }
+    }
+
     void showRestartRequired() {
         restartLabel.setVisible(true);
     }
@@ -350,6 +377,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
         recordingsDirectory.setDisable(!local);
         recordingsDirectoryButton.setDisable(!local);
         splitAfter.setDisable(!local);
+        maxResolution.setDisable(!local);
     }
 
     private ChangeListener<? super Boolean> createRecordingsDirectoryFocusListener() {