Added auto-merging for local mode
In local mode files can be automatically merged after a recording is finished. This can be configured in the settings.
This commit is contained in:
parent
e6807acec6
commit
b12644cfbf
|
@ -1,5 +1,6 @@
|
||||||
package ctbrec;
|
package ctbrec;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
@ -116,4 +117,23 @@ public class Recording {
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static File mergedFileFromDirectory(File recDir) {
|
||||||
|
String date = recDir.getName();
|
||||||
|
String model = recDir.getParentFile().getName();
|
||||||
|
String filename = model + "-" + date + ".ts";
|
||||||
|
File mergedFile = new File(recDir, filename);
|
||||||
|
return mergedFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isMergedRecording(File recDir) {
|
||||||
|
File mergedFile = mergedFileFromDirectory(recDir);
|
||||||
|
return mergedFile.exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isMergedRecording(Recording recording) {
|
||||||
|
String recordingsDir = Config.getInstance().getSettings().recordingsDir;
|
||||||
|
File recDir = new File(recordingsDir, recording.getPath());
|
||||||
|
return isMergedRecording(recDir);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ 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 automerge = false;
|
||||||
|
public boolean automergeKeepSegments = false;
|
||||||
public boolean determineResolution = false;
|
public boolean determineResolution = false;
|
||||||
public boolean requireAuthentication = false;
|
public boolean requireAuthentication = false;
|
||||||
public byte[] key = null;
|
public byte[] key = null;
|
||||||
|
|
|
@ -30,10 +30,10 @@ import ctbrec.Config;
|
||||||
import ctbrec.HttpClient;
|
import ctbrec.HttpClient;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
|
import ctbrec.Recording.STATUS;
|
||||||
|
import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException;
|
||||||
import ctbrec.recorder.download.Download;
|
import ctbrec.recorder.download.Download;
|
||||||
import ctbrec.recorder.download.HlsDownload;
|
import ctbrec.recorder.download.HlsDownload;
|
||||||
import ctbrec.recorder.server.PlaylistGenerator;
|
|
||||||
import ctbrec.recorder.server.PlaylistGenerator.InvalidPlaylistException;
|
|
||||||
|
|
||||||
public class LocalRecorder implements Recorder {
|
public class LocalRecorder implements Recorder {
|
||||||
|
|
||||||
|
@ -43,12 +43,14 @@ public class LocalRecorder implements Recorder {
|
||||||
private Lock lock = new ReentrantLock();
|
private Lock lock = new ReentrantLock();
|
||||||
private Map<Model, Download> recordingProcesses = Collections.synchronizedMap(new HashMap<>());
|
private Map<Model, Download> recordingProcesses = Collections.synchronizedMap(new HashMap<>());
|
||||||
private Map<File, PlaylistGenerator> playlistGenerators = new HashMap<>();
|
private Map<File, PlaylistGenerator> playlistGenerators = new HashMap<>();
|
||||||
|
private Map<File, SegmentMerger> segmentMergers = new HashMap<>();
|
||||||
private Config config;
|
private Config config;
|
||||||
private ProcessMonitor processMonitor;
|
private ProcessMonitor processMonitor;
|
||||||
private OnlineMonitor onlineMonitor;
|
private OnlineMonitor onlineMonitor;
|
||||||
private PlaylistGeneratorTrigger playlistGenTrigger;
|
private PlaylistGeneratorTrigger playlistGenTrigger;
|
||||||
private HttpClient client = HttpClient.getInstance();
|
private HttpClient client = HttpClient.getInstance();
|
||||||
private volatile boolean recording = true;
|
private volatile boolean recording = true;
|
||||||
|
private List<File> deleteInProgress = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
public LocalRecorder(Config config) {
|
public LocalRecorder(Config config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
@ -238,7 +240,7 @@ public class LocalRecorder implements Recorder {
|
||||||
LOG.debug("Recording terminated for model {}", m.getName());
|
LOG.debug("Recording terminated for model {}", m.getName());
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
restart.add(m);
|
restart.add(m);
|
||||||
generatePlaylist(d.getDirectory());
|
finishRecording(d.getDirectory());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Model m : restart) {
|
for (Model m : restart) {
|
||||||
|
@ -257,13 +259,57 @@ public class LocalRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
LOG.debug(getName() + " terminated");
|
LOG.debug(getName() + " terminated");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generatePlaylist(File recDir) {
|
private void finishRecording(File directory) {
|
||||||
Thread t = new Thread() {
|
Thread t = new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
boolean local = Config.getInstance().getSettings().localRecording;
|
||||||
|
boolean automerge = Config.getInstance().getSettings().automerge;
|
||||||
|
generatePlaylist(directory);
|
||||||
|
if(local && automerge) {
|
||||||
|
File mergedFile = merge(directory);
|
||||||
|
if(mergedFile != null && mergedFile.exists() && mergedFile.length() > 0) {
|
||||||
|
LOG.debug("Merged file {}", mergedFile.getAbsolutePath());
|
||||||
|
if (!Config.getInstance().getSettings().automergeKeepSegments) {
|
||||||
|
try {
|
||||||
|
LOG.debug("Deleting directory {}", directory);
|
||||||
|
delete(directory, mergedFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Couldn't delete directory {}", directory, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.error("Merged file not found {}", mergedFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.setName("Postprocessing" + directory.toString());
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private File merge(File recDir) {
|
||||||
|
SegmentMerger segmentMerger = new SegmentMerger();
|
||||||
|
segmentMergers.put(recDir, segmentMerger);
|
||||||
|
try {
|
||||||
|
File mergedFile = Recording.mergedFileFromDirectory(recDir);
|
||||||
|
segmentMerger.merge(recDir, mergedFile);
|
||||||
|
return mergedFile;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Couldn't generate playlist file", e);
|
||||||
|
} catch (ParseException | PlaylistException | InvalidPlaylistException e) {
|
||||||
|
LOG.error("Playlist is invalid", e);
|
||||||
|
} finally {
|
||||||
|
segmentMergers.remove(recDir);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generatePlaylist(File recDir) {
|
||||||
PlaylistGenerator playlistGenerator = new PlaylistGenerator();
|
PlaylistGenerator playlistGenerator = new PlaylistGenerator();
|
||||||
playlistGenerators.put(recDir, playlistGenerator);
|
playlistGenerators.put(recDir, playlistGenerator);
|
||||||
try {
|
try {
|
||||||
|
@ -279,11 +325,6 @@ public class LocalRecorder implements Recorder {
|
||||||
playlistGenerators.remove(recDir);
|
playlistGenerators.remove(recDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
t.setDaemon(true);
|
|
||||||
t.setName("Playlist Generator " + recDir.toString());
|
|
||||||
t.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OnlineMonitor extends Thread {
|
private class OnlineMonitor extends Thread {
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
|
@ -354,8 +395,11 @@ public class LocalRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!recordingProcessFound) {
|
if(!recordingProcessFound) {
|
||||||
// finished recording without playlist -> generate it
|
if(deleteInProgress.contains(recDir)) {
|
||||||
generatePlaylist(recDir);
|
LOG.debug("{} is being deleted. Not going to generate a playlist", recDir);
|
||||||
|
} else {
|
||||||
|
finishRecording(recDir);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -405,15 +449,26 @@ public class LocalRecorder implements Recorder {
|
||||||
recording.setHasPlaylist(playlist.exists());
|
recording.setHasPlaylist(playlist.exists());
|
||||||
if(recording.hasPlaylist()) {
|
if(recording.hasPlaylist()) {
|
||||||
recording.setStatus(FINISHED);
|
recording.setStatus(FINISHED);
|
||||||
|
} else {
|
||||||
|
// this might be a merged recording
|
||||||
|
if(Recording.isMergedRecording(rec)) {
|
||||||
|
recording.setStatus(FINISHED);
|
||||||
} else {
|
} else {
|
||||||
PlaylistGenerator playlistGenerator = playlistGenerators.get(rec);
|
PlaylistGenerator playlistGenerator = playlistGenerators.get(rec);
|
||||||
if(playlistGenerator != null) {
|
if(playlistGenerator != null) {
|
||||||
recording.setStatus(GENERATING_PLAYLIST);
|
recording.setStatus(GENERATING_PLAYLIST);
|
||||||
recording.setProgress(playlistGenerator.getProgress());
|
recording.setProgress(playlistGenerator.getProgress());
|
||||||
|
} else {
|
||||||
|
SegmentMerger merger = segmentMergers.get(rec);
|
||||||
|
if(merger != null) {
|
||||||
|
recording.setStatus(STATUS.MERGING);
|
||||||
|
recording.setProgress(merger.getProgress());
|
||||||
} else {
|
} else {
|
||||||
recording.setStatus(RECORDING);
|
recording.setStatus(RECORDING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
recordings.add(recording);
|
recordings.add(recording);
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
LOG.debug("Ignoring {}", rec.getAbsolutePath());
|
LOG.debug("Ignoring {}", rec.getAbsolutePath());
|
||||||
|
@ -437,21 +492,47 @@ public class LocalRecorder implements Recorder {
|
||||||
public void delete(Recording recording) throws IOException {
|
public void delete(Recording recording) throws IOException {
|
||||||
File recordingsDir = new File(config.getSettings().recordingsDir);
|
File recordingsDir = new File(config.getSettings().recordingsDir);
|
||||||
File directory = new File(recordingsDir, recording.getPath());
|
File directory = new File(recordingsDir, recording.getPath());
|
||||||
|
delete(directory, new File[] {});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete(File directory, File...excludes) throws IOException {
|
||||||
if(!directory.exists()) {
|
if(!directory.exists()) {
|
||||||
throw new IOException("Recording does not exist");
|
throw new IOException("Recording does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
deleteInProgress.add(directory);
|
||||||
File[] files = directory.listFiles();
|
File[] files = directory.listFiles();
|
||||||
boolean deletedAllFiles = true;
|
boolean deletedAllFiles = true;
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
|
boolean skip = false;
|
||||||
|
for (File exclude : excludes) {
|
||||||
|
if(file.equals(exclude)) {
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(skip) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.delete(file.toPath());
|
Files.delete(file.toPath());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
deletedAllFiles = false;
|
deletedAllFiles = false;
|
||||||
LOG.debug("Couldn't delete {}", file, e);
|
LOG.debug("Couldn't delete {}", file, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(deletedAllFiles) {
|
if(deletedAllFiles) {
|
||||||
|
if(directory.list().length == 0) {
|
||||||
boolean deleted = directory.delete();
|
boolean deleted = directory.delete();
|
||||||
if(deleted) {
|
if(deleted) {
|
||||||
if(directory.getParentFile().list().length == 0) {
|
if(directory.getParentFile().list().length == 0) {
|
||||||
|
@ -460,8 +541,23 @@ public class LocalRecorder implements Recorder {
|
||||||
} else {
|
} else {
|
||||||
throw new IOException("Couldn't delete " + directory);
|
throw new IOException("Couldn't delete " + directory);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new IOException("Couldn't delete all files in " + directory);
|
throw new IOException("Couldn't delete all files in " + directory);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
deleteInProgress.remove(directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void merge(Recording rec, boolean keepSegments) throws IOException {
|
||||||
|
File recordingsDir = new File(config.getSettings().recordingsDir);
|
||||||
|
File directory = new File(recordingsDir, rec.getPath());
|
||||||
|
merge(directory);
|
||||||
|
if(!keepSegments) {
|
||||||
|
File mergedFile = Recording.mergedFileFromDirectory(directory);
|
||||||
|
delete(directory, mergedFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ctbrec.recorder.server;
|
package ctbrec.recorder;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ctbrec.recorder.server;
|
package ctbrec.recorder;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface ProgressListener {
|
public interface ProgressListener {
|
||||||
|
|
|
@ -23,6 +23,8 @@ public interface Recorder {
|
||||||
|
|
||||||
public List<Recording> getRecordings() throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException;
|
public List<Recording> getRecordings() throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException;
|
||||||
|
|
||||||
|
public void merge(Recording recording, boolean keepSegments) throws IOException;
|
||||||
|
|
||||||
public void delete(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException;
|
public void delete(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException;
|
||||||
|
|
||||||
public void shutdown();
|
public void shutdown();
|
||||||
|
|
|
@ -233,4 +233,9 @@ public class RemoteRecorder implements Recorder {
|
||||||
//throw new IOException("Couldn't delete recording: " + response.code() + " " + json);
|
//throw new IOException("Couldn't delete recording: " + response.code() + " " + json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void merge(Recording recording, boolean keepSegments) throws IOException {
|
||||||
|
throw new RuntimeException("Merging not available for remote recorder");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package ctbrec.recorder;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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.MediaPlaylist;
|
||||||
|
import com.iheartradio.m3u8.data.Playlist;
|
||||||
|
import com.iheartradio.m3u8.data.TrackData;
|
||||||
|
|
||||||
|
|
||||||
|
public class SegmentMerger {
|
||||||
|
private int lastPercentage;
|
||||||
|
|
||||||
|
public void merge(File recDir, File targetFile) throws IOException, ParseException, PlaylistException {
|
||||||
|
if (targetFile.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File playlistFile = new File(recDir, "playlist.m3u8");
|
||||||
|
try (FileInputStream fin = new FileInputStream(playlistFile); FileOutputStream fos = new FileOutputStream(targetFile)) {
|
||||||
|
PlaylistParser parser = new PlaylistParser(fin, Format.EXT_M3U, Encoding.UTF_8);
|
||||||
|
Playlist playlist = parser.parse();
|
||||||
|
MediaPlaylist mediaPlaylist = playlist.getMediaPlaylist();
|
||||||
|
List<TrackData> tracks = mediaPlaylist.getTracks();
|
||||||
|
for (int i = 0; i < tracks.size(); i++) {
|
||||||
|
TrackData trackData = tracks.get(i);
|
||||||
|
File segment = new File(recDir, trackData.getUri());
|
||||||
|
try (FileInputStream segmentStream = new FileInputStream(segment)) {
|
||||||
|
int length = -1;
|
||||||
|
byte[] b = new byte[1024 * 1024];
|
||||||
|
while ((length = segmentStream.read(b)) >= 0) {
|
||||||
|
fos.write(b, 0, length);
|
||||||
|
}
|
||||||
|
lastPercentage = (int) (i * 100.0 / tracks.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProgress() {
|
||||||
|
return lastPercentage;
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,11 +56,12 @@ public class Launcher extends Application {
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
// TODO enable this again
|
||||||
client.login();
|
// try {
|
||||||
} catch (IOException e1) {
|
// client.login();
|
||||||
LOG.warn("Initial login failed" , e1);
|
// } catch (IOException e1) {
|
||||||
}
|
// LOG.warn("Initial login failed" , e1);
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
}.start();
|
}.start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,12 @@ public class Player {
|
||||||
try {
|
try {
|
||||||
if (Config.getInstance().getSettings().localRecording && rec != null) {
|
if (Config.getInstance().getSettings().localRecording && rec != null) {
|
||||||
File dir = new File(Config.getInstance().getSettings().recordingsDir, rec.getPath());
|
File dir = new File(Config.getInstance().getSettings().recordingsDir, rec.getPath());
|
||||||
File file = new File(dir, "playlist.m3u8");
|
File file = null;
|
||||||
|
if(Recording.isMergedRecording(rec)) {
|
||||||
|
file = Recording.mergedFileFromDirectory(dir);
|
||||||
|
} else {
|
||||||
|
file = new File(dir, "playlist.m3u8");
|
||||||
|
}
|
||||||
playerProcess = rt.exec(Config.getInstance().getSettings().mediaPlayer + " " + file, OS.getEnvironment(), dir);
|
playerProcess = rt.exec(Config.getInstance().getSettings().mediaPlayer + " " + file, OS.getEnvironment(), dir);
|
||||||
} else {
|
} else {
|
||||||
playerProcess = rt.exec(Config.getInstance().getSettings().mediaPlayer + " " + url);
|
playerProcess = rt.exec(Config.getInstance().getSettings().mediaPlayer + " " + url);
|
||||||
|
|
|
@ -4,7 +4,6 @@ import static javafx.scene.control.ButtonType.NO;
|
||||||
import static javafx.scene.control.ButtonType.YES;
|
import static javafx.scene.control.ButtonType.YES;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -46,6 +45,7 @@ import javafx.scene.Cursor;
|
||||||
import javafx.scene.control.Alert.AlertType;
|
import javafx.scene.control.Alert.AlertType;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.scene.control.ContextMenu;
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.Menu;
|
||||||
import javafx.scene.control.MenuItem;
|
import javafx.scene.control.MenuItem;
|
||||||
import javafx.scene.control.ScrollPane;
|
import javafx.scene.control.ScrollPane;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
|
@ -250,23 +250,36 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
contextMenu.getItems().add(downloadRecording);
|
contextMenu.getItems().add(downloadRecording);
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuItem mergeRecording = new MenuItem("Merge segments");
|
Menu mergeRecording = new Menu("Merge segments");
|
||||||
mergeRecording.setOnAction((e) -> {
|
MenuItem mergeKeep = new MenuItem("… and keep segments");
|
||||||
|
mergeKeep.setOnAction((e) -> {
|
||||||
try {
|
try {
|
||||||
merge(recording);
|
merge(recording, true);
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
showErrorDialog("Error while merging recording", "The playlist does not exist", e1);
|
showErrorDialog("Error while merging recording", "The playlist does not exist", e1);
|
||||||
LOG.error("Error while merging recording", e);
|
LOG.error("Error while merging recording", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
MenuItem mergeDelete = new MenuItem("… and delete segments");
|
||||||
|
mergeDelete.setOnAction((e) -> {
|
||||||
|
try {
|
||||||
|
merge(recording, false);
|
||||||
|
} catch (IOException e1) {
|
||||||
|
showErrorDialog("Error while merging recording", "The playlist does not exist", e1);
|
||||||
|
LOG.error("Error while merging recording", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mergeRecording.getItems().addAll(mergeKeep, mergeDelete);
|
||||||
if (Config.getInstance().getSettings().localRecording && recording.getStatus() == STATUS.FINISHED) {
|
if (Config.getInstance().getSettings().localRecording && recording.getStatus() == STATUS.FINISHED) {
|
||||||
|
if(!Recording.isMergedRecording(recording)) {
|
||||||
contextMenu.getItems().add(mergeRecording);
|
contextMenu.getItems().add(mergeRecording);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void merge(Recording recording) throws IOException {
|
private void merge(Recording recording, boolean keepSegments) throws IOException {
|
||||||
File recDir = new File (Config.getInstance().getSettings().recordingsDir, recording.getPath());
|
File recDir = new File (Config.getInstance().getSettings().recordingsDir, recording.getPath());
|
||||||
File playlistFile = new File(recDir, "playlist.m3u8");
|
File playlistFile = new File(recDir, "playlist.m3u8");
|
||||||
if(!playlistFile.exists()) {
|
if(!playlistFile.exists()) {
|
||||||
|
@ -282,36 +295,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
Thread t = new Thread() {
|
Thread t = new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try(
|
try {
|
||||||
FileInputStream fin = new FileInputStream(playlistFile);
|
recorder.merge(recording, keepSegments);
|
||||||
FileOutputStream fos = new FileOutputStream(targetFile))
|
|
||||||
{
|
|
||||||
PlaylistParser parser = new PlaylistParser(fin, Format.EXT_M3U, Encoding.UTF_8);
|
|
||||||
Playlist playlist = parser.parse();
|
|
||||||
MediaPlaylist mediaPlaylist = playlist.getMediaPlaylist();
|
|
||||||
List<TrackData> tracks = mediaPlaylist.getTracks();
|
|
||||||
for (int i = 0; i < tracks.size(); i++) {
|
|
||||||
TrackData trackData = tracks.get(i);
|
|
||||||
File segment = new File(recDir, trackData.getUri());
|
|
||||||
try(FileInputStream segmentStream = new FileInputStream(segment)) {
|
|
||||||
int length = -1;
|
|
||||||
byte[] b = new byte[1024 * 1024];
|
|
||||||
while( (length = segmentStream.read(b)) >= 0 ) {
|
|
||||||
fos.write(b, 0, length);
|
|
||||||
}
|
|
||||||
int progress = (int)(i * 100.0 / tracks.size());
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
recording.setStatus(STATUS.MERGING);
|
|
||||||
recording.setProgress(progress);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
showErrorDialog("Error while merging segments", "The merged file could not be created", e);
|
showErrorDialog("Error while merging segments", "The merged file could not be created", e);
|
||||||
LOG.error("Error while merging segments", e);
|
LOG.error("Error while merging segments", e);
|
||||||
} catch (ParseException | PlaylistException e) {
|
|
||||||
showErrorDialog("Error while merging recording", "Couldn't read playlist", e);
|
|
||||||
LOG.error("Error while merging recording", e);
|
|
||||||
} finally {
|
} finally {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
recording.setStatus(STATUS.FINISHED);
|
recording.setStatus(STATUS.FINISHED);
|
||||||
|
@ -323,7 +311,6 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
t.setDaemon(true);
|
t.setDaemon(true);
|
||||||
t.setName("Segment Merger " + recording.getPath());
|
t.setName("Segment Merger " + recording.getPath());
|
||||||
t.start();
|
t.start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void download(Recording recording) throws IOException, ParseException, PlaylistException {
|
private void download(Recording recording) throws IOException, ParseException, PlaylistException {
|
||||||
|
|
|
@ -45,8 +45,11 @@ public class SettingsTab extends Tab {
|
||||||
private TextField username;
|
private TextField username;
|
||||||
private TextField server;
|
private TextField server;
|
||||||
private TextField port;
|
private TextField port;
|
||||||
|
private static final int CHECKBOX_MARGIN = 6;
|
||||||
private CheckBox loadResolution;
|
private CheckBox loadResolution;
|
||||||
private CheckBox secureCommunication;
|
private CheckBox secureCommunication = new CheckBox();
|
||||||
|
private CheckBox automerge = new CheckBox();
|
||||||
|
private CheckBox automergeKeepSegments = new CheckBox();
|
||||||
private PasswordField password;
|
private PasswordField password;
|
||||||
private RadioButton recordLocal;
|
private RadioButton recordLocal;
|
||||||
private RadioButton recordRemote;
|
private RadioButton recordRemote;
|
||||||
|
@ -107,7 +110,8 @@ 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);
|
Label l = new Label("Display stream resolution in overview");
|
||||||
|
layout.add(l, 0, ++row);
|
||||||
loadResolution = new CheckBox();
|
loadResolution = new CheckBox();
|
||||||
loadResolution.setSelected(Config.getInstance().getSettings().determineResolution);
|
loadResolution.setSelected(Config.getInstance().getSettings().determineResolution);
|
||||||
loadResolution.setOnAction((e) -> {
|
loadResolution.setOnAction((e) -> {
|
||||||
|
@ -116,9 +120,22 @@ public class SettingsTab extends Tab {
|
||||||
ThumbOverviewTab.queue.clear();
|
ThumbOverviewTab.queue.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
||||||
|
GridPane.setMargin(loadResolution, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
||||||
layout.add(loadResolution, 1, row);
|
layout.add(loadResolution, 1, row);
|
||||||
|
|
||||||
layout.add(new Label(), 0, ++row);
|
l = new Label("Auto-merge recordings");
|
||||||
|
layout.add(l, 0, ++row);
|
||||||
|
automerge.setSelected(Config.getInstance().getSettings().automerge);
|
||||||
|
automerge.setOnAction((e) -> Config.getInstance().getSettings().automerge = automerge.isSelected());
|
||||||
|
GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
||||||
|
GridPane.setMargin(automerge, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
||||||
|
layout.add(automerge, 1, row);
|
||||||
|
|
||||||
|
automergeKeepSegments.setText("Keep segments");
|
||||||
|
automergeKeepSegments.setOnAction((e) -> Config.getInstance().getSettings().automergeKeepSegments = automergeKeepSegments.isSelected());
|
||||||
|
GridPane.setMargin(automergeKeepSegments, new Insets(CHECKBOX_MARGIN, 0, 30, 0));
|
||||||
|
layout.add(automergeKeepSegments, 1, ++row);
|
||||||
|
|
||||||
layout.add(new Label("Record Location"), 0, ++row);
|
layout.add(new Label("Record Location"), 0, ++row);
|
||||||
recordLocation = new ToggleGroup();
|
recordLocation = new ToggleGroup();
|
||||||
|
@ -132,9 +149,7 @@ public class SettingsTab extends Tab {
|
||||||
layout.add(recordRemote, 2, row);
|
layout.add(recordRemote, 2, row);
|
||||||
recordLocation.selectedToggleProperty().addListener((e) -> {
|
recordLocation.selectedToggleProperty().addListener((e) -> {
|
||||||
Config.getInstance().getSettings().localRecording = recordLocal.isSelected();
|
Config.getInstance().getSettings().localRecording = recordLocal.isSelected();
|
||||||
server.setDisable(recordLocal.isSelected());
|
setRecordingMode(recordLocal.isSelected());
|
||||||
port.setDisable(recordLocal.isSelected());
|
|
||||||
|
|
||||||
Alert restart = new AutosizeAlert(AlertType.INFORMATION);
|
Alert restart = new AutosizeAlert(AlertType.INFORMATION);
|
||||||
restart.setTitle("Restart required");
|
restart.setTitle("Restart required");
|
||||||
restart.setHeaderText("Restart required");
|
restart.setHeaderText("Restart required");
|
||||||
|
@ -173,8 +188,8 @@ public class SettingsTab extends Tab {
|
||||||
GridPane.setColumnSpan(port, 2);
|
GridPane.setColumnSpan(port, 2);
|
||||||
layout.add(port, 1, row);
|
layout.add(port, 1, row);
|
||||||
|
|
||||||
layout.add(new Label("Require authentication"), 0, ++row);
|
l = new Label("Require authentication");
|
||||||
secureCommunication = new CheckBox();
|
layout.add(l, 0, ++row);
|
||||||
secureCommunication.setSelected(Config.getInstance().getSettings().requireAuthentication);
|
secureCommunication.setSelected(Config.getInstance().getSettings().requireAuthentication);
|
||||||
secureCommunication.setOnAction((e) -> {
|
secureCommunication.setOnAction((e) -> {
|
||||||
Config.getInstance().getSettings().requireAuthentication = secureCommunication.isSelected();
|
Config.getInstance().getSettings().requireAuthentication = secureCommunication.isSelected();
|
||||||
|
@ -196,11 +211,19 @@ public class SettingsTab extends Tab {
|
||||||
keyDialog.show();
|
keyDialog.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
||||||
|
GridPane.setMargin(secureCommunication, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
||||||
layout.add(secureCommunication, 1, row);
|
layout.add(secureCommunication, 1, row);
|
||||||
|
|
||||||
|
setRecordingMode(recordLocal.isSelected());
|
||||||
|
}
|
||||||
|
|
||||||
server.setDisable(recordLocal.isSelected());
|
private void setRecordingMode(boolean local) {
|
||||||
port.setDisable(recordLocal.isSelected());
|
server.setDisable(local);
|
||||||
|
port.setDisable(local);
|
||||||
|
secureCommunication.setDisable(local);
|
||||||
|
automerge.setDisable(!local);
|
||||||
|
automergeKeepSegments.setDisable(!local);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChangeListener<? super Boolean> createRecordingsDirectoryFocusListener() {
|
private ChangeListener<? super Boolean> createRecordingsDirectoryFocusListener() {
|
||||||
|
|
Loading…
Reference in New Issue