Skip to content

Conversation

@lennartkloock
Copy link
Member

@lennartkloock lennartkloock commented Apr 28, 2025

This splits the original scuffle-mp4 crate into multiple crates:

  • A new crate called isobmff which contains everything described by ISO/IEC 14496-12
  • scuffle-h264 now contains the AVC boxes described by ISO/IEC 14496-15
  • scuffle-h265 now contains the HEVC boxes described by ISO/IEC 14496-15
  • scuffle-av1 now contains the AV1 boxes described by AV1 Codec ISO Media File Format Binding
  • A new crated called scuffle-opus which contains the Opus boxes described by Encapsulation of Opus in ISO Base Media File Format
  • The original scuffle-mp4 crate now only contains the actual MP4 boxes (not ISOBMFF) described by ISO/IEC 14496-14

Other changes:

  • New derive macro to derive the new isobmff::IsoBox, zero-copy Deserialize and zero-copy Serialize traits

Note for reviewers: isobmff::boxes contains a lot of code that can only be reviewed by going through the spec step by step. Maybe start with other modules first.

CLOUD-31 CLOUD-88

@codecov
Copy link

codecov bot commented Apr 28, 2025

Codecov Report

❌ Patch coverage is 61.23135% with 1845 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.17%. Comparing base (6cafac7) to head (ee540e2).

Files with missing lines Patch % Lines
crates/isobmff/src/boxes/file_delivery_format.rs 0.00% 359 Missing ⚠️
crates/isobmff/src/boxes/audio_media.rs 17.33% 267 Missing ⚠️
crates/isobmff/src/boxes/sample_group.rs 40.24% 199 Missing ⚠️
crates/isobmff/src/boxes/sample_groups.rs 52.63% 162 Missing ⚠️
crates/isobmff/src/boxes/metadata.rs 73.73% 161 Missing ⚠️
crates/isobmff/src/boxes/video_media.rs 36.48% 141 Missing ⚠️
crates/isobmff/src/boxes/movie_fragments.rs 85.22% 94 Missing ⚠️
crates/bytes-util/src/zero_copy/serde.rs 48.17% 71 Missing ⚠️
crates/isobmff/src/common_types.rs 41.66% 70 Missing ⚠️
...tes/isobmff/src/boxes/post_decoder_requirements.rs 0.00% 55 Missing ⚠️
... and 21 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #442      +/-   ##
==========================================
- Coverage   61.48%   57.17%   -4.31%     
==========================================
  Files         385      359      -26     
  Lines       31171    30376     -795     
==========================================
- Hits        19165    17368    -1797     
- Misses      12006    13008    +1002     
Files with missing lines Coverage Δ
crates/av1/src/boxes.rs 100.00% <100.00%> (ø)
crates/bytes-util/src/bit_write.rs 100.00% <ø> (ø)
crates/ffmpeg/src/encoder.rs 97.26% <ø> (ø)
crates/ffmpeg/src/io/output.rs 88.12% <ø> (ø)
crates/flv/src/video.rs 100.00% <ø> (ø)
crates/flv/src/video/body.rs 100.00% <ø> (ø)
crates/flv/src/video/body/legacy.rs 100.00% <100.00%> (ø)
crates/h264/src/boxes.rs 100.00% <100.00%> (ø)
crates/h264/src/config.rs 100.00% <100.00%> (ø)
crates/h264/src/sps/sps_ext.rs 100.00% <ø> (ø)
... and 42 more

... and 58 files with indirect coverage changes

