davinci-resolve-mcp 2.108.0 → 2.111.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/reference/api-limitations.md +8 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/resolve-advanced/server/author-interchange.mjs +50 -7
- package/resolve-advanced/server/tools/drt.mjs +11 -6
- package/resolve-advanced/vendor/drp-format/composition-text.js +9 -0
- package/resolve-advanced/vendor/drp-format/place-fusion-title.js +1 -10
- package/resolve-advanced/vendor/drp-format/place-generator.js +5 -11
- package/resolve-advanced/vendor/drp-format/templates/fusion-title-r19.xml +2 -0
- package/resolve-advanced/vendor/drp-format/templates/generator-solid-color-r19.xml +2 -0
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
- package/src/utils/api_truth.py +32 -0
|
@@ -12,7 +12,7 @@ that none exists).
|
|
|
12
12
|
|
|
13
13
|
**Verified on:** DaVinci Resolve Studio 21.0.2
|
|
14
14
|
|
|
15
|
-
**Totals:** 33 missing capabilities,
|
|
15
|
+
**Totals:** 33 missing capabilities, 42 bugs / unreliable behaviors.
|
|
16
16
|
|
|
17
17
|
The authoritative source is the runtime-queryable `api_truth` ledger
|
|
18
18
|
(`resolve_control api_truth "<query>"`); this document is generated from
|
|
@@ -526,6 +526,13 @@ values, or automation-hostile modal prompts.
|
|
|
526
526
|
- **Reference:** [issue #171](https://github.com/samuelgursky/davinci-resolve-mcp/issues/171)
|
|
527
527
|
- **Tags:** timeline, import, silent-failure, unreliable-return
|
|
528
528
|
|
|
529
|
+
### Imported Fusion comps render via byte-keyed disk cache on 19.x (offline comp edits render black)
|
|
530
|
+
|
|
531
|
+
- **Object:** `Fusion / render engine`
|
|
532
|
+
- **Behavior:** On Studio 19.1.3.7, a Fusion composition arriving via timeline import renders only when the machine's Fusion disk cache (CacheClip/) holds frames keyed to the comp blob's EXACT bytes. Measured by discrimination: the untouched harvested title rendered its text; the same blob after an IDENTITY recompression — byte-identical Lua, different zlib bytes, verified consistent framing — imported, read back perfectly, and rendered black; a text-patched blob (also byte-verified) rendered black the same way. The live-render fallback for imported comps does not produce frames on 19; 21-generation hosts render imported comps live (the template-splice title/generator primitives were proven there).
|
|
533
|
+
- **Workaround / current handling:** Never edit an imported comp's bytes offline for a 19.x host — no valid re-encoding can hit the cache. Author media offline (renders everywhere via the native-descriptor transplant) and set title text POST-IMPORT with timeline.set_title_text, whose Fusion-comp write path is live-verified on 19.1.3. Built-in GENERATORS are exempt: Sm2TiGenerator clips carry no Fusion comp, and offline-authored Solid Color / SMPTE Color Bar / Grey Scale all render live from an imported .drt (measured YAVG 16 / 104.9 / 125.1 over a 234 white base). Render-verify any imported Fusion TITLE before delivery; structural readback cannot see this.
|
|
534
|
+
- **Tags:** fusion, render, import, silent-failure
|
|
535
|
+
|
|
529
536
|
### MediaPool.ImportTimelineFromFile (.drt requirements and filename naming)
|
|
530
537
|
|
|
531
538
|
- **Object:** `MediaPool`
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "2.
|
|
40
|
+
VERSION = "2.111.0"
|
|
41
41
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
42
42
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
43
43
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
|
@@ -202,9 +202,11 @@ export function eventsToOTIO(events, opts = {}) {
|
|
|
202
202
|
* timeline runs 24fps with origin 86400, so rec/src frames convert as
|
|
203
203
|
* round(frames × 24 / nominalFps). Placement anchors the EARLIEST video
|
|
204
204
|
* event at the origin. Honesty ledger in the returned report: flattened
|
|
205
|
-
* retimes (the template clip schema has no per-clip speed),
|
|
206
|
-
* transitions (
|
|
207
|
-
*
|
|
205
|
+
* retimes (the template clip schema has no per-clip speed), authored vs
|
|
206
|
+
* dropped transitions (cross-dissolves are AUTHORED when the predecessor
|
|
207
|
+
* abuts the cut and both sides have handle media — render-verified on
|
|
208
|
+
* 19.1.3.7; otherwise dropped with the reason, as a cut at the boundary),
|
|
209
|
+
* and skipped audio events (cuts carry linked A1 audio already).
|
|
208
210
|
*
|
|
209
211
|
* @param {Array} events - normalized events (parseInterchange shape)
|
|
210
212
|
* @param {object} opts
|
|
@@ -238,6 +240,7 @@ export function eventsToAssembleSpec(events, opts = {}) {
|
|
|
238
240
|
const toTl = (frames, fps) => Math.round((frames * 24) / Math.round(fps || 24));
|
|
239
241
|
const flattenedRetimes = [];
|
|
240
242
|
const droppedTransitions = [];
|
|
243
|
+
const transitionCandidates = [];
|
|
241
244
|
const perSource = new Map();
|
|
242
245
|
const placements = [];
|
|
243
246
|
|
|
@@ -250,11 +253,20 @@ export function eventsToAssembleSpec(events, opts = {}) {
|
|
|
250
253
|
if ((e.speed ?? 100) !== 100 || e.reverse) {
|
|
251
254
|
flattenedRetimes.push({ index: e.index, source: e.source, speed: e.speed, reverse: !!e.reverse });
|
|
252
255
|
}
|
|
256
|
+
const cut = { startFrame: recIn, durationFrames, srcIn: toTl(e.srcIn ?? 0, e.fps) };
|
|
253
257
|
if (e.transition) {
|
|
254
|
-
|
|
258
|
+
// A dissolve INTO this event, at its record-in boundary. Whether it can
|
|
259
|
+
// be authored (abutting predecessor + handles both sides) is decided
|
|
260
|
+
// after all placements are known.
|
|
261
|
+
let d = Math.max(2, toTl(e.transition.duration || 0, e.fps) || 2);
|
|
262
|
+
d += d % 2; // placeTransition centers on the cut; keep it even
|
|
263
|
+
transitionCandidates.push({
|
|
264
|
+
atFrame: recIn, durationFrames: d,
|
|
265
|
+
index: e.index, type: e.transition.type, rawDuration: e.transition.duration,
|
|
266
|
+
source: e.source, srcIn: cut.srcIn,
|
|
267
|
+
});
|
|
255
268
|
}
|
|
256
|
-
|
|
257
|
-
placements.push({ start: recIn, end: recOut, index: e.index });
|
|
269
|
+
placements.push({ start: recIn, end: recOut, index: e.index, source: e.source, srcIn: cut.srcIn, durationFrames });
|
|
258
270
|
if (!perSource.has(e.source)) perSource.set(e.source, []);
|
|
259
271
|
perSource.get(e.source).push(cut);
|
|
260
272
|
}
|
|
@@ -276,13 +288,44 @@ export function eventsToAssembleSpec(events, opts = {}) {
|
|
|
276
288
|
cuts,
|
|
277
289
|
}));
|
|
278
290
|
|
|
291
|
+
// Author cross-dissolves where the geometry allows it (render-verified on
|
|
292
|
+
// 19.1.3.7: an offline Sm2TiTransition over transplanted cross-source media
|
|
293
|
+
// blends 124→181.6→234 at the cut — transitions carry no Fusion comp, so
|
|
294
|
+
// the byte-keyed comp-cache law does not apply). A candidate is authorable
|
|
295
|
+
// when a predecessor ends EXACTLY at its cut and both sides have handle
|
|
296
|
+
// media for the centered span; anything else stays in droppedTransitions
|
|
297
|
+
// with the reason.
|
|
298
|
+
const transitions = [];
|
|
299
|
+
for (const c of transitionCandidates) {
|
|
300
|
+
const prev = placements.find((pl) => pl.end === c.atFrame);
|
|
301
|
+
if (!prev) {
|
|
302
|
+
droppedTransitions.push({ index: c.index, type: c.type, duration: c.rawDuration, reason: 'no abutting predecessor at the cut' });
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const half = c.durationFrames / 2;
|
|
306
|
+
const bHandle = c.srcIn >= half;
|
|
307
|
+
const aSpec = sourceMap[prev.source] && sourceMap[prev.source].spec;
|
|
308
|
+
const aFrames = aSpec && Number(aSpec.frameCount);
|
|
309
|
+
const aHandle = Number.isFinite(aFrames) ? prev.srcIn + prev.durationFrames + half <= aFrames : false;
|
|
310
|
+
if (!bHandle || !aHandle) {
|
|
311
|
+
droppedTransitions.push({
|
|
312
|
+
index: c.index, type: c.type, duration: c.rawDuration,
|
|
313
|
+
reason: `insufficient handles for a centered ${c.durationFrames}f dissolve` +
|
|
314
|
+
`${bHandle ? '' : ' (incoming srcIn < half)'}${aHandle ? '' : ' (outgoing tail media < half)'}`,
|
|
315
|
+
});
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
transitions.push({ track: 1, atFrame: c.atFrame, durationFrames: c.durationFrames });
|
|
319
|
+
}
|
|
320
|
+
|
|
279
321
|
return {
|
|
280
|
-
spec: { timelineName, media },
|
|
322
|
+
spec: { timelineName, media, ...(transitions.length ? { transitions } : {}) },
|
|
281
323
|
report: {
|
|
282
324
|
videoEvents: vids.length,
|
|
283
325
|
sources: media.length,
|
|
284
326
|
audioEventsSkipped: audioSkipped,
|
|
285
327
|
flattenedRetimes,
|
|
328
|
+
authoredTransitions: transitions,
|
|
286
329
|
droppedTransitions,
|
|
287
330
|
origin: ORIGIN,
|
|
288
331
|
},
|
|
@@ -46,7 +46,7 @@ const assembleSchema = z.object({
|
|
|
46
46
|
spec: z
|
|
47
47
|
.object({})
|
|
48
48
|
.passthrough()
|
|
49
|
-
.describe("assembleTimeline spec: { timelineName?, media?: {mediaFilePath, spec:{width,height,frameCount,fps}, cuts:[{startFrame,durationFrames,srcIn?}]} | [same, ...] (multi-source needs media_pool.capture_media_template run once per file), elements?: [{type:'title'|'generator', track, startFrame, durationFrames?, text?, ...}], transitions? }. startFrame is timeline-absolute (origin 86400)."),
|
|
49
|
+
.describe("assembleTimeline spec: { timelineName?, media?: {mediaFilePath, spec:{width,height,frameCount,fps}, cuts:[{startFrame,durationFrames,srcIn?}]} | [same, ...] (multi-source needs media_pool.capture_media_template run once per file), elements?: [{type:'title'|'generator', track, startFrame, durationFrames?, text?, generatorName? ('Solid Color'|'SMPTE Color Bar'|'Grey Scale' render-verified on 19), ...}], transitions? }. startFrame is timeline-absolute (origin 86400)."),
|
|
50
50
|
outputPath: z.string().describe('Absolute path where the importable .drt will be written'),
|
|
51
51
|
targetAppVersion: z
|
|
52
52
|
.union([z.string(), z.number()])
|
|
@@ -148,7 +148,7 @@ function requirePathArg(args, key, action) {
|
|
|
148
148
|
export const drtTool = {
|
|
149
149
|
name: 'drt',
|
|
150
150
|
description:
|
|
151
|
-
'DaVinci Resolve Timeline (.drt) operations — offline, no Resolve required. Actions: assemble_from_interchange (EDL/OTIO/XML/AAF + sourceMap → IMPORTABLE RENDERING native .drt in one call; retimes flatten
|
|
151
|
+
'DaVinci Resolve Timeline (.drt) operations — offline, no Resolve required. Actions: assemble_from_interchange (EDL/OTIO/XML/AAF + sourceMap → IMPORTABLE RENDERING native .drt in one call; retimes flatten; cross-dissolves are AUTHORED when the cut abuts with handles both sides (render-verified on 19), else dropped with reason; ledger in `conform`), assemble (spec → IMPORTABLE native-schema .drt via template-spliced real structures; pass targetAppVersion e.g. \'19.1\' for pre-21 hosts), parse, list_sequences (enumerate the timelines inside a .drp/.drt → [{id,name,eventCount,index}] to drive a "which sequence?" picker), author, validate, inject_into_drp, extract_from_drp (pull one SeqContainer out as a .drt — feed the .drt to the Python davinci-resolve MCP timeline.import_timeline_checked, or use timeline.import_from_drp to do both), downgrade (stamp <ProjectVersion> down so an OLDER Resolve will import a .drt/.drp from a newer one — pass targetAppVersion like "19.1.3" or targetProjectVersion).',
|
|
152
152
|
async handler({ action, args }) {
|
|
153
153
|
if (action === 'parse') {
|
|
154
154
|
const p = parseSchema.parse(requirePathArg(args, 'drtPath', 'parse'));
|
|
@@ -284,12 +284,17 @@ export const drtTool = {
|
|
|
284
284
|
stamped,
|
|
285
285
|
templateVersion: spec.templateVersion ?? 21,
|
|
286
286
|
mediaDescriptor: mediaDescriptor ?? 'none',
|
|
287
|
-
...((spec.elements || []).
|
|
287
|
+
...((spec.elements || []).some((e) => e && e.type === 'title') && (spec.templateVersion ?? 21) < 21
|
|
288
288
|
? {
|
|
289
289
|
elementsWarning:
|
|
290
|
-
'
|
|
291
|
-
'
|
|
292
|
-
'
|
|
290
|
+
'On a pre-21 host, imported Fusion TITLE comps render only via the machine\'s ' +
|
|
291
|
+
'Fusion disk cache, keyed to the EXACT comp bytes (measured: an identity ' +
|
|
292
|
+
'recompression rendered black) — offline-authored titles may render black and ' +
|
|
293
|
+
'offline text patching cannot work there. The working pre-21 title flow: assemble ' +
|
|
294
|
+
'without title text, then set it post-import with timeline.set_title_text (its ' +
|
|
295
|
+
'Fusion-comp path is live-verified on 19.1.3). Media cuts and built-in GENERATORS ' +
|
|
296
|
+
'(Solid Color / SMPTE Color Bar / Grey Scale — plain Sm2TiGenerator, no Fusion ' +
|
|
297
|
+
'comp) render everywhere: generator kinds render-verified on 19.1.3.',
|
|
293
298
|
}
|
|
294
299
|
: {}),
|
|
295
300
|
...(mediaDescriptor === 'repoint-fallback'
|
|
@@ -82,6 +82,15 @@ function rewriteInner(hex, transform) {
|
|
|
82
82
|
const b = Buffer.from(hex, 'hex');
|
|
83
83
|
const outer = zlib.inflateSync(b.subarray(4));
|
|
84
84
|
|
|
85
|
+
// NOTE (measured 2026-08-30, Studio 19.1.3.7): the mechanics below are
|
|
86
|
+
// generation-agnostic and byte-correct — a patched blob re-inflates with
|
|
87
|
+
// StyledText updated and consistent framing. But on 19, IMPORTED comps
|
|
88
|
+
// render only via the machine's Fusion disk cache, keyed to the EXACT
|
|
89
|
+
// compressed bytes: even an identity reframe (same Lua, different
|
|
90
|
+
// compression) rendered black, while the untouched harvest rendered its
|
|
91
|
+
// cached frames. Offline text patching therefore works on 21-generation
|
|
92
|
+
// hosts (live Fusion render) and CANNOT work on 19 imports — set text
|
|
93
|
+
// post-import with timeline.set_title_text instead.
|
|
85
94
|
const marker = outer.lastIndexOf(COMPRESSED_MARKER);
|
|
86
95
|
if (marker < 0) throw new Error('composition-text: composition marker not found (unexpected framing)');
|
|
87
96
|
const zNull = outer.indexOf(0x00, marker);
|
|
@@ -42,16 +42,7 @@ const TEMPLATE_PATH = path.join(__dirname, 'templates', 'fusion-title.xml');
|
|
|
42
42
|
// harvested live from Studio 19.1.3.7.
|
|
43
43
|
const TEMPLATE_PATH_R19 = path.join(__dirname, 'templates', 'fusion-title-r19.xml');
|
|
44
44
|
function snippetPathFor(templateVersion) {
|
|
45
|
-
|
|
46
|
-
// separate Sm2TiCompositionTable dependency is not carried, and the title's
|
|
47
|
-
// comp-blob patching assumes the R21 layout — with them, render jobs FAIL
|
|
48
|
-
// outright (measured), which is worse than the R21 snippet's silent black.
|
|
49
|
-
// Selection stays on the R21 snippet for every generation until the element
|
|
50
|
-
// transplant (comp table + per-generation blob patch) lands; drt.assemble
|
|
51
|
-
// warns when elements target a pre-21 host.
|
|
52
|
-
void templateVersion;
|
|
53
|
-
void TEMPLATE_PATH_R19;
|
|
54
|
-
return TEMPLATE_PATH;
|
|
45
|
+
return (Number(templateVersion) || 21) >= 21 ? TEMPLATE_PATH : TEMPLATE_PATH_R19;
|
|
55
46
|
}
|
|
56
47
|
|
|
57
48
|
/**
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Clone-based: carry the bundled Solid Color template and swap PrettyType/Name/Start/
|
|
11
11
|
* Duration + a fresh DbId. The same Sm2TiGenerator shape serves the other simple built-in
|
|
12
|
-
* generators by changing PrettyType
|
|
12
|
+
* generators by changing PrettyType — RENDER-VERIFIED on Studio 19.1.3.7 (2026-08-30):
|
|
13
|
+
* Solid Color YAVG 16 over white 234, SMPTE Color Bar 104.9, Grey Scale 125.1, all from a
|
|
14
|
+
* fully offline-authored .drt. Unlike Fusion titles, Sm2TiGenerator has no comp blob, so
|
|
15
|
+
* the byte-keyed Fusion render-cache law does NOT apply: generators render live everywhere.
|
|
13
16
|
*
|
|
14
17
|
* @module drp-format/place-generator
|
|
15
18
|
*/
|
|
@@ -32,16 +35,7 @@ const TEMPLATE_PATH_R19 = path.join(__dirname, 'templates', 'generator-solid-col
|
|
|
32
35
|
// Generation-bound like every other harvested structure (R21 snippet renders
|
|
33
36
|
// black on 19 — measured); r19 variant harvested live from 19.1.3.7.
|
|
34
37
|
function snippetPathFor(templateVersion) {
|
|
35
|
-
|
|
36
|
-
// separate Sm2TiCompositionTable dependency is not carried, and the title's
|
|
37
|
-
// comp-blob patching assumes the R21 layout — with them, render jobs FAIL
|
|
38
|
-
// outright (measured), which is worse than the R21 snippet's silent black.
|
|
39
|
-
// Selection stays on the R21 snippet for every generation until the element
|
|
40
|
-
// transplant (comp table + per-generation blob patch) lands; drt.assemble
|
|
41
|
-
// warns when elements target a pre-21 host.
|
|
42
|
-
void templateVersion;
|
|
43
|
-
void TEMPLATE_PATH_R19;
|
|
44
|
-
return TEMPLATE_PATH;
|
|
38
|
+
return (Number(templateVersion) || 21) >= 21 ? TEMPLATE_PATH : TEMPLATE_PATH_R19;
|
|
45
39
|
}
|
|
46
40
|
|
|
47
41
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<Element>
|
|
1
2
|
<Sm2TiVideoClip DbId="8c729549-aded-4c80-b143-c1fce23c5712">
|
|
2
3
|
<FieldsBlob>0000000200000027800a120a0e2205546578742b4a05546578742b200112100000000000000005ffffffffffffffff</FieldsBlob>
|
|
3
4
|
<PrettyType>Fusion Title</PrettyType>
|
|
@@ -52,3 +53,4 @@
|
|
|
52
53
|
</Thumbnail>
|
|
53
54
|
<ThumbnailDirtyFlag>true</ThumbnailDirtyFlag>
|
|
54
55
|
</Sm2TiVideoClip>
|
|
56
|
+
</Element>
|
package/src/granular/common.py
CHANGED
|
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
|
|
|
87
87
|
handlers=[logging.StreamHandler()],
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
VERSION = "2.
|
|
90
|
+
VERSION = "2.111.0"
|
|
91
91
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
92
92
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
93
93
|
logger.info(f"Detected platform: {get_platform()}")
|
package/src/server.py
CHANGED
package/src/utils/api_truth.py
CHANGED
|
@@ -1914,6 +1914,38 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
1914
1914
|
"structural readback cannot see this class.",
|
|
1915
1915
|
"tags": ["media-pool", "import", "render", "silent-failure", "readback"],
|
|
1916
1916
|
},
|
|
1917
|
+
{
|
|
1918
|
+
"symbol": "Imported Fusion comps render via byte-keyed disk cache on 19.x (offline comp edits render black)",
|
|
1919
|
+
"object": "Fusion / render engine",
|
|
1920
|
+
"reality": "On Studio 19.1.3.7, a Fusion composition arriving via "
|
|
1921
|
+
"timeline import renders only when the machine's Fusion "
|
|
1922
|
+
"disk cache (CacheClip/) holds frames keyed to the comp "
|
|
1923
|
+
"blob's EXACT bytes. Measured by discrimination: the "
|
|
1924
|
+
"untouched harvested title rendered its text; the same "
|
|
1925
|
+
"blob after an IDENTITY recompression — byte-identical "
|
|
1926
|
+
"Lua, different zlib bytes, verified consistent framing — "
|
|
1927
|
+
"imported, read back perfectly, and rendered black; a "
|
|
1928
|
+
"text-patched blob (also byte-verified) rendered black "
|
|
1929
|
+
"the same way. The live-render fallback for imported "
|
|
1930
|
+
"comps does not produce frames on 19; 21-generation "
|
|
1931
|
+
"hosts render imported comps live (the template-splice "
|
|
1932
|
+
"title/generator primitives were proven there).",
|
|
1933
|
+
"recommended": "Never edit an imported comp's bytes offline for a "
|
|
1934
|
+
"19.x host — no valid re-encoding can hit the cache. "
|
|
1935
|
+
"Author media offline (renders everywhere via the "
|
|
1936
|
+
"native-descriptor transplant) and set title text "
|
|
1937
|
+
"POST-IMPORT with timeline.set_title_text, whose "
|
|
1938
|
+
"Fusion-comp write path is live-verified on 19.1.3. "
|
|
1939
|
+
"Built-in GENERATORS are exempt: Sm2TiGenerator "
|
|
1940
|
+
"clips carry no Fusion comp, and offline-authored "
|
|
1941
|
+
"Solid Color / SMPTE Color Bar / Grey Scale all "
|
|
1942
|
+
"render live from an imported .drt (measured YAVG "
|
|
1943
|
+
"16 / 104.9 / 125.1 over a 234 white base). "
|
|
1944
|
+
"Render-verify any imported Fusion TITLE before "
|
|
1945
|
+
"delivery; structural readback cannot see this.",
|
|
1946
|
+
"tags": ["fusion", "render", "import", "silent-failure"],
|
|
1947
|
+
"submit": "bug",
|
|
1948
|
+
},
|
|
1917
1949
|
{
|
|
1918
1950
|
"symbol": "MediaPool.ImportTimelineFromFile (.drt requirements and filename naming)",
|
|
1919
1951
|
"object": "MediaPool",
|