davinci-resolve-mcp 2.66.0 → 2.67.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.
@@ -0,0 +1,68 @@
1
+ """Render format/codec id normalization — display names to the ids Resolve wants.
2
+
3
+ `Project.GetRenderFormats()` returns ``{display_name: format_id}`` (e.g.
4
+ ``{"QuickTime": "mov"}``) and `Project.GetRenderCodecs(format_id)` returns
5
+ ``{display_name: codec_id}`` (e.g. ``{"Apple ProRes 422 HQ": "ProRes422HQ"}``).
6
+ But `GetRenderCodecs`, `GetRenderResolutions` and `SetCurrentRenderFormatAndCodec`
7
+ all expect the **id**, so passing the label the user actually sees in the UI is
8
+ silently rejected.
9
+
10
+ The format half of this was issue #59. The codec half hid longer because for
11
+ ``mp4`` the label and id are identical (``{"H.264": "H.264"}``), so the common web
12
+ case works by coincidence; only formats where they diverge — QuickTime/ProRes,
13
+ i.e. every professional master — actually break.
14
+
15
+ These live here rather than in `server.py` so the compound and granular servers
16
+ share one implementation. The format fix originally landed only in `server.py`
17
+ and never reached `src/granular/project.py`; a shared module is what stops that
18
+ recurring.
19
+
20
+ Both resolvers **normalize, they do not validate**: an unrecognized value passes
21
+ through unchanged for Resolve to reject. Callers that need a hard failure must
22
+ check the Resolve return value (see `_prepare_render_job`, which refuses to queue
23
+ a job when `SetCurrentRenderFormatAndCodec` returns False).
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from typing import Any, Dict, Optional
29
+
30
+ __all__ = [
31
+ "render_format_id_from_formats",
32
+ "render_codec_id_from_codecs",
33
+ ]
34
+
35
+
36
+ def _match_by_label_or_id(mapping: Dict[Any, Any], value: str) -> Optional[str]:
37
+ """Case-insensitive match of `value` against either side of a {label: id} map."""
38
+ needle = value.lower()
39
+ for label, identifier in mapping.items():
40
+ label_text = str(label)
41
+ id_text = str(identifier)
42
+ if label_text.lower() == needle:
43
+ return id_text or value
44
+ if id_text.lower() == needle:
45
+ return id_text
46
+ return None
47
+
48
+
49
+ def render_format_id_from_formats(formats: Any, fmt: str) -> str:
50
+ """Normalize a render format display name/extension to its format id."""
51
+ if not isinstance(fmt, str) or not fmt or not isinstance(formats, dict):
52
+ return fmt
53
+ if fmt in formats:
54
+ return formats.get(fmt) or fmt
55
+ if fmt in formats.values():
56
+ return fmt
57
+ return _match_by_label_or_id(formats, fmt) or fmt
58
+
59
+
60
+ def render_codec_id_from_codecs(codecs: Any, codec: str) -> str:
61
+ """Normalize a render codec display name to its codec id."""
62
+ if not isinstance(codec, str) or not codec or not isinstance(codecs, dict):
63
+ return codec
64
+ if codec in codecs:
65
+ return codecs.get(codec) or codec
66
+ if codec in codecs.values():
67
+ return codec
68
+ return _match_by_label_or_id(codecs, codec) or codec