| name | core-architecture-patterns |
| description | Use when adding a segment type, platform adapter, editor manager, or core service in packages/ffmpeg-video-composer, or when wiring new dependencies into the tsyringe container. |
Core Architecture Patterns
Overview
packages/ffmpeg-video-composer is built from a few repeating patterns: adapters abstract the platform, the PlatformBridge selects them at runtime, tsyringe DI wires everything, and a director → builder → segments → managers pipeline compiles a template. Follow these patterns; reuse existing pieces before adding new ones. Full design: docs/architecture.md.
The pipeline
TemplateDirector (packages/ffmpeg-video-composer/src/director/TemplateDirector.ts) orchestrates:
init → build each section → concat → apply music.
- Sections created by
SegmentFactory (editor/factories/SegmentFactory.ts) → rendered by *Segment classes (editor/segments/).
- FFmpeg commands assembled by editor managers (
editor/managers/): asset, variable, map, filter, formatter.
VideoEditor concatenates; MusicComposer mixes audio.
Adding a segment type
- Add the section schema variant in
packages/ffmpeg-video-composer/src/schemas/template.schemas.ts (extend BaseSectionSchema, add to the SectionSchema discriminated union).
- Create
editor/segments/<Name>Segment.ts following an existing segment (e.g. VideoSegment.ts); use @injectable().
- Register it in
editor/factories/SegmentFactory.ts (map the new type literal → class).
- Add a test under
packages/ffmpeg-video-composer/tests/.
Adding a platform adapter
- Implement the matching
Abstract* base in packages/ffmpeg-video-composer/src/platform/<capability>/ (e.g. AbstractFFmpeg, AbstractFilesystem, AbstractLogger). Name it <Thing><Platform>Adapter.
- Register/select it in
PlatformBridge.ts for the right runtime (Node / browser / React Native).
- Wire it into the container at the entry point:
index.ts (Node) or browser.ts (browser/WASM).
Dependency injection (tsyringe)
@injectable()
class VideoEditor {
constructor(@inject('ffmpegAdapter') private readonly ffmpeg: AbstractFFmpeg) {}
}
container.registerInstance('ffmpegAdapter', await bridge.create('ffmpeg'));
- Use
@singleton() for shared models/state, @injectable() for services.
- Register by string token in
index.ts / browser.ts; resolve via container or constructor injection.
reflect-metadata must be imported once at the entry point or all DI breaks at runtime.
Conventions
- PascalCase class + file name;
Abstract* bases; *Adapter implementations; *Manager for the editor layer.
- Keep platform-specific code behind an adapter — never branch on the runtime outside
PlatformBridge.
- Validate with
pnpm --filter ffmpeg-video-composer exec tsc --noEmit, then pnpm test and pnpm lint.
Common mistakes
- Adding a segment to the factory but forgetting the schema union (or vice versa) → validation/runtime mismatch.
- Importing a Node-only module from code that also runs in the browser bundle (
browser.ts path) — put it behind an adapter.
- Forgetting to register a new adapter in
PlatformBridge for every runtime it should serve.