Skip to content

Commit 6acfdd3

Browse files
committed
Add Copilot instructions
1 parent 2869dd4 commit 6acfdd3

File tree

4 files changed

+427
-3
lines changed

4 files changed

+427
-3
lines changed

.github/copilot-instructions.md

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# MLT Framework — Copilot Instructions
2+
3+
MLT is a LGPL multimedia framework for video editing, written primarily in C with C++ for Qt-dependent modules.
4+
Version: 7.x (see `CMakeLists.txt`). Schema v7.0 metadata YAML files describe every plugin service.
5+
6+
---
7+
8+
## Build
9+
10+
```bash
11+
# Standard out-of-tree build
12+
# Configure (from repo root; output goes to build/cc-debug)
13+
cmake --preset cc-debug
14+
15+
# Reformat code with clang-format
16+
cmake --build build/cc-debug --target clang-format
17+
18+
# Build
19+
cmake --build build/cc-debug
20+
21+
# Install (optional, default prefix is $HOME/opt)
22+
cmake --install build/cc-debug
23+
24+
# Or install to system (also optional, requires sudo)
25+
cmake --install build/cc-debug --prefix /usr/local
26+
27+
# With vcpkg preset (BUILD_TESTING=ON included)
28+
cmake --preset vcpkg # or vcpkg-ninja
29+
cmake --build build --parallel $(($(nproc) - 1))
30+
```
31+
32+
After building, source `setenv` from the repo root to configure runtime paths for the uninstalled build:
33+
34+
```bash
35+
ln -s build/cc-debug/out
36+
source setenv # bash only; sets MLT_REPOSITORY, LD_LIBRARY_PATH, etc.
37+
```
38+
39+
Check formatting (CI enforces clang-format-14):
40+
41+
```bash
42+
cmake --build build/cc-debug --target clang-format-check # from build dir configured with -DCLANG_FORMAT=ON
43+
```
44+
45+
## Tests
46+
47+
```bash
48+
ln -s build/cc-debug/out
49+
source ../setenv
50+
cd build
51+
ctest --output-on-failure
52+
```
53+
54+
Test suites live in `src/tests/` (one subdirectory per area: `test_filter`, `test_properties`, etc.).
55+
56+
---
57+
58+
## Architecture
59+
60+
```
61+
src/
62+
framework/ Pure-C core library: mlt_filter, mlt_frame, mlt_properties,
63+
mlt_producer, mlt_consumer, mlt_tractor, mlt_playlist, mlt_service
64+
modules/ Optional plugins (one shared library each, loaded at runtime)
65+
melt/ CLI tool
66+
mlt++/ C++ wrapper classes
67+
swig/ Language bindings (Python, etc.)
68+
tests/ Unit tests
69+
```
70+
71+
Plugins are enabled at configure time via `MOD_*` CMake options (e.g. `MOD_QT6`, `MOD_PLUS`).
72+
73+
---
74+
75+
## Module / Plugin Conventions
76+
77+
Each module in `src/modules/<name>/` contains:
78+
79+
| File | Purpose |
80+
|------|---------|
81+
| `factory.c` | Registers all services with `mlt_register_*`; declares `extern` init functions |
82+
| `filter_<name>.c/.cpp` | Filter implementation |
83+
| `filter_<name>.yml` | Metadata descriptor (schema_version 7.0) |
84+
| `producer_<name>.c/.yml`, `consumer_<name>.c/.yml`, `transition_<name>.c/.yml` | Same pattern for other service types |
85+
| `CMakeLists.txt` | Module-local build rules |
86+
87+
**Language by module:**
88+
- `src/framework/` and most modules (core, avformat, plus majority) → pure C (`.c`)
89+
- Modules requiring Qt (`qt/`) → C++ (`.cpp`)
90+
- `plus/` is mixed: mostly C with a few `.cpp` files for subtitle/gradient helpers
91+
- `mlt++/` → C++
92+
93+
---
94+
95+
## Filter Implementation Pattern
96+
97+
### Pixel-processing filter (push/pop)
98+
99+
```c
100+
static int filter_get_image(mlt_frame frame, uint8_t **image,
101+
mlt_image_format *format, int *width, int *height, int writable)
102+
{
103+
mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame);
104+
mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
105+
// read properties, call mlt_frame_get_image(), process pixels
106+
return mlt_frame_get_image(frame, image, format, width, height, writable);
107+
}
108+
109+
static mlt_frame filter_process(mlt_filter filter, mlt_frame frame)
110+
{
111+
mlt_frame_push_service(frame, filter);
112+
mlt_frame_push_get_image(frame, filter_get_image);
113+
return frame;
114+
}
115+
```
116+
117+
### Delegating filter (pass-through to another filter)
118+
119+
```c
120+
static mlt_frame filter_process(mlt_filter filter, mlt_frame frame)
121+
{
122+
mlt_filter other = (mlt_filter) mlt_properties_get_data(
123+
MLT_FILTER_PROPERTIES(filter), "_other", NULL);
124+
mlt_properties text_props = mlt_frame_unique_properties(frame, MLT_FILTER_SERVICE(other));
125+
mlt_properties_pass_list(text_props, MLT_FILTER_PROPERTIES(filter), "key1 key2 key3");
126+
return mlt_filter_process(other, frame);
127+
}
128+
```
129+
130+
### Init function
131+
132+
```c
133+
extern "C" // for .cpp files
134+
mlt_filter filter_<name>_init(mlt_profile profile,
135+
mlt_service_type type, const char *id, char *arg)
136+
{
137+
mlt_filter filter = mlt_filter_new();
138+
if (filter) {
139+
mlt_properties p = MLT_FILTER_PROPERTIES(filter);
140+
mlt_properties_set_string(p, "key", "default");
141+
mlt_properties_set_int(p, "_filter_private", 1); // suppress in serialization
142+
filter->process = filter_process;
143+
}
144+
return filter;
145+
}
146+
```
147+
148+
---
149+
150+
## Key API Reference
151+
152+
| API | Use |
153+
|-----|-----|
154+
| `MLT_FILTER_PROPERTIES(f)` | Get `mlt_properties` from a filter |
155+
| `MLT_FILTER_SERVICE(f)` | Get `mlt_service` from a filter |
156+
| `MLT_FRAME_PROPERTIES(f)` | Get `mlt_properties` from a frame |
157+
| `mlt_properties_set/get` | String property access |
158+
| `mlt_properties_set_int/get_int` | Integer property access |
159+
| `mlt_properties_set_data/get_data` | Store owned C++ objects with destructor |
160+
| `mlt_properties_set_int64/get_int64` | 64-bit integer (e.g. timestamps) |
161+
| `mlt_properties_pass_list(dst, src, "k1 k2")` | Copy named properties between objects |
162+
| `mlt_properties_anim_get_double(p, "key", pos, len)` | Keyframed (animated) value |
163+
| `mlt_properties_exists(p, "key")` | Test presence before reading |
164+
| `mlt_frame_unique_properties(frame, service)` | Per-frame filter properties (thread-safe) |
165+
| `mlt_events_listen(p, owner, "property-changed", cb)` | React to property changes |
166+
| `mlt_slices_run_normal(n, proc, cookie)` | Multithreaded pixel loop |
167+
| `mlt_log_debug/info/error(service, fmt, ...)` | Structured logging |
168+
169+
**Private properties** (not serialized to XML) must be prefixed with `_` (e.g. `"_subtitles"`, `"_mtime"`).
170+
171+
---
172+
173+
## YAML Metadata (.yml)
174+
175+
Schema version: **7.0**. Validated against `src/framework/metaschema.yaml`.
176+
177+
Required top-level fields: `schema_version`, `type`, `identifier`, `language`.
178+
179+
Parameter `type` values: `string`, `integer`, `float`, `boolean`, `rect`, `color`, `binary`.
180+
181+
Widget hints: `spinner`, `slider`, `combo`, `checkbox`, `color`, `text`.
182+
183+
Add `animation: yes` for keyframeable parameters, `mutable: yes` for runtime-changeable ones.
184+
185+
---
186+
187+
## Naming Conventions
188+
189+
- **Source files:** `filter_<name>.c/.cpp`, `producer_<name>.c`, `transition_<name>.c`, `consumer_<name>.c`
190+
- **Init function:** `filter_<name>_init(...)` — must be declared `extern "C"` in `.cpp` files
191+
- **Internal static functions:** `filter_process`, `filter_get_image`, `property_changed` — `snake_case`
192+
- **Structs:** `snake_case`
193+
- **C++ classes:** `PascalCase` (e.g. `Subtitles::SubtitleVector`)
194+
- **Private/internal properties:** prefix with `_`
195+
196+
---
197+
198+
## Code Style
199+
200+
- 4-space indentation (no tabs)
201+
- Formatting enforced by `.clang-format` (derived from Qt Creator style)
202+
- Run `make clang-format-check` before committing; CI will fail on violations
203+
- `formatOnSave` is enabled in the dev container VS Code settings
204+
- Always update the copyright year in the header of any modified file
205+
206+
---
207+
208+
## Dev Container
209+
210+
The repo includes `.devcontainer/` with a `devcontainer.json` and `Dockerfile`.
211+
212+
- Default "Restricted Mode": audio routed to dummy driver, GPU via DRI passthrough only
213+
- For full hardware audio: uncomment `--privileged` / `--ipc=host` in `devcontainer.json` and set `SDL_AUDIODRIVER=alsa` or `pulseaudio`
214+
- After rebuilding: `Dev Containers: Rebuild Container` in VS Code
215+
216+
---
217+
218+
## Common Pitfalls
219+
220+
- **Thread safety:** use `mlt_frame_unique_properties(frame, service)` when setting per-frame filter properties; never write directly to shared filter properties during `filter_get_image`.
221+
- **Missing `extern "C"`:** every `filter_*_init` in a `.cpp` file must be wrapped in `extern "C" { ... }` so the loader can find it.
222+
- **Property serialization:** properties starting with `_` are private and not written to XML. Use this for cached data, file modification times, sub-filter handles, and reset flags.
223+
- **`mlt_properties_pass_list` in `filter_process`:** pass properties to the sub-filter on `mlt_frame_unique_properties`, not on the sub-filter's own properties, to avoid data races.
224+
- **YAML `readonly`:** set `readonly: no` on any parameter the user is expected to set; omitting it defaults to read-only in some tooling.
225+
- **Delegating to `qtext` vs `text`:** `filter_subtitle` tries `qtext` first, then falls back to `text`. Properties specific to `qtext` (e.g. `typewriter.*`) are silently ignored by the `text` fallback.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
description: "Use when writing or editing MLT filter, producer, consumer, or transition plugins in src/modules/. Covers extern C for C++ init functions, private property naming, and thread-safe per-frame property access."
3+
applyTo: "src/modules/**"
4+
---
5+
6+
## C++ init functions — mandatory `extern "C"`
7+
8+
Every `filter_<name>_init` (or `producer_*`, `consumer_*`, `transition_*`) in a `.cpp` file **must** be wrapped:
9+
10+
```cpp
11+
extern "C" {
12+
13+
mlt_filter filter_<name>_init(mlt_profile profile,
14+
mlt_service_type type,
15+
const char *id,
16+
char *arg)
17+
{ ... }
18+
19+
} // extern "C"
20+
```
21+
22+
Without this, C++ name mangling prevents the dynamic loader from finding the symbol. The service silently fails to load — no error is logged.
23+
24+
## Private properties — `_` prefix
25+
26+
Properties prefixed with `_` are **never serialized to XML**. Use this for:
27+
- Cached data (`"_subtitles"`, `"_mtime"`)
28+
- Owned C++ objects stored via `mlt_properties_set_data`
29+
- Sub-filter handles (`"_t"`)
30+
- One-shot reset flags (`"_reset"`)
31+
32+
Do **not** prefix user-visible parameters with `_`. Do **not** list `_`-prefixed properties in `.yml` metadata files.
33+
34+
## Thread-safe per-frame state — `mlt_frame_unique_properties`
35+
36+
`filter_get_image` runs on worker threads concurrently for different frames. Rules:
37+
38+
- **Reading** from shared filter properties is safe.
39+
- **Writing** to shared filter properties is a data race — never do it.
40+
- Use `mlt_frame_unique_properties(frame, MLT_FILTER_SERVICE(filter))` to get a per-frame writable property bag.
41+
42+
For delegating filters, pass properties to the **frame's** unique bag, not to the sub-filter's own properties:
43+
44+
```c
45+
// CORRECT — thread-safe
46+
mlt_properties fp = mlt_frame_unique_properties(frame, MLT_FILTER_SERVICE(sub_filter));
47+
mlt_properties_pass_list(fp, MLT_FILTER_PROPERTIES(filter), "key1 key2");
48+
49+
// WRONG — data race across concurrent frames
50+
mlt_properties_pass_list(MLT_FILTER_PROPERTIES(sub_filter),
51+
MLT_FILTER_PROPERTIES(filter), "key1 key2");
52+
```

0 commit comments

Comments
 (0)