From 4ae191f3abca142210462318e5fbf5d934adddaa Mon Sep 17 00:00:00 2001 From: quietvoid <39477805+quietvoid@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:25:02 -0500 Subject: [PATCH] extract: Support Matroska input --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 2 +- README.md | 10 +++++++++- assets/hevc_tests/regular.mkv | Bin 0 -> 43612 bytes src/commands/extract.rs | 4 ++-- src/core/parser.rs | 16 +++++++--------- tests/hevc/extract.rs | 32 ++++++++++++++++++++++++++++++++ 7 files changed, 62 insertions(+), 24 deletions(-) create mode 100644 assets/hevc_tests/regular.mkv diff --git a/Cargo.lock b/Cargo.lock index 57c366f..bda6007 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -620,12 +620,13 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hevc_parser" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72930110fcf33e323721aefb8effbe172dc02a669a32657b8bbf9da948b87cf5" +checksum = "3d77019045d27217e651c0054a40cf830667b8ef7e2eff6324eba18d461c2bca" dependencies = [ "anyhow", "bitvec_helpers", + "matroska-demuxer", "nom", "regex", ] @@ -756,16 +757,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] -name = "memchr" -version = "2.7.4" +name = "matroska-demuxer" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "4d62ca6ff378b07542b477763bea55c3540733fc2db537a0caa45cd5ea2c59f6" [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "memchr" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" @@ -779,12 +780,11 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.3" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ "memchr", - "minimal-lexical", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 35124bb..49b4713 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" [dependencies] bitvec_helpers = { version = "3.1.6", default-features = false, features = ["bitstream-io"] } hdr10plus = { path = "./hdr10plus", features = ["hevc", "json"] } -hevc_parser = { version = "0.6.4", features = ["hevc_io"] } +hevc_parser = { version = "0.6.6", features = ["hevc_io"] } clap = { version = "4.5.29", features = ["derive", "wrap_help", "deprecated"] } serde_json = { version = "1.0.138", features = ["preserve_order"] } diff --git a/README.md b/README.md index 5c60c2a..a8823a9 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,16 @@ Options that apply to the commands: ## Commands * ### **extract** - Extracts the HDR10+ metadata from HEVC SEI messages to a JSON file. + Extracts the HDR10+ metadata from a HEVC file to a JSON file. Also calculates the scene information for compatibility with Samsung tools. If no output is specified, the file is only parsed partially to verify presence of metadata. + Input file: + - HEVC bitstream + - Matroska (experimental): MKV file containing a HEVC video track. + + **Flags**: * `--skip-reorder` Skip metadata reordering after extracting. - [Explanation on when to use `--skip-reorder`](README.md#wrong-metadata-order-workaround). @@ -34,6 +39,9 @@ Options that apply to the commands: **Examples**: ```console hdr10plus_tool extract video.hevc -o metadata.json + + # Experimental + hdr10plus_tool extract video.mkv -o metadata.json ``` ```console ffmpeg -i input.mkv -map 0:v:0 -c copy -bsf:v hevc_mp4toannexb -f hevc - | hdr10plus_tool extract -o metadata.json - diff --git a/assets/hevc_tests/regular.mkv b/assets/hevc_tests/regular.mkv new file mode 100644 index 0000000000000000000000000000000000000000..6cb40a9f6af64a622149d5e47227bbe43aa4ab06 GIT binary patch literal 43612 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 Tbi@v*g$)ZR3qR-pmiT`GjeZo1 literal 0 HcmV?d00001 diff --git a/src/commands/extract.rs b/src/commands/extract.rs index 54cb681..dd3bee4 100644 --- a/src/commands/extract.rs +++ b/src/commands/extract.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Result}; +use anyhow::Result; use hevc_parser::io::IoFormat; use super::{input_from_either, CliOptions, ExtractArgs}; @@ -21,7 +21,7 @@ impl Extractor { let format = hevc_parser::io::format_from_path(&input)?; if format == IoFormat::Matroska { - bail!("Extractor: Matroska format unsupported"); + println!("Extractor: Matroska input is experimental!"); } if !options.verify && output.is_none() { diff --git a/src/core/parser.rs b/src/core/parser.rs index 652d5fd..100cc0a 100644 --- a/src/core/parser.rs +++ b/src/core/parser.rs @@ -1,5 +1,5 @@ use std::fs::File; -use std::io::{stdout, BufRead, BufReader, BufWriter, Write}; +use std::io::{stdout, BufWriter, Write}; use std::path::PathBuf; use anyhow::{bail, ensure, Result}; @@ -78,15 +78,13 @@ impl Parser { }; let mut processor = HevcProcessor::new(format.clone(), processor_opts, chunk_size); - let stdin = std::io::stdin(); - let mut reader = Box::new(stdin.lock()) as Box; - - if let IoFormat::Raw = format { - let file = File::open(&self.input)?; - reader = Box::new(BufReader::with_capacity(100_000, file)); - } + let file_path = if let IoFormat::RawStdin = format { + None + } else { + Some(self.input.clone()) + }; - processor.process_io(&mut reader, self) + processor.process_file(self, file_path) } pub fn add_hdr10plus_sei(&mut self, nals: &[NALUnit], chunk: &[u8]) -> Result<()> { diff --git a/tests/hevc/extract.rs b/tests/hevc/extract.rs index 1953f2a..66d098d 100644 --- a/tests/hevc/extract.rs +++ b/tests/hevc/extract.rs @@ -1989,3 +1989,35 @@ fn dts_injected() -> Result<()> { Ok(()) } + +#[test] +fn regular() -> Result<()> { + let input_file = Path::new("assets/hevc_tests/regular.hevc"); + let temp = assert_fs::TempDir::new()?; + + let output_json = temp.child("metadata.json"); + let expected_json = Path::new("assets/hevc_tests/regular_metadata.json"); + + assert_cmd_output(input_file, output_json.as_ref(), true)?; + output_json + .assert(predicate::path::is_file()) + .assert(predicate::path::eq_file(expected_json)); + + Ok(()) +} + +#[test] +fn regular_mkv() -> Result<()> { + let input_file = Path::new("assets/hevc_tests/regular.mkv"); + let temp = assert_fs::TempDir::new()?; + + let output_json = temp.child("metadata.json"); + let expected_json = Path::new("assets/hevc_tests/regular_metadata.json"); + + assert_cmd_output(input_file, output_json.as_ref(), true)?; + output_json + .assert(predicate::path::is_file()) + .assert(predicate::path::eq_file(expected_json)); + + Ok(()) +}