]> git.nihav.org Git - nihav-encoder.git/commitdiff
add a mode for multiple palettes per video (that is not one per frame)
authorKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 23 Apr 2026 16:51:50 +0000 (18:51 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 23 Apr 2026 16:51:50 +0000 (18:51 +0200)
src/main.rs
src/palettise.rs
src/transcoder.rs

index 24094678a334f224084026d03f39731cf596a66f..b476cdfb3c60bcf246b0d4196f05fc0c4b6a2f19 100644 (file)
@@ -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");
index ba4023df52a81b672334222c4867018b4e501815..e36eef6763655fef9e724ac65db573473362e0b8 100644 (file)
@@ -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<PalSegment>,
+}
+
+impl MultiCount {
+    fn new() -> Option<Box<Self>> {
+        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<u64>),
@@ -692,12 +758,14 @@ pub struct ColourCounter {
     oinfo:      NAVideoInfo,
     ctype:      CounterType,
     debug:      bool,
+    multi:      Option<Box<MultiCount>>,
 }
 
 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::<usize>() {
+                            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::<usize>() {
+                            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<u8>, 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<PalSegment> {
+        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)]
+    }
 }
index ab2afa77e5f52c11e279410e738271bd8b72ddb3..c7c9957bd1570519fd904b2501a0e1f5bb6b2f22 100644 (file)
@@ -237,11 +237,22 @@ pub struct VideoEncodeContext {
     pub last_ts:    Option<u64>,
     pub plt:        Option<Palettiser>,
     pub plt_buf:    NABufferType,
+    pub pals:       Vec<PalSegment>,
+    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<DebugLog>) -> EncoderResult<bool> {
         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<usize>,
     pub global_tb:      (u32, u32),
     pub fixed_rate:     bool,
-    pub glbl_pal:       Vec<(usize, [[u8; 3]; 256])>,
+    pub pals:           Vec<(usize, Vec<PalSegment>)>,
 }
 
 #[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
                         })
                     }
                 },