Skip to content

Commit aaad770

Browse files
Zaggy1024kinetiknz
authored andcommitted
Read the loop count from AVIS files.
The loop count is calculated as the number of times the animation must play to fill the `trak` duration based on the actual animation duration stored in the `elst` box.
1 parent 19f50c2 commit aaad770

File tree

11 files changed

+188
-3
lines changed

11 files changed

+188
-3
lines changed

mp4parse/src/lib.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,7 @@ pub struct TrackHeaderBox {
988988
/// Edit list box 'elst'
989989
#[derive(Debug)]
990990
struct EditListBox {
991+
looped: bool,
991992
edits: TryVec<Edit>,
992993
}
993994

@@ -2242,7 +2243,9 @@ where
22422243
pub struct Track {
22432244
pub id: usize,
22442245
pub track_type: TrackType,
2246+
pub looped: Option<bool>,
22452247
pub empty_duration: Option<MediaScaledTime>,
2248+
pub edited_duration: Option<MediaScaledTime>,
22462249
pub media_time: Option<TrackScaledTime<u64>>,
22472250
pub timescale: Option<TrackTimeScale<u64>>,
22482251
pub duration: Option<TrackScaledTime<u64>>,
@@ -4402,6 +4405,8 @@ fn read_edts<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
44024405
if media_time < 0 {
44034406
debug!("unexpected negative media time in edit");
44044407
}
4408+
track.looped = Some(elst.looped);
4409+
track.edited_duration = Some(MediaScaledTime(elst.edits[idx].segment_duration));
44054410
track.media_time = Some(TrackScaledTime::<u64>(
44064411
std::cmp::max(0, media_time) as u64,
44074412
track.id,
@@ -4668,7 +4673,7 @@ fn read_tkhd<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackHeaderBox> {
46684673
/// Parse a elst box.
46694674
/// See ISOBMFF (ISO 14496-12:2020) § 8.6.6
46704675
fn read_elst<T: Read>(src: &mut BMFFBox<T>) -> Result<EditListBox> {
4671-
let (version, _) = read_fullbox_extra(src)?;
4676+
let (version, flags) = read_fullbox_extra(src)?;
46724677
let edit_count = be_u32(src)?;
46734678
let mut edits = TryVec::with_capacity(edit_count.to_usize())?;
46744679
for _ in 0..edit_count {
@@ -4696,7 +4701,10 @@ fn read_elst<T: Read>(src: &mut BMFFBox<T>) -> Result<EditListBox> {
46964701
// Padding could be added in some contents.
46974702
skip_box_remain(src)?;
46984703

4699-
Ok(EditListBox { edits })
4704+
Ok(EditListBox {
4705+
looped: flags == 1,
4706+
edits,
4707+
})
47004708
}
47014709

47024710
/// Parse a mdhd box.

mp4parse/tests/loop_forever.avif

2.9 KB
Binary file not shown.

mp4parse/tests/loop_none.avif

2.9 KB
Binary file not shown.

mp4parse/tests/public.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ static AVIF_AVIS_MAJOR_NO_PITM: &str =
8686
/// but with https://github.com/AOMediaCodec/av1-avif/issues/177 fixed
8787
static AVIF_AVIS_MAJOR_WITH_PITM_AND_ALPHA: &str = "tests/alpha_video_fixed.avif";
8888
static AVIF_AVIS_MAJOR_NO_MOOV: &str = "tests/corrupt/alpha_video_moov_is_moop.avif";
89+
static AVIF_AVIS_NO_LOOP: &str = "tests/loop_none.avif";
90+
static AVIF_AVIS_LOOP_FOREVER: &str = "tests/loop_forever.avif";
8991
static AVIF_NO_PIXI_IMAGES: &[&str] = &[IMAGE_AVIF_NO_PIXI, IMAGE_AVIF_NO_ALPHA_PIXI];
9092
static AVIF_UNSUPPORTED_IMAGES: &[&str] = &[
9193
AVIF_A1LX,
@@ -1207,7 +1209,13 @@ fn public_avis_major_with_pitm_and_alpha() {
12071209
assert_eq!(context.major_brand, mp4::AVIS_BRAND);
12081210
assert!(context.primary_item_coded_data().is_some());
12091211
assert!(context.alpha_item_coded_data().is_some());
1210-
assert!(context.sequence.is_some());
1212+
match context.sequence {
1213+
Some(sequence) => {
1214+
assert!(!sequence.tracks.is_empty());
1215+
assert_eq!(sequence.tracks[0].looped, None);
1216+
}
1217+
None => panic!("Expected sequence"),
1218+
}
12111219
}
12121220
Err(e) => panic!("Expected Ok(_), found {:?}", e),
12131221
}
@@ -1218,6 +1226,36 @@ fn public_avif_avis_major_no_moov() {
12181226
assert_avif_shall(AVIF_AVIS_MAJOR_NO_MOOV, Status::MoovMissing);
12191227
}
12201228

1229+
fn public_avis_loop_impl(path: &str, looped: bool) {
1230+
let input = &mut File::open(path).expect("Unknown file");
1231+
match mp4::read_avif(input, ParseStrictness::Normal) {
1232+
Ok(context) => match context.sequence {
1233+
Some(sequence) => {
1234+
assert!(!sequence.tracks.is_empty());
1235+
assert_eq!(sequence.tracks[0].looped, Some(looped));
1236+
if looped {
1237+
assert!(sequence.tracks[0].edited_duration.is_some());
1238+
}
1239+
}
1240+
None => panic!(
1241+
"Expected sequence in {}",
1242+
AVIF_AVIS_MAJOR_WITH_PITM_AND_ALPHA
1243+
),
1244+
},
1245+
Err(e) => panic!("Expected Ok(_), found {:?}", e),
1246+
}
1247+
}
1248+
1249+
#[test]
1250+
fn public_avif_avis_no_loop() {
1251+
public_avis_loop_impl(AVIF_AVIS_NO_LOOP, false);
1252+
}
1253+
1254+
#[test]
1255+
fn public_avif_avis_loop_forever() {
1256+
public_avis_loop_impl(AVIF_AVIS_LOOP_FOREVER, true);
1257+
}
1258+
12211259
#[test]
12221260
fn public_avif_read_samples() {
12231261
public_avif_read_samples_impl(ParseStrictness::Normal);

mp4parse_capi/src/lib.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,20 @@ pub struct Mp4parseParser {
312312
video_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackVideoSampleInfo>>,
313313
}
314314

315+
#[repr(C)]
316+
#[derive(Debug)]
317+
pub enum Mp4parseAvifLoopMode {
318+
NoEdits,
319+
LoopByCount,
320+
LoopInfinitely,
321+
}
322+
323+
impl Default for Mp4parseAvifLoopMode {
324+
fn default() -> Self {
325+
Mp4parseAvifLoopMode::NoEdits
326+
}
327+
}
328+
315329
#[repr(C)]
316330
#[derive(Debug)]
317331
pub struct Mp4parseAvifInfo {
@@ -338,6 +352,14 @@ pub struct Mp4parseAvifInfo {
338352

339353
/// Whether there is a sequence. Can be true with no primary image.
340354
pub has_sequence: bool,
355+
/// Indicates whether the EditListBox requests that the image be looped.
356+
pub loop_mode: Mp4parseAvifLoopMode,
357+
/// Number of times to loop the animation during playback.
358+
///
359+
/// The duration of the animation specified in `elst` must be looped to fill the
360+
/// duration of the color track. If the resulting loop count is not an integer,
361+
/// then it will be ceiled to play past and fill the entire track's duration.
362+
pub loop_count: u64,
341363
/// The color track's ID, which must be valid if has_sequence is true.
342364
pub color_track_id: u32,
343365
pub color_track_bit_depth: u8,
@@ -1096,6 +1118,8 @@ fn mp4parse_avif_get_info_safe(context: &AvifContext) -> mp4parse::Result<Mp4par
10961118
alpha_item_bit_depth: 0,
10971119

10981120
has_sequence: false,
1121+
loop_mode: Mp4parseAvifLoopMode::NoEdits,
1122+
loop_count: 0,
10991123
color_track_id: 0,
11001124
color_track_bit_depth: 0,
11011125
alpha_track_id: 0,
@@ -1182,10 +1206,38 @@ fn mp4parse_avif_get_info_safe(context: &AvifContext) -> mp4parse::Result<Mp4par
11821206
_ => (0, 0),
11831207
};
11841208

1209+
let (loop_mode, loop_count) = match color_track.tkhd.as_ref().map(|tkhd| tkhd.duration) {
1210+
Some(movie_duration) if movie_duration == std::u64::MAX => {
1211+
(Mp4parseAvifLoopMode::LoopInfinitely, 0)
1212+
}
1213+
Some(movie_duration) => match color_track.looped {
1214+
Some(true) => match color_track.edited_duration.map(|v| v.0) {
1215+
Some(segment_duration) => {
1216+
match movie_duration.checked_div(segment_duration).and_then(|n| {
1217+
match movie_duration.checked_rem(segment_duration) {
1218+
Some(0) => Some(n.saturating_sub(1)),
1219+
Some(_) => Some(n),
1220+
None => None,
1221+
}
1222+
}) {
1223+
Some(n) => (Mp4parseAvifLoopMode::LoopByCount, n),
1224+
None => (Mp4parseAvifLoopMode::LoopInfinitely, 0),
1225+
}
1226+
}
1227+
None => (Mp4parseAvifLoopMode::NoEdits, 0),
1228+
},
1229+
Some(false) => (Mp4parseAvifLoopMode::LoopByCount, 0),
1230+
None => (Mp4parseAvifLoopMode::NoEdits, 0),
1231+
},
1232+
None => (Mp4parseAvifLoopMode::LoopInfinitely, 0),
1233+
};
1234+
11851235
return Ok(Mp4parseAvifInfo {
11861236
primary_item_bit_depth,
11871237
alpha_item_bit_depth,
11881238
has_sequence: true,
1239+
loop_mode,
1240+
loop_count,
11891241
color_track_id,
11901242
color_track_bit_depth,
11911243
alpha_track_id,

mp4parse_capi/tests/loop_1.avif

2.9 KB
Binary file not shown.

mp4parse_capi/tests/loop_2.avif

2.9 KB
Binary file not shown.
2.9 KB
Binary file not shown.

mp4parse_capi/tests/loop_forever.avif

2.9 KB
Binary file not shown.

mp4parse_capi/tests/no_edts.avif

2 KB
Binary file not shown.

0 commit comments

Comments
 (0)