From b136fce0dbe3d380436dc4afaea58839f313e5f3 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Sun, 2 Jun 2019 19:53:25 +0200 Subject: [PATCH] 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. --- CHANGELOG.md | 6 +- .../main/java/ctbrec/ui/JavaFxRecording.java | 3 - .../main/java/ctbrec/ui/RecordingsTab.java | 86 +++++++++++-------- common/src/main/java/ctbrec/Recording.java | 1 - .../ctbrec/recorder/NextGenLocalRecorder.java | 55 ++++++++---- .../main/java/ctbrec/recorder/Recorder.java | 11 +++ .../java/ctbrec/recorder/RemoteRecorder.java | 29 +++++++ .../download/AbstractHlsDownload.java | 7 +- .../recorder/server/RecorderServlet.java | 5 ++ 9 files changed, 143 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 594e32c7..cd86a317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ -1.21.2 +2.0.0 ======================== +* Complete rewrite of the recording code * 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: post-processing for split recordings * Fix: All recordings are finished properly on shutdown (with playlist diff --git a/client/src/main/java/ctbrec/ui/JavaFxRecording.java b/client/src/main/java/ctbrec/ui/JavaFxRecording.java index 7062bf0b..76508b28 100644 --- a/client/src/main/java/ctbrec/ui/JavaFxRecording.java +++ b/client/src/main/java/ctbrec/ui/JavaFxRecording.java @@ -78,9 +78,6 @@ public class JavaFxRecording extends Recording { case POST_PROCESSING: statusProperty.set("post-processing"); break; - case STOPPED: - statusProperty.set("stopped"); - break; case DELETED: statusProperty.set("deleted"); break; diff --git a/client/src/main/java/ctbrec/ui/RecordingsTab.java b/client/src/main/java/ctbrec/ui/RecordingsTab.java index 5877c845..4dabbd9a 100644 --- a/client/src/main/java/ctbrec/ui/RecordingsTab.java +++ b/client/src/main/java/ctbrec/ui/RecordingsTab.java @@ -373,11 +373,12 @@ public class RecordingsTab extends Tab implements TabSelectionListener { contextMenu.setAutoHide(true); contextMenu.setAutoFix(true); + JavaFxRecording first = recordings.get(0); MenuItem openInPlayer = new MenuItem("Open in Player"); openInPlayer.setOnAction((e) -> { 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); } @@ -399,14 +400,14 @@ public class RecordingsTab extends Tab implements TabSelectionListener { deleteRecording.setOnAction((e) -> { 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); } MenuItem openDir = new MenuItem("Open directory"); openDir.setOnAction((e) -> { String recordingsDir = Config.getInstance().getSettings().recordingsDir; - String path = recordings.get(0).getPath(); + String path = first.getPath(); File tsFile = new File(recordingsDir, path); new Thread(() -> { DesktopIntegration.open(tsFile.getParent()); @@ -419,24 +420,44 @@ public class RecordingsTab extends Tab implements TabSelectionListener { MenuItem downloadRecording = new MenuItem("Download"); downloadRecording.setOnAction((e) -> { try { - download(recordings.get(0)); + download(first); } catch (IOException | ParseException | PlaylistException e1) { showErrorDialog("Error while downloading recording", "The recording could not be downloaded", 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); } + 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) { openInPlayer.setDisable(true); openDir.setDisable(true); + rerunPostProcessing.setDisable(true); } 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 { String filename = recording.getPath().substring(1).replaceAll("/", "-") + ".ts"; FileChooser chooser = new FileChooser(); @@ -455,40 +476,37 @@ public class RecordingsTab extends Tab implements TabSelectionListener { URL url = new URL(hlsBase + recording.getPath() + "/playlist.m3u8"); LOG.info("Downloading {}", recording.getPath()); - Thread t = new Thread() { - @Override - public void run() { - try { - MergedHlsDownload download = new MergedHlsDownload(CamrecApplication.httpClient); - download.start(url.toString(), target, (progress) -> { - Platform.runLater(() -> { - if (progress == 100) { - recording.setStatus(FINISHED); - recording.setProgress(-1); - LOG.debug("Download finished for recording {}", recording.getPath()); - } else { - recording.setStatus(DOWNLOADING); - recording.setProgress(progress); - } - }); - }); - } catch (FileNotFoundException e) { - showErrorDialog("Error while downloading recording", "The target file couldn't be created", e); - LOG.error("Error while downloading recording", e); - } catch (IOException e) { - showErrorDialog("Error while downloading recording", "The recording could not be downloaded", e); - LOG.error("Error while downloading recording", e); - } finally { - Platform.runLater(new Runnable() { - @Override - public void run() { + Thread t = new Thread(() -> { + try { + MergedHlsDownload download = new MergedHlsDownload(CamrecApplication.httpClient); + download.start(url.toString(), target, (progress) -> { + Platform.runLater(() -> { + if (progress == 100) { recording.setStatus(FINISHED); recording.setProgress(-1); + LOG.debug("Download finished for recording {}", recording.getPath()); + } else { + recording.setStatus(DOWNLOADING); + recording.setProgress(progress); } }); - } + }); + } catch (FileNotFoundException e) { + showErrorDialog("Error while downloading recording", "The target file couldn't be created", e); + LOG.error("Error while downloading recording", e); + } catch (IOException e) { + showErrorDialog("Error while downloading recording", "The recording could not be downloaded", e); + LOG.error("Error while downloading recording", e); + } finally { + Platform.runLater(new Runnable() { + @Override + public void run() { + recording.setStatus(FINISHED); + recording.setProgress(-1); + } + }); } - }; + }); t.setDaemon(true); t.setName("Download Thread " + recording.getPath()); t.start(); diff --git a/common/src/main/java/ctbrec/Recording.java b/common/src/main/java/ctbrec/Recording.java index 9d9be0eb..e7701c1c 100644 --- a/common/src/main/java/ctbrec/Recording.java +++ b/common/src/main/java/ctbrec/Recording.java @@ -27,7 +27,6 @@ public class Recording { public static enum State { RECORDING("recording"), - STOPPED("stopped"), GENERATING_PLAYLIST("generating playlist"), POST_PROCESSING("post-processing"), FINISHED("finished"), diff --git a/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java b/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java index acc60203..c456009a 100644 --- a/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java +++ b/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java @@ -109,24 +109,7 @@ public class NextGenLocalRecorder implements Recorder { } if (recording.getStatus() == State.WAITING) { LOG.debug("Download finished for {} -> Starting post-processing", recording.getModel().getName()); - 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); - } - } - }); + submitPostProcessingJob(recording); // check, if we have to restart the recording Model model = recording.getModel(); @@ -158,6 +141,27 @@ public class NextGenLocalRecorder implements Recorder { }, 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) { recording.setStatus(status); 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 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); + } } diff --git a/common/src/main/java/ctbrec/recorder/Recorder.java b/common/src/main/java/ctbrec/recorder/Recorder.java index 1306a6c6..06dbea27 100644 --- a/common/src/main/java/ctbrec/recorder/Recorder.java +++ b/common/src/main/java/ctbrec/recorder/Recorder.java @@ -82,4 +82,15 @@ public interface Recorder { * @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; } diff --git a/common/src/main/java/ctbrec/recorder/RemoteRecorder.java b/common/src/main/java/ctbrec/recorder/RemoteRecorder.java index 4dd6cb51..3454a588 100644 --- a/common/src/main/java/ctbrec/recorder/RemoteRecorder.java +++ b/common/src/main/java/ctbrec/recorder/RemoteRecorder.java @@ -47,6 +47,7 @@ public class RemoteRecorder implements Recorder { private JsonAdapter recordingListResponseAdapter = moshi.adapter(RecordingListResponse.class); private JsonAdapter modelRequestAdapter = moshi.adapter(ModelRequest.class); private JsonAdapter recordingRequestAdapter = moshi.adapter(RecordingRequest.class); + private JsonAdapter simpleResponseAdapter = moshi.adapter(SimpleResponse.class); private List models = Collections.emptyList(); private List onlineModels = Collections.emptyList(); @@ -321,6 +322,11 @@ public class RemoteRecorder implements Recorder { public List models; } + private static class SimpleResponse { + public String status; + public String msg; + } + private static class RecordingListResponse { public String status; public String msg; @@ -459,4 +465,27 @@ public class RemoteRecorder implements Recorder { public long getFreeSpaceBytes() { 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()); + } + } + } } diff --git a/common/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java b/common/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java index 8e006798..a69915cd 100644 --- a/common/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java @@ -192,12 +192,13 @@ public abstract class AbstractHlsDownload implements Download { @Override 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; if (postProcessing != null && !postProcessing.isEmpty()) { + File target = recording.getAbsoluteFile(); Runtime rt = Runtime.getRuntime(); try { String[] args = new String[] { @@ -206,7 +207,7 @@ public abstract class AbstractHlsDownload implements Download { target.getAbsolutePath(), getModel().getName(), getModel().getSite().getName(), - Long.toString(getStartTime().getEpochSecond()) + Long.toString(recording.getStartDate().getEpochSecond()) }; LOG.debug("Running {}", Arrays.toString(args)); Process process = rt.exec(args, OS.getEnvironment()); diff --git a/server/src/main/java/ctbrec/recorder/server/RecorderServlet.java b/server/src/main/java/ctbrec/recorder/server/RecorderServlet.java index e5500147..3fc8a959 100644 --- a/server/src/main/java/ctbrec/recorder/server/RecorderServlet.java +++ b/server/src/main/java/ctbrec/recorder/server/RecorderServlet.java @@ -129,6 +129,11 @@ public class RecorderServlet extends AbstractCtbrecServlet { resp.getWriter().write(recAdapter.toJson(request.recording)); resp.getWriter().write("]}"); break; + case "rerunPostProcessing": + recorder.rerunPostProcessing(request.recording); + recAdapter = moshi.adapter(Recording.class); + resp.getWriter().write("{\"status\": \"success\", \"msg\": \"Post-Processing triggered\"}"); + break; case "switch": recorder.switchStreamSource(request.model); response = "{\"status\": \"success\", \"msg\": \"Resolution switched\"}";