autoneat 0.2.2__tar.gz

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.
Files changed (32) hide show
  1. autoneat-0.2.2/LICENSE +21 -0
  2. autoneat-0.2.2/PKG-INFO +102 -0
  3. autoneat-0.2.2/README.md +84 -0
  4. autoneat-0.2.2/pyproject.toml +39 -0
  5. autoneat-0.2.2/setup.cfg +4 -0
  6. autoneat-0.2.2/src/autoneat/__init__.py +5 -0
  7. autoneat-0.2.2/src/autoneat/__main__.py +3 -0
  8. autoneat-0.2.2/src/autoneat/api.py +56 -0
  9. autoneat-0.2.2/src/autoneat/cli.py +101 -0
  10. autoneat-0.2.2/src/autoneat/core/__init__.py +1 -0
  11. autoneat-0.2.2/src/autoneat/core/batch.py +526 -0
  12. autoneat-0.2.2/src/autoneat/core/click.py +31 -0
  13. autoneat-0.2.2/src/autoneat/core/neat_ofx.py +195 -0
  14. autoneat-0.2.2/src/autoneat/core/ocr.py +337 -0
  15. autoneat-0.2.2/src/autoneat/core/recorder.py +39 -0
  16. autoneat-0.2.2/src/autoneat/core/resolve_client.py +84 -0
  17. autoneat-0.2.2/src/autoneat/core/shotid.py +82 -0
  18. autoneat-0.2.2/src/autoneat/core/subprocess_utils.py +61 -0
  19. autoneat-0.2.2/src/autoneat/core/ui_driver.py +229 -0
  20. autoneat-0.2.2/src/autoneat/core/windows.py +143 -0
  21. autoneat-0.2.2/src/autoneat/doctor.py +46 -0
  22. autoneat-0.2.2/src/autoneat/resolve.py +74 -0
  23. autoneat-0.2.2/src/autoneat/resources/vision_ocr.swift +69 -0
  24. autoneat-0.2.2/src/autoneat.egg-info/PKG-INFO +102 -0
  25. autoneat-0.2.2/src/autoneat.egg-info/SOURCES.txt +30 -0
  26. autoneat-0.2.2/src/autoneat.egg-info/dependency_links.txt +1 -0
  27. autoneat-0.2.2/src/autoneat.egg-info/entry_points.txt +2 -0
  28. autoneat-0.2.2/src/autoneat.egg-info/requires.txt +7 -0
  29. autoneat-0.2.2/src/autoneat.egg-info/top_level.txt +1 -0
  30. autoneat-0.2.2/tests/test_api.py +34 -0
  31. autoneat-0.2.2/tests/test_cli.py +35 -0
  32. autoneat-0.2.2/tests/test_resolve.py +83 -0
