forked from j62/ctbrec
1
0
Fork 0

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:
0xboobface 2019-06-02 19:53:25 +02:00
parent da486bbf4a
commit b136fce0db
9 changed files with 143 additions and 60 deletions

View File

@ -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

View File

@ -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;

View File

@ -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();

View File

@ -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"),

View File

@ -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);
}
} }

View File

@ -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;
} }

View File

@ -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());
}
}
}
} }

View File

@ -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());

View File

@ -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\"}";