Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
vi committed May 4, 2023
0 parents commit ca0f43d
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
151 changes: 151 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

<details><summary> bpg2hevc --help output</summary>

```
ARGS:
<path>
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.
```
</details>
83 changes: 83 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
}

#[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<R: std::io::Read + std::io::Seek>(
reader: &mut R,
endian: binrw::Endian,
args: Self::Args<'_>,
) -> binrw::BinResult<Self> {
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);
}
58 changes: 58 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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:Write,E:Endianness>(w: &mut BitWriter<W,E>, 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(())
}

0 comments on commit ca0f43d

Please sign in to comment.