Components Coverage Δ
scuffle-aac 89.47% <ø> (ø)
scuffle-amf0 90.31% <ø> (ø)
scuffle-av1 96.70% <76.92%> (-1.71%) ⬇️
scuffle-batching 100.00% <ø> (ø)
scuffle-bootstrap 82.91% <ø> (ø)
scuffle-bytes-util 84.72% <63.69%> (-11.99%) ⬇️
scuffle-context 100.00% <ø> (ø)
scuffle-expgolomb 100.00% <ø> (ø)
scuffle-ffmpeg 90.00% <ø> (ø)
scuffle-flv 95.23% <60.00%> (-0.36%) ⬇️
scuffle-future-ext 50.00% <ø> (ø)
scuffle-h264 99.68% <100.00%> (+<0.01%) ⬆️
scuffle-h265 73.53% <100.00%> (+0.11%) ⬆️
scuffle-http 86.45% <ø> (-0.10%) ⬇️
isobmff 60.93% <60.11%> (∅)
scuffle-metrics 95.02% <ø> (ø)
scuffle-mp4 25.96% <ø> (-51.53%) ⬇️
nutype-enum ∅ <ø> (∅)
scuffle-opus 0.00% <ø> (∅)
postcompile 44.24% <ø> (ø)
scuffle-pprof 100.00% <ø> (ø)
scuffle-rtmp 89.95% <ø> (-0.94%) ⬇️
scuffle-settings 99.11% <ø> (ø)
scuffle-signal 95.41% <ø> (ø)
scuffle-transmuxer 94.65% <ø> (-0.37%) ⬇️
tinc 46.94% <ø> (ø)
openapiv3_1 49.74% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lennartkloock lennartkloock force-pushed the lennart/CLOUD-31 branch 4 times, most recently from b641b8c to f76aaa9 Compare May 23, 2025 22:13
///
/// The caller must ensure that the length of the bytes is less than or equal to 8,
/// otherwise this function will panic.
pub fn pad_to_u64_be(&self) -> u64 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should these functions be here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are needed to pad an unknown number of bytes to an u32/u64. I thought they are useful so I made them part of the bytes-util crate.

use crate::{BytesCow, StringCow};

/// A trait that should be implemented by types that can contain other deserializable types.
pub trait Container {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what this is used for?

Copy link
Member Author

@lennartkloock lennartkloock Jun 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's trait which is implemented for all containers of boxes (Option and Vec). It is used in the derive macro to figure out the contained type.


impl<'a> Deserialize<'a> for f32 {
fn deserialize<R: ZeroCopyReader<'a>>(mut reader: R) -> io::Result<Self> {
reader.as_std().read_f32::<byteorder::BigEndian>()
Copy link
Member

@TroyKomodo TroyKomodo May 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not really a fan of deciding that all these number types should always be BigEndian.

I think, a better approach is to add a new type that is BigEndian<T> and LittleEndian<T>.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that apply to other number types too?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes all number types.


/// A 24-bit signed integer in big-endian byte order.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct I24Be(pub i32);
Copy link
Member

@TroyKomodo TroyKomodo May 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a bad type, i think if you want to create an i24 type we should first reach for something like this i24. This should be feature gated as i24


/// A 48-bit signed integer in big-endian byte order.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct I48Be(pub i64);
Copy link
Member

@TroyKomodo TroyKomodo May 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to i24 we should also use i48. This should be feature gated as i48


/// A 24-bit unsigned integer in big-endian byte order.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct U24Be(pub u32);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There doesnt seem to be a unsized version of a 24 bit integer crate in the wild.
I created this issue jmg049/i24#14.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems doable


/// A 48-bit unsigned integer in big-endian byte order.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct U48Be(pub u64);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like the u24, no seemingly premade u48 number type. Chubercik/i48#5

111,
109,
],
UnknownBox {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is clearly wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firstly, this seems to not find the ftyp box which is strange, and more alarmingly it stops after failing to decode that box. There are other boxes here which are not decoded.

#[iso_box(nested_box)]
pub config: AVCConfigurationBox<'a>,
/// The optional MPEG-4 extension descriptors box contained in this box.
#[iso_box(nested_box(collect))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a fan, ideally we should just be able to say this is a nested_box without adding the collect part, likewise we should be able to do this for vecs without collect.

Comment on lines +353 to +372
match nested {
IsoBoxFieldNestedBox::Single => {
fields_in_self.push(quote! {
#field_name: ::std::option::Option::ok_or(#field_name, ::std::io::Error::new(::std::io::ErrorKind::InvalidData, format!("{} not found", #field_name_str)))?
});
field_serializers.push(quote! {
#crate_path::reexports::scuffle_bytes_util::zero_copy::Serialize::serialize(&self.#field_name, &mut writer)?;
});
}
IsoBoxFieldNestedBox::Collect | IsoBoxFieldNestedBox::CollectUnknown => {
fields_in_self.push(field_name.to_token_stream());
field_serializers.push(quote! {
#[allow(for_loops_over_fallibles)]
for item in &self.#field_name {
#crate_path::reexports::scuffle_bytes_util::zero_copy::Serialize::serialize(item, &mut writer)?;
}
});
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this can be simplified to be handled by the trait objects instead of generating an iterator.

///
/// ISO/IEC 14496-12 - 12.2.2
#[derive(IsoBox, Debug, PartialEq, Eq, Default)]
#[iso_box(box_type = b"smhd", skip_impl(deserialize_seed, serialize, sized), crate_path = crate)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why even bother with the box impl anyways why cant this box be done with macro?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Serialize and Deserialize are not implemented for the fixed point numbers. They should be.

pub loudness_bases: Vec<LoudnessBase>,
}

impl<'a> DeserializeSeed<'a, &FullBoxHeader> for LoudnessBaseBox {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this take the FullBoxHeader by reference?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the FullBoxHeader of the parent box. Some values in this box depend on the version that is stored in the FullBoxHeader.

///
/// ISO/IEC 14996-12 - 8.13.2
#[derive(IsoBox, Debug, PartialEq, Eq)]
#[iso_box(box_type = b"fiin", skip_impl(deserialize_seed, serialize), crate_path = crate)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What can we do here to make this implementable with the macro?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number of items in the partition_entries vec depends on the value of entry_count. I don't think it's easily possible to model that in the macro.

#[iso_box(box_type = b"fiin", skip_impl(deserialize_seed, serialize), crate_path = crate)]
pub struct FDItemInformationBox {
pub full_header: FullBoxHeader,
pub entry_count: u16,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, i dont really like this field.

I understand its part of the "spec", but its really just a length delimiter for the vector below. Perhaps we can remove it and infer it from the vector instead.

Perhaps a

#[iso_box(repeated(u16))]
pub partition_entries: Vec<PartitionEntry>,

annotation which then tells the decoder to first decode a u16 which then tells it how many iterations to perform when doing decode.

codegen could look something like this

let tmp_length = <$ty as Deserialize>::deserialize(r)? as usize;
let partition_entries = (0..tmp_length).map(|_| Deserialize::deserialize(r)).collect::<Result<_, _>>()?;

that way we generalize over anything that implements collect...

where
R: scuffle_bytes_util::zero_copy::ZeroCopyReader<'a>,
{
let full_header = FullBoxHeader::deserialize(&mut reader)?;
Copy link
Member

@TroyKomodo TroyKomodo May 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems wrong, why wouldnt the fullbox header first take the box header?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 13 to 18
#[derive(IsoBox, PartialEq, Eq)]
#[iso_box(box_type = b"mdat", crate_path = crate)]
pub struct MediaDataBox<'a> {
pub data: BytesCow<'a>,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesnt the mdat box have a box header?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, as every other box. The macro takes care of it.

@TroyKomodo TroyKomodo force-pushed the lennart/CLOUD-31 branch 6 times, most recently from 7174e79 to a8422c5 Compare June 2, 2025 11:48
@github-actions
Copy link
Contributor

github-actions bot commented Oct 4, 2025

🚀 Preview Deployments

Deployment Status Preview URL
dashboard ⏭️ -
docs ⏭️ -
rustdoc https://docs-scuffle-hdrntuoj9-scufflecloud.vercel.app
cloud emails render https://scufflecloud-email-preview-jqgayzdkj-scufflecloud.vercel.app

@vrtgs
Copy link

vrtgs commented Nov 9, 2025

consider nbyte-int (or on crates.io) for the u24/u48 integers, please open issues on any functionality you may need I will try to help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants