Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
74772e0
Add working Python MVP: discover, import, compose pipeline
scrumlord8 Apr 9, 2026
7c6e812
Harden discovery modules and fix compose loudness for viral content
claude Apr 10, 2026
8821c54
Add tests for discovery modules, loudness presets, CLI, and ffmpeg wr…
claude Apr 10, 2026
904201f
Merge pull request #1 from scrumlord8/claude/refactor-discovery-compo…
scrumlord8 Apr 10, 2026
0e1a5e3
Port full Python implementation to Rust
claude Apr 12, 2026
8e455f0
Remove Python implementation, Rust port is the source of truth
claude Apr 12, 2026
e30ea23
Merge pull request #2 from scrumlord8/claude/refactor-discovery-compo…
scrumlord8 Apr 12, 2026
0051484
Add 44 unit tests across CLI parsing, models, output, and downloader
scrumlord8 Apr 13, 2026
1778f48
Merge pull request #4 from scrumlord8/add-unit-tests
scrumlord8 Apr 13, 2026
abbb7ad
Add autopilot command for end-to-end clip generation
scrumlord8 Apr 13, 2026
a2eb4d3
Add strategy-driven media discovery and document fallback workflows
scrumlord8 Apr 13, 2026
c0f01a7
Merge branch 'main' into scrumlord8/LobstarFinalBoss
scrumlord8 Apr 13, 2026
07fdfca
Merge pull request #5 from scrumlord8/scrumlord8/LobstarFinalBoss
scrumlord8 Apr 13, 2026
c458700
Add Actions + Codespaces paths for trending-driven clip batches
claude Apr 18, 2026
66c7c4f
Drop Python from bootstrap; use 'capcut-cli deps install' for yt-dlp
claude Apr 18, 2026
c1e94c9
Scope down to manual-URL spine; make compose verifiable end-to-end
claude Apr 18, 2026
61087f6
Align secondary docs with the manual-URL spine
claude Apr 18, 2026
2a04157
Merge pull request #6 from scrumlord8/ninerpride/outputs
scrumlord8 Apr 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "capcut-cli",
"image": "mcr.microsoft.com/devcontainers/rust:1-bookworm",
"postCreateCommand": "bash .devcontainer/post-create.sh",
"remoteEnv": {
"TIKTOK_RESEARCH_ACCESS_TOKEN": "${localEnv:TIKTOK_RESEARCH_ACCESS_TOKEN}",
"TWITTER_BEARER_TOKEN": "${localEnv:TWITTER_BEARER_TOKEN}"
},
"customizations": {
"vscode": {
"extensions": [
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml"
]
}
}
}
13 changes: 13 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# One-shot Codespace bootstrap: install ffmpeg, build the CLI, let the CLI
# fetch its own standalone yt-dlp binary via `deps install`.
set -euo pipefail

sudo apt-get update
sudo apt-get install -y ffmpeg jq

cargo build --release
./target/release/capcut-cli deps install
./target/release/capcut-cli deps check >/dev/null && echo "deps ok" >&2

echo "Run 'make clips' once TIKTOK_RESEARCH_ACCESS_TOKEN and TWITTER_BEARER_TOKEN are set." >&2
27 changes: 27 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copy this file to `.env` or export these variables in your shell.
# Do not commit real secrets.
#
# None of these are required for the primary manual-URL flow
# (library import + compose). They only affect the optional, API-gated
# discovery path (see "Optional: API-gated discovery" in README.md).

# Optional: X/Twitter API bearer token. Enables `discover x-clips` and the
# autopilot clip path. Requires the paid Basic tier or higher.
TWITTER_BEARER_TOKEN=

# Optional: TikTok Research API client access token. Enables `discover
# tiktok-sounds` and the autopilot sound path. Access is gated and granted
# mainly to qualifying researchers.
TIKTOK_RESEARCH_ACCESS_TOKEN=

# Optional: control which local browsers yt-dlp should try for X media import.
# Comma-separated list, checked in order.
CAPCUT_X_COOKIE_BROWSERS=chrome,safari,firefox,edge

# Optional: set to 1 for extra discovery debugging logs.
CAPCUT_DEBUG_DISCOVERY=0

# Optional (tests only): override the yt-dlp binary path. Used by the
# end-to-end import→compose integration test to inject a shim. Leave unset
# in production environments.
CAPCUT_YTDLP_PATH=
121 changes: 121 additions & 0 deletions .github/workflows/build-clips.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
name: build-clips

