diff options
Diffstat (limited to 'crates/yt_dlp/src/lib.rs')
-rw-r--r-- | crates/yt_dlp/src/lib.rs | 140 |
1 files changed, 110 insertions, 30 deletions
diff --git a/crates/yt_dlp/src/lib.rs b/crates/yt_dlp/src/lib.rs index 34b8a5d..99197b2 100644 --- a/crates/yt_dlp/src/lib.rs +++ b/crates/yt_dlp/src/lib.rs @@ -1,19 +1,18 @@ //! The `yt_dlp` interface is completely contained in the [`YoutubeDL`] structure. -use std::io::Write; -use std::mem; -use std::{env, fs::File, path::PathBuf}; +use std::{self, env, mem, path::PathBuf}; use indexmap::IndexMap; use log::{Level, debug, error, info, log_enabled}; use logging::setup_logging; -use rustpython::vm::builtins::PyList; use rustpython::{ InterpreterConfig, vm::{ - self, Interpreter, PyObjectRef, PyRef, VirtualMachine, - builtins::{PyBaseException, PyDict, PyStr}, + self, AsObject, Interpreter, PyObjectRef, PyPayload, PyRef, VirtualMachine, + builtins::{PyBaseException, PyBaseExceptionRef, PyDict, PyList, PyStr}, function::{FuncArgs, KwArgs, PosArgs}, + py_io::Write, + suggestion::offer_suggestions, }, }; use url::Url; @@ -177,12 +176,16 @@ impl YoutubeDL { Ok::<_, PyRef<PyBaseException>>((yt_dlp_module, youtube_dl_class)) }) { - Ok(ok) => ok, + Ok(ok) => Ok(ok), Err(err) => { - interpreter.finalize(Some(err)); - return Err(build::Error::Python); + // TODO(@bpeetz): Do we want to run `interpreter.finalize` here? <2025-06-14> + // interpreter.finalize(Some(err)); + interpreter.enter(|vm| { + let buffer = process_exception(vm, &err); + Err(build::Error::Python(buffer)) + }) } - }; + }?; Ok(Self { interpreter, @@ -331,12 +334,10 @@ impl YoutubeDL { Ok::<_, PyRef<PyBaseException>>(result_json) }) { Ok(ok) => Ok(ok), - Err(err) => { - self.interpreter.enter(|vm| { - vm.print_exception(err); - }); - Err(extract_info::Error::Python) - } + Err(err) => self.interpreter.enter(|vm| { + let buffer = process_exception(vm, &err); + Err(extract_info::Error::Python(buffer)) + }), } } @@ -387,30 +388,28 @@ impl YoutubeDL { Ok::<_, PyRef<PyBaseException>>(result_json) }) { Ok(ok) => Ok(ok), - Err(err) => { - self.interpreter.enter(|vm| { - vm.print_exception(err); - }); - Err(process_ie_result::Error::Python) - } + Err(err) => self.interpreter.enter(|vm| { + let buffer = process_exception(vm, &err); + Err(process_ie_result::Error::Python(buffer)) + }), } } } #[allow(missing_docs)] pub mod process_ie_result { - #[derive(Debug, thiserror::Error, Clone, Copy)] + #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Python threw an exception")] - Python, + #[error("Python threw an exception: {0}")] + Python(String), } } #[allow(missing_docs)] pub mod extract_info { - #[derive(Debug, thiserror::Error, Clone, Copy)] + #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Python threw an exception")] - Python, + #[error("Python threw an exception: {0}")] + Python(String), } } @@ -488,8 +487,8 @@ impl YoutubeDLOptions { pub mod build { #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Python threw an exception")] - Python, + #[error("Python threw an exception: {0}")] + Python(String), #[error("Io error: {0}")] Io(#[from] std::io::Error), @@ -539,3 +538,84 @@ pub fn json_dumps( _ => unreachable!("These should not be json.dumps output"), } } + +// Inlined and changed from `vm.write_exception_inner` +fn write_exception<W: Write>( + vm: &VirtualMachine, + output: &mut W, + exc: &PyBaseExceptionRef, +) -> Result<(), W::Error> { + let varargs = exc.args(); + let args_repr = { + match varargs.len() { + 0 => vec![], + 1 => { + let args0_repr = if true { + varargs[0] + .str(vm) + .unwrap_or_else(|_| PyStr::from("<element str() failed>").into_ref(&vm.ctx)) + } else { + varargs[0].repr(vm).unwrap_or_else(|_| { + PyStr::from("<element repr() failed>").into_ref(&vm.ctx) + }) + }; + vec![args0_repr] + } + _ => varargs + .iter() + .map(|vararg| { + vararg.repr(vm).unwrap_or_else(|_| { + PyStr::from("<element repr() failed>").into_ref(&vm.ctx) + }) + }) + .collect(), + } + }; + + let exc_class = exc.class(); + + if exc_class.fast_issubclass(vm.ctx.exceptions.syntax_error) { + unreachable!( + "A syntax error should never be raised, \ + as yt_dlp should not have them and neither our embedded code" + ); + } + + let exc_name = exc_class.name(); + match args_repr.len() { + 0 => write!(output, "{exc_name}"), + 1 => write!(output, "{}: {}", exc_name, args_repr[0]), + _ => write!( + output, + "{}: ({})", + exc_name, + args_repr + .iter() + .map(|val| val.as_str()) + .collect::<Vec<_>>() + .join(", "), + ), + }?; + + match offer_suggestions(exc, vm) { + Some(suggestions) => { + write!(output, ". Did you mean: '{suggestions}'?") + } + None => Ok(()), + } +} + +fn process_exception(vm: &VirtualMachine, err: &PyBaseExceptionRef) -> String { + let mut buffer = String::new(); + write_exception(vm, &mut buffer, err) + .expect("We are writing into an *in-memory* string, it will always work"); + + if log_enabled!(Level::Debug) { + let mut output = String::new(); + vm.write_exception(&mut output, err) + .expect("We are writing into an *in-memory* string, it will always work"); + debug!("Python threw an exception: {output}"); + } + + buffer +} |