From 25b78bb991fa96864558cccd651519c296af1e91 Mon Sep 17 00:00:00 2001 From: gennyble Date: Mon, 9 Oct 2023 17:45:19 -0500 Subject: changes --- gifcheck/Cargo.toml | 1 + gifcheck/src/fix.rs | 103 ++++++++++++++++++++ gifcheck/src/main.rs | 103 +++++++++++++++++++- gifed/src/block/extension/application.rs | 1 + gifed/src/block/indexedimage.rs | 24 +++-- gifed/src/block/mod.rs | 2 + gifed/src/block/palette.rs | 160 ++++++++++++++++++++++++++++++- gifed/src/block/screendescriptor.rs | 1 + gifed/src/gif.rs | 154 ++++------------------------- gifed/src/writer/mod.rs | 4 +- 10 files changed, 406 insertions(+), 147 deletions(-) create mode 100644 gifcheck/src/fix.rs diff --git a/gifcheck/Cargo.toml b/gifcheck/Cargo.toml index 8ae0b40..5c63df7 100644 --- a/gifcheck/Cargo.toml +++ b/gifcheck/Cargo.toml @@ -7,3 +7,4 @@ license = "ISC" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +gifed = { path = "../gifed" } diff --git a/gifcheck/src/fix.rs b/gifcheck/src/fix.rs new file mode 100644 index 0000000..95f7ef2 --- /dev/null +++ b/gifcheck/src/fix.rs @@ -0,0 +1,103 @@ +use gifed::{ + block::{Block, Palette}, + Color, Gif, +}; + +use crate::PaletteReport; + +pub fn palette_errors(gif: &Gif, report: PaletteReport) -> Option { + if report.local_matching_indicies { + let mut new = gif.clone(); + + for block in new.blocks.iter_mut() { + if let Block::CompressedImage(comp) = block { + comp.image_descriptor.packed.set_color_table(false); + comp.image_descriptor.packed.set_color_table_size(0); + + if let Some(plt) = comp.local_color_table.take() { + new.global_color_table.get_or_insert(plt); + } + } + } + + Some(new) + } else { + None + } +} + +pub fn images_match_exactly(gifa: &Gif, gifb: &Gif) -> bool { + let mut a_buf = vec![0; gifa.width() * gifa.height() * 4]; + let mut b_buf = vec![0; gifb.width() * gifb.height() * 4]; + + for (a, b) in gifa.images().zip(gifb.images()) { + if a.width() != b.width() || a.height() != b.height() { + return false; + } + + if a.left() != b.left() || a.top() != b.top() { + return false; + } + + let a_decomp = a.decompess(); + let b_decomp = b.decompess(); + + let a_size = deindex( + &a_decomp.indicies, + a.palette(), + a.transparent_index(), + &mut a_buf, + ); + + let b_size = deindex( + &b_decomp.indicies, + b.palette(), + b.transparent_index(), + &mut b_buf, + ); + + match (a_size, b_size) { + (None, _) | (_, None) => return false, + (Some(asize), Some(bsize)) => { + if asize != bsize { + return false; + } + + if a_buf[..asize] != b_buf[..bsize] { + return false; + } + } + } + } + + true +} + +fn deindex(indicies: &[u8], plt: &Palette, trns: Option, buffer: &mut [u8]) -> Option { + let mut rgba = |idx: usize, clr: Option| match clr { + None => { + buffer[idx] = 0; + buffer[idx + 1] = 0; + buffer[idx + 2] = 0; + buffer[idx + 3] = 0; + } + Some(clr) => { + buffer[idx] = clr.r; + buffer[idx + 1] = clr.g; + buffer[idx + 2] = clr.b; + buffer[idx + 3] = 255; + } + }; + + for (idx, color_idx) in indicies.iter().enumerate() { + match (trns, plt.get(*color_idx)) { + (Some(trns_idx), _) if trns_idx == *color_idx => rgba(idx * 4, None), + (_, Some(color)) => rgba(idx * 4, Some(color)), + (Some(_) | None, None) => { + return None; + } + } + } + + Some(indicies.len() * 4) +} diff --git a/gifcheck/src/main.rs b/gifcheck/src/main.rs index a30eb95..8eec4c1 100644 --- a/gifcheck/src/main.rs +++ b/gifcheck/src/main.rs @@ -1,3 +1,104 @@ +use std::{ops::Deref, path::PathBuf}; + +use gifed::{reader::Decoder, Gif}; + +mod fix; + fn main() { - println!("Hello, world!"); + let file = std::env::args().nth(1).unwrap(); + let arg = std::env::args().nth(2).map(|cmd| cmd.to_lowercase()); + + let gif = Decoder::file(&file).unwrap().read_all().unwrap(); + + let plt_report = same_palette(&gif); + match plt_report { + PaletteReport { + has_local: true, + local_redundant: true, + local_matching_indicies, + } => { + if local_matching_indicies { + println!("!!! LOCPLT_NORE. This could've been a global palette"); + } else { + println!("!! LOCPLT. This gif can be reindexed and have a global palette"); + } + } + PaletteReport { + has_local: true, + local_redundant: false, + .. + } => { + println!(" gif has local palettes and they differ"); + } + PaletteReport { + has_local: false, .. + } => { + println!(" gif only has a global palette"); + } + } + + if arg.as_deref() == Some("fix") { + if let Some(fix_gif) = fix::palette_errors(&gif, plt_report) { + if !fix::images_match_exactly(&gif, &fix_gif) { + panic!("fixed images did not exactly match, this is a hard error") + } + + println!("--- fixing, writing!"); + let mut path = PathBuf::from(file); + path.set_file_name(format!( + "{}_fix", + path.file_stem().unwrap().to_string_lossy() + )); + + fix_gif.save(path).unwrap(); + } + } +} + +pub struct PaletteReport { + // Does the gif even contain local color tables? + has_local: bool, + // ... do those color tables always contain the same colors? + local_redundant: bool, + // ... and do those colors all have matching inidices, making it possible + // to simply set the global palette and remove the locals? + local_matching_indicies: bool, +} + +fn same_palette(gif: &Gif) -> PaletteReport { + let mut palette = gif.global_color_table.as_ref(); + let mut report = PaletteReport { + has_local: false, + local_redundant: true, + local_matching_indicies: true, + }; + + for img in gif.images() { + if let Some(local_palette) = img.compressed.palette() { + report.has_local = true; + + match palette { + None => palette = Some(local_palette), + Some(known_palette) => { + if !local_palette.eq(known_palette) { + // Are the palletes equal, even? + report.local_redundant = false; + report.local_matching_indicies = false; + return report; + } else if report.local_matching_indicies { + // it's matching, but are the indicies the same? + for known_color in known_palette.deref() { + for local_color in local_palette.deref() { + if known_color != local_color { + report.local_matching_indicies = false; + } + } + } + } + } + } + } + } + + report } diff --git a/gifed/src/block/extension/application.rs b/gifed/src/block/extension/application.rs index 2244c35..f13047a 100644 --- a/gifed/src/block/extension/application.rs +++ b/gifed/src/block/extension/application.rs @@ -1,3 +1,4 @@ +#[derive(Clone, Debug)] pub struct Application { pub(crate) identifier: [u8; 8], pub(crate) authentication_code: [u8; 3], diff --git a/gifed/src/block/indexedimage.rs b/gifed/src/block/indexedimage.rs index 1868382..261ff38 100644 --- a/gifed/src/block/indexedimage.rs +++ b/gifed/src/block/indexedimage.rs @@ -34,7 +34,7 @@ impl IndexedImage { /// The `lzw_code_size` should be None if there is a local color table present. If /// this image is using the Global Color Table, you must provide an - /// LZW Minimum Code Size here. It is equal to the value of [Palette::packed_len], but + /// LZW Minimum Code Size here. It is equal to the value of [Palette::packed_len] + 1, but /// must also be at least 2. pub fn compress(self, lzw_code_size: Option) -> Result { // gen- The old code had a +1 here. Why? @@ -46,14 +46,12 @@ impl IndexedImage { Some(palette) => palette.lzw_code_size(), None => match lzw_code_size { None => return Err(EncodeError::InvalidCodeSize { lzw_code_size: 0 }), - Some(mcs) => mcs, + Some(mcs) => mcs.max(2), }, }; - let mcs = if mcs < 2 { 2 } else { mcs }; - //FIXME: gen- This seems broken - //let compressed = LZW::encode(mcs, &self.indicies); + //let compressed = crate::LZW::encode(mcs, &self.indicies); let compressed = Encoder::new(weezl::BitOrder::Lsb, mcs) .encode(&self.indicies) .unwrap(); @@ -86,7 +84,7 @@ impl CompressedImage { } pub fn top(&self) -> u16 { - self.image_descriptor.left + self.image_descriptor.top } pub fn width(&self) -> u16 { @@ -105,6 +103,11 @@ impl CompressedImage { let mut ret = vec![]; ret.extend_from_slice(&self.image_descriptor.as_bytes()); + + if let Some(palette) = &self.local_color_table { + ret.extend_from_slice(&palette.as_bytes()); + } + ret.push(self.lzw_code_size); for block in &self.blocks { @@ -128,8 +131,15 @@ impl CompressedImage { let data: Vec = blocks.into_iter().map(<_>::into_iter).flatten().collect(); + println!("lzw: {lzw_code_size}"); + + if local_color_table.is_some() { + let lct = local_color_table.as_ref().unwrap(); + println!("lct-lzw: {}", lct.lzw_code_size()); + } + //TODO: remove unwrap - let mut decompressor = weezl::decode::Decoder::new(weezl::BitOrder::Msb, lzw_code_size); + let mut decompressor = weezl::decode::Decoder::new(weezl::BitOrder::Lsb, lzw_code_size); let indicies = match decompressor.decode(&data) { Err(LzwError::InvalidCode) => Err(DecodeError::LzwInvalidCode), Ok(o) => Ok(o), diff --git a/gifed/src/block/mod.rs b/gifed/src/block/mod.rs index 36cc6fe..3a18a07 100644 --- a/gifed/src/block/mod.rs +++ b/gifed/src/block/mod.rs @@ -16,6 +16,7 @@ pub use version::Version; use self::extension::Application; use self::extension::GraphicControl; +#[derive(Clone, Debug)] pub enum Block { CompressedImage(CompressedImage), //TODO: Extension(Extension), @@ -26,6 +27,7 @@ pub enum Block { LoopingExtension(LoopCount), } +#[derive(Clone, Debug)] pub enum LoopCount { Forever, Number(u16), diff --git a/gifed/src/block/palette.rs b/gifed/src/block/palette.rs index d5cc696..67d9ae8 100644 --- a/gifed/src/block/palette.rs +++ b/gifed/src/block/palette.rs @@ -27,7 +27,8 @@ impl Palette { } pub fn lzw_code_size(&self) -> u8 { - self.packed_len() + 1 + let table_log = (self.table.len() as f32).log2() as u8; + table_log.max(2) } /// Returns the number of colours in the pallette @@ -62,17 +63,20 @@ impl Palette { /// How many padding bytes we need to write. /// We need to pad the colour table because the size must be a power of two. //TODO: gen- better docs - pub fn padding(&self) -> usize { + fn padding(&self) -> usize { let comp = self.computed_len(); (comp as usize - self.len()) * 3 } + /// The palette with padding if required pub fn as_bytes(&self) -> Vec { let mut bytes = Vec::with_capacity(self.table.len() * 3); for color in &self.table { bytes.extend_from_slice(&[color.r, color.g, color.b]); } + bytes.extend(std::iter::repeat(0u8).take(self.padding())); + bytes } } @@ -91,6 +95,22 @@ impl AsRef for Palette { } } +impl PartialEq for Palette { + fn eq(&self, other: &Self) -> bool { + if self.len() != other.len() { + return false; + } + + for color in &other.table { + if !self.table.contains(color) { + return false; + } + } + + true + } +} + //TODO: TryFrom Vec (must be multiple of 3 len) and From Vec impl TryFrom<&[u8]> for Palette { type Error = (); @@ -134,3 +154,139 @@ impl TryFrom> for Palette { } } } + +#[cfg(test)] +mod test { + use super::*; + + fn vec_tuple_test(vec: Vec<(u8, u8, u8)>, expected: &[u8]) { + let plt: Palette = vec.try_into().unwrap(); + let bytes = plt.as_bytes(); + + assert_eq!(expected, bytes.as_slice()) + } + + #[test] + fn writes_one_with_padding() { + vec_tuple_test(vec![(1, 2, 3)], &[1, 2, 3, 0, 0, 0]) + } + + #[test] + fn writes_two_without_padding() { + vec_tuple_test(vec![(1, 2, 3), (4, 5, 6)], &[1, 2, 3, 4, 5, 6]) + } + + fn test_n_with_padding(real_count: usize, exected_padding_bytes: usize) { + let mut palette = Palette::new(); + let mut expected = vec![]; + + for x in 0..real_count { + let x = x as u8; + palette.push(Color { r: x, g: x, b: x }); + expected.extend_from_slice(&[x, x, x]) + } + + // yes, this is really how I'm doing it. I have... trust issues with + // myself and iter::repeat. i hope you understand + for _ in 0..exected_padding_bytes { + expected.push(0x00); + } + + let bytes = palette.as_bytes(); + assert_eq!(expected, bytes.as_slice()) + } + + fn test_n_with_padding_range(real_count_low: u8, real_count_high: u8, next_padstop: usize) { + for x in real_count_low..=real_count_high { + test_n_with_padding(x as usize, (next_padstop as usize - x as usize) * 3) + } + } + + #[test] + fn writes_three_with_padding() { + test_n_with_padding(3, 3); + } + + #[test] + fn writes_four_without_padding() { + test_n_with_padding(4, 0); + } + + #[test] + fn writes_five_to_seven_with_padding() { + test_n_with_padding_range(5, 7, 8); + } + + #[test] + fn writes_eight_without_padding() { + test_n_with_padding(8, 0); + } + + #[test] + fn writes_nine_to_fifteen_with_padding() { + test_n_with_padding_range(9, 15, 16); + } + + #[test] + fn writes_sixteen_without_padding() { + test_n_with_padding(16, 0); + } + + #[test] + fn writes_seventeen_to_thirtyone_with_padding() { + test_n_with_padding_range(17, 31, 32); + } + + #[test] + fn writes_thirtytwo_without_padding() { + test_n_with_padding(32, 0); + } + + #[test] + fn writes_thirtythree_to_sixtythree_with_padding() { + test_n_with_padding_range(33, 63, 64); + } + + #[test] + fn writes_sixtyfour_without_padding() { + test_n_with_padding(64, 0); + } + + #[test] + fn writes_sixtyfive_to_onehundredtwentyseven_with_padding() { + test_n_with_padding_range(65, 127, 128); + } + + #[test] + fn writes_onetwentyeight_without_padding() { + test_n_with_padding(128, 0); + } + + #[test] + fn writes_onetwentynine_to_twofiftyfive_with_padding() { + test_n_with_padding_range(129, 255, 256); + } + + #[test] + fn writes_256_without_padding() { + test_n_with_padding(256, 0); + } + + #[test] + fn packed_len_are_correct() { + let black = Color::new(0, 0, 0); + let mut palette = Palette::new(); + + // Nothing is nothing + assert_eq!(0, palette.packed_len()); + + // One color is still 0 because the formula is + // 2 ^ (len + 1) + // which means we should increase at 3 + palette.push(black); + assert_eq!(0, palette.packed_len()); + + palette.push(black); + assert_eq!(0, palette.packed_len()); + } +} diff --git a/gifed/src/block/screendescriptor.rs b/gifed/src/block/screendescriptor.rs index d44ca2f..a23bfdd 100644 --- a/gifed/src/block/screendescriptor.rs +++ b/gifed/src/block/screendescriptor.rs @@ -2,6 +2,7 @@ use std::convert::TryInto; use super::{packed::ScreenPacked, Palette}; +#[derive(Clone, Debug)] pub struct ScreenDescriptor { pub width: u16, pub height: u16, diff --git a/gifed/src/gif.rs b/gifed/src/gif.rs index 09b052e..4e70ffd 100644 --- a/gifed/src/gif.rs +++ b/gifed/src/gif.rs @@ -8,6 +8,8 @@ use crate::{ }, writer::GifBuilder, }; + +#[derive(Clone, Debug)] pub struct Gif { pub header: Version, pub screen_descriptor: ScreenDescriptor, @@ -20,6 +22,24 @@ impl Gif { GifBuilder::new(width, height) } + pub fn width(&self) -> usize { + self.screen_descriptor.width as usize + } + + pub fn height(&self) -> usize { + self.screen_descriptor.height as usize + } + + pub fn background_color(&self) -> Option { + // vii) Background Color Index - If the Global Color Table Flag is set + // to (zero), this field should be zero and should be ignored. + if self.screen_descriptor.has_color_table() { + Some(self.screen_descriptor.background_color_index) + } else { + None + } + } + pub fn as_bytes(&self) -> Vec { let mut out = vec![]; @@ -191,137 +211,3 @@ pub enum FrameControl { Input, InputOrDelay(Duration), } - -#[cfg(test)] -pub mod gif { - use std::convert::TryInto; - use std::io::Write; - - use crate::block::extension::DisposalMethod; - use crate::writer::{GifBuilder, ImageBuilder}; - use crate::Color; - - #[test] - fn to_vec_gif87a() { - let gct = vec![Color::new(1, 2, 3), Color::new(253, 254, 255)]; - let colortable = vec![Color::new(0, 0, 0), Color::new(128, 0, 255)]; - let indicies = vec![0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]; - - let expected_out = vec![ - 0x47, - 0x49, - 0x46, - 0x38, - 0x37, - 0x61, // Version - GIF87a - 0x04, - 0x00, - 0x04, - 0x00, - 0b1000_0000, - 0x00, - 0x00, // Logical Screen Descriptor - 1, - 2, - 3, - 253, - 254, - 255, // Global Color Table - 0x2C, - 0x00, - 0x00, - 0x00, - 0x00, - 0x04, - 0x00, - 0x04, - 0x00, - 0b1000_0000, // Image Descriptor 1 - 0, - 0, - 0, - 128, - 0, - 255, // Color Table - 0x02, - 0x05, - 0x84, - 0x1D, - 0x81, - 0x7A, - 0x50, - 0x00, // Image Data 1 - 0x2C, - 0x00, - 0x00, - 0x00, - 0x00, - 0x04, - 0x00, - 0x04, - 0x00, - 0b0000_0000, // Image Descriptor 2 - 0x02, - 0x05, - 0x84, - 0x1D, - 0x81, - 0x7A, - 0x50, - 0x00, // Image Data 2 - 0x3B, // Trailer - ]; - - let actual = GifBuilder::new(4, 4) - .palette(gct.try_into().unwrap()) - .image( - ImageBuilder::new(4, 4) - .palette(colortable.try_into().unwrap()) - .build(indicies.clone()) - .unwrap(), - ) - .image(ImageBuilder::new(4, 4).build(indicies).unwrap()); - - let bytes = actual.build().unwrap().as_bytes(); - assert_eq!(bytes, expected_out); - } - - #[test] - fn to_vec_gif89a() { - let gct = vec![Color::new(1, 2, 3), Color::new(253, 254, 255)]; - let colortable = vec![Color::new(0, 0, 0), Color::new(128, 0, 255)]; - let indicies = vec![0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]; - - let expected_out = vec![ - 71, 73, 70, 56, 57, 97, 4, 0, 4, 0, 128, 0, 0, 1, 2, 3, 253, 254, 255, 33, 249, 4, 8, - 64, 0, 0, 0, 44, 0, 0, 0, 0, 4, 0, 4, 0, 128, 0, 0, 0, 128, 0, 255, 2, 5, 132, 29, 129, - 122, 80, 0, 44, 0, 0, 0, 0, 4, 0, 4, 0, 0, 2, 5, 132, 29, 129, 122, 80, 0, 59, - ]; - - let actual_out = GifBuilder::new(4, 4) - .palette(gct.try_into().unwrap()) - .image( - ImageBuilder::new(4, 4) - .palette(colortable.try_into().unwrap()) - .disposal_method(DisposalMethod::RestoreBackground) - .delay(64) - .build(indicies.clone()) - .unwrap(), - ) - .image(ImageBuilder::new(4, 4).build(indicies).unwrap()) - .build() - .unwrap() - .as_bytes(); - - std::fs::File::create("ah.gif") - .unwrap() - .write_all(&actual_out) - .unwrap(); - std::fs::File::create("ah_hand.gif") - .unwrap() - .write_all(&expected_out) - .unwrap(); - - assert_eq!(actual_out, expected_out); - } -} diff --git a/gifed/src/writer/mod.rs b/gifed/src/writer/mod.rs index 493514b..c2e8382 100644 --- a/gifed/src/writer/mod.rs +++ b/gifed/src/writer/mod.rs @@ -4,7 +4,7 @@ mod imagebuilder; use std::{error::Error, fmt, io::Write}; pub use gifbuilder::GifBuilder; -pub use imagebuilder::ImageBuilder; +pub use imagebuilder::{BuiltImage, ImageBuilder}; use crate::block::{encode_block, Block, LoopCount, Palette, ScreenDescriptor, Version}; @@ -47,9 +47,7 @@ impl Writer { this.write_all(&screen_descriptor.as_bytes())?; if let Some(palette) = this.global_palette.as_ref() { - let padding: Vec = std::iter::repeat(0u8).take(palette.padding()).collect(); this.write_all(&palette.as_bytes())?; - this.write_all(&padding)?; } Ok(this) -- cgit 1.4.1-3-g733a5 From 8f89665a63b9d027905f3a1303e5cde8e2359685 Mon Sep 17 00:00:00 2001 From: gennyble Date: Thu, 12 Oct 2023 00:17:55 -0500 Subject: parse mp3 frames! --- gaudio/src/main.rs | 1 + gaudio/src/mp3.rs | 550 ---------------------------------------------- gaudio/src/mp3/bitrate.rs | 385 ++++++++++++++++++++++++++++++++ gaudio/src/mp3/mod.rs | 277 +++++++++++++++++++++++ 4 files changed, 663 insertions(+), 550 deletions(-) delete mode 100644 gaudio/src/mp3.rs create mode 100644 gaudio/src/mp3/bitrate.rs create mode 100644 gaudio/src/mp3/mod.rs diff --git a/gaudio/src/main.rs b/gaudio/src/main.rs index 7d881b8..5632889 100644 --- a/gaudio/src/main.rs +++ b/gaudio/src/main.rs @@ -5,4 +5,5 @@ fn main() { let data = std::fs::read(file).unwrap(); let mut breaker = mp3::Breaker::new(); breaker.split(data).unwrap(); + println!("{} frames", breaker.frames.len()); } diff --git a/gaudio/src/mp3.rs b/gaudio/src/mp3.rs deleted file mode 100644 index 3d643dc..0000000 --- a/gaudio/src/mp3.rs +++ /dev/null @@ -1,550 +0,0 @@ -use std::io::{BufRead, BufReader, Cursor, Read}; - -/// Destroy an MP3, ripping it's frames apart. Also removes any ID3v2 tags -/// because who needs metadata? -pub struct Breaker { - frames: Vec, -} - -impl Breaker { - pub fn new() -> Self { - Self { frames: vec![] } - } - - pub fn split(&mut self, mut data: Vec) -> Result<(), std::io::Error> { - let cursor = Cursor::new(data); - let mut reader = BufReader::new(cursor); - - let mut consumed = 0; - loop { - let mut three = [0x00, 0x00, 0x00]; - reader.read_exact(&mut three)?; - consumed += 3; - - if &three == b"ID3" { - println!("ID3v2 offset {:X}", consumed); - Self::skip_id3v2(&mut reader)? - } else if three[0] == 0xFF && three[2] & 0b1110_0000 == 0b1110_0000 { - let mut one_more = [0x00]; - reader.read_exact(&mut one_more)?; - } - } - - todo!() - } - - /// Assumes the ident "TAG" was already consumed - fn skip_id3v2(reader: &mut R) -> Result<(), std::io::Error> { - // We don't actually want this, but want to get rid of it. - let mut version_and_flags = [0x00, 0x00, 0x00]; - reader.read_exact(&mut version_and_flags)?; - - println!( - "Version {} Revision {}", - version_and_flags[0], version_and_flags[1] - ); - - let mut syncsafe_size = [0x00, 0x00, 0x00, 0x00]; - reader.read_exact(&mut syncsafe_size)?; - - // Size is MSB - let mut size = syncsafe_size[3] as u32; - // Shift right eight, but back one because most significant bit is 0 due to syncsafe - size |= (syncsafe_size[2] as u32) << 7; - size |= (syncsafe_size[1] as u32) << 14; - size |= (syncsafe_size[0] as u32) << 21; - - let human = if size > 1024 * 1024 { - format!("{:.2}MiB", size as f32 / (1024.0 * 1024.0)) - } else if size > 1024 { - format!("{:.2}KiB", size as f32 / 1024.0) - } else { - format!("{size}B") - }; - - println!("ID3v2 size is {human} bytes"); - - // Make a vec size big. We're not here to be efficient, sorry if this dissapoint you. - let mut skip = vec![0x00; size as usize]; - reader.read_exact(&mut skip) - } -} - -pub struct Frame { - header: Header, - data: Vec, -} - -pub struct Header { - // I only want to parse what i need, but we need this for writing out, so - raw: [u8; 4], - version: Version, - layer: Layer, - crc: bool, -} - -impl Header { - pub fn from_bytes(raw: [u8; 4]) -> Result { - if raw[0] != 0xFF || raw[1] & 0b1110_0000 != 0b1110_0000 { - return Err(Error::HeaderUnsync); - } - - let version = Version::from(raw[1]); - let layer = Layer::from(raw[1]); - let crc = raw[1] & 1 == 0; - - let bitrate = Bitrate::resolve(raw[2], version, layer); - - //TODO: gen- love, you were trying to get the size of the data field. We need - //to know the sampling rate and the pad bit for that, which happen to be the - //next three bits. - - todo!() - } - - // Algorithm taken from: - // http://www.multiweb.cz/twoinches/mp3inside.htm - /// The length of the header and data - pub fn length(&self) -> usize { - todo!() - } - - /// The length of the audio data. This is just the length - 4 - pub fn data_length(&self) -> usize { - self.length() - 4 - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("tried to parse header, but first 11 bits were not 1; not synced!")] - HeaderUnsync, - #[error("The version or the layer was a reserved value")] - CannotResolveBitrate, - #[error("Bitrate bits were all 1")] - BitrateBad, -} - -#[derive(Copy, Clone, Debug)] -pub enum Version { - Mpeg2_5, - Reserved, - Mpeg2, - Mpeg1, -} - -impl From for Version { - fn from(byte: u8) -> Self { - match byte & 0b000_11_000 { - 0b000_00_000 => Version::Mpeg2_5, - 0b000_01_000 => Version::Reserved, - 0b000_10_000 => Version::Mpeg2, - 0b000_11_000 => Version::Mpeg1, - _ => unreachable!(), - } - } -} - -#[derive(Copy, Clone, Debug)] -pub enum Layer { - Reserved, - Layer3, - Layer2, - Layer1, -} - -impl From for Layer { - fn from(byte: u8) -> Self { - match byte & 0b000_00_110 { - 0b000_00_000 => Layer::Reserved, - 0b000_00_010 => Layer::Layer3, - 0b000_00_100 => Layer::Layer2, - 0b000_00_110 => Layer::Layer1, - _ => unreachable!(), - } - } -} - -/// What do you want from me it's hard to name a thing that's just number. -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum Bitrate { - RateFree, - Rate8, - Rate16, - Rate24, - Rate32, - Rate40, - Rate48, - Rate56, - Rate64, - Rate80, - Rate96, - Rate112, - Rate128, - Rate144, - Rate160, - Rate176, - Rate192, - Rate224, - Rate256, - Rate288, - Rate320, - Rate352, - Rate384, - Rate416, - Rate448, -} - -impl Bitrate { - /// Takes the third byte of the header and other neccesary information - pub fn resolve(third: u8, version: Version, layer: Layer) -> Result { - #[rustfmt::skip] - macro_rules! v { - (Any) => { _ }; - (v1) => { Version::Mpeg1 }; - (v2) => { Version::Mpeg2 | Version::Mpeg2_5 }; - } - - #[rustfmt::skip] - macro_rules! l { - (Any) => { _ }; - (l1) => { Layer::Layer1 }; - (l2) => { Layer::Layer2 }; - (l3) => { Layer::Layer3 }; - (l23) => { Layer::Layer2 | Layer::Layer3 }; - } - - #[rustfmt::skip] - macro_rules! br { - (b0000) => { Fourbit::Zero }; - (b0001) => { Fourbit::One }; - (b0010) => { Fourbit::Two }; - (b0011) => { Fourbit::Three }; - (b0100) => { Fourbit::Four }; - (b0101) => { Fourbit::Five }; - (b0110) => { Fourbit::Six }; - (b0111) => { Fourbit::Seven }; - (b1000) => { Fourbit::Eight }; - (b1001) => { Fourbit::Nine }; - (b1010) => { Fourbit::Ten }; - (b1011) => { Fourbit::Eleven }; - (b1100) => { Fourbit::Twelve }; - (b1101) => { Fourbit::Thirteen }; - (b1110) => { Fourbit::Fourteen }; - (b1111) => { Fourbit::Fifteen }; - - (down1 b0010) => {br!(b0011)}; - (down1 b0011) => {br!(b0100)}; - (down1 b0100) => {br!(b0101)}; - (down1 b0101) => {br!(b0110)}; - (down1 b0110) => {br!(b0111)}; - (down1 b0111) => {br!(b1000)}; - (down1 b1000) => {br!(b1001)}; - - (down4 b0010) => {br!(b0110)}; - (down4 b0011) => {br!(b0111)}; - (down4 b0100) => {br!(b1000)}; - (down4 b0101) => {br!(b1001)}; - (down4 b0110) => {br!(b1010)}; - (down4 b0111) => {br!(b1011)}; - (down4 b1000) => {br!(b1100)}; - - (down5 b0110) => {br!(b1011)}; - (down5 b0111) => {br!(b1100)}; - (down5 b1000) => {br!(b1101)}; - - (down6 b0110) => {br!(b1100)}; - (down6 b0111) => {br!(b1101)}; - (down6 b1000) => {br!(b1110)}; - } - - macro_rules! vl1 { - ($br:ident) => { - brvl!($br v1 l1) - }; - } - - macro_rules! v2_l23 { - ($br:ident) => { - brvl!($br v2 l23) - }; - } - - macro_rules! brvl { - ($br:ident $v:ident $l:ident) => { - (br!($br), v!($v), l!($l)) - }; - - (down1 $br:ident $v:ident $l:ident) => { - (br!(down1 $br), v!($v), l!($l)) - }; - - (down4 $br:ident $v:ident $l:ident) => { - (br!(down4 $br), v!($v), l!($l)) - }; - - (down5 $br:ident $v:ident $l:ident) => { - (br!(down5 $br), v!($v), l!($l)) - }; - - (down6 $br:ident $v:ident $l:ident) => { - (br!(down6 $br), v!($v), l!($l)) - }; - } - - macro_rules! heartbeat_bird { - ($col_v1_l2:ident) => { - brvl!($col_v1_l2 v1 l2) | brvl!($col_v1_l2 v2 l1) | brvl!(down1 $col_v1_l2 v1 l3) | brvl!(down4 $col_v1_l2 v2 l23) - }; - } - - // rustc was apparently unhappy about my brvl!(down4) calls. And down5, down6 :( - macro_rules! crooked_down { - ($col_v1_l1:ident) => { - brvl!($col_v1_l1 v1 l1) | (br!(down4 $col_v1_l1), v!(v1), l!(l2)) | (br!(down5 $col_v1_l1), v!(v1), l!(l3)) | (br!(down6 $col_v1_l1), v!(v2), l!(l1)) - }; - } - - let br_4bit = Fourbit::from_u8((third & 0b1111_0000) >> 4).expect("this can't happen"); - - let tuple = (br_4bit, version, layer); - - match tuple { - // These patterns cover a very large surface area - heartbeat_bird!(b0010) => Ok(Bitrate::Rate48), - heartbeat_bird!(b0011) => Ok(Bitrate::Rate56), - heartbeat_bird!(b0100) => Ok(Bitrate::Rate64), - heartbeat_bird!(b0101) => Ok(Bitrate::Rate80), - heartbeat_bird!(b0110) => Ok(Bitrate::Rate96), - heartbeat_bird!(b0111) => Ok(Bitrate::Rate112), - heartbeat_bird!(b1000) => Ok(Bitrate::Rate128), - - crooked_down!(b0110) => Ok(Bitrate::Rate192), - crooked_down!(b0111) => Ok(Bitrate::Rate224), - crooked_down!(b1000) => Ok(Bitrate::Rate256), - - // Then we start at the top and work our way down, - // row by row as long as there are still values we have to match there - brvl!(b0000 Any Any) => Ok(Bitrate::RateFree), - - brvl!(b0001 v1 Any) => Ok(Bitrate::Rate32), - brvl!(b0001 v2 l1) => Ok(Bitrate::Rate32), - brvl!(b0001 v2 l23) => Ok(Bitrate::Rate8), - - vl1!(b0010) => Ok(Bitrate::Rate64), - brvl!(b0010 v1 l3) => Ok(Bitrate::Rate40), - v2_l23!(b0010) => Ok(Bitrate::Rate16), - - vl1!(b0011) => Ok(Bitrate::Rate96), - v2_l23!(b0011) => Ok(Bitrate::Rate24), - - vl1!(b0100) => Ok(Bitrate::Rate128), - v2_l23!(b0100) => Ok(Bitrate::Rate32), - - vl1!(b0101) => Ok(Bitrate::Rate160), - v2_l23!(b0101) => Ok(Bitrate::Rate40), - - // 0110, 0111, and 1000 are entirely covered by the patterns :D - vl1!(b1001) => Ok(Bitrate::Rate288), - brvl!(b1001 v1 l2) => Ok(Bitrate::Rate160), - brvl!(b1001 v2 l1) => Ok(Bitrate::Rate144), - - brvl!(b1010 v1 l1) => Ok(Bitrate::Rate320), - brvl!(b1010 v1 l3) => Ok(Bitrate::Rate160), - brvl!(b1010 v2 l1) => Ok(Bitrate::Rate160), - - vl1!(b1011) => Ok(Bitrate::Rate352), - brvl!(b1011 v2 l1) => Ok(Bitrate::Rate176), - - vl1!(b1100) => Ok(Bitrate::Rate384), - - vl1!(b1101) => Ok(Bitrate::Rate416), - brvl!(b1101 v1 l2) => Ok(Bitrate::Rate320), - v2_l23!(b1101) => Ok(Bitrate::Rate144), - - vl1!(b1110) => Ok(Bitrate::Rate448), - brvl!(b1110 v1 l2) => Ok(Bitrate::Rate384), - brvl!(b1110 v1 l3) => Ok(Bitrate::Rate320), - v2_l23!(b1110) => Ok(Bitrate::Rate160), - - (br!(b1111), _, _) => Err(Error::BitrateBad), - (_, Version::Reserved, _) | (_, _, Layer::Reserved) => Err(Error::CannotResolveBitrate), - } - } - - pub fn from_kbps(kbps: usize) -> Option { - println!("{kbps}"); - match kbps { - 8 => Some(Bitrate::Rate8), - 16 => Some(Bitrate::Rate16), - 24 => Some(Bitrate::Rate24), - 32 => Some(Bitrate::Rate32), - 40 => Some(Bitrate::Rate40), - 48 => Some(Bitrate::Rate48), - 56 => Some(Bitrate::Rate56), - 64 => Some(Bitrate::Rate64), - 80 => Some(Bitrate::Rate80), - 96 => Some(Bitrate::Rate96), - 112 => Some(Bitrate::Rate112), - 128 => Some(Bitrate::Rate128), - 144 => Some(Bitrate::Rate144), - 160 => Some(Bitrate::Rate160), - 176 => Some(Bitrate::Rate176), - 192 => Some(Bitrate::Rate192), - 224 => Some(Bitrate::Rate224), - 256 => Some(Bitrate::Rate256), - 288 => Some(Bitrate::Rate288), - 320 => Some(Bitrate::Rate320), - 352 => Some(Bitrate::Rate352), - 384 => Some(Bitrate::Rate384), - 416 => Some(Bitrate::Rate416), - 448 => Some(Bitrate::Rate448), - _ => None, - } - } - - pub const fn kbps(&self) -> Option { - match self { - Bitrate::RateFree => None, - Bitrate::Rate8 => Some(8), - Bitrate::Rate16 => Some(16), - Bitrate::Rate24 => Some(24), - Bitrate::Rate32 => Some(32), - Bitrate::Rate40 => Some(40), - Bitrate::Rate48 => Some(48), - Bitrate::Rate56 => Some(56), - Bitrate::Rate64 => Some(64), - Bitrate::Rate80 => Some(80), - Bitrate::Rate96 => Some(96), - Bitrate::Rate112 => Some(112), - Bitrate::Rate128 => Some(128), - Bitrate::Rate144 => Some(144), - Bitrate::Rate160 => Some(160), - Bitrate::Rate176 => Some(176), - Bitrate::Rate192 => Some(192), - Bitrate::Rate224 => Some(224), - Bitrate::Rate256 => Some(256), - Bitrate::Rate288 => Some(288), - Bitrate::Rate320 => Some(320), - Bitrate::Rate352 => Some(352), - Bitrate::Rate384 => Some(384), - Bitrate::Rate416 => Some(416), - Bitrate::Rate448 => Some(448), - } - } - - pub const fn bitrate(&self) -> Option { - match self.kbps() { - None => None, - Some(kbr) => Some(kbr * 1000), - } - } -} - -pub enum Fourbit { - Zero, - One, - Two, - Three, - Four, - Five, - Six, - Seven, - Eight, - Nine, - Ten, - Eleven, - Twelve, - Thirteen, - Fourteen, - Fifteen, -} - -impl Fourbit { - pub fn from_u8(value: u8) -> Option { - if value > 15 { - return None; - } - - Some(match value { - 0 => Fourbit::Zero, - 1 => Fourbit::One, - 2 => Fourbit::Two, - 3 => Fourbit::Three, - 4 => Fourbit::Four, - 5 => Fourbit::Five, - 6 => Fourbit::Six, - 7 => Fourbit::Seven, - 8 => Fourbit::Eight, - 9 => Fourbit::Nine, - 10 => Fourbit::Ten, - 11 => Fourbit::Eleven, - 12 => Fourbit::Twelve, - 13 => Fourbit::Thirteen, - 14 => Fourbit::Fourteen, - 15 => Fourbit::Fifteen, - _ => unreachable!(), - }) - } -} - -#[cfg(test)] -mod test { - - use super::{Bitrate, Layer, Version}; - - // Lookup table of bitrates excluding 0000 and 1111. The former is Free, the latter is Bad - #[rustfmt::skip] - const BR_LUT: [usize; 5 * 14] = [ - 32, 32, 32, 32, 8, - 64, 48, 40, 48, 16, - 96, 56, 48, 56, 24, - 128, 64, 56, 64, 32, - 160, 80, 64, 80, 40, - 192, 96, 80, 96, 48, - 224, 112, 96, 112, 56, - 256, 128, 112, 128, 64, - 288, 160, 128, 144, 80, - 320, 192, 160, 160, 96, - 352, 224, 192, 176, 112, - 384, 256, 224, 192, 128, - 416, 320, 256, 224, 144, - 448, 384, 320, 256, 160 - ]; - - fn bitrate_lut() -> Vec { - BR_LUT - .into_iter() - .map(|kbps| Bitrate::from_kbps(kbps).unwrap()) - .collect() - } - - #[test] - fn correctly_resolves_bitrates() { - let lut = bitrate_lut(); - for index in 0..75 { - let br = (index as u8 / 5) << 4; - - let (version, layer) = match index % 5 { - 0 => (Version::Mpeg1, Layer::Layer1), - 1 => (Version::Mpeg1, Layer::Layer2), - 2 => (Version::Mpeg1, Layer::Layer3), - 3 => (Version::Mpeg2, Layer::Layer1), - 4 => (Version::Mpeg2, Layer::Layer2), - _ => unreachable!(), - }; - - let resolved_bitrate = Bitrate::resolve(br, version, layer).unwrap(); - let bre = match index { - 0 | 1 | 2 | 3 | 4 => Bitrate::RateFree, - _ => lut[index - 5], - }; - - if resolved_bitrate != bre { - panic!("Failed on {:04b}, {version:?}, {layer:?}", br >> 4); - } - - assert_eq!(bre, resolved_bitrate) - } - } -} diff --git a/gaudio/src/mp3/bitrate.rs b/gaudio/src/mp3/bitrate.rs new file mode 100644 index 0000000..34d332f --- /dev/null +++ b/gaudio/src/mp3/bitrate.rs @@ -0,0 +1,385 @@ +use super::{Error, Layer, Version}; + +/// What do you want from me it's hard to name a thing that's just number. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Bitrate { + RateFree, + Rate8, + Rate16, + Rate24, + Rate32, + Rate40, + Rate48, + Rate56, + Rate64, + Rate80, + Rate96, + Rate112, + Rate128, + Rate144, + Rate160, + Rate176, + Rate192, + Rate224, + Rate256, + Rate288, + Rate320, + Rate352, + Rate384, + Rate416, + Rate448, +} + +impl Bitrate { + /// Takes the third byte of the header and other neccesary information + pub fn resolve(third: u8, version: Version, layer: Layer) -> Result { + #[rustfmt::skip] + macro_rules! v { + (Any) => { _ }; + (v1) => { Version::Mpeg1 }; + (v2) => { Version::Mpeg2 | Version::Mpeg2_5 }; + } + + #[rustfmt::skip] + macro_rules! l { + (Any) => { _ }; + (l1) => { Layer::Layer1 }; + (l2) => { Layer::Layer2 }; + (l3) => { Layer::Layer3 }; + (l23) => { Layer::Layer2 | Layer::Layer3 }; + } + + #[rustfmt::skip] + macro_rules! br { + (b0000) => { Fourbit::Zero }; + (b0001) => { Fourbit::One }; + (b0010) => { Fourbit::Two }; + (b0011) => { Fourbit::Three }; + (b0100) => { Fourbit::Four }; + (b0101) => { Fourbit::Five }; + (b0110) => { Fourbit::Six }; + (b0111) => { Fourbit::Seven }; + (b1000) => { Fourbit::Eight }; + (b1001) => { Fourbit::Nine }; + (b1010) => { Fourbit::Ten }; + (b1011) => { Fourbit::Eleven }; + (b1100) => { Fourbit::Twelve }; + (b1101) => { Fourbit::Thirteen }; + (b1110) => { Fourbit::Fourteen }; + (b1111) => { Fourbit::Fifteen }; + + (down1 b0010) => {br!(b0011)}; + (down1 b0011) => {br!(b0100)}; + (down1 b0100) => {br!(b0101)}; + (down1 b0101) => {br!(b0110)}; + (down1 b0110) => {br!(b0111)}; + (down1 b0111) => {br!(b1000)}; + (down1 b1000) => {br!(b1001)}; + + (down4 b0010) => {br!(b0110)}; + (down4 b0011) => {br!(b0111)}; + (down4 b0100) => {br!(b1000)}; + (down4 b0101) => {br!(b1001)}; + (down4 b0110) => {br!(b1010)}; + (down4 b0111) => {br!(b1011)}; + (down4 b1000) => {br!(b1100)}; + + (down5 b0110) => {br!(b1011)}; + (down5 b0111) => {br!(b1100)}; + (down5 b1000) => {br!(b1101)}; + + (down6 b0110) => {br!(b1100)}; + (down6 b0111) => {br!(b1101)}; + (down6 b1000) => {br!(b1110)}; + } + + macro_rules! vl1 { + ($br:ident) => { + brvl!($br v1 l1) + }; + } + + macro_rules! v2_l23 { + ($br:ident) => { + brvl!($br v2 l23) + }; + } + + macro_rules! brvl { + ($br:ident $v:ident $l:ident) => { + (br!($br), v!($v), l!($l)) + }; + + (down1 $br:ident $v:ident $l:ident) => { + (br!(down1 $br), v!($v), l!($l)) + }; + + (down4 $br:ident $v:ident $l:ident) => { + (br!(down4 $br), v!($v), l!($l)) + }; + + (down5 $br:ident $v:ident $l:ident) => { + (br!(down5 $br), v!($v), l!($l)) + }; + + (down6 $br:ident $v:ident $l:ident) => { + (br!(down6 $br), v!($v), l!($l)) + }; + } + + macro_rules! heartbeat_bird { + ($col_v1_l2:ident) => { + brvl!($col_v1_l2 v1 l2) | brvl!($col_v1_l2 v2 l1) | brvl!(down1 $col_v1_l2 v1 l3) | brvl!(down4 $col_v1_l2 v2 l23) + }; + } + + // rustc was apparently unhappy about my brvl!(down4) calls. And down5, down6 :( + macro_rules! crooked_down { + ($col_v1_l1:ident) => { + brvl!($col_v1_l1 v1 l1) | (br!(down4 $col_v1_l1), v!(v1), l!(l2)) | (br!(down5 $col_v1_l1), v!(v1), l!(l3)) | (br!(down6 $col_v1_l1), v!(v2), l!(l1)) + }; + } + + let br_4bit = Fourbit::from_u8((third & 0b1111_0000) >> 4).expect("this can't happen"); + + let tuple = (br_4bit, version, layer); + + match tuple { + // These patterns cover a very large surface area + heartbeat_bird!(b0010) => Ok(Bitrate::Rate48), + heartbeat_bird!(b0011) => Ok(Bitrate::Rate56), + heartbeat_bird!(b0100) => Ok(Bitrate::Rate64), + heartbeat_bird!(b0101) => Ok(Bitrate::Rate80), + heartbeat_bird!(b0110) => Ok(Bitrate::Rate96), + heartbeat_bird!(b0111) => Ok(Bitrate::Rate112), + heartbeat_bird!(b1000) => Ok(Bitrate::Rate128), + + crooked_down!(b0110) => Ok(Bitrate::Rate192), + crooked_down!(b0111) => Ok(Bitrate::Rate224), + crooked_down!(b1000) => Ok(Bitrate::Rate256), + + // Then we start at the top and work our way down, + // row by row as long as there are still values we have to match there + brvl!(b0000 Any Any) => Ok(Bitrate::RateFree), + + brvl!(b0001 v1 Any) => Ok(Bitrate::Rate32), + brvl!(b0001 v2 l1) => Ok(Bitrate::Rate32), + brvl!(b0001 v2 l23) => Ok(Bitrate::Rate8), + + vl1!(b0010) => Ok(Bitrate::Rate64), + brvl!(b0010 v1 l3) => Ok(Bitrate::Rate40), + v2_l23!(b0010) => Ok(Bitrate::Rate16), + + vl1!(b0011) => Ok(Bitrate::Rate96), + v2_l23!(b0011) => Ok(Bitrate::Rate24), + + vl1!(b0100) => Ok(Bitrate::Rate128), + v2_l23!(b0100) => Ok(Bitrate::Rate32), + + vl1!(b0101) => Ok(Bitrate::Rate160), + v2_l23!(b0101) => Ok(Bitrate::Rate40), + + // 0110, 0111, and 1000 are entirely covered by the patterns :D + vl1!(b1001) => Ok(Bitrate::Rate288), + brvl!(b1001 v1 l2) => Ok(Bitrate::Rate160), + brvl!(b1001 v2 l1) => Ok(Bitrate::Rate144), + + brvl!(b1010 v1 l1) => Ok(Bitrate::Rate320), + brvl!(b1010 v1 l3) => Ok(Bitrate::Rate160), + brvl!(b1010 v2 l1) => Ok(Bitrate::Rate160), + + vl1!(b1011) => Ok(Bitrate::Rate352), + brvl!(b1011 v2 l1) => Ok(Bitrate::Rate176), + + vl1!(b1100) => Ok(Bitrate::Rate384), + + vl1!(b1101) => Ok(Bitrate::Rate416), + brvl!(b1101 v1 l2) => Ok(Bitrate::Rate320), + v2_l23!(b1101) => Ok(Bitrate::Rate144), + + vl1!(b1110) => Ok(Bitrate::Rate448), + brvl!(b1110 v1 l2) => Ok(Bitrate::Rate384), + brvl!(b1110 v1 l3) => Ok(Bitrate::Rate320), + v2_l23!(b1110) => Ok(Bitrate::Rate160), + + (br!(b1111), _, _) => Err(Error::BitrateBad), + (_, Version::Reserved, _) | (_, _, Layer::Reserved) => Err(Error::BitrateReserve), + } + } + + pub fn from_kbps(kbps: usize) -> Option { + println!("{kbps}"); + match kbps { + 8 => Some(Bitrate::Rate8), + 16 => Some(Bitrate::Rate16), + 24 => Some(Bitrate::Rate24), + 32 => Some(Bitrate::Rate32), + 40 => Some(Bitrate::Rate40), + 48 => Some(Bitrate::Rate48), + 56 => Some(Bitrate::Rate56), + 64 => Some(Bitrate::Rate64), + 80 => Some(Bitrate::Rate80), + 96 => Some(Bitrate::Rate96), + 112 => Some(Bitrate::Rate112), + 128 => Some(Bitrate::Rate128), + 144 => Some(Bitrate::Rate144), + 160 => Some(Bitrate::Rate160), + 176 => Some(Bitrate::Rate176), + 192 => Some(Bitrate::Rate192), + 224 => Some(Bitrate::Rate224), + 256 => Some(Bitrate::Rate256), + 288 => Some(Bitrate::Rate288), + 320 => Some(Bitrate::Rate320), + 352 => Some(Bitrate::Rate352), + 384 => Some(Bitrate::Rate384), + 416 => Some(Bitrate::Rate416), + 448 => Some(Bitrate::Rate448), + _ => None, + } + } + + pub const fn kbps(&self) -> Option { + match self { + Bitrate::RateFree => None, + Bitrate::Rate8 => Some(8), + Bitrate::Rate16 => Some(16), + Bitrate::Rate24 => Some(24), + Bitrate::Rate32 => Some(32), + Bitrate::Rate40 => Some(40), + Bitrate::Rate48 => Some(48), + Bitrate::Rate56 => Some(56), + Bitrate::Rate64 => Some(64), + Bitrate::Rate80 => Some(80), + Bitrate::Rate96 => Some(96), + Bitrate::Rate112 => Some(112), + Bitrate::Rate128 => Some(128), + Bitrate::Rate144 => Some(144), + Bitrate::Rate160 => Some(160), + Bitrate::Rate176 => Some(176), + Bitrate::Rate192 => Some(192), + Bitrate::Rate224 => Some(224), + Bitrate::Rate256 => Some(256), + Bitrate::Rate288 => Some(288), + Bitrate::Rate320 => Some(320), + Bitrate::Rate352 => Some(352), + Bitrate::Rate384 => Some(384), + Bitrate::Rate416 => Some(416), + Bitrate::Rate448 => Some(448), + } + } + + pub const fn bitrate(&self) -> Option { + match self.kbps() { + None => None, + Some(kbr) => Some(kbr * 1000), + } + } +} + +pub enum Fourbit { + Zero, + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Ten, + Eleven, + Twelve, + Thirteen, + Fourteen, + Fifteen, +} + +impl Fourbit { + pub fn from_u8(value: u8) -> Option { + if value > 15 { + return None; + } + + Some(match value { + 0 => Fourbit::Zero, + 1 => Fourbit::One, + 2 => Fourbit::Two, + 3 => Fourbit::Three, + 4 => Fourbit::Four, + 5 => Fourbit::Five, + 6 => Fourbit::Six, + 7 => Fourbit::Seven, + 8 => Fourbit::Eight, + 9 => Fourbit::Nine, + 10 => Fourbit::Ten, + 11 => Fourbit::Eleven, + 12 => Fourbit::Twelve, + 13 => Fourbit::Thirteen, + 14 => Fourbit::Fourteen, + 15 => Fourbit::Fifteen, + _ => unreachable!(), + }) + } +} + +#[cfg(test)] +mod test { + + use super::{Bitrate, Layer, Version}; + + // Lookup table of bitrates excluding 0000 and 1111. The former is Free, the latter is Bad + #[rustfmt::skip] + const BR_LUT: [usize; 5 * 14] = [ + 32, 32, 32, 32, 8, + 64, 48, 40, 48, 16, + 96, 56, 48, 56, 24, + 128, 64, 56, 64, 32, + 160, 80, 64, 80, 40, + 192, 96, 80, 96, 48, + 224, 112, 96, 112, 56, + 256, 128, 112, 128, 64, + 288, 160, 128, 144, 80, + 320, 192, 160, 160, 96, + 352, 224, 192, 176, 112, + 384, 256, 224, 192, 128, + 416, 320, 256, 224, 144, + 448, 384, 320, 256, 160 + ]; + + fn bitrate_lut() -> Vec { + BR_LUT + .into_iter() + .map(|kbps| Bitrate::from_kbps(kbps).unwrap()) + .collect() + } + + #[test] + fn correctly_resolves_bitrates() { + let lut = bitrate_lut(); + for index in 0..75 { + let br = (index as u8 / 5) << 4; + + let (version, layer) = match index % 5 { + 0 => (Version::Mpeg1, Layer::Layer1), + 1 => (Version::Mpeg1, Layer::Layer2), + 2 => (Version::Mpeg1, Layer::Layer3), + 3 => (Version::Mpeg2, Layer::Layer1), + 4 => (Version::Mpeg2, Layer::Layer2), + _ => unreachable!(), + }; + + let resolved_bitrate = Bitrate::resolve(br, version, layer).unwrap(); + let bre = match index { + 0 | 1 | 2 | 3 | 4 => Bitrate::RateFree, + _ => lut[index - 5], + }; + + if resolved_bitrate != bre { + panic!("Failed on {:04b}, {version:?}, {layer:?}", br >> 4); + } + + assert_eq!(bre, resolved_bitrate) + } + } +} diff --git a/gaudio/src/mp3/mod.rs b/gaudio/src/mp3/mod.rs new file mode 100644 index 0000000..952e9b2 --- /dev/null +++ b/gaudio/src/mp3/mod.rs @@ -0,0 +1,277 @@ +use std::io::{BufRead, BufReader, Cursor, ErrorKind, Read}; + +use crate::mp3::bitrate::Bitrate; + +mod bitrate; + +/// Destroy an MP3, ripping it's frames apart. Also removes any ID3v2 tags +/// because who needs metadata? +pub struct Breaker { + pub frames: Vec, +} + +impl Breaker { + pub fn new() -> Self { + Self { frames: vec![] } + } + + pub fn split(&mut self, data: Vec) -> Result<(), std::io::Error> { + let cursor = Cursor::new(data); + let mut reader = BufReader::new(cursor); + + let mut consumed = 0; + loop { + print!("[{consumed:06X}] reading... "); + let mut three = [0x00, 0x00, 0x00]; + if let Err(e) = reader.read_exact(&mut three) { + if e.kind() == ErrorKind::UnexpectedEof { + println!("out of bytes!"); + break; + } else { + println!("failed!"); + return Err(e); + } + } + consumed += 3; + + if &three == b"ID3" { + println!("found ID3v2!"); + Self::skip_id3v2(&mut reader, &mut consumed)? + } else if three[0] == 0xFF && three[1] & 0b1110_0000 == 0b1110_0000 { + print!("Have header - "); + let mut one_more = [0x00]; + reader.read_exact(&mut one_more)?; + consumed += 1; + + let header = + Header::from_bytes([three[0], three[1], three[2], one_more[0]]).unwrap(); + let dat_len = header.data_length(); + let mut data = vec![0; dat_len]; + reader.read_exact(&mut data)?; + consumed += dat_len; + + println!( + "{}kbps {}kHz {}bytes", + header.bitrate.kbps().unwrap(), + header.samplerate.freq() / 1000, + header.length() + ); + + self.frames.push(Frame { header, data }); + } else { + println!("unsynced!"); + panic!() + } + } + + Ok(()) + } + + /// Assumes the ident "TAG" was already consumed + fn skip_id3v2(reader: &mut R, consumed: &mut usize) -> Result<(), std::io::Error> { + // We don't actually want this, but want to get rid of it. + let mut version_and_flags = [0x00, 0x00, 0x00]; + reader.read_exact(&mut version_and_flags)?; + *consumed += 3; + + println!( + "Version {} Revision {}", + version_and_flags[0], version_and_flags[1] + ); + + let mut syncsafe_size = [0x00, 0x00, 0x00, 0x00]; + reader.read_exact(&mut syncsafe_size)?; + *consumed += 4; + + // Size is MSB + let mut size = syncsafe_size[3] as u32; + // Shift right eight, but back one because most significant bit is 0 due to syncsafe + size |= (syncsafe_size[2] as u32) << 7; + size |= (syncsafe_size[1] as u32) << 14; + size |= (syncsafe_size[0] as u32) << 21; + + let human = if size > 1024 * 1024 { + format!("{:.2}MiB", size as f32 / (1024.0 * 1024.0)) + } else if size > 1024 { + format!("{:.2}KiB", size as f32 / 1024.0) + } else { + format!("{size}B") + }; + + println!("ID3v2 size is {human} bytes"); + + // Make a vec size big. We're not here to be efficient, sorry if this dissapoint you. + let mut skip = vec![0x00; size as usize]; + reader.read_exact(&mut skip)?; + *consumed += size as usize; + + Ok(()) + } +} + +pub struct Frame { + pub header: Header, + pub data: Vec, +} + +pub struct Header { + // I only want to parse what i need, but we need this for writing out, so + pub raw: [u8; 4], + pub version: Version, + pub layer: Layer, + pub crc: bool, + pub bitrate: Bitrate, + pub samplerate: SampleRate, + pub pad: bool, +} + +impl Header { + pub fn from_bytes(raw: [u8; 4]) -> Result { + if raw[0] != 0xFF || raw[1] & 0b1110_0000 != 0b1110_0000 { + return Err(Error::HeaderUnsync); + } + + //TODO: gen- yell if the version and layer aren't V1 L3? + let version = Version::from_packed(raw[1]); + let layer = Layer::from_packed(raw[1]); + // CRC is 2bytes and directly follows the frame header + let crc = raw[1] & 1 == 0; + let bitrate = Bitrate::resolve(raw[2], version, layer)?; + let samplerate = SampleRate::from_packed(raw[2]); + + if let SampleRate::Reserved = samplerate { + return Err(Error::SampleRateReserve); + } + + let pad = raw[2] & 2 > 0; + + //TODO: gen- love, you were trying to get the size of the data field. We need + //to know the sampling rate and the pad bit for that, which happen to be the + //next three bits. + + //Things i did not parse because i do not care about them: + // - private bit + // - channels + // - mode extension + // - copyright (lol) + // - original (lmfao) + // - emphasis + + Ok(Self { + raw, + version, + layer, + crc, + bitrate, + samplerate, + pad, + }) + } + + // Algorithm taken from: + // http://www.multiweb.cz/twoinches/mp3inside.htm + /// The length of the header and data + pub fn length(&self) -> usize { + // what, do we not care about crc? won't it add 2 bytes? + let size = (144 * self.bitrate.bitrate().unwrap()) / self.samplerate.freq(); + if self.pad { + size + 1 + } else { + size + } + } + + /// The length of the audio data. This is just the length - 4 + pub fn data_length(&self) -> usize { + self.length() - 4 + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("tried to parse header, but first 11 bits were not 1; not synced!")] + HeaderUnsync, + #[error("The version or the layer was a reserved value")] + BitrateReserve, + #[error("Bitrate bits were all 1")] + BitrateBad, + #[error("SampleRate was a reserved value")] + SampleRateReserve, +} + +#[derive(Copy, Clone, Debug)] +pub enum Version { + Mpeg2_5, + Reserved, + Mpeg2, + Mpeg1, +} + +impl Version { + /// Parse the Version from the second byte of the frame header + fn from_packed(byte: u8) -> Self { + #[allow(clippy::unusual_byte_groupings)] + match byte & 0b000_11_000 { + 0b000_00_000 => Version::Mpeg2_5, + 0b000_01_000 => Version::Reserved, + 0b000_10_000 => Version::Mpeg2, + 0b000_11_000 => Version::Mpeg1, + _ => unreachable!(), + } + } +} + +#[derive(Copy, Clone, Debug)] +pub enum Layer { + Reserved, + Layer3, + Layer2, + Layer1, +} + +impl Layer { + /// Parse the Layer from the second byte of the frame header. + fn from_packed(byte: u8) -> Self { + #[allow(clippy::unusual_byte_groupings)] + match byte & 0b000_00_110 { + 0b000_00_000 => Layer::Reserved, + 0b000_00_010 => Layer::Layer3, + 0b000_00_100 => Layer::Layer2, + 0b000_00_110 => Layer::Layer1, + _ => unreachable!(), + } + } +} + +#[derive(Copy, Clone, Debug)] +pub enum SampleRate { + Hz44100, + Hz48000, + Hz32000, + Reserved, +} + +impl SampleRate { + /// Parse the SampleRate from the third byte of the frame header + fn from_packed(byte: u8) -> Self { + #[allow(clippy::unusual_byte_groupings)] + match byte & 0b0000_11_0_0 { + 0b0000_00_0_0 => SampleRate::Hz44100, + 0b0000_01_0_0 => SampleRate::Hz48000, + 0b0000_10_0_0 => SampleRate::Hz32000, + 0b0000_11_0_0 => SampleRate::Reserved, + _ => unreachable!(), + } + } + + pub fn freq(&self) -> usize { + match self { + SampleRate::Hz44100 => 44100, + SampleRate::Hz48000 => 48000, + SampleRate::Hz32000 => 32000, + SampleRate::Reserved => { + panic!("sample rate was a reserved value; unable to determien a frequency") + } + } + } +} -- cgit 1.4.1-3-g733a5 From 2106c47cc9b16aaf4831d9005dfeafdd3b078db2 Mon Sep 17 00:00:00 2001 From: gennyble Date: Sat, 14 Oct 2023 17:02:09 -0500 Subject: need to work on gifed's api sorry the audio_extension doc is rambly --- audio-extension.md | 39 +++++++++++++++++++++++++++++++++------ gaudio/README.md | 2 ++ gaudio/src/mp3/mod.rs | 39 +++++++++++++++++++++++++++++++++------ 3 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 gaudio/README.md diff --git a/audio-extension.md b/audio-extension.md index a213a8f..dd8503c 100644 --- a/audio-extension.md +++ b/audio-extension.md @@ -1,11 +1,38 @@ # Audio Extension DRAFT Add an MP3 audio stream to a gif. -An application extension with the identifier "GENNYBLE" and auth code "AUD". The data is simply MP3 frames. +An application extension with the identifier "GENNYBLE" and auth code "AUD". -Questions yet answered: -- what do we do if the animation and audio length differ? -- what if there is no graphics extension and thus no length? do we behave differently? -- what if audio data starts before image data? do we play audio before we display? +Rough idea: +- we need an "Audio Control Extension", which is similar to a "Graphic Control Extension". It will provide detail on the upcoming audio data and where it appears so it may inform the decoder. +- two version: + - one that's more era appropriate with MP3 + - one with Opus which is just cuter -What I'd like to do is just say "all we're doing is shoving MP3 frames in the extension, the rest is on you" and like, the decoder is just supposed to buffer and play the audio when it's received, but that seems.. not great. \ No newline at end of file +## Audio Control Extension +Application Extension. Ident "GENNYBLE" auth code "ACE" *(audio control extension)*. + +problems: +- a decoder may stop reading blocks after it draws an image that has a graphic control with delay. if there is supposed to be audio playing with this frame, it won't know. + +## ahh +- a fixed timescale counting up from the first image every hundreth of a second. audio may not play first. + +The stream is driven by the gif video and assumed to be in sync from when it starts. + +for audio to be played, there **must** be an ACN extension before the image it's to be played with. this informs the decoder that it's to continue processing after it draws the image. directly after the image should appear the ADT extension + +The gif image data drives the audio. The audio **must not** extend the time of +the file. + +Because the minimal length of an MP3 frame is 1152 samples *(something about size)* the buffer **must** be able to contain a frame of MP3 data. + +## Audio Data Block Extension +Application Extension. Ident "GENNYBLE" auth code "ADT" *(audio data)*. + + +## Example Data Stream +GCE - delay 0.1 +ACE - audio after image +IMG - image +ADT - audio, dur 0.09, delay 0.01 \ No newline at end of file diff --git a/gaudio/README.md b/gaudio/README.md new file mode 100644 index 0000000..b8b16b2 --- /dev/null +++ b/gaudio/README.md @@ -0,0 +1,2 @@ +# gaudio +shove mp3 into a gif. \ No newline at end of file diff --git a/gaudio/src/mp3/mod.rs b/gaudio/src/mp3/mod.rs index 952e9b2..fe6433f 100644 --- a/gaudio/src/mp3/mod.rs +++ b/gaudio/src/mp3/mod.rs @@ -1,4 +1,7 @@ -use std::io::{BufRead, BufReader, Cursor, ErrorKind, Read}; +use std::{ + io::{BufRead, BufReader, Cursor, ErrorKind, Read}, + time::Duration, +}; use crate::mp3::bitrate::Bitrate; @@ -49,15 +52,17 @@ impl Breaker { let mut data = vec![0; dat_len]; reader.read_exact(&mut data)?; consumed += dat_len; + let frame = Frame { header, data }; println!( - "{}kbps {}kHz {}bytes", - header.bitrate.kbps().unwrap(), - header.samplerate.freq() / 1000, - header.length() + "{}kbps {}kHz {:<4}bytes [{}ms]", + frame.header.bitrate.kbps().unwrap(), + frame.header.samplerate.freq() / 1000, + frame.header.length(), + frame.duration().as_millis() ); - self.frames.push(Frame { header, data }); + self.frames.push(frame); } else { println!("unsynced!"); panic!() @@ -114,6 +119,28 @@ pub struct Frame { pub data: Vec, } +impl Frame { + /// The number of moments-in-time this frame represents. This is constant + /// and related to the [Layer] + pub fn sample_count(&self) -> usize { + // http://www.datavoyage.com/mpgscript/mpeghdr.htm + // > Frame size is the number of samples contained in a frame. It is + // > constant and always 384 samples for Layer I and 1152 samples for + // > Layer II and Layer III. + match self.header.layer { + Layer::Reserved => panic!(), + Layer::Layer1 => 384, + Layer::Layer2 | Layer::Layer3 => 1152, + } + } + + /// Compute the duration of this audio frame + pub fn duration(&self) -> Duration { + let millis = (self.sample_count() * 1000) / self.header.samplerate.freq(); + Duration::from_millis(millis as u64) + } +} + pub struct Header { // I only want to parse what i need, but we need this for writing out, so pub raw: [u8; 4], -- cgit 1.4.1-3-g733a5