ASC Framing Decision List (FDL) Python library
Project description
ASC Framing Decision List (FDL) - Python API
A Python library for creating, manipulating, and processing ASC Framing Decision Lists (FDLs). This implementation provides a high-level API for working with framing intents, canvas transformations, and framing decisions in professional video production workflows.
Features
- Comprehensive FDL Support: Full implementation of the ASC FDL v2.0 specification
- Type-Safe API: Built with Pydantic v2 for robust validation and type checking
- Framing Calculations: Automatic computation such as framing geometry, anchor points, protection regions, and more
- Serialization: Load and save FDL documents in standard JSON format
- Validation: Built-in uniqueness constraints and semantic validation for all FDL components
- Canvas Template Processing: Apply CanvasTemplate transformations to derive new canvases with proper dimension scaling and framing decision inheritance
Installation
Install the ASC FDL Python library using pip:
pip install fdl-python
Development Setup
This project uses uv for dependency management:
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Clone the repository
git clone https://github.com/eden-annn/fdl-python.git
cd fdl-python
# Install dependencies (including dev dependencies)
uv sync
# Run tests
uv run pytest
Quick Start
Creating an FDL from Scratch
import fdl
# Initialize an FDL document
asc_fdl = fdl.AscFramingDecisionList(fdl_creator="ASC FDL Committee")
# Define a Framing Intent
framing_intent = fdl.FramingIntent(
label="2:1 Framing",
id=fdl.FdlId("FDLSMP01"),
aspect_ratio=fdl.DimensionsInt(width=2, height=1),
protection=0.05, # 5% protection area
)
# Add Framing Intent to FDL document
asc_fdl.add_framing_intent(framing_intent)
# Create a Canvas representing your source media
canvas = fdl.Canvas(
label="Open Gate Vignette",
id=fdl.FdlId("20260101"),
source_canvas_id=fdl.FdlId("20260101"),
dimensions=fdl.DimensionsInt(width=4448, height=3096),
anamorphic_squeeze=1.0
)
# Add Framing Decision to Canvas
framing_decision = canvas.add_framing_decision(framing_intent)
# Create a Context and add the Canvas
context = fdl.Context(
label="ArriLF",
context_creator="ASC FDL Committee",
clip_id=fdl.ClipId(clip_name="A001_C001", file="A001_C001.ari"),
canvases=[canvas]
)
# Add Context to FDL document
asc_fdl.add_context(context)
# Save to file
asc_fdl.to_file("output.fdl")
Loading and Processing an Existing FDL
import fdl
# Load an FDL document
asc_fdl = fdl.AscFramingDecisionList.from_file("input.fdl")
# Print full FDL structure
print(asc_fdl.to_json())
# Access framing intents
for framing_intent in asc_fdl.iter_framing_intents():
print(f"FramingIntent: {framing_intent}")
# Access contexts and canvases
for context in asc_fdl.iter_contexts():
print(f"Context: {context.label}")
for canvas in context.iter_canvases():
print(f" Canvas: {canvas.label} - {canvas.dimensions}")
Canvas Template Processing
Apply transformations to create derived canvases:
import fdl
# Define a canvas template for VFX delivery
template = fdl.CanvasTemplate(
label="VFX PULL",
id=fdl.FdlId("VFXPULL"),
target_dimensions=fdl.DimensionsInt(width=3840, height=2160),
fit_source=fdl.FitSource.framing_decision_dimensions,
fit_method=fdl.FitMethod.width,
preserve_from_source_canvas=fdl.PreserveFromSourceCanvas.canvas_dimensions,
maximum_dimensions=fdl.DimensionsInt(width=5000, height=2700),
pad_to_maximum=True,
round=fdl.Round(even=fdl.Even.even, mode=fdl.Mode.up),
)
# Create processor and derive new canvas
processor = fdl.CanvasTemplateProcessor(template)
new_canvas = processor.create_canvas(
source_canvas=canvas,
framing_decision_id=framing_decision.id
)
print(f"New canvas dimensions: {new_canvas.dimensions}")
print(f"Effective dimensions: {new_canvas.effective_dimensions}")
Core Concepts
FDL Document Structure
An FDL document consists of:
- Framing Intents: Target aspect ratios and delivery specifications
- Contexts: Groups of related canvases (e.g., per camera, per scene)
- Canvases: Representations of media with specific dimensions and framing decisions
- Framing Decisions: Computed geometry showing how intents map to canvases
- Canvas Templates: Transformation rules for deriving new canvases
FDL Model Hierarchy
AscFramingDecisionList
├── framing_intents[]
├── contexts[]
│ └── canvases[]
│ └── framing_decisions[]
└── canvas_templates[]
Framing Decision Calculation
When you add a framing decision to a canvas, the library automatically computes:
- Dimensions: The frame dimensions for the target aspect ratio
- Anchor Point: Position of the frame within the canvas
- Protection Area: Optional safe zone around the creative framing
- Protection Anchor Point: Position of the protection frame
Canvas Template Processing
Canvas templates enable transformations like:
- Fit Methods: Width, height, fit-all, or fill
- Source Selection: Use canvas dimensions, effective dimensions, or framing decisions
- Anamorphic Desqueeze: Apply anamorphic desqueeze transformation after source selection
- Preservation Rules: Maintain canvas dimensions while fitting framing decisions
- Maximum/Padding: Enforce size limits and pad to fixed dimensions
- Rounding: Apply even/odd pixel constraints
The processor automatically:
- Selects source dimensions based on fit_source configuration
- Applies anamorphic desqueeze (if specified in template)
- Computes target dimensions and scale factors
- Rescales framing decisions (dimensions, anchors, protection)
- Calculates effective dimensions for padded outputs
- Applies rounding rules
API Reference
Core Classes
AscFramingDecisionList
Main FDL document class with convenience methods:
# Creation and loading
fdl = AscFramingDecisionList()
fdl = AscFramingDecisionList.from_file("path.fdl")
fdl = AscFramingDecisionList.from_json(json_string)
fdl = AscFramingDecisionList.from_dict(dict_data)
# Adding components
fdl.add_context(context)
fdl.add_framing_intent(framing_intent)
fdl.add_canvas_template(template)
# Querying
framing_intent = fdl.get_framing_intent_by_id(id)
canvas = fdl.get_canvas_by_id(id, context=None)
# Iteration
for framing_intents in fdl.iter_framing_intents(): ...
for context in fdl.iter_contexts(): ...
for template in fdl.iter_canvas_templates(): ...
# Serialization
fdl.to_file("output.fdl", indent=2)
json_str = fdl.to_json(indent=2)
dict_data = fdl.to_dict()
Canvas
Represents a media canvas with dimensions and framing decisions:
canvas = Canvas(
label="My Canvas",
id=FdlId("canvas_001"),
source_canvas_id=FdlId("source_001"),
dimensions=DimensionsInt(width=4096, height=2160),
effective_dimensions=DimensionsInt(width=3840, height=2160),
anamorphic_squeeze=1.0,
)
# Add framing decisions
framing_decision = canvas.add_framing_decision(framing_intent, label="Hero Frame")
Important: Canvas IDs must be unique within a context. Attempting to add duplicate IDs
raises ValueError.
FramingIntent
Defines a target aspect ratio and delivery specification:
intent = FramingIntent(
label="16:9 HD",
id=FdlId("HD_16x9"),
aspect_ratio=DimensionsInt(width=16, height=9),
protection=0.10, # 10% protection border
)
Important: Framing intent IDs must be unique across the FDL document.
Context
Groups related canvases:
context = Context(
label="Camera A",
context_creator="DIT Team",
clip_id=ClipId(clip_name="A001_C001", file="A001_C001.MOV"),
canvases=[canvas1, canvas2],
)
# Add canvases
context.add_canvas(canvas)
# Query canvases
canvas = context.get_canvas_by_id(canvas_id)
CanvasTemplate
Defines transformation rules:
template = CanvasTemplate(
label="UHD Deliverable",
id=FdlId("UHD_TEMPLATE"),
target_dimensions=DimensionsInt(width=3840, height=2160),
fit_source=FitSource.framing_decision_dimensions,
fit_method=FitMethod.width,
preserve_from_source_canvas=PreserveFromSourceCanvas.none,
round=Round(even=Even.even, mode=Mode.round),
)
Fit Source Options:
FitSource.framing_decision_dimensions: Use a specific framing decisionFitSource.effective_dimensions: Use canvas effective dimensionsFitSource.canvas_dimensions: Use full canvas dimensions
Fit Methods:
FitMethod.width: Scale to match target widthFitMethod.height: Scale to match target heightFitMethod.fit_all: Scale to fit entire source (letterbox/pillarbox)FitMethod.fill: Scale to fill target (crop source)
CanvasTemplateProcessor
Applies templates to generate derived canvases:
processor = CanvasTemplateProcessor(template)
new_canvas = processor.create_canvas(
source_canvas=canvas,
framing_decision_id=FdlIdFramingDecision("canvas_001-intent_001"),
new_canvas_id=FdlId("derived_001"),
new_canvas_label="Derived Canvas",
)
The processor automatically:
- Computes target dimensions and scale
- Rescales all framing decisions
- Calculates effective dimensions when padding is applied
- Applies rounding rules
Schema Types
Key data types from the FDL schema:
# Identifiers
FdlId("unique_id")
FdlIdFramingDecision("canvas_id-intent_id")
# Dimensions
DimensionsInt(width=1920, height=1080)
DimensionsFloat(width=1920.5, height=1080.25)
# Points
PointFloat(x=960.0, y=540.0)
# Rounding
Round(even=Even.even, mode=Mode.up)
Round(even=Even.whole, mode=Mode.down)
# Enums
FitSource
FitMethod
PreserveFromSourceCanvas
Mode
Even
AlignmentMethodHorizontal
AlignmentMethodVertical
Additional validations and implementations
Validations
All models include validation respecting FDL guideline:
# Duplicate canvas IDs raise ValueError
try:
context.add_canvas(canvas_with_duplicate_id)
except ValueError as e:
print(f"Canvas ID must be unique in a Context: {e}")
# Invalid maximum_dimensions raise ValueError
try:
template = CanvasTemplate(
target_dimensions=DimensionsInt(width=3840, height=2160),
maximum_dimensions=DimensionsInt(width=1920, height=1080), # Too small!
)
except ValueError as e:
print(f"maximum_dimensions must be greater or equal to the target_dimensions: {e}")
Working with Effective Dimensions
Canvases can specify effective dimensions (active pixel region):
canvas = Canvas(
dimensions=DimensionsInt(width=4096, height=2160),
effective_dimensions=DimensionsInt(width=3840, height=2160),
# effective_anchor_point is computed automatically if not provided
)
# When processing with pad_to_maximum=True, effective dimensions track active pixels
processor = CanvasTemplateProcessor(template_with_padding)
new_canvas = processor.create_canvas(source_canvas)
print(f"Total dimensions: {new_canvas.dimensions}")
print(f"Active region: {new_canvas.effective_dimensions}")
Framing Decision and Scaling via CanvasTemplate
When CanvasTemplate creates derived canvases, framing decisions are rescaled accordingly:
processor = CanvasTemplateProcessor(template)
new_canvas = processor.create_canvas(source_canvas, framing_decision_id=fd_id)
# Original framing decisions remain unchanged
# New canvas has scaled copies with:
# - Dimensions scaled by computed scale factor
# - Anchor points scaled proportionally
# - Protection dimensions and anchors scaled
# - IDs and labels preserved
Schema Generation
The schema types are generated from the official ASC FDL v2.0 JSON Schema. While the generated models are used as-is, some models have been extended with custom validation and convenience methods in separate modules, for instance:
Canvas(extends_Canvas)Context(extends_Context)CanvasTemplate(extends_CanvasTemplate)
This approach maintains schema compatibility while providing enhanced Python ergonomics.
Requirements
- Python 3.10+
- pydantic >= 2.0
Development
# Install development dependencies
pip install pydantic mypy ruff pytest
# Run tests
pytest tests/
# Type checking
mypy python/fdl/
# Format code
ruff format python/
Contributing
Contributions are welcome! Please ensure:
- Code follows the existing style (100-character line limit)
- All tests pass
- Type hints are provided
- Docstrings follow the project conventions
License
[To be added...]
References
Acknowledgments
This Python library is a project developed in adherence to the official ASC Framing Decision List specification. Special thanks to the American Society of Cinematographers (ASC) Motion Imaging Technology Council (MITC) for developing and maintaining the FDL standard.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fdl_python-0.0.2.tar.gz.
File metadata
- Download URL: fdl_python-0.0.2.tar.gz
- Upload date:
- Size: 15.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0bd7f00b9d541ec334d2e8f2c3af1a57ee3730644d8fc80fa393297879e3cb3a
|
|
| MD5 |
c6c5b3a276a60cf847b87646af55cacf
|
|
| BLAKE2b-256 |
ac2a78dc2eac9497b235b614de628c5fb6e022c89e0d5a4364ddc690bd8c8313
|
File details
Details for the file fdl_python-0.0.2-py3-none-any.whl.
File metadata
- Download URL: fdl_python-0.0.2-py3-none-any.whl
- Upload date:
- Size: 18.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c098be5768a936cc3a8bc8178f38711cd01c73663f47bba454db00b7bea79da
|
|
| MD5 |
e94e97239e782eeae4edc455a25d6d56
|
|
| BLAKE2b-256 |
4226254169c862949ff26488d946f8cb0dbc39d25f1b0b593b0c0269efa3b484
|