aboutsummaryrefslogtreecommitdiffstats
path: root/crates/yt_dlp/src
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-05-26 18:11:35 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-05-26 18:14:48 +0200
commitdd135af27160f954c8f9c937d1fdb5b2a1032ccf (patch)
tree36814abb76a4c5c17ccd3eb7ff9c6df291ad08c0 /crates/yt_dlp/src
parentbuild(update.sh): Remove all redundant `update.sh` files (diff)
downloadyt-dd135af27160f954c8f9c937d1fdb5b2a1032ccf.zip
feat(yt/download/hooks): Show progress of post-processors
Otherwise, `yt` will just show everything downloaded, but does not continue. Now users see, what is _actually_ happening.
Diffstat (limited to '')
-rw-r--r--crates/yt_dlp/src/hooks.rs62
-rw-r--r--crates/yt_dlp/src/info_json.rs36
-rw-r--r--crates/yt_dlp/src/lib.rs2
-rw-r--r--crates/yt_dlp/src/options.rs28
-rw-r--r--crates/yt_dlp/src/progress_hook.rs67
5 files changed, 124 insertions, 71 deletions
diff --git a/crates/yt_dlp/src/hooks.rs b/crates/yt_dlp/src/hooks.rs
new file mode 100644
index 0000000..df70ecd
--- /dev/null
+++ b/crates/yt_dlp/src/hooks.rs
@@ -0,0 +1,62 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+// This file is part of Yt.
+//
+// You should have received a copy of the License along with this program.
+// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+#[macro_export]
+macro_rules! wrap_progress_hook {
+ ($name:ident, $new_name:ident) => {
+ yt_dlp::wrap_hook! {"progress_hook", $name, $new_name}
+ };
+}
+
+#[macro_export]
+macro_rules! wrap_post_processor_hook {
+ ($name:ident, $new_name:ident) => {
+ yt_dlp::wrap_hook! {"post_processor_hook", $name, $new_name}
+ };
+}
+
+#[macro_export]
+macro_rules! wrap_hook {
+ ($ty:literal, $name:ident, $new_name:ident) => {
+ pub(crate) fn $new_name(
+ py: yt_dlp::hooks::__priv::pyo3::Python<'_>,
+ ) -> yt_dlp::hooks::__priv::pyo3::PyResult<
+ yt_dlp::hooks::__priv::pyo3::Bound<'_, yt_dlp::hooks::__priv::pyo3::types::PyCFunction>,
+ > {
+ #[yt_dlp::hooks::__priv::pyo3::pyfunction]
+ #[pyo3(crate = "yt_dlp::hooks::__priv::pyo3")]
+ fn inner(
+ input: yt_dlp::hooks::__priv::pyo3::Bound<
+ '_,
+ yt_dlp::hooks::__priv::pyo3::types::PyDict,
+ >,
+ ) -> yt_dlp::hooks::__priv::pyo3::PyResult<()> {
+ let processed_input = {
+ let new_dict = yt_dlp::hooks::__priv::filter_dict(input);
+ yt_dlp::hooks::__priv::json_dumps(&new_dict)
+ };
+
+ $name(processed_input)?;
+
+ Ok(())
+ }
+
+ let module = yt_dlp::hooks::__priv::pyo3::types::PyModule::new(py, $ty)?;
+ let fun = yt_dlp::hooks::__priv::pyo3::wrap_pyfunction!(inner, module)?;
+
+ Ok(fun)
+ }
+ };
+}
+
+pub mod __priv {
+ pub use crate::info_json::{json_dumps, json_loads, filter_dict};
+ pub use pyo3;
+}
diff --git a/crates/yt_dlp/src/info_json.rs b/crates/yt_dlp/src/info_json.rs
index 402acb4..df49218 100644
--- a/crates/yt_dlp/src/info_json.rs
+++ b/crates/yt_dlp/src/info_json.rs
@@ -54,3 +54,39 @@ pub fn json_dumps(input: &Bound<'_, PyDict>) -> serde_json::Map<String, serde_js
_ => unreachable!("These should not be json.dumps output"),
}
}
+
+/// # Panics
+/// If expectation about python operations fail.
+#[must_use]
+pub fn filter_dict(input: Bound<'_, PyDict>) -> Bound<'_, PyDict> {
+ let new_dict = PyDict::new(input.py());
+
+ input
+ .into_iter()
+ .filter_map(|(name, value)| {
+ let real_name = name.extract::<String>().expect("Should always be a string");
+
+ if real_name.starts_with('_') {
+ None
+ } else {
+ let value = if value.is_instance_of::<PyDict>() {
+ filter_dict(
+ value
+ .cast_into::<PyDict>()
+ .expect("to be a dict, because we checked"),
+ )
+ .into_any()
+ } else {
+ value
+ };
+ Some((real_name, value))
+ }
+ })
+ .for_each(|(key, value)| {
+ new_dict
+ .set_item(&key, value)
+ .expect("This is a transposition, should always be valid");
+ });
+
+ new_dict
+}
diff --git a/crates/yt_dlp/src/lib.rs b/crates/yt_dlp/src/lib.rs
index 4b252de..abe766d 100644
--- a/crates/yt_dlp/src/lib.rs
+++ b/crates/yt_dlp/src/lib.rs
@@ -27,7 +27,7 @@ use crate::{
pub mod info_json;
pub mod options;
pub mod post_processors;
-pub mod progress_hook;
+pub mod hooks;
pub mod python_error;
#[macro_export]
diff --git a/crates/yt_dlp/src/options.rs b/crates/yt_dlp/src/options.rs
index 4b8906e..a87473d 100644
--- a/crates/yt_dlp/src/options.rs
+++ b/crates/yt_dlp/src/options.rs
@@ -21,7 +21,7 @@ use crate::{
python_error::{IntoPythonError, PythonError},
};
-pub type ProgressHookFunction = fn(py: Python<'_>) -> PyResult<Bound<'_, PyCFunction>>;
+pub type HookFunction = fn(py: Python<'_>) -> PyResult<Bound<'_, PyCFunction>>;
pub type PostProcessorFunction = fn(py: Python<'_>) -> PyResult<Bound<'_, PyAny>>;
/// Options, that are used to customize the download behaviour.
@@ -32,7 +32,8 @@ pub type PostProcessorFunction = fn(py: Python<'_>) -> PyResult<Bound<'_, PyAny>
#[derive(Default, Debug)]
pub struct YoutubeDLOptions {
options: serde_json::Map<String, serde_json::Value>,
- progress_hook: Option<ProgressHookFunction>,
+ progress_hook: Option<HookFunction>,
+ post_processor_hook: Option<HookFunction>,
post_processors: Vec<PostProcessorFunction>,
}
@@ -42,6 +43,7 @@ impl YoutubeDLOptions {
let me = Self {
options: serde_json::Map::new(),
progress_hook: None,
+ post_processor_hook: None,
post_processors: vec![],
};
@@ -57,7 +59,7 @@ impl YoutubeDLOptions {
}
#[must_use]
- pub fn with_progress_hook(self, progress_hook: ProgressHookFunction) -> Self {
+ pub fn with_progress_hook(self, progress_hook: HookFunction) -> Self {
if let Some(_previous_hook) = self.progress_hook {
todo!()
} else {
@@ -67,6 +69,17 @@ impl YoutubeDLOptions {
}
}
}
+ #[must_use]
+ pub fn with_post_processor_hook(self, post_processor_hook: HookFunction) -> Self {
+ if let Some(_previous_hook) = self.post_processor_hook {
+ todo!()
+ } else {
+ Self {
+ post_processor_hook: Some(post_processor_hook),
+ ..self
+ }
+ }
+ }
#[must_use]
pub fn with_post_processor(mut self, pp: PostProcessorFunction) -> Self {
@@ -135,6 +148,15 @@ signal.signal(signal.SIGINT, signal.SIG_DFL)
opts.set_item(intern!(py, "progress_hooks"), vec![ph(py).wrap_exc(py)?])
.wrap_exc(py)?;
}
+
+ // Setup the post_processor hook
+ if let Some(ph) = options.post_processor_hook {
+ opts.set_item(
+ intern!(py, "postprocessor_hooks"),
+ vec![ph(py).wrap_exc(py)?],
+ )
+ .wrap_exc(py)?;
+ }
}
{
diff --git a/crates/yt_dlp/src/progress_hook.rs b/crates/yt_dlp/src/progress_hook.rs
deleted file mode 100644
index 7e5f8a5..0000000
--- a/crates/yt_dlp/src/progress_hook.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-// yt - A fully featured command line YouTube client
-//
-// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-// SPDX-License-Identifier: GPL-3.0-or-later
-//
-// This file is part of Yt.
-//
-// You should have received a copy of the License along with this program.
-// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-#[macro_export]
-macro_rules! wrap_progress_hook {
- ($name:ident, $new_name:ident) => {
- pub(crate) fn $new_name(
- py: yt_dlp::progress_hook::__priv::pyo3::Python<'_>,
- ) -> yt_dlp::progress_hook::__priv::pyo3::PyResult<
- yt_dlp::progress_hook::__priv::pyo3::Bound<
- '_,
- yt_dlp::progress_hook::__priv::pyo3::types::PyCFunction,
- >,
- > {
- #[yt_dlp::progress_hook::__priv::pyo3::pyfunction]
- #[pyo3(crate = "yt_dlp::progress_hook::__priv::pyo3")]
- fn inner(
- input: yt_dlp::progress_hook::__priv::pyo3::Bound<
- '_,
- yt_dlp::progress_hook::__priv::pyo3::types::PyDict,
- >,
- ) -> yt_dlp::progress_hook::__priv::pyo3::PyResult<()> {
- let processed_input = {
- let new_dict = yt_dlp::progress_hook::__priv::pyo3::types::PyDict::new(input.py());
-
- input
- .into_iter()
- .filter_map(|(name, value)| {
- let real_name = yt_dlp::progress_hook::__priv::pyo3::types::PyAnyMethods::extract::<String>(&name).expect("Should always be a string");
-
- if real_name.starts_with('_') {
- None
- } else {
- Some((real_name, value))
- }
- })
- .for_each(|(key, value)| {
- yt_dlp::progress_hook::__priv::pyo3::types::PyDictMethods::set_item(&new_dict, &key, value)
- .expect("This is a transpositions, should always be valid");
- });
- yt_dlp::progress_hook::__priv::json_dumps(&new_dict)
- };
-
- $name(processed_input)?;
-
- Ok(())
- }
-
- let module = yt_dlp::progress_hook::__priv::pyo3::types::PyModule::new(py, "progress_hook")?;
- let fun = yt_dlp::progress_hook::__priv::pyo3::wrap_pyfunction!(inner, module)?;
-
- Ok(fun)
- }
- };
-}
-
-pub mod __priv {
- pub use crate::info_json::{json_dumps, json_loads};
- pub use pyo3;
-}