Skip to content

Commit

Permalink
Add VS message handling
Browse files Browse the repository at this point in the history
Closes #9
  • Loading branch information
quietvoid committed Feb 28, 2022
1 parent 077ce68 commit 14125fe
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 76 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Also, frame props are easily accessible.
Main parts of the UI:
- A window with the current state, including access to frame props and settings.
- A bottom panel with a slider to change frame quickly, as well as the clip info.
- An error window for VapourSynth messages or errors while rendering.

See more from the [UI documentation](UI.md).

Expand Down
9 changes: 9 additions & 0 deletions UI.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,12 @@ Original frame props require an extra frame request, so they can be obtained on

Provides a slider to seek through frames, as well as an input box to enter a specific frame.
Various informations about the clip.

 

## Error/message window

![Error window](/assets/04logs.jpg?raw=true "Error window")

Provides info about the different messages from VapourSynth, and fatal errors.
Cleared on reload or window close.
Binary file added assets/04logs.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 60 additions & 40 deletions src/app/ui/error_window.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,51 @@
use super::{egui, VSPreviewer};
use eframe::egui::RichText;
use eframe::egui::{RichText, Ui};

pub struct ErrorWindowUi {}

impl ErrorWindowUi {
pub fn ui(pv: &mut VSPreviewer, ctx: &egui::Context) {
let mut vs_errors = None;
if let Some(mut script_lock) = pv.script.try_lock() {
if let Some(errors) = script_lock.vs_error.as_mut() {
if !errors.is_empty() {
vs_errors = Some(errors.clone());
errors.clear();
let mut vs_messages = None;

if let Some(script_lock) = pv.script.try_lock() {
if let Some(mut messages) = script_lock.vs_messages.try_lock() {
if !messages.is_empty() {
vs_messages = Some(messages.clone());
messages.clear();
}
}
}

if let Some(errors) = &vs_errors {
pv.add_errors("vapoursynth", errors);
if let Some(messages) = &vs_messages {
// Keep critical errors to avoid rendering image
let mapped: Vec<String> = messages
.iter()
.map(|e| format!("{:?}: {}", &e.message_type, &e.message))
.collect();

pv.add_errors("vapoursynth", &mapped);
}

if !pv.errors.is_empty() {
egui::Window::new(RichText::new("Some errors occurred!").size(20.0))
.collapsible(false)
.resizable(true)
.auto_sized()
.anchor(egui::Align2::CENTER_CENTER, egui::Vec2::new(0.0, -300.0))
.collapsible(false)
.default_pos((pv.available_size.x / 2.0, pv.available_size.y / 2.0))
.show(ctx, |ui| {
if let Some(errors) = pv.errors.get("vapoursynth") {
let header = RichText::new("VapourSynth errors").size(20.0);
egui::CollapsingHeader::new(header).show(ui, |ui| {
for (i, e) in errors.iter().enumerate() {
let value = format!("{}. {e}", i + 1);
ui.label(RichText::new(value).size(18.0));
}
});
}
Self::draw_error_section(pv, ui, "vapoursynth", "VapourSynth messages");

if let Some(errors) = pv.errors.get("callbacks") {
let header = RichText::new("Error fetching frames or reloading").size(20.0);
egui::CollapsingHeader::new(header).show(ui, |ui| {
for (i, e) in errors.iter().enumerate() {
let value = format!("{}. {e}", i + 1);
ui.label(RichText::new(value).size(18.0));
}
});
}

if let Some(errors) = pv.errors.get("preview") {
let header = RichText::new("Error rendering the preview or GUI").size(20.0);
egui::CollapsingHeader::new(header).show(ui, |ui| {
for (i, e) in errors.iter().enumerate() {
let value = format!("{}. {e}", i + 1);
ui.label(RichText::new(value).size(18.0));
}
});
}
Self::draw_error_section(
pv,
ui,
"callbacks",
"Error fetching frames or reloading",
);
Self::draw_error_section(
pv,
ui,
"preview",
"Error rendering the preview or GUI",
);

ui.separator();
ui.add_space(10.0);
Expand All @@ -74,4 +65,33 @@ impl ErrorWindowUi {
});
}
}

pub fn draw_error_section(pv: &mut VSPreviewer, ui: &mut Ui, key: &str, header: &str) {
if let Some(errors) = pv.errors.get(key) {
let header = RichText::new(header).size(20.0);
egui::CollapsingHeader::new(header).show(ui, |ui| {
for (i, e) in errors.iter().enumerate() {
Self::draw_error_label(ui, format!("{}. {e}", i + 1));
}
});
}
}

pub fn draw_error_label(ui: &mut Ui, value: String) {
let max_size = value.len().min(75);

let final_text = if value.len() > 75 {
let trimmed = value[..max_size].replace('\n', " ");

format!("{} ...", trimmed)
} else {
value.trim().to_string()
};

let res = ui.label(RichText::new(final_text).size(18.0));

if value.len() > 75 {
res.on_hover_text(RichText::new(value).size(16.0));
}
}
}
3 changes: 2 additions & 1 deletion src/app/ui/preview_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ impl UiPreviewImage {
};
}

if !painted_image && pv.errors.is_empty() {
// Show loading when reloading or when no image and errors cleared
if pv.reload_data.is_some() || (!painted_image && pv.errors.is_empty()) {
ui.add(egui::Spinner::new().size(200.0));
}
});
Expand Down
77 changes: 46 additions & 31 deletions src/app/vs_previewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,16 @@ impl VSPreviewer {

// Always reloads the script
pub fn reload(&mut self, frame: epi::Frame) {
if self.reload_data.is_some() {
return;
}

let script = self.script.clone();

if !self.errors.is_empty() {
self.errors.clear();
}

self.reload_data = Some(poll_promise::Promise::spawn_thread(
"initialization/reload",
move || {
Expand Down Expand Up @@ -187,43 +195,50 @@ impl VSPreviewer {

pub fn check_reload_finish(&mut self) -> Result<()> {
if let Some(promise) = &self.reload_data {
if let Some(Some(outputs)) = promise.ready() {
self.outputs = outputs
.iter()
.map(|(key, o)| {
let new = PreviewOutput {
vsoutput: o.clone(),
..Default::default()
};

(*key, new)
})
.collect();

if !self.outputs.contains_key(&self.state.cur_output) {
// Fallback to first output in order
let mut keys: Vec<&i32> = self.outputs.keys().collect();
keys.sort();
if let Some(promise_res) = promise.ready() {
self.outputs.clear();

if let Some(outputs) = promise_res {
self.outputs = outputs
.iter()
.map(|(key, o)| {
let new = PreviewOutput {
vsoutput: o.clone(),
..Default::default()
};

(*key, new)
})
.collect();

if !self.outputs.contains_key(&self.state.cur_output) {
// Fallback to first output in order
let mut keys: Vec<&i32> = self.outputs.keys().collect();
keys.sort();

self.state.cur_output =
**keys.first().ok_or(anyhow!("No outputs available"))?;
}

self.state.cur_output =
**keys.first().ok_or(anyhow!("No outputs available"))?;
}
let output = self
.outputs
.get_mut(&self.state.cur_output)
.ok_or(anyhow!("outputs reload: Invalid current output key"))?;
let node_info = &output.vsoutput.node_info;

let output = self
.outputs
.get_mut(&self.state.cur_output)
.ok_or(anyhow!("outputs reload: Invalid current output key"))?;
let node_info = &output.vsoutput.node_info;
self.reload_data = None;
self.last_output_key = self.state.cur_output;

self.reload_data = None;
self.last_output_key = self.state.cur_output;
if self.state.cur_frame_no >= node_info.num_frames {
self.state.cur_frame_no = node_info.num_frames - 1;
}

if self.state.cur_frame_no >= node_info.num_frames {
self.state.cur_frame_no = node_info.num_frames - 1;
// Fetch a frame for new current output
self.rerender = true;
}

// Fetch a frame for new current output
self.rerender = true;
// Reset reload data even if errored
self.reload_data = None;
}
}

Expand Down
51 changes: 47 additions & 4 deletions src/vs_handler/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use parking_lot::Mutex;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::{anyhow, bail, Result};
use vapoursynth::api::MessageHandlerId;
use vapoursynth::prelude::*;

use crate::utils::frame_to_dynimage;
Expand All @@ -23,7 +26,10 @@ pub struct PreviewedScript {
#[serde(skip)]
env: Option<Environment>,
#[serde(skip)]
pub vs_error: Option<Vec<String>>,
message_handler_id: Option<MessageHandlerId>,

#[serde(skip)]
pub vs_messages: Arc<Mutex<Vec<VSMessage>>>,
}

#[derive(Default, Clone, Debug)]
Expand All @@ -32,6 +38,12 @@ pub struct VSOutput {
pub node_info: VSNode,
}

#[derive(Clone, Debug)]
pub struct VSMessage {
pub message_type: MessageType,
pub message: String,
}

impl PreviewedScript {
pub fn new(script_path: PathBuf) -> Self {
let mut script_dir = script_path.clone();
Expand All @@ -46,7 +58,8 @@ impl PreviewedScript {
script_file,
script_dir,
env: None,
vs_error: None,
message_handler_id: None,
vs_messages: Arc::new(Mutex::new(Vec::new())),
}
}

Expand All @@ -59,6 +72,23 @@ impl PreviewedScript {
self.env.get_or_insert(Environment::new()?)
};

if self.message_handler_id.is_none() {
let api = API::get().ok_or(anyhow!("Couldn't retrieve API object"))?;

let vserrors = self.vs_messages.clone();
let id = api.add_message_handler(move |message_type, message| {
let message = message.to_str().unwrap().to_string();

let mut errors = vserrors.lock();
errors.push(VSMessage {
message_type,
message,
});
});

self.message_handler_id = Some(id);
}

env.eval_file(&self.script_file, EvalFlags::SetWorkingDir)?;

Ok(())
Expand Down Expand Up @@ -179,8 +209,21 @@ impl PreviewedScript {

pub fn add_vs_error<T>(&mut self, res: &Result<T>) {
if let Err(e) = res {
let errors = self.vs_error.get_or_insert(Vec::new());
errors.push(format!("{:?}", e));
let mut messages = self.vs_messages.lock();

messages.push(VSMessage {
message_type: MessageType::Fatal,
message: format!("{:?}", e),
});
}
}
}

impl Drop for PreviewedScript {
fn drop(&mut self) {
if let Some(handler_id) = self.message_handler_id {
let api = API::get().unwrap();
api.remove_message_handler(handler_id);
}
}
}

0 comments on commit 14125fe

Please sign in to comment.