autoneat-0.2.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mhadifilms
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: autoneat
3
+ Version: 0.2.2
4
+ Summary: Standalone Neat Video Auto Profile automation for DaVinci Resolve
5
+ Author: Mohammad Hadi
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mhadifilms/autoneat
8
+ Project-URL: Repository, https://github.com/mhadifilms/autoneat
9
+ Project-URL: Issues, https://github.com/mhadifilms/autoneat/issues
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: dvr>=1.1
14
+ Requires-Dist: pyobjc-framework-Quartz>=11.0; platform_system == "Darwin"
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest>=8; extra == "test"
17
+ Dynamic: license-file
18
+
19
+ # autoneat
20
+
21
+ Standalone Neat Video Pro 6 Auto Profile automation for DaVinci Resolve.
22
+
23
+ Neat Video has no public scripting API for Auto Profile. `autoneat` scripts the
24
+ parts Resolve exposes, then uses macOS window automation plus Apple Vision OCR
25
+ for the two controls Neat does not expose: **Auto Profile** and **Apply**.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ python3 -m pip install autoneat
31
+ ```
32
+
33
+ DaVinci Resolve Studio, Neat Video Pro 6, and macOS Accessibility / Screen
34
+ Recording permissions are required. Run:
35
+
36
+ ```bash
37
+ autoneat doctor
38
+ ```
39
+
40
+ ## CLI
41
+
42
+ Run against the current Resolve project/timeline:
43
+
44
+ ```bash
45
+ autoneat profile --state artifacts/neat/state.json
46
+ ```
47
+
48
+ Run against a specific project/timeline and shot filter:
49
+
50
+ ```bash
51
+ autoneat profile \
52
+ --project "My Show" \
53
+ --timeline "My Show_Neat" \
54
+ --shot-ids 001,002,003 \
55
+ --state artifacts/neat/state.json
56
+ ```
57
+
58
+ Important options:
59
+
60
+ - `--continue` resumes from the state JSON.
61
+ - `--retry-failed` retries previously failed clips when resuming.
62
+ - `--all-tracks` processes all video tracks instead of `--track 1`.
63
+ - `--no-color-wrap` skips the ACES/HDR ColorSpaceTransform wrapper.
64
+ - `--json` prints the final summary object after the live log.
65
+
66
+ ## Python API
67
+
68
+ ```python
69
+ from autoneat import ProfileOptions, run_profile
70
+
71
+ result = run_profile(
72
+ ProfileOptions(
73
+ project_name="My Show",
74
+ timeline_name="My Show_Neat",
75
+ shot_ids=["001", "002"],
76
+ )
77
+ )
78
+ ```
79
+
80
+ ## How It Works
81
+
82
+ For each selected timeline clip, `autoneat`:
83
+
84
+ 1. Moves the Resolve playhead to the clip.
85
+ 2. Adds or reuses the Neat Video OFX node.
86
+ 3. Wraps ACES/HDR clips with ColorSpaceTransform nodes so Neat sees
87
+ display-referred pixels.
88
+ 4. Opens Neat via the OFX `Prepare Profile___` button control.
89
+ 5. OCR-clicks Auto Profile, waits for readiness, then OCR-clicks Apply.
90
+ 6. Writes a state JSON after every clip for resumable batches.
91
+
92
+ ## Development
93
+
94
+ ```bash
95
+ python3 -m venv .venv
96
+ ./.venv/bin/python -m pip install -e ".[test]"
97
+ ./.venv/bin/python -m pytest
98
+ ```
99
+
100
+ ## License
101
+
102
+ [MIT](LICENSE).
@@ -0,0 +1,84 @@
1
+ # autoneat
2
+
3
+ Standalone Neat Video Pro 6 Auto Profile automation for DaVinci Resolve.
4
+
5
+ Neat Video has no public scripting API for Auto Profile. `autoneat` scripts the
6
+ parts Resolve exposes, then uses macOS window automation plus Apple Vision OCR
7
+ for the two controls Neat does not expose: **Auto Profile** and **Apply**.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ python3 -m pip install autoneat
13
+ ```
14
+
15
+ DaVinci Resolve Studio, Neat Video Pro 6, and macOS Accessibility / Screen
16
+ Recording permissions are required. Run:
17
+
18
+ ```bash
19
+ autoneat doctor
20
+ ```
21
+
22
+ ## CLI
23
+
24
+ Run against the current Resolve project/timeline:
25
+
26
+ ```bash
27
+ autoneat profile --state artifacts/neat/state.json
28
+ ```
29
+
30
+ Run against a specific project/timeline and shot filter:
31
+
32
+ ```bash
33
+ autoneat profile \
34
+ --project "My Show" \
35
+ --timeline "My Show_Neat" \
36
+ --shot-ids 001,002,003 \
37
+ --state artifacts/neat/state.json
38
+ ```
39
+
40
+ Important options:
41
+
42
+ - `--continue` resumes from the state JSON.
43
+ - `--retry-failed` retries previously failed clips when resuming.
44
+ - `--all-tracks` processes all video tracks instead of `--track 1`.
45
+ - `--no-color-wrap` skips the ACES/HDR ColorSpaceTransform wrapper.
46
+ - `--json` prints the final summary object after the live log.
47
+
48
+ ## Python API
49
+
50
+ ```python
51
+ from autoneat import ProfileOptions, run_profile
52
+
53
+ result = run_profile(
54
+ ProfileOptions(
55
+ project_name="My Show",
56
+ timeline_name="My Show_Neat",
57
+ shot_ids=["001", "002"],
58
+ )
59
+ )
60
+ ```
61
+
62
+ ## How It Works
63
+
64
+ For each selected timeline clip, `autoneat`:
65
+
66
+ 1. Moves the Resolve playhead to the clip.
67
+ 2. Adds or reuses the Neat Video OFX node.
68
+ 3. Wraps ACES/HDR clips with ColorSpaceTransform nodes so Neat sees
69
+ display-referred pixels.
70
+ 4. Opens Neat via the OFX `Prepare Profile___` button control.
71
+ 5. OCR-clicks Auto Profile, waits for readiness, then OCR-clicks Apply.
72
+ 6. Writes a state JSON after every clip for resumable batches.
73
+
74
+ ## Development
75
+
76
+ ```bash
77
+ python3 -m venv .venv
78
+ ./.venv/bin/python -m pip install -e ".[test]"
79
+ ./.venv/bin/python -m pytest
80
+ ```
81
+
82
+ ## License
83
+
84
+ [MIT](LICENSE).
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "autoneat"
7
+ version = "0.2.2"
8
+ description = "Standalone Neat Video Auto Profile automation for DaVinci Resolve"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [{ name = "Mohammad Hadi" }]
13
+ dependencies = [
14
+ "dvr>=1.1",
15
+ "pyobjc-framework-Quartz>=11.0; platform_system == 'Darwin'",
16
+ ]
17
+
18
+ [project.urls]
19
+ Homepage = "https://github.com/mhadifilms/autoneat"
20
+ Repository = "https://github.com/mhadifilms/autoneat"
21
+ Issues = "https://github.com/mhadifilms/autoneat/issues"
22
+
23
+ [project.optional-dependencies]
24
+ test = [
25
+ "pytest>=8",
26
+ ]
27
+
28
+ [project.scripts]
29
+ autoneat = "autoneat.cli:main"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+
34
+ [tool.setuptools.package-data]
35
+ autoneat = ["resources/*"]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
39
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """Standalone Neat Video Auto Profile automation."""
2
+
3
+ from autoneat.api import ProfileOptions, run_profile
4
+
5
+ __all__ = ["ProfileOptions", "run_profile"]
@@ -0,0 +1,3 @@
1
+ from autoneat.cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,56 @@
1
+ """Public Python API for standalone autoneat runs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, Callable, Optional
7
+
8
+ from autoneat.core.batch import BatchSettings, run_batch
9
+ from autoneat.resolve import connect_resolve
10
+
11
+
12
+ @dataclass
13
+ class ProfileOptions(BatchSettings):
14
+ """Options for a standalone Neat Video Auto Profile batch."""
15
+
16
+ project_name: Optional[str] = None
17
+ timeline_name: Optional[str] = None
18
+
19
+
20
+ def run_profile(
21
+ options: ProfileOptions,
22
+ *,
23
+ resolve: Any = None,
24
+ project: Any = None,
25
+ timeline: Any = None,
26
+ sink: Optional[Callable[[str], None]] = None,
27
+ cancel_event: Any = None,
28
+ ) -> dict:
29
+ """Run Auto Profile against a Resolve project/timeline.
30
+
31
+ Tests and embedding callers may pass explicit Resolve handles. Normal CLI
32
+ usage lets autoneat connect through ``dvr`` and select the requested
33
+ project/timeline.
34
+ """
35
+ if resolve is not None and project is not None and timeline is not None:
36
+ return run_batch(
37
+ resolve,
38
+ project,
39
+ timeline,
40
+ options,
41
+ sink=sink,
42
+ cancel_event=cancel_event,
43
+ )
44
+
45
+ with connect_resolve(
46
+ project_name=options.project_name,
47
+ timeline_name=options.timeline_name,
48
+ ) as session:
49
+ return run_batch(
50
+ session.resolve,
51
+ session.project,
52
+ session.timeline,
53
+ options,
54
+ sink=sink,
55
+ cancel_event=cancel_event,
56
+ )
@@ -0,0 +1,101 @@
1
+ """Command-line interface for autoneat."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from autoneat.api import ProfileOptions, run_profile
11
+ from autoneat.doctor import check_environment, print_report
12
+
13
+
14
+ def _parse_shot_ids(value: str | None) -> list[str]:
15
+ if not value:
16
+ return []
17
+ return [part.strip() for part in value.replace(",", " ").split() if part.strip()]
18
+
19
+
20
+ def _profile(args: argparse.Namespace) -> int:
21
+ options = ProfileOptions(
22
+ project_name=args.project,
23
+ timeline_name=args.timeline,
24
+ track=args.track,
25
+ all_video_tracks=args.all_tracks,
26
+ shot_ids=_parse_shot_ids(args.shot_ids),
27
+ start_from=args.start_from,
28
+ limit=args.limit,
29
+ continue_run=args.continue_run,
30
+ retry_failed=args.retry_failed,
31
+ reuse_existing_neat=not args.no_reuse_existing,
32
+ no_color_wrap=args.no_color_wrap,
33
+ open_timeout=args.open_timeout,
34
+ editor_timeout=args.editor_timeout,
35
+ prepare_timeout=args.prepare_timeout,
36
+ profile_wait=args.profile_wait,
37
+ ready_timeout=args.ready_timeout,
38
+ apply_delay=args.apply_delay,
39
+ close_timeout=args.close_timeout,
40
+ step_delay=args.step_delay,
41
+ sidecar_path=Path(args.state).expanduser() if args.state else None,
42
+ )
43
+ result = run_profile(options, sink=lambda line: print(line, flush=True))
44
+ if args.json:
45
+ print(json.dumps(result, indent=2, sort_keys=True), flush=True)
46
+ return 0 if result.get("ok") else 1
47
+
48
+
49
+ def _doctor(_args: argparse.Namespace) -> int:
50
+ return print_report(check_environment())
51
+
52
+
53
+ def build_parser() -> argparse.ArgumentParser:
54
+ parser = argparse.ArgumentParser(prog="autoneat")
55
+ sub = parser.add_subparsers(dest="command", required=True)
56
+
57
+ doctor = sub.add_parser("doctor", help="Check macOS/Resolve automation prerequisites")
58
+ doctor.set_defaults(func=_doctor)
59
+
60
+ profile = sub.add_parser("profile", help="Run Neat Video Auto Profile over timeline clips")
61
+ profile.add_argument("--project", help="Resolve project name (default: current project)")
62
+ profile.add_argument("--timeline", help="Resolve timeline name (default: current timeline)")
63
+ profile.add_argument("--track", type=int, default=1, help="Video track to process")
64
+ profile.add_argument("--all-tracks", action="store_true", help="Process all video tracks")
65
+ profile.add_argument("--shot-ids", help="Comma/space-separated shot ids to include")
66
+ profile.add_argument("--start-from", type=int, default=1, help="1-based clip offset after filters")
67
+ profile.add_argument("--limit", type=int, default=0, help="Maximum clips to process")
68
+ profile.add_argument("--continue", dest="continue_run", action="store_true", help="Resume from state")
69
+ profile.add_argument("--retry-failed", action="store_true", help="Retry failed clips on resume")
70
+ profile.add_argument("--no-reuse-existing", action="store_true", help="Add a fresh Neat node")
71
+ profile.add_argument("--no-color-wrap", action="store_true", help="Skip ACES/HDR CST wrapping")
72
+ profile.add_argument("--state", help="Path to run state JSON")
73
+ profile.add_argument("--open-timeout", type=float, default=18.0)
74
+ profile.add_argument("--editor-timeout", type=float, default=60.0)
75
+ profile.add_argument("--prepare-timeout", type=float, default=1800.0)
76
+ profile.add_argument("--profile-wait", type=float, default=3.0)
77
+ profile.add_argument("--ready-timeout", type=float, default=90.0)
78
+ profile.add_argument("--apply-delay", type=float, default=5.0)
79
+ profile.add_argument("--close-timeout", type=float, default=20.0)
80
+ profile.add_argument("--step-delay", type=float, default=1.0)
81
+ profile.add_argument("--json", action="store_true", help="Print final summary JSON")
82
+ profile.set_defaults(func=_profile)
83
+
84
+ return parser
85
+
86
+
87
+ def main(argv: list[str] | None = None) -> int:
88
+ parser = build_parser()
89
+ args = parser.parse_args(argv)
90
+ try:
91
+ return int(args.func(args) or 0)
92
+ except KeyboardInterrupt:
93
+ print("Interrupted", file=sys.stderr)
94
+ return 130
95
+ except Exception as exc:
96
+ print(f"error: {exc}", file=sys.stderr)
97
+ return 1
98
+
99
+
100
+ if __name__ == "__main__":
101
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ """Engine layer — pure Python, no UI imports."""