|
#![feature(seek_stream_len)] |
|
#![feature(let_chains)] |
|
|
|
// NOTE: This uses a patched version of png_pong, so it doesn't attempt to recompress image data when writing! |
|
|
|
use std::{fs::{self, File}, io::{Seek, Read, Write, BufWriter}, collections::HashMap, rc::Rc, cell::RefCell, borrow::BorrowMut}; |
|
use m3u8_rs::{ByteRange, parse_media_playlist_res}; |
|
use pix::el::Pixel; |
|
use png_pong::chunk::{Chunk, ImageEnd, ImageData}; |
|
|
|
struct AndThen<I, F>(I, Option<F>); |
|
|
|
impl<I: Iterator, F: FnOnce()> Iterator for AndThen<I, F> { |
|
type Item = I::Item; |
|
fn next(&mut self) -> Option<Self::Item> { |
|
let res = self.0.next(); |
|
if res.is_none() && let Some(fun) = self.1.take() { |
|
fun(); |
|
} |
|
res |
|
} |
|
} |
|
|
|
const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; |
|
|
|
struct WrapWriter<W>(RefCell<W>); |
|
|
|
impl<W> WrapWriter<W> { |
|
pub fn new(inner: W) -> Self { |
|
WrapWriter(RefCell::new(inner)) |
|
} |
|
} |
|
|
|
impl<W: Write> Write for &WrapWriter<W> { |
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { |
|
self.0.borrow_mut().write(buf) |
|
} |
|
|
|
fn flush(&mut self) -> std::io::Result<()> { |
|
self.0.borrow_mut().flush() |
|
} |
|
} |
|
|
|
impl<W: Seek> Seek for &WrapWriter<W> { |
|
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> { |
|
self.0.borrow_mut().seek(pos) |
|
} |
|
} |
|
|
|
fn main() { |
|
let target_segments = File::open("output.json").unwrap(); |
|
let target_segments: Vec<Vec<String>> = serde_json::from_reader(target_segments).unwrap(); |
|
let src_png: Vec<Chunk> = png_pong::Decoder::new(File::open("dvd_logo.png").unwrap()) |
|
.unwrap() |
|
.into_chunks() |
|
.map(Result::unwrap) |
|
.filter(|c| matches!(c, |
|
Chunk::ImageHeader(_) | Chunk::ImageData(_) | Chunk::ImageEnd(_) | Chunk::Palette(_) |
|
)).collect(); |
|
|
|
let mut segment_map: HashMap<String, (String, ByteRange)> = target_segments |
|
.into_iter() |
|
.enumerate() |
|
.flat_map(|(pack_idx, items)| { |
|
let pack_name = format!("pack_{}.png", pack_idx); |
|
// there's certainly a better way to do this but this already almost works so i'm just bodging it. |
|
let mut pack_writer = &WrapWriter::new(BufWriter::new(Box::new(File::create(&pack_name).unwrap()))); |
|
pack_writer.write_all(&PNG_SIGNATURE).unwrap(); |
|
let mut pack = png_pong::Encoder::new(pack_writer).into_chunk_enc(); |
|
for chunk in src_png.iter() { |
|
match chunk { |
|
Chunk::ImageHeader(ihdr) => pack.encode(&mut Chunk::ImageHeader(ihdr.clone())).unwrap(), |
|
Chunk::ImageData(idat) => pack.encode(&mut Chunk::ImageData(ImageData::with_data(idat.data.clone()))).unwrap(), |
|
Chunk::Palette(palette) => { |
|
let mut palette = palette.clone(); |
|
palette.palette[1] = pix::hsv::Hsv8::new(pack_idx as u8, 255, 255).convert(); |
|
pack.encode(&mut Chunk::Palette(palette)).unwrap(); |
|
} |
|
Chunk::ImageEnd(_) => break, |
|
_ => unreachable!() |
|
} |
|
} |
|
|
|
let res: Vec<_> = items.into_iter().map(|segment_name| { |
|
// i'm checking the position before we write the chunk header, 4 bytes for size, 4 bytes for name. |
|
let offset = pack_writer.stream_position().unwrap() + 4 + 4; |
|
let segment = fs::read(&segment_name).unwrap(); |
|
let length = segment.len(); |
|
pack.encode(&mut Chunk::ImageData(ImageData::with_data(segment))).unwrap(); |
|
(segment_name.clone(), (pack_name.clone(), ByteRange { offset: Some(offset), length: length as u64 })) |
|
}).collect(); |
|
pack.encode(&mut Chunk::ImageEnd(ImageEnd)).unwrap(); |
|
res |
|
}).collect(); |
|
|
|
let mut playlist = parse_media_playlist_res(&fs::read("bbb_4k.m3u8").unwrap()).unwrap(); |
|
for segment in playlist.segments.iter_mut() { |
|
let (pack_name, byte_range) = segment_map.remove(&segment.uri).unwrap(); |
|
segment.byte_range = Some(byte_range); |
|
segment.uri = pack_name; |
|
} |
|
|
|
playlist.write_to(&mut File::create("bbb_4k_pack.m3u8").unwrap()).unwrap(); |
|
} |