use crate::api::EncodeSpeed;
use crate::error::{AvifError as Error, Result};
use crate::mux::{
prop_av1c, prop_clap, prop_colr, prop_ispe, prop_pixi, sequence_brands, ProfileBrand,
};
use crate::still::{
av1_err, av1c_from_seq, coded_extent, colr_is_full_range, encode_coded_item,
full_range_temporal_unit, pad_plane, pixi_bits, top_left_clap, CodedTuning, StillImage,
STILL_MAX_CODED_DIM,
};
use oxideav_av1::encoder::inter_frame::{encode_gop_yuv_with_q, GOP_MAX_FRAMES};
use oxideav_av1::encoder::yuv_frame::YuvFrame;
use oxideav_heif::{HeifWriter, SequenceWriter};
#[non_exhaustive]
#[derive(Clone, Copy, Debug)]
pub struct SequenceEncodeOptions {
pub timescale: u32,
pub frame_duration: u32,
pub base_q_idx: u8,
pub all_intra: bool,
pub gop_length: usize,
}
impl SequenceEncodeOptions {
#[allow(clippy::too_many_arguments)]
pub fn new(
timescale: u32,
frame_duration: u32,
base_q_idx: u8,
all_intra: bool,
gop_length: usize,
) -> Self {
Self {
timescale,
frame_duration,
base_q_idx,
all_intra,
gop_length,
}
}
pub fn with_timescale(mut self, timescale: u32) -> Self {
self.timescale = timescale;
self
}
pub fn with_frame_duration(mut self, frame_duration: u32) -> Self {
self.frame_duration = frame_duration;
self
}
pub fn with_base_q_idx(mut self, base_q_idx: u8) -> Self {
self.base_q_idx = base_q_idx;
self
}
pub fn with_all_intra(mut self, all_intra: bool) -> Self {
self.all_intra = all_intra;
self
}
pub fn with_gop_length(mut self, gop_length: usize) -> Self {
self.gop_length = gop_length;
self
}
}
impl Default for SequenceEncodeOptions {
fn default() -> Self {
Self {
timescale: 30,
frame_duration: 1,
base_q_idx: 0,
all_intra: false,
gop_length: GOP_MAX_FRAMES,
}
}
}
pub fn encode_sequence(frames: &[StillImage], opts: &SequenceEncodeOptions) -> Result<Vec<u8>> {
encode_sequence_timed(frames, opts, None)
}
pub(crate) fn encode_sequence_timed(
frames: &[StillImage],
opts: &SequenceEncodeOptions,
durations: Option<&[u32]>,
) -> Result<Vec<u8>> {
let first = frames
.first()
.ok_or_else(|| Error::invalid("avif sequence: no frames"))?;
if let Some(d) = durations {
if d.len() != frames.len() {
return Err(Error::invalid(format!(
"avif sequence: {} durations for {} frames",
d.len(),
frames.len()
)));
}
}
if opts.timescale == 0 || opts.frame_duration == 0 {
return Err(Error::invalid(
"avif sequence: timescale and frame_duration must be non-zero",
));
}
if !(1..=GOP_MAX_FRAMES).contains(&opts.gop_length) && !opts.all_intra {
return Err(Error::invalid(format!(
"avif sequence: gop_length {} outside 1..={GOP_MAX_FRAMES}",
opts.gop_length
)));
}
let (pw, ph) = (coded_extent(first.width), coded_extent(first.height));
if pw > STILL_MAX_CODED_DIM || ph > STILL_MAX_CODED_DIM {
return Err(Error::unsupported(format!(
"avif sequence: coded extents {pw}x{ph} exceed {STILL_MAX_CODED_DIM}"
)));
}
let mut yuv = Vec::with_capacity(frames.len());
for (i, f) in frames.iter().enumerate() {
f.validate()?;
if (f.width, f.height, f.bit_depth, f.chroma)
!= (first.width, first.height, first.bit_depth, first.chroma)
{
return Err(Error::invalid(format!(
"avif sequence: frame {i} ({}x{} {}-bit {:?}) differs from frame 0 ({}x{} {}-bit {:?})",
f.width, f.height, f.bit_depth, f.chroma, first.width, first.height,
first.bit_depth, first.chroma
)));
}
if f.alpha.is_some() || f.depth_map.is_some() {
return Err(Error::unsupported(
"avif sequence: auxiliary (alpha / depth) image sequences are not supported",
));
}
let (w, h) = (f.width as usize, f.height as usize);
let y = pad_plane(&f.y, w, h, pw as usize, ph as usize);
let (u, v) = if f.chroma.has_chroma() {
let (sx, sy) = f.chroma.subsampling();
let (cw, ch) = f.chroma_dims();
let (pcw, pch) = ((pw >> sx) as usize, (ph >> sy) as usize);
(
pad_plane(&f.u, cw as usize, ch as usize, pcw, pch),
pad_plane(&f.v, cw as usize, ch as usize, pcw, pch),
)
} else {
(Vec::new(), Vec::new())
};
yuv.push(YuvFrame {
width: pw,
height: ph,
bit_depth: f.bit_depth,
format: f.chroma.to_av1(),
y,
u,
v,
});
}
let full_range = colr_is_full_range(first);
let mut samples: Vec<(Vec<u8>, bool)> = Vec::with_capacity(frames.len());
let mut av1c: Option<Vec<u8>> = None;
let mut seq_profile = 0u8;
if opts.all_intra {
for f in &yuv {
let coded = encode_coded_item(
pw,
ph,
f.bit_depth,
f.format,
f.y.clone(),
f.u.clone(),
f.v.clone(),
opts.base_q_idx,
full_range,
CodedTuning::sequence(EncodeSpeed::Fast, 1),
"sequence frame",
)?;
if av1c.is_none() {
av1c = Some(coded.av1c);
seq_profile = coded.seq_profile;
}
samples.push((coded.payload, true));
}
} else {
for chunk in yuv.chunks(opts.gop_length) {
let gop = encode_gop_yuv_with_q(chunk, opts.base_q_idx)
.map_err(|e| av1_err("sequence GOP", e))?;
if av1c.is_none() {
av1c = Some(av1c_from_seq(&gop.seq));
seq_profile = gop.seq.seq_profile;
}
for (i, unit) in gop.temporal_units.into_iter().enumerate() {
let unit = if i == 0 && full_range {
full_range_temporal_unit(&unit, &gop.seq)?
} else {
unit
};
samples.push((unit, i == 0));
}
}
}
let av1c = av1c.expect("at least one sample");
if samples.len() != frames.len() {
return Err(Error::invalid(format!(
"avif sequence: coded {} samples for {} frames",
samples.len(),
frames.len()
)));
}
let profile_brand = match seq_profile {
0 => ProfileBrand::Baseline,
1 => ProfileBrand::Advanced,
_ => ProfileBrand::Bare,
};
let clap = ((pw, ph) != (first.width, first.height))
.then(|| top_left_clap(first.width, first.height, pw, ph));
let all_sync = samples.iter().all(|(_, s)| *s);
if pw > u32::from(u16::MAX) || ph > u32::from(u16::MAX) {
return Err(Error::unsupported(
"avif sequence: coded extents exceed the 16-bit sample-entry fields",
));
}
let pixi = pixi_bits(first);
let mut props = vec![
prop_av1c(&av1c, "sequence ")?,
prop_ispe(pw, ph),
prop_pixi(&pixi),
];
let mut entry_properties = Vec::new();
if let Some(c) = first.colr.as_ref() {
let (colr, _) = prop_colr(c)?;
entry_properties.push(colr.clone());
props.push((colr, false));
}
if let Some(c) = clap.as_ref() {
let (clap, essential) = prop_clap(c);
entry_properties.push(clap.clone());
props.push((clap, essential));
}
let (av1c_prop, _) = prop_av1c(&av1c, "sequence ")?;
let (major, compat) = sequence_brands(profile_brand, all_sync);
let mut writer = SequenceWriter::new(*b"av01", av1c_prop, pw as u16, ph as u16, opts.timescale)
.with_brands(major, compat);
writer.entry_properties = entry_properties;
writer.coding_constraints = if all_sync {
(true, true, 0)
} else {
(false, true, 15)
};
let mut still = HeifWriter::new();
let cover = still.add_coded_item(*b"av01", Vec::new(), props);
still.set_primary(cover);
writer.still = Some(still);
writer.cover_sample = Some(0);
for (i, (data, sync)) in samples.into_iter().enumerate() {
let duration = durations
.and_then(|d| d.get(i).copied())
.unwrap_or(opts.frame_duration);
writer.push_sample(data, duration, sync);
}
Ok(writer.write_to_vec()?)
}