1use crate::api::EncodeSpeed;
31use crate::error::{AvifError as Error, Result};
32use crate::mux::{
33 prop_av1c, prop_clap, prop_colr, prop_ispe, prop_pixi, sequence_brands, ProfileBrand,
34};
35use crate::still::{
36 av1_err, av1c_from_seq, coded_extent, colr_is_full_range, encode_coded_item,
37 full_range_temporal_unit, pad_plane, pixi_bits, top_left_clap, CodedTuning, StillImage,
38 STILL_MAX_CODED_DIM,
39};
40use oxideav_av1::encoder::inter_frame::{encode_gop_yuv_with_q, GOP_MAX_FRAMES};
41use oxideav_av1::encoder::yuv_frame::YuvFrame;
42use oxideav_heif::{HeifWriter, SequenceWriter};
43
44#[non_exhaustive]
46#[derive(Clone, Copy, Debug)]
47pub struct SequenceEncodeOptions {
48 pub timescale: u32,
50 pub frame_duration: u32,
52 pub base_q_idx: u8,
54 pub all_intra: bool,
57 pub gop_length: usize,
60}
61
62impl SequenceEncodeOptions {
63 #[allow(clippy::too_many_arguments)]
68 pub fn new(
69 timescale: u32,
70 frame_duration: u32,
71 base_q_idx: u8,
72 all_intra: bool,
73 gop_length: usize,
74 ) -> Self {
75 Self {
76 timescale,
77 frame_duration,
78 base_q_idx,
79 all_intra,
80 gop_length,
81 }
82 }
83 pub fn with_timescale(mut self, timescale: u32) -> Self {
85 self.timescale = timescale;
86 self
87 }
88 pub fn with_frame_duration(mut self, frame_duration: u32) -> Self {
90 self.frame_duration = frame_duration;
91 self
92 }
93 pub fn with_base_q_idx(mut self, base_q_idx: u8) -> Self {
95 self.base_q_idx = base_q_idx;
96 self
97 }
98 pub fn with_all_intra(mut self, all_intra: bool) -> Self {
100 self.all_intra = all_intra;
101 self
102 }
103 pub fn with_gop_length(mut self, gop_length: usize) -> Self {
105 self.gop_length = gop_length;
106 self
107 }
108}
109
110impl Default for SequenceEncodeOptions {
111 fn default() -> Self {
112 Self {
113 timescale: 30,
114 frame_duration: 1,
115 base_q_idx: 0,
116 all_intra: false,
117 gop_length: GOP_MAX_FRAMES,
118 }
119 }
120}
121
122pub fn encode_sequence(frames: &[StillImage], opts: &SequenceEncodeOptions) -> Result<Vec<u8>> {
127 encode_sequence_timed(frames, opts, None)
128}
129
130pub(crate) fn encode_sequence_timed(
134 frames: &[StillImage],
135 opts: &SequenceEncodeOptions,
136 durations: Option<&[u32]>,
137) -> Result<Vec<u8>> {
138 let first = frames
139 .first()
140 .ok_or_else(|| Error::invalid("avif sequence: no frames"))?;
141 if let Some(d) = durations {
142 if d.len() != frames.len() {
143 return Err(Error::invalid(format!(
144 "avif sequence: {} durations for {} frames",
145 d.len(),
146 frames.len()
147 )));
148 }
149 }
150 if opts.timescale == 0 || opts.frame_duration == 0 {
151 return Err(Error::invalid(
152 "avif sequence: timescale and frame_duration must be non-zero",
153 ));
154 }
155 if !(1..=GOP_MAX_FRAMES).contains(&opts.gop_length) && !opts.all_intra {
156 return Err(Error::invalid(format!(
157 "avif sequence: gop_length {} outside 1..={GOP_MAX_FRAMES}",
158 opts.gop_length
159 )));
160 }
161 let (pw, ph) = (coded_extent(first.width), coded_extent(first.height));
162 if pw > STILL_MAX_CODED_DIM || ph > STILL_MAX_CODED_DIM {
163 return Err(Error::unsupported(format!(
164 "avif sequence: coded extents {pw}x{ph} exceed {STILL_MAX_CODED_DIM}"
165 )));
166 }
167 let mut yuv = Vec::with_capacity(frames.len());
168 for (i, f) in frames.iter().enumerate() {
169 f.validate()?;
170 if (f.width, f.height, f.bit_depth, f.chroma)
171 != (first.width, first.height, first.bit_depth, first.chroma)
172 {
173 return Err(Error::invalid(format!(
174 "avif sequence: frame {i} ({}x{} {}-bit {:?}) differs from frame 0 ({}x{} {}-bit {:?})",
175 f.width, f.height, f.bit_depth, f.chroma, first.width, first.height,
176 first.bit_depth, first.chroma
177 )));
178 }
179 if f.alpha.is_some() || f.depth_map.is_some() {
180 return Err(Error::unsupported(
181 "avif sequence: auxiliary (alpha / depth) image sequences are not supported",
182 ));
183 }
184 let (w, h) = (f.width as usize, f.height as usize);
185 let y = pad_plane(&f.y, w, h, pw as usize, ph as usize);
186 let (u, v) = if f.chroma.has_chroma() {
187 let (sx, sy) = f.chroma.subsampling();
188 let (cw, ch) = f.chroma_dims();
189 let (pcw, pch) = ((pw >> sx) as usize, (ph >> sy) as usize);
190 (
191 pad_plane(&f.u, cw as usize, ch as usize, pcw, pch),
192 pad_plane(&f.v, cw as usize, ch as usize, pcw, pch),
193 )
194 } else {
195 (Vec::new(), Vec::new())
196 };
197 yuv.push(YuvFrame {
198 width: pw,
199 height: ph,
200 bit_depth: f.bit_depth,
201 format: f.chroma.to_av1(),
202 y,
203 u,
204 v,
205 });
206 }
207 let full_range = colr_is_full_range(first);
208
209 let mut samples: Vec<(Vec<u8>, bool)> = Vec::with_capacity(frames.len());
211 let mut av1c: Option<Vec<u8>> = None;
212 let mut seq_profile = 0u8;
213 if opts.all_intra {
214 for f in &yuv {
215 let coded = encode_coded_item(
216 pw,
217 ph,
218 f.bit_depth,
219 f.format,
220 f.y.clone(),
221 f.u.clone(),
222 f.v.clone(),
223 opts.base_q_idx,
224 full_range,
225 CodedTuning::sequence(EncodeSpeed::Fast, 1),
226 "sequence frame",
227 )?;
228 if av1c.is_none() {
229 av1c = Some(coded.av1c);
230 seq_profile = coded.seq_profile;
231 }
232 samples.push((coded.payload, true));
233 }
234 } else {
235 for chunk in yuv.chunks(opts.gop_length) {
236 let gop = encode_gop_yuv_with_q(chunk, opts.base_q_idx)
237 .map_err(|e| av1_err("sequence GOP", e))?;
238 if av1c.is_none() {
239 av1c = Some(av1c_from_seq(&gop.seq));
240 seq_profile = gop.seq.seq_profile;
241 }
242 for (i, unit) in gop.temporal_units.into_iter().enumerate() {
243 let unit = if i == 0 && full_range {
244 full_range_temporal_unit(&unit, &gop.seq)?
245 } else {
246 unit
247 };
248 samples.push((unit, i == 0));
249 }
250 }
251 }
252 let av1c = av1c.expect("at least one sample");
253 if samples.len() != frames.len() {
254 return Err(Error::invalid(format!(
255 "avif sequence: coded {} samples for {} frames",
256 samples.len(),
257 frames.len()
258 )));
259 }
260 let profile_brand = match seq_profile {
261 0 => ProfileBrand::Baseline,
262 1 => ProfileBrand::Advanced,
263 _ => ProfileBrand::Bare,
264 };
265 let clap = ((pw, ph) != (first.width, first.height))
266 .then(|| top_left_clap(first.width, first.height, pw, ph));
267 let all_sync = samples.iter().all(|(_, s)| *s);
268 if pw > u32::from(u16::MAX) || ph > u32::from(u16::MAX) {
269 return Err(Error::unsupported(
270 "avif sequence: coded extents exceed the 16-bit sample-entry fields",
271 ));
272 }
273 let pixi = pixi_bits(first);
282 let mut props = vec![
283 prop_av1c(&av1c, "sequence ")?,
284 prop_ispe(pw, ph),
285 prop_pixi(&pixi),
286 ];
287 let mut entry_properties = Vec::new();
288 if let Some(c) = first.colr.as_ref() {
289 let (colr, _) = prop_colr(c)?;
290 entry_properties.push(colr.clone());
291 props.push((colr, false));
292 }
293 if let Some(c) = clap.as_ref() {
294 let (clap, essential) = prop_clap(c);
295 entry_properties.push(clap.clone());
296 props.push((clap, essential));
297 }
298 let (av1c_prop, _) = prop_av1c(&av1c, "sequence ")?;
299 let (major, compat) = sequence_brands(profile_brand, all_sync);
300 let mut writer = SequenceWriter::new(*b"av01", av1c_prop, pw as u16, ph as u16, opts.timescale)
301 .with_brands(major, compat);
302 writer.entry_properties = entry_properties;
303 writer.coding_constraints = if all_sync {
309 (true, true, 0)
310 } else {
311 (false, true, 15)
312 };
313 let mut still = HeifWriter::new();
314 let cover = still.add_coded_item(*b"av01", Vec::new(), props);
316 still.set_primary(cover);
317 writer.still = Some(still);
318 writer.cover_sample = Some(0);
319 for (i, (data, sync)) in samples.into_iter().enumerate() {
320 let duration = durations
321 .and_then(|d| d.get(i).copied())
322 .unwrap_or(opts.frame_duration);
323 writer.push_sample(data, duration, sync);
324 }
325 Ok(writer.write_to_vec()?)
326}