on:
workflow_dispatch:
inputs:
mode:
description: "urls (primary: caller supplies links) | discovery (optional: requires API tokens)"
type: choice
default: "urls"
options:
- urls
- discovery
# ─── urls-mode inputs ───────────────────────────────────────────
sound_url:
description: "[urls] Trending sound URL (TikTok music, YouTube, etc.)"
required: false
clip_url_1:
description: "[urls] Source clip URL #1"
required: false
clip_url_2:
description: "[urls] Source clip URL #2"
required: false
clip_url_3:
description: "[urls] Source clip URL #3"
required: false
# ─── discovery-mode inputs ──────────────────────────────────────
query:
description: "[discovery] Topic for X/Twitter clip search"
default: "ai agents"
region:
description: "[discovery] TikTok region code"
default: "US"
window_days:
description: "[discovery] TikTok rolling window (days)"
default: "7"
min_likes:
description: "[discovery] X minimum likes threshold"
default: "1000"
# ─── shared compose knobs ───────────────────────────────────────
duration:
description: "Output duration per finished clip (seconds)"
default: "15"
resolution:
description: "Output resolution (WxH)"
default: "1080x1920"

jobs:
build:
runs-on: ubuntu-latest
env:
TIKTOK_RESEARCH_ACCESS_TOKEN: ${{ secrets.TIKTOK_RESEARCH_ACCESS_TOKEN }}
TWITTER_BEARER_TOKEN: ${{ secrets.TWITTER_BEARER_TOKEN }}
DURATION: ${{ inputs.duration }}
RESOLUTION: ${{ inputs.resolution }}
steps:
- uses: actions/checkout@v4

- name: Validate inputs for selected mode
run: |
if [[ "${{ inputs.mode }}" == "urls" ]]; then
for name in sound_url clip_url_1 clip_url_2 clip_url_3; do
val="${{ inputs.sound_url }}${{ inputs.clip_url_1 }}${{ inputs.clip_url_2 }}${{ inputs.clip_url_3 }}"
done
missing=""
[[ -z "${{ inputs.sound_url }}" ]] && missing+=" sound_url"
[[ -z "${{ inputs.clip_url_1 }}" ]] && missing+=" clip_url_1"
[[ -z "${{ inputs.clip_url_2 }}" ]] && missing+=" clip_url_2"
[[ -z "${{ inputs.clip_url_3 }}" ]] && missing+=" clip_url_3"
if [[ -n "$missing" ]]; then
echo "urls mode requires:$missing" >&2
exit 1
fi
else
missing=""
[[ -z "${TIKTOK_RESEARCH_ACCESS_TOKEN}" ]] && missing+=" TIKTOK_RESEARCH_ACCESS_TOKEN"
[[ -z "${TWITTER_BEARER_TOKEN}" ]] && missing+=" TWITTER_BEARER_TOKEN"
if [[ -n "$missing" ]]; then
echo "discovery mode requires repo secrets:$missing" >&2
echo "Add them under Settings → Secrets and variables → Actions." >&2
exit 1
fi
fi

- name: Install ffmpeg and jq
run: |
sudo apt-get update
sudo apt-get install -y ffmpeg jq

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2

- name: Build capcut-cli
run: cargo build --release

- name: Install yt-dlp via the CLI's own deps bootstrap
run: ./target/release/capcut-cli deps install

- name: Compose (urls mode)
if: inputs.mode == 'urls'
env:
SOUND_URL: ${{ inputs.sound_url }}
CLIP_URLS: "${{ inputs.clip_url_1 }} ${{ inputs.clip_url_2 }} ${{ inputs.clip_url_3 }}"
run: ./scripts/build-clips-from-urls.sh

- name: Discover + compose (discovery mode)
if: inputs.mode == 'discovery'
env:
QUERY: ${{ inputs.query }}
REGION: ${{ inputs.region }}
WINDOW_DAYS: ${{ inputs.window_days }}
MIN_LIKES: ${{ inputs.min_likes }}
run: ./scripts/build-clips.sh

- name: Upload clips artifact
uses: actions/upload-artifact@v4
with:
name: clips
path: clips/
if-no-files-found: error
retention-days: 14
25 changes: 25 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: test

on:
push:
branches: ["**"]
pull_request:
branches: [main]

jobs:
cargo-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install ffmpeg (required by the compose smoke test)
run: |
sudo apt-get update
sudo apt-get install -y ffmpeg

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2

- name: cargo test
run: cargo test --all-targets
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,23 @@
/target
.env
.env.*
!.env.example
*.local

# Library working files (runtime-generated assets)
library/.tmp/
library/output/

# Ignore non-demo asset directories; un-ignore the committed demo fixtures
library/clips/*
!library/clips/clp_demo001
library/clips/clp_demo001/*
!library/clips/clp_demo001/video.mp4

library/sounds/assets/*
!library/sounds/assets/snd_demo001
library/sounds/assets/snd_demo001/*
!library/sounds/assets/snd_demo001/audio.mp3

# Agent-run batch outputs (repo-root /clips, not library/clips)
/clips/
Loading