brandly-cli 0.3.2__tar.gz → 0.3.3__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.
Potentially problematic release.
This version of brandly-cli might be problematic. Click here for more details.
- brandly_cli-0.3.3/.github/workflows/ci.yml +71 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/.github/workflows/release.yml +10 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/.gitignore +2 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/.pre-commit-config.yaml +1 -1
- brandly_cli-0.3.3/LICENSE +21 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/Makefile +20 -7
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/PKG-INFO +2 -1
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/pyproject.toml +1 -1
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/__about__.py +1 -1
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/autodirector.py +1 -1
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/beat_sync.py +1 -1
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/cli.py +102 -28
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/export_platforms.py +1 -1
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/sharing.py +1 -1
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/stitch.py +1 -1
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/thumbnails.py +3 -3
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/utils.py +1 -1
- brandly_cli-0.3.2/$null +0 -2
- brandly_cli-0.3.2/.github/workflows/ci.yml +0 -37
- brandly_cli-0.3.2/byteplus-ark-cli-1.0.24.tgz +0 -0
- brandly_cli-0.3.2/generate_svd.py +0 -101
- brandly_cli-0.3.2/package/LICENSE +0 -201
- brandly_cli-0.3.2/package/README.md +0 -47
- brandly_cli-0.3.2/package/manifest.json +0 -72
- brandly_cli-0.3.2/package/package.json +0 -48
- brandly_cli-0.3.2/package/scripts/postinstall.js +0 -545
- brandly_cli-0.3.2/package/scripts/run.js +0 -44
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/.env.example +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/README.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/assets/banner.png +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/dev-notes/AUDIT.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/dev-notes/DEVELOPMENT.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/dev-notes/GOAL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/dev-notes/IMPLEMENTATION.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/dev-notes/PROGRESS.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/dev-notes/SECURITY.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/dev-notes/SPEC.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/scripts/install-hooks.sh +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/README.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-animal-sheet/SKILL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-animal-sheet/references/habitats.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-animal-sheet/references/prompt-variants.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-animal-sheet/references/species-guide.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-character-sheet/SKILL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-character-sheet/references/common-types.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-character-sheet/references/consistency-checklist.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-character-sheet/references/prompt-variants.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-consistency/SKILL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-consistency/references/anchoring-system.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-consistency/references/drift-checklist.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-consistency/references/lock-strength.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-location-sheet/SKILL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-location-sheet/references/lighting-setups.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-location-sheet/references/location-types.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-location-sheet/references/prompt-variants.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-mecha-sheet/SKILL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-mecha-sheet/references/mecha-types.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-mecha-sheet/references/prompt-variants.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-mecha-sheet/references/technical-reference.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-object-sheet/SKILL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-object-sheet/references/photography-setups.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-object-sheet/references/product-categories.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-object-sheet/references/prompt-variants.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-plant-sheet/SKILL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-plant-sheet/references/plant-species.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-plant-sheet/references/prompt-variants.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-plant-sheet/references/seasonal-reference.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-production-bible/SKILL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-production-bible/references/asset-map.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-production-bible/references/bible-template.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-production-bible/references/example-bible.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-storyboard/SKILL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-storyboard/references/examples.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-storyboard/references/pacing-guide.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-storyboard/references/shot-formats.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-vehicle-sheet/SKILL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-vehicle-sheet/references/prompt-variants.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-vehicle-sheet/references/shot-angles.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-vehicle-sheet/references/vehicle-types.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-video-generation/SKILL.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-video-generation/references/camera-control.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-video-generation/references/character-consistency.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-video-generation/references/lighting-presets.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-video-generation/references/troubleshooting.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/skills/brandly-video-generation/references/video-styles.md +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/__init__.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/__main__.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/agnes_client.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/analyzer.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/ark_client.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/audio_client.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/captions.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/constants.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/cost_tracker.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/director.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/dubbing.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/edit.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/memory.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/minimax_client.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/project_manager.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/style_presets.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/sync.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/templates.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/trends.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/types.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/video_prompts.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/src/brandly_cli/webhook.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/__init__.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/_check.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_agnes_client.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_analyzer.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_ark_client.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_autodirector.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_beat_sync.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_brandly_cli.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_cli.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_dubbing.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_edit.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_export_platforms.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_reference.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_sharing.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_stitch.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_templates.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_thumbnails.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_trends.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_video_prompts.py +0 -0
- {brandly_cli-0.3.2 → brandly_cli-0.3.3}/tests/test_webhook.py +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
# Cancel superseded runs on the same ref (for PRs); keep main runs to
|
|
10
|
+
# avoid clobbering a long build when a new commit lands mid-run.
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
quality:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
strategy:
|
|
19
|
+
fail-fast: false
|
|
20
|
+
matrix:
|
|
21
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Install FFmpeg
|
|
26
|
+
run: sudo apt-get update && sudo apt-get install -y ffmpeg
|
|
27
|
+
|
|
28
|
+
- uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: ${{ matrix.python-version }}
|
|
31
|
+
|
|
32
|
+
- name: Install dependencies
|
|
33
|
+
run: |
|
|
34
|
+
python -m pip install --upgrade pip
|
|
35
|
+
pip install -e ".[dev]" build twine
|
|
36
|
+
|
|
37
|
+
- name: Lint
|
|
38
|
+
run: ruff check src/ tests/
|
|
39
|
+
|
|
40
|
+
- name: Type check
|
|
41
|
+
run: mypy src/
|
|
42
|
+
|
|
43
|
+
- name: Syntax check
|
|
44
|
+
run: python -m py_compile src/brandly_cli/*.py
|
|
45
|
+
|
|
46
|
+
- name: Run tests
|
|
47
|
+
run: |
|
|
48
|
+
python -m pytest tests/ -v --tb=short \
|
|
49
|
+
--cov=brandly_cli --cov-report=xml --cov-report=term-missing
|
|
50
|
+
|
|
51
|
+
- name: Build package
|
|
52
|
+
run: python -m build
|
|
53
|
+
|
|
54
|
+
- name: Check build
|
|
55
|
+
run: python -m twine check dist/*
|
|
56
|
+
|
|
57
|
+
- name: Upload dist
|
|
58
|
+
if: ${{ matrix.python-version == '3.12' }}
|
|
59
|
+
uses: actions/upload-artifact@v4
|
|
60
|
+
with:
|
|
61
|
+
name: dist
|
|
62
|
+
path: dist/
|
|
63
|
+
if-no-files-found: error
|
|
64
|
+
|
|
65
|
+
- name: Upload coverage
|
|
66
|
+
if: ${{ matrix.python-version == '3.12' }}
|
|
67
|
+
uses: actions/upload-artifact@v4
|
|
68
|
+
with:
|
|
69
|
+
name: coverage
|
|
70
|
+
path: coverage.xml
|
|
71
|
+
if-no-files-found: warn
|
|
@@ -29,5 +29,15 @@ jobs:
|
|
|
29
29
|
- name: Build package
|
|
30
30
|
run: python -m build
|
|
31
31
|
|
|
32
|
+
- name: Check build
|
|
33
|
+
run: python -m twine check dist/*
|
|
34
|
+
|
|
35
|
+
- name: Upload dist
|
|
36
|
+
uses: actions/upload-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: dist-${{ github.event.release.tag_name }}
|
|
39
|
+
path: dist/
|
|
40
|
+
if-no-files-found: error
|
|
41
|
+
|
|
32
42
|
- name: Release to PyPI
|
|
33
43
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dream Pixels Forge
|
|
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.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Brandly CLI — Makefile
|
|
2
2
|
|
|
3
3
|
PYTHON := python3
|
|
4
|
-
PNPM := pnpm
|
|
5
4
|
PIP := $(PYTHON) -m pip
|
|
6
5
|
PYTEST := $(PYTHON) -m pytest
|
|
7
6
|
RUFF := $(PYTHON) -m ruff
|
|
7
|
+
MYPY := $(PYTHON) -m mypy
|
|
8
8
|
ROOT ?= .
|
|
9
9
|
|
|
10
|
-
.PHONY: help install dev test lint type-check
|
|
11
|
-
pre-commit ci e2e upgrade sync
|
|
10
|
+
.PHONY: help install dev test cov lint lint-fix check-syntax type-check \
|
|
11
|
+
build clean pre-commit ci e2e upgrade sync
|
|
12
12
|
|
|
13
13
|
## Show this help
|
|
14
14
|
help:
|
|
@@ -19,6 +19,10 @@ help:
|
|
|
19
19
|
install: ## install
|
|
20
20
|
$(PIP) install -e ".[dev]"
|
|
21
21
|
|
|
22
|
+
## Install full dev environment (package + build tooling, mirrors CI)
|
|
23
|
+
dev: ## dev
|
|
24
|
+
$(PIP) install -e ".[dev]" build twine
|
|
25
|
+
|
|
22
26
|
## Install pre-commit hooks
|
|
23
27
|
pre-commit: ## pre-commit
|
|
24
28
|
$(PIP) install pre-commit
|
|
@@ -34,18 +38,27 @@ cov: ## cov
|
|
|
34
38
|
|
|
35
39
|
## Run linter
|
|
36
40
|
lint: ## lint
|
|
37
|
-
$(RUFF) check src/
|
|
41
|
+
$(RUFF) check src/ tests/
|
|
38
42
|
|
|
39
43
|
## Fix lint issues automatically
|
|
40
44
|
lint-fix: ## lint-fix
|
|
41
|
-
$(RUFF) check src/ --fix
|
|
45
|
+
$(RUFF) check src/ tests/ --fix
|
|
46
|
+
|
|
47
|
+
## Type check
|
|
48
|
+
type-check: ## type-check
|
|
49
|
+
$(MYPY) src/
|
|
42
50
|
|
|
43
51
|
## Syntax check all modules
|
|
44
52
|
check-syntax: ## check-syntax
|
|
45
53
|
$(PYTHON) -m py_compile src/brandly_cli/*.py
|
|
46
54
|
|
|
47
|
-
##
|
|
48
|
-
|
|
55
|
+
## Build sdist + wheel (run 'make dev' first to install build/twine)
|
|
56
|
+
build: ## build
|
|
57
|
+
$(PYTHON) -m build
|
|
58
|
+
$(PYTHON) -m twine check dist/*
|
|
59
|
+
|
|
60
|
+
## Run all quality gates (lint + type-check + test + build)
|
|
61
|
+
ci: lint type-check test build ## ci
|
|
49
62
|
@echo "All quality gates passed."
|
|
50
63
|
|
|
51
64
|
## End-to-end CLI smoke test
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: brandly-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: AI product video orchestrator — add image, video, and sound generation to any AI tool
|
|
5
5
|
Project-URL: Homepage, https://github.com/Dream-Pixels-Forge/brandly-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/Dream-Pixels-Forge/brandly-cli
|
|
7
7
|
Project-URL: Issues, https://github.com/Dream-Pixels-Forge/brandly-cli/issues
|
|
8
8
|
Author-email: Dream Pixels Forge <contact@dreampixelsforge.com>
|
|
9
9
|
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
10
11
|
Keywords: agent,ai,cli,generation,image,orchestrator,video
|
|
11
12
|
Classifier: Development Status :: 4 - Beta
|
|
12
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -56,7 +56,7 @@ def _get_duration(path: Path) -> float:
|
|
|
56
56
|
return 0.0
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
async def _run_ffmpeg(cmd: list[str]) -> tuple[int, str]:
|
|
59
|
+
async def _run_ffmpeg(cmd: list[str]) -> tuple[int | None, str]:
|
|
60
60
|
"""Run FFmpeg command and return (returncode, stderr)."""
|
|
61
61
|
proc = await asyncio.create_subprocess_exec(
|
|
62
62
|
*cmd,
|
|
@@ -2283,10 +2283,10 @@ async def speed(
|
|
|
2283
2283
|
# ---------------------------------------------------------------------------
|
|
2284
2284
|
|
|
2285
2285
|
|
|
2286
|
-
@cli.command()
|
|
2286
|
+
@cli.command(name="probe")
|
|
2287
2287
|
@click.argument("input", type=click.Path(exists=True))
|
|
2288
2288
|
@click.option("-o", "--output", type=click.Choice(["table", "json"]), default="table")
|
|
2289
|
-
def
|
|
2289
|
+
def probe(input: str, output: str) -> None:
|
|
2290
2290
|
"""Analyze a video file's metadata and properties."""
|
|
2291
2291
|
info = asyncio.run(get_video_info(input))
|
|
2292
2292
|
if "error" in info:
|
|
@@ -2864,31 +2864,56 @@ async def ark_cancel(task_id: str) -> None:
|
|
|
2864
2864
|
@click.argument("output")
|
|
2865
2865
|
@click.option("--transition", default="fade", help="Transition type (fade, dissolve, wipe, slide)")
|
|
2866
2866
|
@click.option("--transition-duration", default=0.5, help="Transition duration in seconds")
|
|
2867
|
-
@click.option(
|
|
2867
|
+
@click.option(
|
|
2868
|
+
"--color-grade", default="cinematic",
|
|
2869
|
+
help="Color grade (cinematic, warm, cool, desaturated, none)",
|
|
2870
|
+
)
|
|
2868
2871
|
@click.option("--root", default=None, help="Working directory")
|
|
2869
|
-
def stitch(
|
|
2872
|
+
def stitch(
|
|
2873
|
+
clips: tuple[str, ...],
|
|
2874
|
+
output: str,
|
|
2875
|
+
transition: str,
|
|
2876
|
+
transition_duration: float,
|
|
2877
|
+
color_grade: str,
|
|
2878
|
+
root: str | None,
|
|
2879
|
+
) -> None:
|
|
2870
2880
|
"""Stitch multiple video clips with transitions and color grading."""
|
|
2871
2881
|
from brandly_cli.stitch import stitch_videos
|
|
2872
2882
|
clip_paths = [Path(c) for c in clips]
|
|
2873
2883
|
output_path = Path(output)
|
|
2874
2884
|
console.print(f"[bold]Stitching[/bold] {len(clip_paths)} clips → {output_path}")
|
|
2875
|
-
result = asyncio.run(
|
|
2885
|
+
result = asyncio.run(
|
|
2886
|
+
stitch_videos(
|
|
2887
|
+
clip_paths,
|
|
2888
|
+
output_path,
|
|
2889
|
+
transition=transition,
|
|
2890
|
+
transition_duration=transition_duration,
|
|
2891
|
+
color_grade=color_grade,
|
|
2892
|
+
)
|
|
2893
|
+
)
|
|
2876
2894
|
if "error" in result:
|
|
2877
2895
|
console.print(f"[red]Error: {result['error']}[/red]")
|
|
2878
2896
|
sys.exit(1)
|
|
2879
2897
|
console.print(f"[green]✓ Output:[/green] {result['output_path']}")
|
|
2880
|
-
console.print(
|
|
2898
|
+
console.print(
|
|
2899
|
+
f" Duration: {result['duration_seconds']:.1f}s | Size: {result['size_bytes']//1024}KB"
|
|
2900
|
+
)
|
|
2881
2901
|
console.print(f" Transitions: {', '.join(result['transitions_applied']) or 'none'}")
|
|
2882
2902
|
console.print(f" Color grade: {result['color_grade']}")
|
|
2883
2903
|
|
|
2884
2904
|
|
|
2885
2905
|
@cli.command()
|
|
2886
2906
|
@click.argument("project_id")
|
|
2887
|
-
@click.option(
|
|
2907
|
+
@click.option(
|
|
2908
|
+
"--platforms", multiple=True,
|
|
2909
|
+
help="Target platforms (tiktok, instagram_reel, youtube_standard, etc.)",
|
|
2910
|
+
)
|
|
2888
2911
|
@click.option("--output", default=None, help="Output directory")
|
|
2889
2912
|
@click.option("--root", default=None, help="Working directory")
|
|
2890
2913
|
def export_platforms(project_id, platforms, output, root):
|
|
2891
2914
|
"""Export project to platform-optimized formats."""
|
|
2915
|
+
from brandly_cli.export_platforms import export_for_platform
|
|
2916
|
+
|
|
2892
2917
|
if not platforms:
|
|
2893
2918
|
platforms = ("tiktok", "youtube_standard")
|
|
2894
2919
|
proj_dir = Path(root or ".") / ".brandly" / "projects" / project_id
|
|
@@ -2923,7 +2948,12 @@ def thumbnail(project_id: str, count: int, style: str, root: str | None) -> None
|
|
|
2923
2948
|
console.print(f"[red]No video found in project: {project_id}[/red]")
|
|
2924
2949
|
sys.exit(1)
|
|
2925
2950
|
output_dir = proj_dir / "thumbnails"
|
|
2926
|
-
result = asyncio.run(
|
|
2951
|
+
result = asyncio.run(
|
|
2952
|
+
generate_thumbnails(
|
|
2953
|
+
video_file, output_dir, count=count, style_preset=style,
|
|
2954
|
+
root=Path(root) if root else None,
|
|
2955
|
+
)
|
|
2956
|
+
)
|
|
2927
2957
|
if "error" in result:
|
|
2928
2958
|
console.print(f"[red]Error: {result['error']}[/red]")
|
|
2929
2959
|
sys.exit(1)
|
|
@@ -2935,14 +2965,27 @@ def thumbnail(project_id: str, count: int, style: str, root: str | None) -> None
|
|
|
2935
2965
|
@cli.command(name="voice-match")
|
|
2936
2966
|
@click.argument("video_path")
|
|
2937
2967
|
@click.option("--source", default="en", help="Source language code")
|
|
2938
|
-
@click.option(
|
|
2968
|
+
@click.option(
|
|
2969
|
+
"--target", required=True,
|
|
2970
|
+
help="Target language code (en, es, fr, de, ja, ko, zh, pt, ar, hi)",
|
|
2971
|
+
)
|
|
2939
2972
|
@click.option("--voice-style", default="professional", help="Voice style")
|
|
2940
2973
|
@click.option("--output", default=None, help="Output path")
|
|
2941
2974
|
@click.option("--root", default=None, help="Working directory")
|
|
2942
|
-
def voice_match(
|
|
2975
|
+
def voice_match(
|
|
2976
|
+
video_path: str, source: str, target: str, voice_style: str,
|
|
2977
|
+
output: str | None, root: str | None,
|
|
2978
|
+
) -> None:
|
|
2943
2979
|
"""Dub a video to a target language."""
|
|
2944
2980
|
from brandly_cli.dubbing import dub_video
|
|
2945
|
-
result = asyncio.run(
|
|
2981
|
+
result = asyncio.run(
|
|
2982
|
+
dub_video(
|
|
2983
|
+
Path(video_path), source, target,
|
|
2984
|
+
voice_style=voice_style,
|
|
2985
|
+
output_path=Path(output) if output else None,
|
|
2986
|
+
root=Path(root) if root else None,
|
|
2987
|
+
)
|
|
2988
|
+
)
|
|
2946
2989
|
if "error" in result:
|
|
2947
2990
|
console.print(f"[red]Error: {result['error']}[/red]")
|
|
2948
2991
|
sys.exit(1)
|
|
@@ -2958,10 +3001,20 @@ def voice_match(video_path: str, source: str, target: str, voice_style: str, out
|
|
|
2958
3001
|
@click.option("--threshold", default=0.5, help="Beat detection threshold")
|
|
2959
3002
|
@click.option("--min-duration", default=1.0, help="Minimum clip duration")
|
|
2960
3003
|
@click.option("--root", default=None, help="Working directory")
|
|
2961
|
-
def beat_sync(
|
|
3004
|
+
def beat_sync(
|
|
3005
|
+
video_path: str, audio_path: str, output: str,
|
|
3006
|
+
threshold: float, min_duration: float, root: str | None,
|
|
3007
|
+
) -> None:
|
|
2962
3008
|
"""Cut video to match beat positions in audio."""
|
|
2963
3009
|
from brandly_cli.beat_sync import beat_sync as bs_sync
|
|
2964
|
-
result = asyncio.run(
|
|
3010
|
+
result = asyncio.run(
|
|
3011
|
+
bs_sync(
|
|
3012
|
+
Path(video_path), Path(audio_path), Path(output),
|
|
3013
|
+
beat_threshold=threshold,
|
|
3014
|
+
min_clip_duration=min_duration,
|
|
3015
|
+
root=Path(root) if root else None,
|
|
3016
|
+
)
|
|
3017
|
+
)
|
|
2965
3018
|
if "error" in result:
|
|
2966
3019
|
console.print(f"[red]Error: {result['error']}[/red]")
|
|
2967
3020
|
sys.exit(1)
|
|
@@ -2976,7 +3029,7 @@ def beat_sync(video_path: str, audio_path: str, output: str, threshold: float, m
|
|
|
2976
3029
|
@click.option("--json", is_flag=True, help="Output as JSON")
|
|
2977
3030
|
def trend(category: str | None, platforms: tuple[str, ...], json: bool) -> None:
|
|
2978
3031
|
"""Research trending video formats."""
|
|
2979
|
-
from brandly_cli.trends import
|
|
3032
|
+
from brandly_cli.trends import list_categories, research_trends
|
|
2980
3033
|
cats = list_categories()
|
|
2981
3034
|
if not category:
|
|
2982
3035
|
console.print("[bold]Available categories:[/bold]")
|
|
@@ -3005,7 +3058,12 @@ def trend(category: str | None, platforms: tuple[str, ...], json: bool) -> None:
|
|
|
3005
3058
|
def analyze(video_path: str, script: str | None, style: str, root: str | None) -> None:
|
|
3006
3059
|
"""Analyze video performance prediction."""
|
|
3007
3060
|
from brandly_cli.analyzer import analyze_video
|
|
3008
|
-
result = asyncio.run(
|
|
3061
|
+
result = asyncio.run(
|
|
3062
|
+
analyze_video(
|
|
3063
|
+
Path(video_path), script=script, style=style,
|
|
3064
|
+
root=Path(root) if root else None,
|
|
3065
|
+
)
|
|
3066
|
+
)
|
|
3009
3067
|
if "error" in result:
|
|
3010
3068
|
console.print(f"[red]Error: {result['error']}[/red]")
|
|
3011
3069
|
sys.exit(1)
|
|
@@ -3032,12 +3090,18 @@ def analyze(video_path: str, script: str | None, style: str, root: str | None) -
|
|
|
3032
3090
|
@click.option("--platforms", multiple=True, help="Target platforms")
|
|
3033
3091
|
@click.option("--save", is_flag=True, help="Save as custom template")
|
|
3034
3092
|
@click.option("--root", default=None, help="Working directory")
|
|
3035
|
-
def template(
|
|
3093
|
+
def template(
|
|
3094
|
+
name: str, style: str, shots: int, duration: int, budget: int,
|
|
3095
|
+
platforms: tuple[str, ...], save: bool, root: str | None,
|
|
3096
|
+
) -> None:
|
|
3036
3097
|
"""Create or manage project templates."""
|
|
3037
|
-
from brandly_cli.templates import
|
|
3098
|
+
from brandly_cli.templates import create_from_template, save_template
|
|
3038
3099
|
if save:
|
|
3039
|
-
config = {
|
|
3040
|
-
|
|
3100
|
+
config = {
|
|
3101
|
+
"style": style, "shots": shots, "duration": duration,
|
|
3102
|
+
"budget": budget, "platforms": list(platforms) or ["tiktok"],
|
|
3103
|
+
}
|
|
3104
|
+
asyncio.run(save_template(name, config, root=Path(root) if root else None))
|
|
3041
3105
|
console.print(f"[green]✓ Template saved: {name}[/green]")
|
|
3042
3106
|
else:
|
|
3043
3107
|
console.print(f"[bold]Template: {name}[/bold]")
|
|
@@ -3050,9 +3114,9 @@ def template(name: str, style: str, shots: int, duration: int, budget: int, plat
|
|
|
3050
3114
|
@click.option("--root", default=None, help="Working directory")
|
|
3051
3115
|
def template_list(root: str | None) -> None:
|
|
3052
3116
|
"""List available templates."""
|
|
3053
|
-
from brandly_cli.templates import
|
|
3117
|
+
from brandly_cli.templates import list_custom_templates, list_templates
|
|
3054
3118
|
builtins = asyncio.run(list_templates())
|
|
3055
|
-
customs = asyncio.run(list_custom_templates(root=root))
|
|
3119
|
+
customs = asyncio.run(list_custom_templates(root=Path(root) if root else None))
|
|
3056
3120
|
console.print("[bold]Built-in Templates[/bold]")
|
|
3057
3121
|
for t in builtins:
|
|
3058
3122
|
console.print(f" • {t}")
|
|
@@ -3069,14 +3133,19 @@ def template_list(root: str | None) -> None:
|
|
|
3069
3133
|
@click.pass_context
|
|
3070
3134
|
def webhook(ctx: click.Context, host: str, port: int) -> None:
|
|
3071
3135
|
"""Start webhook server for CI/CD integration."""
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3136
|
+
import importlib.util
|
|
3137
|
+
|
|
3138
|
+
if (
|
|
3139
|
+
importlib.util.find_spec("fastapi") is None
|
|
3140
|
+
or importlib.util.find_spec("uvicorn") is None
|
|
3141
|
+
):
|
|
3076
3142
|
console.print("[red]FastAPI/uvicorn not installed. Run: pip install fastapi uvicorn[/red]")
|
|
3077
3143
|
sys.exit(1)
|
|
3078
3144
|
console.print(f"[bold]Starting webhook server[/bold] at http://{host}:{port}")
|
|
3079
|
-
console.print(
|
|
3145
|
+
console.print(
|
|
3146
|
+
"[dim]Endpoints: POST /webhook/generate, "
|
|
3147
|
+
"GET /webhook/jobs/{id}, POST /webhook/jobs/{id}/cancel[/dim]"
|
|
3148
|
+
)
|
|
3080
3149
|
|
|
3081
3150
|
|
|
3082
3151
|
@cli.command()
|
|
@@ -3086,11 +3155,16 @@ def webhook(ctx: click.Context, host: str, port: int) -> None:
|
|
|
3086
3155
|
def share(file_path: str, provider: str, root: str | None) -> None:
|
|
3087
3156
|
"""Upload file to cloud for sharing."""
|
|
3088
3157
|
from brandly_cli.sharing import share_file
|
|
3089
|
-
result = asyncio.run(
|
|
3158
|
+
result = asyncio.run(
|
|
3159
|
+
share_file(
|
|
3160
|
+
Path(file_path), provider=provider,
|
|
3161
|
+
root=Path(root) if root else None,
|
|
3162
|
+
)
|
|
3163
|
+
)
|
|
3090
3164
|
if "error" in result:
|
|
3091
3165
|
console.print(f"[red]Error: {result['error']}[/red]")
|
|
3092
3166
|
sys.exit(1)
|
|
3093
|
-
console.print(
|
|
3167
|
+
console.print("[green]✓ Shared[/green]")
|
|
3094
3168
|
console.print(f" URL: {result.get('share_url', 'N/A')}")
|
|
3095
3169
|
console.print(f" Provider: {result.get('provider', provider)}")
|
|
3096
3170
|
if result.get("file_size_bytes"):
|
|
@@ -80,7 +80,7 @@ def _ffprobe_available() -> bool:
|
|
|
80
80
|
return False
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
async def _run_ffmpeg(cmd: list[str]) -> tuple[int, str]:
|
|
83
|
+
async def _run_ffmpeg(cmd: list[str]) -> tuple[int | None, str]:
|
|
84
84
|
"""Run FFmpeg command and return (returncode, stderr)."""
|
|
85
85
|
proc = await asyncio.create_subprocess_exec(
|
|
86
86
|
*cmd,
|
|
@@ -103,7 +103,7 @@ async def share_file(
|
|
|
103
103
|
base_dir = Path(root) / ".brandly" / "shares"
|
|
104
104
|
else:
|
|
105
105
|
base_dir = Path.cwd() / ".brandly" / "shares"
|
|
106
|
-
p = LocalShareProvider(base_dir)
|
|
106
|
+
p: ShareProvider = LocalShareProvider(base_dir)
|
|
107
107
|
elif provider == "s3":
|
|
108
108
|
p = S3ShareProvider("default-bucket")
|
|
109
109
|
else:
|
|
@@ -210,7 +210,7 @@ async def stitch_videos(
|
|
|
210
210
|
return _result(output, clips, transitions_applied, color_grade)
|
|
211
211
|
|
|
212
212
|
|
|
213
|
-
async def _run(cmd: list[str]) -> tuple[int, str]:
|
|
213
|
+
async def _run(cmd: list[str]) -> tuple[int | None, str]:
|
|
214
214
|
"""Run an FFmpeg command, returning (returncode, stderr)."""
|
|
215
215
|
proc = await asyncio.create_subprocess_exec(
|
|
216
216
|
*cmd,
|
|
@@ -27,7 +27,7 @@ def _ffmpeg_available() -> bool:
|
|
|
27
27
|
return False
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
async def _run_ffmpeg(cmd: list[str]) -> tuple[bytes, bytes, int]:
|
|
30
|
+
async def _run_ffmpeg(cmd: list[str]) -> tuple[bytes, bytes, int | None]:
|
|
31
31
|
"""Execute an FFmpeg command and return (stdout, stderr, returncode)."""
|
|
32
32
|
proc = await asyncio.create_subprocess_exec(
|
|
33
33
|
*cmd,
|
|
@@ -208,7 +208,7 @@ async def generate_thumbnails(
|
|
|
208
208
|
thumbnails.append(entry)
|
|
209
209
|
|
|
210
210
|
for platform, (pw, ph) in PLATFORM_SIZES.items():
|
|
211
|
-
resized = img.resize((pw, ph), Image.LANCZOS)
|
|
211
|
+
resized = img.resize((pw, ph), Image.Resampling.LANCZOS)
|
|
212
212
|
out_path = output_dir / f"thumb_{platform}.jpg"
|
|
213
213
|
resized.convert("RGB").save(out_path, "JPEG", quality=90)
|
|
214
214
|
thumbnails.append({
|
|
@@ -248,7 +248,7 @@ def _draw_text_overlay(
|
|
|
248
248
|
try:
|
|
249
249
|
font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeSansBold.ttf", fw)
|
|
250
250
|
except OSError:
|
|
251
|
-
font = ImageFont.load_default()
|
|
251
|
+
font = ImageFont.load_default() # type: ignore[assignment]
|
|
252
252
|
|
|
253
253
|
bbox = draw.textbbox((0, 0), text, font=font)
|
|
254
254
|
text_w = bbox[2] - bbox[0]
|
|
@@ -510,7 +510,7 @@ def get_reference_image_urls(project_id: str, root: Path | None = None) -> list[
|
|
|
510
510
|
# Async FFmpeg runner for subprocess operations
|
|
511
511
|
# ---------------------------------------------------------------------------
|
|
512
512
|
|
|
513
|
-
async def async_run_ffmpeg(cmd: list[str]) -> tuple[int, str]:
|
|
513
|
+
async def async_run_ffmpeg(cmd: list[str]) -> tuple[int | None, str]:
|
|
514
514
|
"""Run an FFmpeg command asynchronously.
|
|
515
515
|
|
|
516
516
|
Args:
|
brandly_cli-0.3.2/$null
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
on:
|
|
3
|
-
pull_request:
|
|
4
|
-
types: [opened, synchronize, reopened]
|
|
5
|
-
push:
|
|
6
|
-
branches: [main]
|
|
7
|
-
|
|
8
|
-
concurrency:
|
|
9
|
-
group: ${{ github.workflow }}-${{ github.ref }}
|
|
10
|
-
cancel-in-progress: true
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
quality:
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v4
|
|
17
|
-
|
|
18
|
-
- name: Install FFmpeg
|
|
19
|
-
run: sudo apt-get update && sudo apt-get install -y ffmpeg
|
|
20
|
-
|
|
21
|
-
- uses: actions/setup-python@v5
|
|
22
|
-
with:
|
|
23
|
-
python-version: "3.12"
|
|
24
|
-
|
|
25
|
-
- name: Install dependencies
|
|
26
|
-
run: |
|
|
27
|
-
python -m pip install --upgrade pip
|
|
28
|
-
pip install -e ".[dev]"
|
|
29
|
-
|
|
30
|
-
- name: Type check
|
|
31
|
-
run: python -m pytest tests/ -v --tb=short
|
|
32
|
-
|
|
33
|
-
- name: Lint
|
|
34
|
-
run: ruff check src/
|
|
35
|
-
|
|
36
|
-
- name: Syntax check
|
|
37
|
-
run: python -m py_compile src/brandly_cli/*.py
|
|
Binary file
|