From ca0f43d6ea47d3e130f919acb813dcfae39a0809 Mon Sep 17 00:00:00 2001 From: Vitaly _Vi Shukela Date: Fri, 5 May 2023 01:21:27 +0200 Subject: [PATCH] . --- .gitignore | 1 + Cargo.lock | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 17 ++++++ README.md | 39 ++++++++++++++ src/lib.rs | 83 +++++++++++++++++++++++++++++ src/main.rs | 58 ++++++++++++++++++++ 6 files changed, 349 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9ca7033 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,151 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + +[[package]] +name = "binrw" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "272caaf6e0bfb7d508c0606e541e2c68f85c0d6352b62d0b299924eed59fe384" +dependencies = [ + "array-init", + "binrw_derive", + "bytemuck", +] + +[[package]] +name = "binrw_derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4b28c1e534d96213c8966bb9240095757aa0909128985f97d16afd2e7257a8" +dependencies = [ + "either", + "owo-colors", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bitstream-io" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d28070975aaf4ef1fd0bd1f29b739c06c2cdd9972e090617fb6dca3b2cb564e" + +[[package]] +name = "bpg2hevc" +version = "0.1.0" +dependencies = [ + "anyhow", + "binrw", + "bitstream-io", + "modular-bitfield", + "xflags", +] + +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "xflags" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4554b580522d0ca238369c16b8f6ce34524d61dafe7244993754bbd05f2c2ea" +dependencies = [ + "xflags-macros", +] + +[[package]] +name = "xflags-macros" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58e7b3ca8977093aae6b87b6a7730216fc4c53a6530bab5c43a783cd810c1a8" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..479591c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "bpg2hevc" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/vi/bpg2hevc" +license = "MIT/Apache-2.0" +description = "CLI tool to convert some BPG pictures to raw HEVC streams (and indirectly to HEIC images)" +categories = ["graphics", "multimedia::video", "command-line-utilities"] +keywords = ["bpg", "hevc", "heif", "heic"] + + +[dependencies] +anyhow = "1.0.71" +binrw = "0.11.1" +bitstream-io = "1.6.0" +modular-bitfield = "0.11.2" +xflags = "0.3.1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2b30e5 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# bpg2hevc + +Command line tool that allows you to losslessly convert compatible [BPG](https://bellard.org/bpg/) files to HEIC format that is more widely supported. +The tool itself handles extraction of [HEVC](https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding) stream from BPG files. That stream can be consumed by other tools like [FFmpeg](https://ffmpeg.org/) or [MP4Box](https://github.com/gpac/gpac/wiki/MP4Box). + +## Example + +``` +$ bpgenc sample.png -o sample.bpg +$ bpg2hevc sample.bpg > sample.hvc +$ MP4Box -add-image sample.hvc:primary -new sample.heic +``` + +## Limitations + +The tool was created to process my own files, which are rather uniform, and may fail to handle arbitrary BPG files. + +* Only basic BPG files are supported: no alpha, no animations, only one specific colourspace, etc. +* Most things are just hard coded - only picture width and height are handled carefully. BPG files that were encoded differently (e.g. non-default `-m` or `-b`) may fail to be converted. + +## Installation + +Download a pre-built executable from [Github releases](https://github.com/vi/bpg2hevc/releases) or install from source code with `cargo install --path .` or `cargo install bpg2hevc`. + +## CLI options + +
bpg2hevc --help output + +``` +ARGS: + + BPG file to read and convert to HEVC raw stream to stdout. + `MP4Box -add-image w.hvc:primary -new w.heic` would help to pack it as a HEIF image. + +OPTIONS: + -h, --help + Prints help information. +``` +
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..48ddac1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,83 @@ +#![allow(unused)] + +use binrw::{binread,BinRead}; +use modular_bitfield::{bitfield,specifiers::{B3,B4}}; + +#[binread] +#[br(big, magic = b"BPG\xFB")] +#[br(assert(!header1.alpha1_flag()))] +#[br(assert(!header1.alpha2_flag()))] +#[br(assert(!header1.limited_range_flag()))] +#[br(assert(!header1.animation_flag()))] +#[br(assert(header1.pixel_format()==0))] +#[br(assert(!header1.extension_present_flag()))] +#[br(assert(hevc_header_lenght.0==3))] +#[br(assert(hevc_header[0]==146))] +#[br(assert(hevc_header[1]==71))] +#[br(assert(hevc_header[2]==64))] +#[derive(Debug)] +pub struct Bpg { + header1: BpgHeader1, + pub picture_width : Ue7, + pub picture_height : Ue7, + picture_data_length : Ue7, + + // no extensions, no alpha + + hevc_header_lenght: Ue7, + #[br(count = hevc_header_lenght.0)] + hevc_header: Vec, +} + +#[bitfield] +#[derive(BinRead)] +#[br(map = Self::from_bytes)] +#[derive(Debug)] +pub struct BpgHeader1 { + pixel_format : B3, + alpha1_flag : bool, + bit_depth_minus_8 : B4, + + color_space : B4, + extension_present_flag : bool, + alpha2_flag : bool, + limited_range_flag : bool, + animation_flag : bool, +} + +/// Varint +#[derive(Debug)] +pub struct Ue7(pub u32); + +impl binrw::BinRead for Ue7 { + type Args<'a> = (); + + fn read_options( + reader: &mut R, + endian: binrw::Endian, + args: Self::Args<'_>, + ) -> binrw::BinResult { + let mut x = 0; + loop { + let mut b = u8::read_options(reader, endian, args)?; + if b & 0x80 != 0 { + b &= 0x7F; + x <<= 7; + x |= b as u32; + continue; + } else { + x <<= 7; + x |= b as u32; + return Ok(Ue7(x)); + } + } + } +} + +#[test] +fn test_ue7() { + use std::io::Cursor; + assert_eq!(Ue7::read_be(&mut Cursor::new(b"\x08")).unwrap().0, 8); + assert_eq!(Ue7::read_be(&mut Cursor::new(b"\x84\x1E")).unwrap().0, 542); + assert_eq!(Ue7::read_be(&mut Cursor::new(b"\xAC\xBE\x17")).unwrap().0, 728855); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..bad0534 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,58 @@ + +use std::{path::PathBuf, io::Write}; + +use binrw::{BinRead}; +use bitstream_io::{BigEndian, BitWriter, BitWrite, Endianness}; +use bpg2hevc::Bpg; + +pub fn write_egc(w: &mut BitWriter, mut x: u32) -> std::io::Result<()> { + x+=1; + let l = x.ilog2(); + for _ in 0..l { + w.write_bit(false)?; + } + for j in 0..=l { + w.write_bit(x & (0x01 << (l-j)) != 0)?; + } + Ok(()) +} + +fn main() -> anyhow::Result<()> { + let flags = xflags::parse_or_exit! { + /// BPG file to read and convert to HEVC raw stream to stdout. + /// `MP4Box -add-image w.hvc:primary -new w.heic` would help to pack it as a HEIF image. + required path: PathBuf + }; + + let mut f = std::fs::File::open(flags.path)?; + let bpg = Bpg::read(&mut f)?; + //eprintln!("{:#?}", bpg); + + let mut so = std::io::stdout(); + so.write_all(b"\x00\x00\x01\x40\x01\x0c\x01\xff\xff\x03\x70\x00\x00\x03\x00\x90\x00\x00\x03\x00\x00\x03\x00\x1e\xaa\x02\x40")?; + + //so.write_all(b"\x00\x00\x01\x42\x01\x01\x03\x70\x00\x00\x03\x00\x90\x00\x00\x03\x00\x00\x03\x00\x1e\xa0\x34\x81\x85\x96\xaa\x49\x1b\x6b\x80\x40\x00\x00\x03\x00\x40\x00\x00\x06\x42")?; + let mut b = BitWriter::<_, BigEndian>::new(so); + + let sps1 = b"000000000000000000000000000000010100001000000001000000010000001101110000000000000000000000000011000000001001000000000000000000000000001100000000000000000000001100000000011110001010"; + for x in sps1 { + b.write_bit(*x == b'1')?; + } + let pic_width_in_luma_samples = (bpg.picture_width.0 + 7) / 8 * 8; + let pic_height_in_luma_samples = (bpg.picture_height.0 + 7) / 8 * 8; + write_egc(&mut b, pic_width_in_luma_samples)?; + write_egc(&mut b, pic_height_in_luma_samples)?; + // 0000000000110110100010000000001111000001 + + let sps2 = b"01100101101010101001001001000110110110101110000000010000000000000000000000110000000000000001000000000000000000000011000000000001100100001000"; + for x in sps2 { + b.write_bit(*x == b'1')?; + } + + b.byte_align()?; + let mut so = b.into_writer(); + + so.write_all(b"\x00\x00\x01")?; + std::io::copy(&mut f, &mut so)?; + Ok(()) +}