-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ca0f43d
Showing
6 changed files
with
349 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/target |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |