Files
craft-libs/crates/aep/src/bytes.rs
T
Dylan M. Taylorandarnav-hiwarkar 119ac0771f Add craft-aep: After Effects project container and typed view from EffectCraft (#888)
* Add craft-aep: After Effects project container and typed view from EffectCraft

The .aep (RIFX 'Egg!') and .aepx container as a lossless chunk tree that
writes back byte for byte, and the typed, read-only view over it:
compositions, layers, properties, keyframes, effects and their settings
data, masks, shapes, text documents, markers, cameras, track mattes,
footage, project settings and the saving version.

Moved from effectcraft crates/aep/src/container at f6b4da0a with its unit
tests, the container integration tests, the aepdump and stubicc tools and
docs/aep-format.md. The match-name tables (effect_map, style_map), the
importer and the exporter are written against EffectCraft's model and
stay there. ExtractorChannels::sources takes the caller's name for a
copied channel instead of an EffectCraft constant.

* craft-aep: read the Photoshop layer a footage item shows

A footage item made from one layer of a .psd stores the layer in its
`opti` (tag `8BPS`): the layer record's index (big-endian i32 at 14, -1
for the merged image), the document's two sides (little-endian u32 at 32
and 36), the layer's bounds (little-endian i32 at 78, 82, 86, 90) and its
name (UTF-8 from 344). `Footage::psd_layer` reads them as `PsdLayer`.

The format part of storytold/effectcraft#3449; the importer's use of it
stays in EffectCraft.

---------

Co-authored-by: arnav-hiwarkar <asharnav2008@gmail.com>
2026-11-11 16:42:18 -05:00

51 lines
1.6 KiB
Rust

//! Bounds-checked big-endian readers shared by the typed views. Every read returns `None` when the
//! bytes are not there; nothing here indexes a slice directly.
pub(crate) fn text(b: &[u8]) -> String {
String::from_utf8_lossy(b).into_owned()
}
/// The bytes up to the first NUL (all of them when there is none).
pub(crate) fn until_nul(b: &[u8]) -> &[u8] {
let end = b.iter().position(|c| *c == 0).unwrap_or(b.len());
b.get(..end).unwrap_or(&[])
}
/// A NUL-terminated ASCII (or Latin-1) string at `at`, at most `max` bytes long.
pub(crate) fn cstr(b: &[u8], at: usize, max: usize) -> Option<String> {
let end = at.checked_add(max)?.min(b.len());
let field = b.get(at..end)?;
Some(until_nul(field).iter().map(|c| char::from(*c)).collect())
}
pub(crate) fn array<const N: usize>(b: &[u8], at: usize) -> Option<[u8; N]> {
let s = b.get(at..at.checked_add(N)?)?;
let mut a = [0u8; N];
a.copy_from_slice(s);
Some(a)
}
pub(crate) fn be_u16(b: &[u8], at: usize) -> Option<u16> {
array::<2>(b, at).map(u16::from_be_bytes)
}
pub(crate) fn be_u32(b: &[u8], at: usize) -> Option<u32> {
array::<4>(b, at).map(u32::from_be_bytes)
}
pub(crate) fn be_i32(b: &[u8], at: usize) -> Option<i32> {
array::<4>(b, at).map(i32::from_be_bytes)
}
pub(crate) fn le_u32(b: &[u8], at: usize) -> Option<u32> {
array::<4>(b, at).map(u32::from_le_bytes)
}
pub(crate) fn be_f32(b: &[u8], at: usize) -> Option<f32> {
array::<4>(b, at).map(f32::from_be_bytes)
}
pub(crate) fn be_f64(b: &[u8], at: usize) -> Option<f64> {
array::<8>(b, at).map(f64::from_be_bytes)
}