forked from j62/ctbrec
Add menu entry to rerun the post-processing script
In client/server mode this will trigger the playlist generation and post-processing. In standalone mode only the post-processing will be executed.
This commit is contained in:
parent
da486bbf4a
commit
b136fce0db
|
@ -1,6 +1,10 @@
|
||||||
1.21.2
|
2.0.0
|
||||||
========================
|
========================
|
||||||
|
* Complete rewrite of the recording code
|
||||||
* Added split recordings for the server
|
* Added split recordings for the server
|
||||||
|
* Added menu entry to rerun the post-processing script
|
||||||
|
* Fix: CamSoda overview
|
||||||
|
* Fix: BongaCams model online check
|
||||||
* Fix: Downloads not working in client/server setup (regression in last version)
|
* Fix: Downloads not working in client/server setup (regression in last version)
|
||||||
* Fix: post-processing for split recordings
|
* Fix: post-processing for split recordings
|
||||||
* Fix: All recordings are finished properly on shutdown (with playlist
|
* Fix: All recordings are finished properly on shutdown (with playlist
|
||||||
|
|
|
@ -78,9 +78,6 @@ public class JavaFxRecording extends Recording {
|
||||||
case POST_PROCESSING:
|
case POST_PROCESSING:
|
||||||
statusProperty.set("post-processing");
|
statusProperty.set("post-processing");
|
||||||
break;
|
break;
|
||||||
case STOPPED:
|
|
||||||
statusProperty.set("stopped");
|
|
||||||
break;
|
|
||||||
case DELETED:
|
case DELETED:
|
||||||
statusProperty.set("deleted");
|
statusProperty.set("deleted");
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -373,11 +373,12 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
contextMenu.setAutoHide(true);
|
contextMenu.setAutoHide(true);
|
||||||
contextMenu.setAutoFix(true);
|
contextMenu.setAutoFix(true);
|
||||||
|
|
||||||
|
JavaFxRecording first = recordings.get(0);
|
||||||
MenuItem openInPlayer = new MenuItem("Open in Player");
|
MenuItem openInPlayer = new MenuItem("Open in Player");
|
||||||
openInPlayer.setOnAction((e) -> {
|
openInPlayer.setOnAction((e) -> {
|
||||||
play(recordings.get(0));
|
play(recordings.get(0));
|
||||||
});
|
});
|
||||||
if(recordings.get(0).getStatus() == State.FINISHED || Config.getInstance().getSettings().localRecording) {
|
if(first.getStatus() == State.FINISHED || Config.getInstance().getSettings().localRecording) {
|
||||||
contextMenu.getItems().add(openInPlayer);
|
contextMenu.getItems().add(openInPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,14 +400,14 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
deleteRecording.setOnAction((e) -> {
|
deleteRecording.setOnAction((e) -> {
|
||||||
delete(recordings);
|
delete(recordings);
|
||||||
});
|
});
|
||||||
if(recordings.get(0).getStatus() == State.FINISHED || recordings.size() > 1) {
|
if(first.getStatus() == State.FINISHED || first.getStatus() == State.WAITING || first.getStatus() == State.FAILED || recordings.size() > 1) {
|
||||||
contextMenu.getItems().add(deleteRecording);
|
contextMenu.getItems().add(deleteRecording);
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuItem openDir = new MenuItem("Open directory");
|
MenuItem openDir = new MenuItem("Open directory");
|
||||||
openDir.setOnAction((e) -> {
|
openDir.setOnAction((e) -> {
|
||||||
String recordingsDir = Config.getInstance().getSettings().recordingsDir;
|
String recordingsDir = Config.getInstance().getSettings().recordingsDir;
|
||||||
String path = recordings.get(0).getPath();
|
String path = first.getPath();
|
||||||
File tsFile = new File(recordingsDir, path);
|
File tsFile = new File(recordingsDir, path);
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
DesktopIntegration.open(tsFile.getParent());
|
DesktopIntegration.open(tsFile.getParent());
|
||||||
|
@ -419,24 +420,44 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
MenuItem downloadRecording = new MenuItem("Download");
|
MenuItem downloadRecording = new MenuItem("Download");
|
||||||
downloadRecording.setOnAction((e) -> {
|
downloadRecording.setOnAction((e) -> {
|
||||||
try {
|
try {
|
||||||
download(recordings.get(0));
|
download(first);
|
||||||
} catch (IOException | ParseException | PlaylistException e1) {
|
} catch (IOException | ParseException | PlaylistException e1) {
|
||||||
showErrorDialog("Error while downloading recording", "The recording could not be downloaded", e1);
|
showErrorDialog("Error while downloading recording", "The recording could not be downloaded", e1);
|
||||||
LOG.error("Error while downloading recording", e1);
|
LOG.error("Error while downloading recording", e1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!Config.getInstance().getSettings().localRecording && recordings.get(0).getStatus() == State.FINISHED) {
|
if (!Config.getInstance().getSettings().localRecording && first.getStatus() == State.FINISHED) {
|
||||||
contextMenu.getItems().add(downloadRecording);
|
contextMenu.getItems().add(downloadRecording);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MenuItem rerunPostProcessing = new MenuItem("Rerun Post-Processing");
|
||||||
|
rerunPostProcessing.setOnAction((e) -> {
|
||||||
|
triggerPostProcessing(first);
|
||||||
|
});
|
||||||
|
if (first.getStatus() == State.FINISHED || first.getStatus() == State.WAITING) {
|
||||||
|
contextMenu.getItems().add(rerunPostProcessing);
|
||||||
|
}
|
||||||
|
|
||||||
if(recordings.size() > 1) {
|
if(recordings.size() > 1) {
|
||||||
openInPlayer.setDisable(true);
|
openInPlayer.setDisable(true);
|
||||||
openDir.setDisable(true);
|
openDir.setDisable(true);
|
||||||
|
rerunPostProcessing.setDisable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void triggerPostProcessing(JavaFxRecording first) {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
recorder.rerunPostProcessing(first.getDelegate());
|
||||||
|
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
||||||
|
showErrorDialog("Error while starting post-processing", "The post-processing could not be started", e1);
|
||||||
|
LOG.error("Error while starting post-processing", e1);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
private void download(Recording recording) throws IOException, ParseException, PlaylistException {
|
private void download(Recording recording) throws IOException, ParseException, PlaylistException {
|
||||||
String filename = recording.getPath().substring(1).replaceAll("/", "-") + ".ts";
|
String filename = recording.getPath().substring(1).replaceAll("/", "-") + ".ts";
|
||||||
FileChooser chooser = new FileChooser();
|
FileChooser chooser = new FileChooser();
|
||||||
|
@ -455,9 +476,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
URL url = new URL(hlsBase + recording.getPath() + "/playlist.m3u8");
|
URL url = new URL(hlsBase + recording.getPath() + "/playlist.m3u8");
|
||||||
LOG.info("Downloading {}", recording.getPath());
|
LOG.info("Downloading {}", recording.getPath());
|
||||||
|
|
||||||
Thread t = new Thread() {
|
Thread t = new Thread(() -> {
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
try {
|
||||||
MergedHlsDownload download = new MergedHlsDownload(CamrecApplication.httpClient);
|
MergedHlsDownload download = new MergedHlsDownload(CamrecApplication.httpClient);
|
||||||
download.start(url.toString(), target, (progress) -> {
|
download.start(url.toString(), target, (progress) -> {
|
||||||
|
@ -487,8 +506,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
};
|
|
||||||
t.setDaemon(true);
|
t.setDaemon(true);
|
||||||
t.setName("Download Thread " + recording.getPath());
|
t.setName("Download Thread " + recording.getPath());
|
||||||
t.start();
|
t.start();
|
||||||
|
|
|
@ -27,7 +27,6 @@ public class Recording {
|
||||||
|
|
||||||
public static enum State {
|
public static enum State {
|
||||||
RECORDING("recording"),
|
RECORDING("recording"),
|
||||||
STOPPED("stopped"),
|
|
||||||
GENERATING_PLAYLIST("generating playlist"),
|
GENERATING_PLAYLIST("generating playlist"),
|
||||||
POST_PROCESSING("post-processing"),
|
POST_PROCESSING("post-processing"),
|
||||||
FINISHED("finished"),
|
FINISHED("finished"),
|
||||||
|
|
|
@ -109,24 +109,7 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
if (recording.getStatus() == State.WAITING) {
|
if (recording.getStatus() == State.WAITING) {
|
||||||
LOG.debug("Download finished for {} -> Starting post-processing", recording.getModel().getName());
|
LOG.debug("Download finished for {} -> Starting post-processing", recording.getModel().getName());
|
||||||
ppPool.submit(() -> {
|
submitPostProcessingJob(recording);
|
||||||
try {
|
|
||||||
setRecordingStatus(recording, State.POST_PROCESSING);
|
|
||||||
recordingManager.saveRecording(recording);
|
|
||||||
recording.postprocess();
|
|
||||||
setRecordingStatus(recording, State.FINISHED);
|
|
||||||
recordingManager.saveRecording(recording);
|
|
||||||
deleteIfTooShort(recording);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Error while post-processing recording {}", recording, e);
|
|
||||||
recording.setStatus(State.FAILED);
|
|
||||||
try {
|
|
||||||
recordingManager.saveRecording(recording);
|
|
||||||
} catch (IOException e1) {
|
|
||||||
LOG.error("Couldn't update recording state for recording {}", recording, e1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// check, if we have to restart the recording
|
// check, if we have to restart the recording
|
||||||
Model model = recording.getModel();
|
Model model = recording.getModel();
|
||||||
|
@ -158,6 +141,27 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
}, 1, 1, TimeUnit.SECONDS);
|
}, 1, 1, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void submitPostProcessingJob(Recording recording) {
|
||||||
|
ppPool.submit(() -> {
|
||||||
|
try {
|
||||||
|
setRecordingStatus(recording, State.POST_PROCESSING);
|
||||||
|
recordingManager.saveRecording(recording);
|
||||||
|
recording.postprocess();
|
||||||
|
setRecordingStatus(recording, State.FINISHED);
|
||||||
|
recordingManager.saveRecording(recording);
|
||||||
|
deleteIfTooShort(recording);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Error while post-processing recording {}", recording, e);
|
||||||
|
recording.setStatus(State.FAILED);
|
||||||
|
try {
|
||||||
|
recordingManager.saveRecording(recording);
|
||||||
|
} catch (IOException e1) {
|
||||||
|
LOG.error("Couldn't update recording state for recording {}", recording, e1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setRecordingStatus(Recording recording, State status) {
|
private void setRecordingStatus(Recording recording, State status) {
|
||||||
recording.setStatus(status);
|
recording.setStatus(status);
|
||||||
RecordingStateChangedEvent evt = new RecordingStateChangedEvent(recording.getDownload().getTarget(), status, recording.getModel(),
|
RecordingStateChangedEvent evt = new RecordingStateChangedEvent(recording.getDownload().getTarget(), status, recording.getModel(),
|
||||||
|
@ -603,4 +607,19 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rerunPostProcessing(Recording recording) {
|
||||||
|
List<Recording> recordings = recordingManager.getAll();
|
||||||
|
for (Recording other : recordings) {
|
||||||
|
if(other.equals(recording)) {
|
||||||
|
Download download = other.getModel().createDownload();
|
||||||
|
download.init(Config.getInstance(), other.getModel());
|
||||||
|
other.setDownload(download);
|
||||||
|
submitPostProcessingJob(other);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.error("Recording {} not found. Can't rerun post-processing", recording);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,4 +82,15 @@ public interface Recorder {
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public long getFreeSpaceBytes() throws IOException;
|
public long getFreeSpaceBytes() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regenerate the playlist for a recording. This is helpful, if the
|
||||||
|
* playlist is corrupt or hasn't been generated for whatever reason
|
||||||
|
* @param recording
|
||||||
|
* @throws IllegalStateException
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* @throws InvalidKeyException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void rerunPostProcessing(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
private JsonAdapter<RecordingListResponse> recordingListResponseAdapter = moshi.adapter(RecordingListResponse.class);
|
private JsonAdapter<RecordingListResponse> recordingListResponseAdapter = moshi.adapter(RecordingListResponse.class);
|
||||||
private JsonAdapter<ModelRequest> modelRequestAdapter = moshi.adapter(ModelRequest.class);
|
private JsonAdapter<ModelRequest> modelRequestAdapter = moshi.adapter(ModelRequest.class);
|
||||||
private JsonAdapter<RecordingRequest> recordingRequestAdapter = moshi.adapter(RecordingRequest.class);
|
private JsonAdapter<RecordingRequest> recordingRequestAdapter = moshi.adapter(RecordingRequest.class);
|
||||||
|
private JsonAdapter<SimpleResponse> simpleResponseAdapter = moshi.adapter(SimpleResponse.class);
|
||||||
|
|
||||||
private List<Model> models = Collections.emptyList();
|
private List<Model> models = Collections.emptyList();
|
||||||
private List<Model> onlineModels = Collections.emptyList();
|
private List<Model> onlineModels = Collections.emptyList();
|
||||||
|
@ -321,6 +322,11 @@ public class RemoteRecorder implements Recorder {
|
||||||
public List<Model> models;
|
public List<Model> models;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class SimpleResponse {
|
||||||
|
public String status;
|
||||||
|
public String msg;
|
||||||
|
}
|
||||||
|
|
||||||
private static class RecordingListResponse {
|
private static class RecordingListResponse {
|
||||||
public String status;
|
public String status;
|
||||||
public String msg;
|
public String msg;
|
||||||
|
@ -459,4 +465,27 @@ public class RemoteRecorder implements Recorder {
|
||||||
public long getFreeSpaceBytes() {
|
public long getFreeSpaceBytes() {
|
||||||
return spaceFree;
|
return spaceFree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rerunPostProcessing(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
||||||
|
RecordingRequest recReq = new RecordingRequest("rerunPostProcessing", recording);
|
||||||
|
String msg = recordingRequestAdapter.toJson(recReq);
|
||||||
|
LOG.debug(msg);
|
||||||
|
RequestBody body = RequestBody.create(JSON, msg);
|
||||||
|
Request.Builder builder = new Request.Builder().url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
||||||
|
.post(body);
|
||||||
|
addHmacIfNeeded(msg, builder);
|
||||||
|
Request request = builder.build();
|
||||||
|
try (Response response = client.execute(request)) {
|
||||||
|
String json = response.body().string();
|
||||||
|
SimpleResponse resp = simpleResponseAdapter.fromJson(json);
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
if (!resp.status.equals("success")) {
|
||||||
|
throw new IOException("Couldn't start post-processing for recording: " + resp.msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new HttpException(response.code(), response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,12 +192,13 @@ public abstract class AbstractHlsDownload implements Download {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postprocess(Recording recording) {
|
public void postprocess(Recording recording) {
|
||||||
runPostProcessingScript(recording.getAbsoluteFile());
|
runPostProcessingScript(recording);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runPostProcessingScript(File target) {
|
private void runPostProcessingScript(Recording recording) {
|
||||||
String postProcessing = Config.getInstance().getSettings().postProcessing;
|
String postProcessing = Config.getInstance().getSettings().postProcessing;
|
||||||
if (postProcessing != null && !postProcessing.isEmpty()) {
|
if (postProcessing != null && !postProcessing.isEmpty()) {
|
||||||
|
File target = recording.getAbsoluteFile();
|
||||||
Runtime rt = Runtime.getRuntime();
|
Runtime rt = Runtime.getRuntime();
|
||||||
try {
|
try {
|
||||||
String[] args = new String[] {
|
String[] args = new String[] {
|
||||||
|
@ -206,7 +207,7 @@ public abstract class AbstractHlsDownload implements Download {
|
||||||
target.getAbsolutePath(),
|
target.getAbsolutePath(),
|
||||||
getModel().getName(),
|
getModel().getName(),
|
||||||
getModel().getSite().getName(),
|
getModel().getSite().getName(),
|
||||||
Long.toString(getStartTime().getEpochSecond())
|
Long.toString(recording.getStartDate().getEpochSecond())
|
||||||
};
|
};
|
||||||
LOG.debug("Running {}", Arrays.toString(args));
|
LOG.debug("Running {}", Arrays.toString(args));
|
||||||
Process process = rt.exec(args, OS.getEnvironment());
|
Process process = rt.exec(args, OS.getEnvironment());
|
||||||
|
|
|
@ -129,6 +129,11 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
||||||
resp.getWriter().write(recAdapter.toJson(request.recording));
|
resp.getWriter().write(recAdapter.toJson(request.recording));
|
||||||
resp.getWriter().write("]}");
|
resp.getWriter().write("]}");
|
||||||
break;
|
break;
|
||||||
|
case "rerunPostProcessing":
|
||||||
|
recorder.rerunPostProcessing(request.recording);
|
||||||
|
recAdapter = moshi.adapter(Recording.class);
|
||||||
|
resp.getWriter().write("{\"status\": \"success\", \"msg\": \"Post-Processing triggered\"}");
|
||||||
|
break;
|
||||||
case "switch":
|
case "switch":
|
||||||
recorder.switchStreamSource(request.model);
|
recorder.switchStreamSource(request.model);
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Resolution switched\"}";
|
response = "{\"status\": \"success\", \"msg\": \"Resolution switched\"}";
|
||||||
|
|
Loading…
Reference in New Issue