Files
craft-libs/crates/time/README.md
T

120 lines
5.4 KiB
Markdown

# Craft Time
`craft-time` provides exact, drift-free media time representation, rational frame-rate mathematics, SMPTE timecode formatting/parsing, and film footage calculations for the Craft suite.
Extracted from the common time engine in **FilmCraft** and **EffectCraft** (refs #2), it is designed for NLEs, motion graphics compositors, audio workstations, and media transcoders.
## Core Concepts
### Exact Ticks (`TICKS_PER_SECOND`)
All media time is tracked as an integer number of clock ticks at:
$$\text{TICKS\_PER\_SECOND} = 254{,}016{,}000{,}000 \text{ ticks/s}$$
This rate was chosen because it divides evenly into every broadcast and cinematic frame rate, as well as every standard professional audio sample rate:
- **Frame Rates**: 23.976, 24, 25, 29.97, 30, 48, 50, 59.94, 60, 119.88, 120 fps.
- **Audio Sample Rates**: 32 kHz, 44.1 kHz, 48 kHz, 88.2 kHz, 96 kHz, 192 kHz.
Edits, clip slicing, frame stepping, and audio/video alignment never suffer cumulative floating-point drift.
```rust
use craft_time::{Tick, TICKS_PER_SECOND};
// Convert from seconds or sample units
let one_sec = Tick::from_seconds_f64(1.0);
assert_eq!(one_sec.0, TICKS_PER_SECOND);
let audio_sample = Tick::from_units(48000, 48000);
assert_eq!(audio_sample, one_sec);
```
### Rational Frame Rates (`FrameRate`)
Frame rates are represented as exact fractions (`num / den`):
```rust
use craft_time::{FrameRate, Tick};
// Standard NTSC and PAL rates
let ntsc_24 = FrameRate::FPS_23_976; // 24000 / 1001
let pal_25 = FrameRate::FPS_25; // 25 / 1
let ntsc_30 = FrameRate::FPS_29_97; // 30000 / 1001
// Start tick of frame 100
let start_tick = ntsc_30.tick_of(100);
// Frame containing a tick (floored)
let frame = ntsc_30.frame_at(start_tick);
assert_eq!(frame, 100);
// Snap to exact frame boundaries
let snapped = ntsc_30.snap(start_tick + Tick(5));
assert_eq!(snapped, start_tick);
```
### SMPTE Timecode (`format_timecode_frames` / `parse_timecode`)
Full support for SMPTE standard drop-frame (semicolon `;` separator) and non-drop frame (colon `:` separator) timecode:
```rust
use craft_time::{FrameRate, format_timecode_frames, parse_timecode};
let r = FrameRate::FPS_29_97;
// Format frame count to drop-frame timecode
let tc = format_timecode_frames(1800, r, true);
assert_eq!(tc, "00;01;00;02"); // Frames 00 and 01 dropped at minute mark
// Parse user-entered timecode (supports relative offsets like +15 or -1.00)
let parsed = parse_timecode("00;01;00;02", r, true, 0).unwrap();
assert_eq!(parsed, 1800);
```
### Film Footage (`format_feet_frames` / `parse_feet_frames`)
Calculates 35mm (16 frames/foot) and 16mm (40 frames/foot) film footage counts:
```rust
use craft_time::{format_feet_frames, parse_feet_frames};
// 35mm (16 frames per foot)
assert_eq!(format_feet_frames(199, 16), "0012+07");
assert_eq!(parse_feet_frames("12+07", 16, 0), Ok(199));
// 16mm (40 frames per foot)
assert_eq!(format_feet_frames(95, 40), "0002+15");
assert_eq!(parse_feet_frames("2+15", 40, 0), Ok(95));
```
## SoundCraft Sample-Time Compatibility
The `soundcraft` module carries SoundCraft's existing sample-index clock (`i64` samples at the
session sample rate), `SampleRate`, `TempoMap`, grid calculations and five timebase parsers and
formatters. It keeps the serialized representation and existing saturation, sanitization and
rounding behavior intact while the rest of the suite can use exact `Tick` values. SoundCraft's
session model imports these types from `craft_time::soundcraft`; the APIs in that module retain
the original SoundCraft names and meanings.
## EffectCraft & FilmCraft Compatibility Differences
`craft-time` unifies time representation across the Craft suite while explicitly documenting application-specific design choices:
1. **After Effects Display Formatting**:
FilmCraft uses standard broadcast SMPTE zero-padded hours (`00:00:02:15` via `format_timecode_frames`).
EffectCraft / After Effects displays hours without leading zeroes (`0:00:02:15` via `format_timecode_ae`). Both functions are provided.
2. **Oversized Timecode Saturation**:
In `craft-time`, `parse_timecode` employs saturating arithmetic (`saturating_mul`, `saturating_add`) when frame calculations exceed 64-bit limits, saturating to `i64::MAX` or `i64::MIN` to keep operations total without unrecoverable errors. In contrast, EffectCraft returns `Err(ParseError("timecode too large"))`.
3. **Degenerate `{0, 0}` Frame Rates**:
In `craft-time`, `FrameRate::sane()` normalizes non-positive or zero rates (such as `{ num: 0, den: 0 }` or negative denominators) to `FrameRate::FPS_23_976` (nominal timecode base 24), maintaining standard broadcast defaults. In contrast, EffectCraft falls back to a timecode base of 1.
4. **Monotonic Operator Division**:
Operator division on `Tick` (`Tick / i64`) and `Tick::saturating_div` saturate on overflow (`Tick(i64::MIN) / -1 -> Tick(i64::MAX)`), preserving strict mathematical monotonicity across the entire signed 64-bit range without inflection.
## Guarantees & Doctrines
- **Zero App Code**: Zero dependencies on application crates.
- **Never Crash**: `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]`.
- **Total Arithmetic**: Division by zero or hostile rates from damaged projects return well-defined fallback states rather than panicking. Arithmetic saturates at 64-bit limits with 128-bit intermediate checks.
- **Forbid Unsafe**: `#![forbid(unsafe_code)]`.