From b468f925fdbef3af2723fa8d67ec4f981b7fcb14 Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Thu, 23 Apr 2026 18:51:50 +0200 Subject: [PATCH] add a mode for multiple palettes per video (that is not one per frame) --- src/main.rs | 4 +- src/palettise.rs | 162 +++++++++++++++++++++++++++++++++++++++++++++- src/transcoder.rs | 31 +++++++-- 3 files changed, 189 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2409467..b476cdf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -728,8 +728,8 @@ fn main() { println!(" generating palette(s)..."); } for (&id, plt) in rev_map.iter().zip(ccounters.iter()) { - let pal = plt.get_pal(); - transcoder.qsupport.glbl_pal.push((id, pal)); + let pals = plt.get_multi_pals(); + transcoder.qsupport.pals.push((id, pals)); } ccounters.clear(); transcoder.debug_log(DebugLog::GENERAL, "Resetting state after palettisation"); diff --git a/src/palettise.rs b/src/palettise.rs index ba4023d..e36eef6 100644 --- a/src/palettise.rs +++ b/src/palettise.rs @@ -635,6 +635,12 @@ impl BucketCounter { nentries: 0, } } + fn reset(&mut self) { + for bkt in self.buckets.iter_mut() { + bkt.clrs.clear(); + } + self.nentries = 0; + } fn add(&mut self, clr: [u8; 3]) { let idx = Bucket::key(clr); if self.buckets[idx].add(clr) { @@ -681,6 +687,66 @@ impl BucketCounter { } } +pub type PalSegment = (usize, [[u8; 3]; 256]); + +struct MultiCount { + glbl_hist: [u64; 1 << 15], + cur_hist: [u64; 1 << 15], + frameno: usize, + fstart: usize, + min_grp: usize, + max_grp: usize, + pals: Vec, +} + +impl MultiCount { + fn new() -> Option> { + Some(Box::new(Self { + glbl_hist: [0; 1 << 15], + cur_hist: [0; 1 << 15], + frameno: 0, + fstart: 0, + min_grp: 32, + max_grp: 0, + pals: Vec::new() + })) + } + fn calc_hist(&mut self, src: &[u8], stride: usize, width: usize, height: usize) { + for el in self.cur_hist.iter_mut() { + *el = 0; + } + for line in src.chunks_exact(stride).take(height) { + for pix in line.chunks_exact(3).take(width) { + let idx = (usize::from(pix[0] >> 3) << 10) | + (usize::from(pix[1] >> 3) << 5) | + usize::from(pix[2] >> 3); + self.cur_hist[idx] += 1; + } + } + } + fn merge_hist(&mut self) { + for (dst, &src) in self.glbl_hist.iter_mut().zip(self.cur_hist.iter()) { + *dst += src; + } + } + fn replace_hist(&mut self) { + self.glbl_hist.copy_from_slice(&self.cur_hist); + } + fn hist_differs(&self) -> bool { + if self.max_grp != 0 && self.frameno >= self.fstart + self.max_grp { + true + } else if self.frameno >= self.fstart + self.min_grp { + let scale = (self.frameno - self.fstart) as u64; + let (corr, acorr) = self.glbl_hist.iter().zip(self.cur_hist.iter()) + .fold((0u64, 0u64), |acc, (&gg, &cc)| + (acc.0 + (gg / scale) * cc, acc.1 + cc * cc)); + corr < acorr / 4 + } else { + false + } + } +} + enum CounterType { Buckets(BucketCounter), Brawn(Vec), @@ -692,12 +758,14 @@ pub struct ColourCounter { oinfo: NAVideoInfo, ctype: CounterType, debug: bool, + multi: Option>, } impl ColourCounter { pub fn new(options: &[OptionArgs]) -> Self { let mut counter_type = "full"; let mut debug = false; + let mut multi = None; for opt in options.iter() { match opt.name.as_str() { "counter" => { @@ -711,6 +779,51 @@ impl ColourCounter { "debug" => { debug = true; }, + "multi" => { + if multi.is_none() { + multi = MultiCount::new(); + } + }, + "min_group" => { + if let Some(val) = opt.value.as_deref() { + if let Ok(gsize) = val.parse::() { + if gsize >= 8 { + if multi.is_none() { + multi = MultiCount::new(); + } + if let Some(ref mut mlti) = multi { + mlti.min_grp = gsize; + } + } else { + println!("too small group size, ignoring"); + } + } else { + println!("group size requires a numeric argument"); + } + } else { + println!("group size requires a numeric argument"); + } + }, + "max_group" => { + if let Some(val) = opt.value.as_deref() { + if let Ok(gsize) = val.parse::() { + if gsize >= 8 { + if multi.is_none() { + multi = MultiCount::new(); + } + if let Some(ref mut mlti) = multi { + mlti.max_grp = gsize; + } + } else { + println!("too small group size, ignoring"); + } + } else { + println!("group size requires a numeric argument"); + } + } else { + println!("group size requires a numeric argument"); + } + }, _ => {}, } } @@ -723,7 +836,7 @@ impl ColourCounter { scaler: None, sc_buf: NABufferType::None, oinfo: NAVideoInfo{ width: 0, height: 0, flipped: false, format: RGB24_FORMAT, bits: 24 }, - ctype, debug, + ctype, debug, multi, } } pub fn add(&mut self, clr: [u8; 3]) { @@ -735,9 +848,45 @@ impl ColourCounter { }, } } + fn reset(&mut self) { + match self.ctype { + CounterType::Buckets(ref mut bkt) => { bkt.reset(); }, + CounterType::Brawn(ref mut hist) => { + for el in hist.iter_mut() { + *el = 0; + } + }, + } + } fn add_rgb24(&mut self, buf: &NAVideoBuffer, width: usize, height: usize) { let src = buf.get_data(); let stride = buf.get_stride(0); + let mut new_pal = false; + let mut pal_start = 0; + let mut pal_end = 0; + if let Some(ref mut multi) = self.multi { + multi.calc_hist(src, stride, width, height); + if multi.hist_differs() { + new_pal = true; + pal_start = multi.fstart; + pal_end = multi.frameno; + multi.replace_hist(); + } else { + multi.merge_hist(); + } + multi.frameno += 1; + } + if new_pal { + if self.debug { + println!(" new pal segment {pal_start}..{pal_end}"); + } + let pal = self.get_pal(); + self.reset(); + if let Some(ref mut multi) = self.multi { + multi.pals.push((multi.fstart, pal)); + multi.fstart = multi.frameno - 1; + } + } for line in src.chunks_exact(stride).take(height) { for pix in line.chunks_exact(3).take(width) { let clr = pix.try_into().unwrap(); @@ -805,4 +954,15 @@ impl ColourCounter { }, } } + pub fn get_multi_pals(&self) -> Vec { + let last_pal = self.get_pal(); + if let Some(ref multi) = self.multi { + let mut pals = multi.pals.clone(); + if multi.fstart < multi.frameno { + pals.push((multi.fstart, last_pal)); + } + return pals; + } + vec![(0, last_pal)] + } } diff --git a/src/transcoder.rs b/src/transcoder.rs index ab2afa7..c7c9957 100644 --- a/src/transcoder.rs +++ b/src/transcoder.rs @@ -237,11 +237,22 @@ pub struct VideoEncodeContext { pub last_ts: Option, pub plt: Option, pub plt_buf: NABufferType, + pub pals: Vec, + pub pal_frm: usize, } impl EncoderInterface for VideoEncodeContext { fn encode_frame(&mut self, dst_id: u32, frm: NAFrameRef, scale_opts: &[(String, String)], queue: &mut OutputQueue, dbg: &mut Option) -> EncoderResult { let buf = frm.get_buffer(); + if let Some(ref mut plt) = self.plt { + if !self.pals.is_empty() && !matches!(buf, NABufferType::None) { + if self.pal_frm >= self.pals[0].0 { + let (_, pal) = self.pals.remove(0); + plt.set_pal(&pal); + } + self.pal_frm += 1; + } + } if let Some(vinfo) = buf.get_video_info() { let mut tgt_vinfo = self.vinfo; if self.plt.is_some() { @@ -533,7 +544,7 @@ pub struct QuirkSupport { pub nframes: Vec, pub global_tb: (u32, u32), pub fixed_rate: bool, - pub glbl_pal: Vec<(usize, [[u8; 3]; 256])>, + pub pals: Vec<(usize, Vec)>, } #[derive(Default)] @@ -972,12 +983,12 @@ impl Transcoder { let mut plt = create_palettiser(&oopts.enc_opts); oopts.enc_opts.retain(|opt| !opt.name.starts_with("pal.")); - for (src_id, gpal) in qsupp.glbl_pal.iter() { - if *src_id == iidx { + for (src_id, pals) in qsupp.pals.iter() { + if *src_id == iidx && !pals.is_empty() { if let Some(ref mut p) = plt { - p.set_pal(gpal); + p.set_pal(&pals[0].1); } else { - plt = Some(Palettiser::new(Palettiser::get_default_mode(), gpal)); + plt = Some(Palettiser::new(Palettiser::get_default_mode(), &pals[0].1)); } } } @@ -1007,6 +1018,12 @@ impl Transcoder { println!("Timebase {}/{} is too much for constant framerate!", enc_stream.tb_num, enc_stream.tb_den); return None; } + let cur_pals = if let Some((idx, _)) = qsupp.pals.iter().enumerate().find(|(_, (sidx, _))| *sidx == iidx) { + let (_, pals) = qsupp.pals.swap_remove(idx); + pals + } else { + Vec::new() + }; if svinfo == dvinfo && !forced_out { Box::new(VideoEncodeContext { encoder, @@ -1019,6 +1036,8 @@ impl Transcoder { tb_den: enc_stream.tb_den, plt, plt_buf: NABufferType::None, + pals: cur_pals, + pal_frm: 0, }) } else { let tgt_fmt = if plt.is_some() { RGB24_FORMAT } else { dvinfo.format }; @@ -1050,6 +1069,8 @@ impl Transcoder { tb_den: enc_stream.tb_den, plt, plt_buf: NABufferType::None, + pals: cur_pals, + pal_frm: 0 }) } }, -- 2.39.5