[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "kinocut" version = "1.15.2" description = "Trusted, guardrailed video editing for AI agents. FFmpeg, Hyperframes, repurposing tools, Python client, CLI, and MCP server." readme = "README.md" license = "Apache-2.0" requires-python = ">=3.11" keywords = [ "video", "editing", "mcp", "ffmpeg", "hyperframes", "ai", "agents", "claude", "claude-code", "cursor", "codex", "subtitle", "subtitles", "whisper", "transcription", "video-processing", "chroma-key", "stabilization", "color-grading", "model-context-protocol", "mcp-server", "ai-video", "video-automation", "cli", ] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Multimedia :: Video", "Topic :: Multimedia :: Sound/Audio", "Topic :: Software Development :: Libraries :: Python Modules", "Natural Language :: English", "Typing :: Typed", ] dependencies = [ # mcp 2.0 removed mcp.server.fastmcp; Kinocut still uses FastMCP Context/app. "mcp>=1.27.0,<2", "pydantic>=2.13.2", "rich>=15.0.0", ] [project.optional-dependencies] client = ["pillow>=10.0"] image = ["pillow>=10.0", "numpy>=1.26", "scikit-learn>=1.3", "webcolors>=1.13"] image-ai = ["pillow>=10.0", "numpy>=1.26", "scikit-learn>=1.3", "webcolors>=1.13", "anthropic>=0.96.0"] audio = ["numpy>=1.24"] audio-enhanced = ["numpy>=1.24", "scipy>=1.10", "soundfile>=0.12"] # audio-ai and audio-midi are capability aliases of `audio` kept for install # compatibility: their heavy engines (basic-pitch, meltysynth) are manual # integrations that pip extras cannot resolve safely — see docs/adr/0006. audio-ai = ["numpy>=1.24"] audio-midi = ["numpy>=1.24"] audio-analysis = ["numpy>=1.24", "librosa>=0.10", "soundfile>=0.12"] hyperframes = [] # External: Node.js + resolvable Hyperframes CLI transcribe = ["openai-whisper>=20231117"] ai-scene = ["imagehash>=4.3", "Pillow>=10.0"] stems = ["demucs>=4.0", "torch>=2.0", "torchaudio>=2.0", "torchcodec>=0.8"] object-matte = ["onnxruntime>=1.17", "numpy>=1.26", "pillow>=10.0"] upscale = [ "realesrgan>=0.3; python_version < '3.13'", "basicsr>=1.4; python_version < '3.13'", "numpy>=2.0.0", "opencv-contrib-python>=4.10", "torch>=2.0", "Pillow>=10.0", ] ai = [ "openai-whisper>=20231117", "demucs>=4.0", "realesrgan>=0.3; python_version < '3.13'", "basicsr>=1.4; python_version < '3.13'", "numpy>=2.0.0", "opencv-contrib-python>=4.10", "torch>=2.0", "torchaudio>=2.0", "torchcodec>=0.8", "imagehash>=4.3", "Pillow>=10.0", ] all-ai = [ "openai-whisper>=20231117", "demucs>=4.0", "realesrgan>=0.3; python_version < '3.13'", "basicsr>=1.4; python_version < '3.13'", "numpy>=2.0.0", "opencv-contrib-python>=4.10", "torch>=2.0", "torchaudio>=2.0", "torchcodec>=0.8", "imagehash>=4.3", "Pillow>=10.0", "scipy>=1.10", "soundfile>=0.12", "librosa>=0.10", ] audio-all = [ "numpy>=1.24", "scipy>=1.10", "soundfile>=0.12", "librosa>=0.10", ] dev = [ "pytest>=8.0", "pytest-asyncio>=0.23", "pytest-cov>=6.0", "pytest-xdist>=3.6", "pytest-timeout>=2.3", "pillow>=10.0", "numpy>=1.26", "rich>=15.0.0", "scikit-learn>=1.3", "webcolors>=1.13", "ruff>=0.15.11", ] [tool.ruff] target-version = "py311" line-length = 120 [tool.ruff.format] # These modules intentionally stay compact to satisfy enforced facade/function # size limits; format them only alongside an architecture-preserving refactor. exclude = ["kinocut/server.py", "kinocut/engine_audio_bed.py"] [tool.ruff.lint] select = ["E", "F", "W", "S", "UP", "B", "SIM", "RUF"] # S603/S607 (subprocess) and S310 (URL open) are suppressed per-line at the # audited call sites instead of globally, so new unaudited sites get flagged. ignore = ["E501", "S311"] [tool.ruff.lint.per-file-ignores] # Tests spawn developer-controlled ffmpeg/node fixtures; per-line subprocess # audits add no security value there. "tests/**/*.py" = ["S101", "S108", "B017", "S603", "S607"] "manual/**/*.py" = ["S101", "S603", "S607"] "demo/**/*.py" = ["S101", "S603", "S607"] # Repo automation runs hardcoded commands/URLs; not a user-input surface. "scripts/**/*.py" = ["S603", "S607", "S310"] "workflows/**/*.py" = ["S603", "S607"] ".github/scripts/*.py" = ["S603", "S607"] [project.scripts] kino = "kinocut.__main__:main" kinocut = "kinocut.__main__:main" mcp-video = "kinocut.__main__:main" [project.urls] Homepage = "https://kinocut.dev/" Documentation = "https://github.com/KyaniteLabs/kinocut#readme" Repository = "https://github.com/KyaniteLabs/kinocut" "Bug Tracker" = "https://github.com/KyaniteLabs/kinocut/issues" Changelog = "https://github.com/KyaniteLabs/kinocut/blob/master/CHANGELOG.md" Discussions = "https://github.com/KyaniteLabs/kinocut/discussions" [tool.hatch.build] exclude = [ ".git/", ".gitignore", ".coverage", ".pytest_cache/", ".ruff_cache/", ".venv/", ".venv-build/", ".playwright-mcp/", ".pi-lens/", ".stitch/", "tests/", "manual/", "out/", "explainer-video/", "dogfood_artifacts/", "dist/", "build/", "test_comprehensive.py", "test_real_video.py", "*.mp3", "*.mp4", "*.mov", "*.wav", "*.webm", "*.egg-info/", "node_modules/", ] [tool.hatch.build.targets.wheel] packages = ["kinocut", "kinocut_sound"] [tool.hatch.build.targets.wheel.force-include] "mcp_video.py" = "mcp_video.py" [tool.hatch.build.targets.sdist] # Do not ship /docs/ in the sdist — publish artifact check forbids that path # (GitHub Release 1.8.0 failed on kinocut-1.8.0.tar.gz including MCPB docs). # MCPB operator docs remain in-repo and under mcpb/README.md. only-include = [ "/kinocut", "/kinocut_sound", "/mcp_video.py", "/CHANGELOG.md", "/LICENSE", "/mcpb", "/README.md", "/pyproject.toml", "/scripts/build-mcpb.py", "/scripts/mcpb", "/server.json", ] [tool.pytest.ini_options] markers = [ "slow: marks tests as slow (real-media integration tests)", "hyperframes: marks tests requiring Node.js + Hyperframes CLI", "network: marks tests that need live network (not in the default suite)", ] [tool.pyright] # Typed-core rollout: the contract modules every engine depends on are # type-checked first; expand module by module as annotations are cleaned up. include = [ "kinocut/ffmpeg_helpers.py", "kinocut/models.py", "kinocut/errors.py", "kinocut/engine_probe.py", "kinocut/engine_runtime_utils.py", "kinocut/validation.py", "kinocut/limits.py", "kinocut/defaults.py", "kinocut/creative", "kinocut/remote", "kinocut/rescue", "kinocut/restorative", "kinocut/semantic", "kinocut/visual_intelligence", "kinocut/cli/formatting.py", "kinocut/cli/rescue_formatting.py", "kinocut/cli/handlers_postrescue.py", "kinocut/cli/handlers_rescue.py", "kinocut/cli/parser/postrescue.py", "kinocut/cli/parser/rescue.py", "kinocut/client/contracts.py", "kinocut/client/postrescue.py", "kinocut/client/rescue.py", "kinocut/postrescue.py", "kinocut/server_tools_postrescue.py", "kinocut/server_tools_rescue.py", ] typeCheckingMode = "basic" pythonVersion = "3.11" venvPath = "." venv = ".venv" [tool.coverage.run] source = ["kinocut"] omit = ["kinocut/_crush_shader/*"] [tool.coverage.report] fail_under = 70 show_missing = true skip_covered = true