buttercut 0.4.0 → 0.6.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.
- checksums.yaml +4 -4
- data/.claude/scripts/script_extractor.rb +66 -0
- data/.claude/settings.local.json +7 -1
- data/.claude/skills/analyze-video/SKILL.md +14 -73
- data/.claude/skills/analyze-video/agent_prompt.md +84 -0
- data/.claude/skills/backup-library/SKILL.md +1 -1
- data/.claude/skills/backup-library/backup_libraries.rb +1 -1
- data/.claude/skills/cut-planner/SKILL.md +74 -0
- data/.claude/skills/release/SKILL.md +21 -11
- data/.claude/skills/roughcut/SKILL.md +41 -47
- data/.claude/skills/roughcut/agent_prompt.md +153 -0
- data/.claude/skills/roughcut/export_to_fcpxml.rb +25 -0
- data/.claude/skills/summarize-video/SKILL.md +31 -0
- data/.claude/skills/summarize-video/agent_prompt.md +39 -0
- data/.claude/skills/summarize-video/summary_skeleton.rb +78 -0
- data/.claude/skills/summarize-video/visual_script_extractor.rb +78 -0
- data/.claude/skills/transcribe-audio/SKILL.md +19 -66
- data/.claude/skills/transcribe-audio/agent_prompt.md +53 -0
- data/.claude/skills/transcribe-audio/refine_instructions.md +114 -0
- data/CLAUDE.md +133 -52
- data/README.md +5 -1
- data/lib/buttercut/version.rb +1 -1
- data/templates/library_template.yaml +2 -0
- data/templates/plan_template.md +53 -0
- data/templates/roughcut_template.yaml +3 -20
- data/templates/settings_template.yaml +13 -0
- metadata +14 -3
- data/.claude/skills/roughcut/agent_instructions.md +0 -109
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 57b76c4460b6e40602877e8d9c3a31f0b9748b1447b2dbb5d7189a497a17ad87
|
|
4
|
+
data.tar.gz: 87b7a604c6f807171c463b5e3b49fef95d88796ebd3803b9ab6f495ebdc2e0e5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bbce3378342cc2c11ae5644f215162f523e564206519873ad58f30fc38cbb61363db95a323dfbb0bf9e4df735f7ed7ea49d812bb9f49f826e0c773a7c33df5d4
|
|
7
|
+
data.tar.gz: 3c602e886a0d2597be5e960f0cd5202b8c4143ae8ab66dfa88e04ccc57a982f075ad0a61b489340bde86c13653e469fac472f146977c2b1d174747eb3d9edee9
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# Extract the plain-text script from a WhisperX-style transcript JSON.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ruby .claude/scripts/script_extractor.rb <transcript.json> <output.txt>
|
|
6
|
+
#
|
|
7
|
+
# Output is one segment per paragraph (blank line between), trimmed, suitable
|
|
8
|
+
# for proofreading by a human or a sub-agent without the overhead of the full
|
|
9
|
+
# transcript JSON (word-level timing, scores, etc.).
|
|
10
|
+
|
|
11
|
+
require 'json'
|
|
12
|
+
|
|
13
|
+
class ScriptExtractor
|
|
14
|
+
def self.extract(transcript_path, output_path)
|
|
15
|
+
new(transcript_path, output_path).extract
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(transcript_path, output_path)
|
|
19
|
+
raise ArgumentError, "transcript_path is required" if transcript_path.nil? || transcript_path.empty?
|
|
20
|
+
raise ArgumentError, "output_path is required" if output_path.nil? || output_path.empty?
|
|
21
|
+
@transcript_path = transcript_path
|
|
22
|
+
@output_path = output_path
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def extract
|
|
26
|
+
write_output(format_script)
|
|
27
|
+
report
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :transcript_path, :output_path
|
|
33
|
+
|
|
34
|
+
def data
|
|
35
|
+
@data ||= JSON.parse(File.read(transcript_path))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def segments
|
|
39
|
+
data["segments"] or raise "transcript JSON has no 'segments' key: #{transcript_path}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def format_script
|
|
43
|
+
paragraphs = segments.map { |s| s["text"].to_s.strip }.reject(&:empty?)
|
|
44
|
+
paragraphs.join("\n\n") + "\n"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def write_output(text)
|
|
48
|
+
File.write(output_path, text)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def report
|
|
52
|
+
in_kb = (File.size(transcript_path) / 1024.0).round(1)
|
|
53
|
+
out_kb = (File.size(output_path) / 1024.0).round(1)
|
|
54
|
+
puts "Extracted script: #{output_path} (#{out_kb} KB from #{in_kb} KB source, #{segments.size} segments)"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if __FILE__ == $PROGRAM_NAME
|
|
59
|
+
transcript_path, output_path = ARGV
|
|
60
|
+
abort("usage: script_extractor.rb <transcript.json> <output.txt>") unless transcript_path && output_path
|
|
61
|
+
abort("file not found: #{transcript_path}") unless File.file?(transcript_path)
|
|
62
|
+
if File.expand_path(output_path) == File.expand_path(transcript_path)
|
|
63
|
+
abort("output path must differ from transcript path: #{transcript_path}")
|
|
64
|
+
end
|
|
65
|
+
ScriptExtractor.extract(transcript_path, output_path)
|
|
66
|
+
end
|
data/.claude/settings.local.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
+
"Agent",
|
|
5
|
+
"Read(tmp/**)",
|
|
6
|
+
"Write(tmp/**)",
|
|
4
7
|
"Bash(./.claude/skills/roughcut/combine_visual_transcripts.rb:*)",
|
|
5
8
|
"Bash(./.claude/skills/roughcut/export_to_fcpxml.rb:*)",
|
|
6
9
|
"Skill(backup-library)",
|
|
@@ -22,7 +25,10 @@
|
|
|
22
25
|
"Bash(git worktree add:*)",
|
|
23
26
|
"Bash(cat:*)",
|
|
24
27
|
"Bash(python3:*)",
|
|
25
|
-
"Bash(gh api:*)"
|
|
28
|
+
"Bash(gh api:*)",
|
|
29
|
+
"Bash(gh pr:*)",
|
|
30
|
+
"Bash(cp *)",
|
|
31
|
+
"Bash(chmod +x .claude/skills/export-video/export_video.rb)"
|
|
26
32
|
],
|
|
27
33
|
"deny": [],
|
|
28
34
|
"ask": []
|
|
@@ -3,87 +3,28 @@ name: analyze-video
|
|
|
3
3
|
description: Adds visual descriptions to transcripts by extracting and analyzing video frames with ffmpeg. Creates visual transcript with periodic visual descriptions of the video clip. Use when all files have audio transcripts present (transcript) but don't yet have visual transcripts created (visual_transcript).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Skill: Analyze Video
|
|
6
|
+
# Skill: Analyze Video (parent brief)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Adds visual descriptions to a video's audio transcript by extracting JPG frames with ffmpeg and analyzing them.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Videos must have audio transcripts. Run **transcribe-audio** skill first if needed.
|
|
13
|
-
|
|
14
|
-
## Workflow
|
|
15
|
-
|
|
16
|
-
### 1. Copy & Clean Audio Transcript
|
|
17
|
-
|
|
18
|
-
Don't read the audio transcript, just copy it and then prepare it by using the prepare_visual_script.rb file. This removes word-level timing data and prettifies the JSON for easier editing:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
cp libraries/[library]/transcripts/video.json libraries/[library]/transcripts/visual_video.json
|
|
22
|
-
ruby .claude/skills/analyze-video/prepare_visual_script.rb libraries/[library]/transcripts/visual_video.json
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### 2. Extract Frames (Binary Search)
|
|
10
|
+
`SKILL.md` is the parent's dispatch brief. The sub-agent's working prompt lives in `agent_prompt.md` — inline its contents when launching the Task agent. Don't pass `SKILL.md`.
|
|
26
11
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
**Videos ≤30s:** Extract one frame at 2s
|
|
30
|
-
**Videos >30s:** Extract start (2s), middle (duration/2), end (duration-2s)
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
ffmpeg -ss 00:00:02 -i video.mov -vframes 1 -vf "scale=1280:-1" tmp/frames/[video_name]/start.jpg
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
**Subdivide when:** Footage start, middle and end have different subjects, setting or angle changes
|
|
37
|
-
**Stop when:** The footage no longer seems to be changing or only has minor changes
|
|
38
|
-
**Never sample** more frequently than once per 30 seconds
|
|
39
|
-
|
|
40
|
-
### 3. Add Visual Descriptions
|
|
41
|
-
|
|
42
|
-
Read the visual video json file that you created earlier.
|
|
43
|
-
|
|
44
|
-
**Read the JPG frames** from `tmp/frames/[video_name]/` using Read tool, then **Edit** `visual_video.json`:
|
|
12
|
+
## Prerequisites
|
|
45
13
|
|
|
46
|
-
|
|
14
|
+
Each video must already have an audio transcript. Run `transcribe-audio` first if any are missing.
|
|
47
15
|
|
|
48
|
-
|
|
49
|
-
```json
|
|
50
|
-
{
|
|
51
|
-
"start": 2.917,
|
|
52
|
-
"end": 7.586,
|
|
53
|
-
"text": "Hey, good afternoon everybody.",
|
|
54
|
-
"visual": "Man in red shirt speaking to camera in medium shot. Home office with bookshelf. Natural lighting.",
|
|
55
|
-
"words": [...]
|
|
56
|
-
}
|
|
57
|
-
```
|
|
16
|
+
## Parallelism
|
|
58
17
|
|
|
59
|
-
**
|
|
60
|
-
```json
|
|
61
|
-
{
|
|
62
|
-
"start": 35.474,
|
|
63
|
-
"end": 56.162,
|
|
64
|
-
"text": "",
|
|
65
|
-
"visual": "Green bicycle parked in front of building. Urban street with trees.",
|
|
66
|
-
"b_roll": true,
|
|
67
|
-
"words": []
|
|
68
|
-
}
|
|
69
|
-
```
|
|
18
|
+
Launch at most **8 in parallel**. ffmpeg frame extraction is a brief CPU burst at the start; the rest of the runtime is LLM API calls. 8 is a comfortable middle ground that won't saturate older machines.
|
|
70
19
|
|
|
71
|
-
|
|
72
|
-
- Descriptions should be 3 sentences max.
|
|
73
|
-
- First segment: detailed (subject, setting, shot type, lighting, camera style)
|
|
74
|
-
- Continuing shots: brief if similar, otherwise can be up to 3 sentences if drastically different.
|
|
20
|
+
## Inputs to gather and pass inline
|
|
75
21
|
|
|
76
|
-
|
|
22
|
+
- `video_path` — absolute path to the video file
|
|
23
|
+
- `audio_transcript_path` — absolute path to the prepared audio transcript JSON
|
|
24
|
+
- `visual_transcript_path` — absolute path to write the visual transcript JSON
|
|
77
25
|
|
|
78
|
-
|
|
79
|
-
rm -rf tmp/frames/[video_name]
|
|
80
|
-
```
|
|
26
|
+
After the agent returns, update `library.yaml` with `visual_transcript: <filename>.json`.
|
|
81
27
|
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
✓ [video_filename.mov] analyzed successfully
|
|
85
|
-
Visual transcript: libraries/[library]/transcripts/visual_video.json
|
|
86
|
-
Video path: /full/path/to/video_filename.mov
|
|
87
|
-
```
|
|
28
|
+
## Next step
|
|
88
29
|
|
|
89
|
-
|
|
30
|
+
Once all videos have visual transcripts, dispatch `summarize-video` (Haiku model) to produce summaries.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Analyze Video (sub-agent prompt)
|
|
2
|
+
|
|
3
|
+
You are a sub-agent. Add visual descriptions to one video's audio transcript by extracting JPG frames with ffmpeg and analyzing them. **Never read the video file directly** — extract frames first.
|
|
4
|
+
|
|
5
|
+
## Inputs (passed inline by the parent)
|
|
6
|
+
|
|
7
|
+
- `video_path` — absolute path to the video file
|
|
8
|
+
- `audio_transcript_path` — absolute path to the prepared audio transcript JSON
|
|
9
|
+
- `visual_transcript_path` — absolute path to write the visual transcript JSON
|
|
10
|
+
|
|
11
|
+
Do NOT read `library.yaml` or `settings.yaml`.
|
|
12
|
+
|
|
13
|
+
## 1. Copy & clean audio transcript
|
|
14
|
+
|
|
15
|
+
Don't read the audio transcript — just copy it, then prepare it via `prepare_visual_script.rb`. This removes word-level timing data and prettifies the JSON for easier editing:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cp <audio_transcript_path> <visual_transcript_path>
|
|
19
|
+
ruby .claude/skills/analyze-video/prepare_visual_script.rb <visual_transcript_path>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 2. Extract frames (binary search)
|
|
23
|
+
|
|
24
|
+
Create frame directory: `mkdir -p tmp/frames/[video_name]`
|
|
25
|
+
|
|
26
|
+
**Videos ≤30s:** extract one frame at 2s
|
|
27
|
+
**Videos >30s:** extract start (2s), middle (duration/2), end (duration-2s)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ffmpeg -ss 00:00:02 -i video.mov -vframes 1 -vf "scale=1280:-1" tmp/frames/[video_name]/start.jpg
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Subdivide when:** start, middle, and end have different subjects, settings, or angle changes
|
|
34
|
+
**Stop when:** the footage no longer seems to be changing or only has minor changes
|
|
35
|
+
**Never sample** more frequently than once per 30 seconds
|
|
36
|
+
|
|
37
|
+
## 3. Add visual descriptions
|
|
38
|
+
|
|
39
|
+
Read the visual transcript JSON you created in step 1.
|
|
40
|
+
|
|
41
|
+
**Read the JPG frames** from `tmp/frames/[video_name]/` using the Read tool, then **Edit** the file at `<visual_transcript_path>`. Do this incrementally — no script needed; just edit the JSON each time you read new frames.
|
|
42
|
+
|
|
43
|
+
**Dialogue segments — add `visual` field:**
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"start": 2.917,
|
|
47
|
+
"end": 7.586,
|
|
48
|
+
"text": "Hey, good afternoon everybody.",
|
|
49
|
+
"visual": "Man in red shirt speaking to camera in medium shot. Home office with bookshelf. Natural lighting.",
|
|
50
|
+
"words": [...]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**B-roll segments — insert new entries:**
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"start": 35.474,
|
|
58
|
+
"end": 56.162,
|
|
59
|
+
"text": "",
|
|
60
|
+
"visual": "Green bicycle parked in front of building. Urban street with trees.",
|
|
61
|
+
"b_roll": true,
|
|
62
|
+
"words": []
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Guidelines:**
|
|
67
|
+
- Descriptions: 3 sentences max
|
|
68
|
+
- First segment: detailed (subject, setting, shot type, lighting, camera style)
|
|
69
|
+
- Continuing shots: brief if similar; up to 3 sentences if drastically different
|
|
70
|
+
|
|
71
|
+
## 4. Cleanup & return
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
rm -rf tmp/frames/[video_name]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Return:
|
|
78
|
+
```
|
|
79
|
+
✓ [video_filename.mov] analyzed successfully
|
|
80
|
+
Visual transcript: <visual_transcript_path>
|
|
81
|
+
Video path: <video_path>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Do NOT update library.yaml** — parent handles this to avoid race conditions in parallel execution.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: backup-library
|
|
3
|
-
description:
|
|
3
|
+
description: Backs up user libraries and all their contents (external video excluded). This skill can also be useful when you need to restore a library.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Skill: Backup Library
|
|
@@ -29,7 +29,7 @@ class LibraryBackup
|
|
|
29
29
|
|
|
30
30
|
files = Dir.glob(File.join(@libraries_dir, '**', '*')).select { |f| File.file?(f) }
|
|
31
31
|
|
|
32
|
-
Zip::File.open(backup_path,
|
|
32
|
+
Zip::File.open(backup_path, create: true) do |zipfile|
|
|
33
33
|
files.each do |file|
|
|
34
34
|
zipfile.add(file.sub("#{File.dirname(@libraries_dir)}/", ''), file)
|
|
35
35
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cut-planner
|
|
3
|
+
description: Plans a cut (roughcut, sequence, or scene) from a library's clip summaries. Reads all clip summaries, then talks with the user and iteratively creates a plan markdown file until both agent and user are happy and understand the plan.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Cut Planner
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
In the cut-planner skill, the main thread reads clip summaries from a library to understand footage coverage, then asks the user about the footage to confirm its understanding. It confirms who the characters and locations are, then updates the library.yaml's footage_summary and user_context as it learns more about the footage. If it determines summaries are wrong or missing details, it also updates the summary markdowns.
|
|
11
|
+
|
|
12
|
+
After confirming its understanding of the footage, it works with the user to create a narrative plan markdown file.
|
|
13
|
+
|
|
14
|
+
This skill runs in the main thread and does not use a sub-agent.
|
|
15
|
+
|
|
16
|
+
## Cut Planner Process
|
|
17
|
+
|
|
18
|
+
### 1. Verify all clips have visual transcripts and summaries
|
|
19
|
+
Read `libraries/[library-name]/library.yaml`. Every clip must have `visual_transcript` and `summary` populated. If either is missing for any clip, stop and tell the user which clips still need processing — don't try to plan from incomplete footage. Then ask if they want to resume processing the library.
|
|
20
|
+
|
|
21
|
+
### 2. Read summaries
|
|
22
|
+
|
|
23
|
+
#### Sequences
|
|
24
|
+
If the user explicitly says they want something short like a short sequence (60 seconds or less), consider asking them about what they want and then grepping through summaries to find the handful of files they might need.
|
|
25
|
+
|
|
26
|
+
#### Rough Cuts
|
|
27
|
+
If the user wants a full roughcut, read every `libraries/[library-name]/summaries/summary_*.md` file. This will give you full knowledge of the library.
|
|
28
|
+
|
|
29
|
+
### 3. Confirm the footage knowledge and update incorrect summaries
|
|
30
|
+
Tell the user what you've learned about the footage, then confirm you understand the Five W's of all the footage: Who, What, When, Where, Why.
|
|
31
|
+
|
|
32
|
+
Don't tell them "Five W's" or label out Who, What, When, Where, Why, just talk with them conversationally like an assistant editor getting a grip on the footage.
|
|
33
|
+
|
|
34
|
+
If they want a full roughcut, spend more time. If they just want a sequence, be brief.
|
|
35
|
+
|
|
36
|
+
Talk with the user until you confirm you understand the footage. Update library.yaml based on the user's responses as you work through questions.
|
|
37
|
+
|
|
38
|
+
Update footage_summary (locations, characters, narrative, dialogue, clips) and user_context (preferences, goals, etc.) as you iteratively learn more about the footage.
|
|
39
|
+
|
|
40
|
+
Updating user_context and footage_summary helps future agents understand the footage and the user.
|
|
41
|
+
|
|
42
|
+
For example, if a summary mentions a generic man or woman but you learn the person is actually the user, replace man/woman with the user's name. Ask the user's name if you don't know it already.
|
|
43
|
+
|
|
44
|
+
### 4. Ask target length
|
|
45
|
+
If available, use the `AskUserQuestion` tool or similar to ask the user what length of video they want to create. Use your judgement based on the footage — options like short sequence (30–60s), medium cut (5–8 min), or longer roughcut (9+ min) make good starting points. Podcast footage will likely require a longer option.
|
|
46
|
+
|
|
47
|
+
### 5. If creating a roughcut, propose 2–3 concepts (titles only)
|
|
48
|
+
Give the user 2–3 genuinely distinct narrative concepts. Keep this round short — it's about picking a direction, not approving a full plan. For each concept, write only:
|
|
49
|
+
- **Title** — short, evocative
|
|
50
|
+
- **Concept** — 1–2 sentences explaining the angle, tone, or arc
|
|
51
|
+
|
|
52
|
+
Do **not** include beats, footage suggestions, runtime breakdowns, or format notes yet. Those come in step 6 once a direction is chosen.
|
|
53
|
+
|
|
54
|
+
Make the options genuinely distinct — different angles, tones, or arcs. End with: "Which feels right, or want me to explore something different?"
|
|
55
|
+
|
|
56
|
+
If the user just wants a short sequence, give them information about what the sequence will contain.
|
|
57
|
+
|
|
58
|
+
### 6. Flesh out the chosen concept
|
|
59
|
+
Once the user picks a direction for a full roughcut, expand it into a full plan and present that for approval. Now include:
|
|
60
|
+
- **Format** — vlog, YouTube Short, long-form, documentary, etc.
|
|
61
|
+
- **Beats** — 3–6 beats, each with editorial intent and a rough share of the runtime ("open with ~3 min of X", "montage of Y", "close on Z")
|
|
62
|
+
- **Footage suggestions per beat** — name a few videos likely to feed each beat, ie DJI_123, panasonic_1234, etc. Include rough or specific dialogue if you think it will be helpful.
|
|
63
|
+
- **Approx. duration**
|
|
64
|
+
|
|
65
|
+
Iterate on the fleshed-out plan until the user explicitly signals go.
|
|
66
|
+
|
|
67
|
+
If the user wants a short sequence, be brief, just a few sentences, including dialogue if that makes sense.
|
|
68
|
+
|
|
69
|
+
### 7. Save the plan
|
|
70
|
+
Copy `templates/plan_template.md` to `libraries/[library-name]/plans/plan_[short-name]_[YYYYMMDD_HHMMSS].md` and fill in every section. The template is the canonical structure — Concept, Format, Target Duration, Beats (with intent / approx. share / footage suggestions), Required Dialogue, Notes for the Build.
|
|
71
|
+
|
|
72
|
+
The plan is direction. The build agent confirms specific clips inside each beat.
|
|
73
|
+
|
|
74
|
+
Tell the user the plan is ready and confirm they want to move forward, then invoke the `roughcut` skill, passing the full plan path (`libraries/[library-name]/plans/plan_[short-name]_[YYYYMMDD_HHMMSS].md`) as a skill argument — `roughcut` hard-stops if it isn't given one.
|
|
@@ -59,7 +59,17 @@ class ButterCut
|
|
|
59
59
|
end
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
### 5.
|
|
62
|
+
### 5. Update Gemfile.lock
|
|
63
|
+
|
|
64
|
+
Run `bundle install` so `Gemfile.lock` reflects the new version:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bundle install
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Verify the version updated in `Gemfile.lock` before proceeding.
|
|
71
|
+
|
|
72
|
+
### 6. Gather Changelog Notes
|
|
63
73
|
|
|
64
74
|
Ask user for release notes. Prompt with:
|
|
65
75
|
- What changed in this release?
|
|
@@ -67,7 +77,7 @@ Ask user for release notes. Prompt with:
|
|
|
67
77
|
- Any bug fixes?
|
|
68
78
|
- Any breaking changes?
|
|
69
79
|
|
|
70
|
-
###
|
|
80
|
+
### 7. Update or Create CHANGELOG.md
|
|
71
81
|
|
|
72
82
|
If `CHANGELOG.md` exists, prepend new entry. Otherwise create it:
|
|
73
83
|
|
|
@@ -89,14 +99,14 @@ All notable changes to ButterCut will be documented in this file.
|
|
|
89
99
|
- Improved W
|
|
90
100
|
```
|
|
91
101
|
|
|
92
|
-
###
|
|
102
|
+
### 8. Commit Version Bump
|
|
93
103
|
|
|
94
104
|
```bash
|
|
95
|
-
git add lib/buttercut/version.rb CHANGELOG.md
|
|
105
|
+
git add lib/buttercut/version.rb Gemfile.lock CHANGELOG.md
|
|
96
106
|
git commit -m "Bump version to 0.2.0"
|
|
97
107
|
```
|
|
98
108
|
|
|
99
|
-
###
|
|
109
|
+
### 9. Create and Push Git Tag
|
|
100
110
|
|
|
101
111
|
```bash
|
|
102
112
|
git tag v0.2.0
|
|
@@ -104,7 +114,7 @@ git push origin main
|
|
|
104
114
|
git push origin v0.2.0
|
|
105
115
|
```
|
|
106
116
|
|
|
107
|
-
###
|
|
117
|
+
### 10. Build Gem
|
|
108
118
|
|
|
109
119
|
```bash
|
|
110
120
|
gem build buttercut.gemspec
|
|
@@ -112,7 +122,7 @@ gem build buttercut.gemspec
|
|
|
112
122
|
|
|
113
123
|
This creates `buttercut-0.2.0.gem` file.
|
|
114
124
|
|
|
115
|
-
###
|
|
125
|
+
### 11. Publish to RubyGems
|
|
116
126
|
|
|
117
127
|
**First time setup check:**
|
|
118
128
|
|
|
@@ -133,7 +143,7 @@ gem push buttercut-0.2.0.gem
|
|
|
133
143
|
|
|
134
144
|
This makes the gem available for `gem install buttercut` worldwide.
|
|
135
145
|
|
|
136
|
-
###
|
|
146
|
+
### 12. Create GitHub Release
|
|
137
147
|
|
|
138
148
|
**Using GitHub CLI:**
|
|
139
149
|
```bash
|
|
@@ -155,21 +165,21 @@ Guide user through manual release creation:
|
|
|
155
165
|
|
|
156
166
|
Then wait for user confirmation that release is created before proceeding to cleanup.
|
|
157
167
|
|
|
158
|
-
###
|
|
168
|
+
### 13. Cleanup
|
|
159
169
|
|
|
160
170
|
```bash
|
|
161
171
|
# Remove local gem file (it's on RubyGems and GitHub now)
|
|
162
172
|
rm buttercut-0.2.0.gem
|
|
163
173
|
```
|
|
164
174
|
|
|
165
|
-
###
|
|
175
|
+
### 14. Verify Release
|
|
166
176
|
|
|
167
177
|
Check that everything worked:
|
|
168
178
|
- RubyGems page: https://rubygems.org/gems/buttercut
|
|
169
179
|
- GitHub releases: https://github.com/andrewford/buttercut/releases
|
|
170
180
|
- Git tags: `git tag -l`
|
|
171
181
|
|
|
172
|
-
###
|
|
182
|
+
### 15. Return Success Response
|
|
173
183
|
|
|
174
184
|
Provide summary:
|
|
175
185
|
```
|
|
@@ -1,71 +1,65 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: roughcut
|
|
3
|
-
description:
|
|
3
|
+
description: Builds a roughcut YAML and exported XML (Final Cut, Premiere, or Resolve) from an approved plan markdown file produced by `cut-planner`. Spins up a sub-agent that reads the library directly, builds the cut iteratively, reviews against format conventions, then returns paths plus conversational editorial notes. If the user asks for a "roughcut", "sequence", or "scene" and no plan exists yet, run `cut-planner` first.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Skill:
|
|
6
|
+
# Skill: Roughcut Build
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Turns an approved plan into a working roughcut YAML and exported XML. The sub-agent runs async — it commits to a complete cut and returns with notes you can dialogue about.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## 1. Locate the Plan
|
|
11
|
+
A plan path **must** be passed in as a skill argument (the format produced by `cut-planner` step 7: `libraries/[library-name]/plans/plan_[short-name]_[timestamp].md`). If no plan path is passed in, stop immediately and return a message to the parent saying a plan path is required and `cut-planner` should be run first. Do not search for plans, do not pick one, do not proceed without one.
|
|
11
12
|
|
|
12
|
-
##
|
|
13
|
+
## 2. Resolve the Editor (Parent Only)
|
|
14
|
+
The sub-agent receives a final editor value:
|
|
15
|
+
1. If `library.yaml` has `editor` set, use it.
|
|
16
|
+
2. Otherwise fall back to `libraries/settings.yaml`'s `editor` and write the value back to `library.yaml`.
|
|
17
|
+
3. If neither has one, ask the user (Final Cut Pro X / Adobe Premiere Pro / DaVinci Resolve), then save the choice to both `library.yaml` and `libraries/settings.yaml`.
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
1. **Check library exists:**
|
|
17
|
-
```bash
|
|
18
|
-
ls libraries/[library-name]/library.yaml
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
2. **Verify visual transcripts:**
|
|
22
|
-
Read `libraries/[library-name]/library.yaml` and check that every video entry has both:
|
|
23
|
-
- `transcript` populated (audio transcript filename)
|
|
24
|
-
- `visual_transcript` populated (visual descriptions filename)
|
|
25
|
-
|
|
26
|
-
If any visual transcripts are missing:
|
|
27
|
-
- Inform user that transcript processing must be completed first
|
|
28
|
-
- Ask if they want Claude to finish transcript processing using the `transcribe-audio` and `analyze-video` skills
|
|
29
|
-
- Do not proceed with roughcut creation until all transcripts are complete
|
|
30
|
-
|
|
31
|
-
## Launch Roughcut Agent
|
|
32
|
-
|
|
33
|
-
Once prerequisites are verified, launch the roughcut creation agent using the Task tool:
|
|
19
|
+
## 3. Launch Build Agent
|
|
34
20
|
|
|
35
21
|
```
|
|
36
|
-
|
|
22
|
+
Agent tool with:
|
|
37
23
|
- subagent_type: "general-purpose"
|
|
38
|
-
- description: "
|
|
39
|
-
- prompt: [
|
|
24
|
+
- description: "Build roughcut YAML and XML from approved plan"
|
|
25
|
+
- prompt: [see template below]
|
|
40
26
|
```
|
|
41
27
|
|
|
42
28
|
### Agent Prompt Template
|
|
43
29
|
|
|
44
|
-
When launching the agent, provide a detailed prompt with all necessary context:
|
|
45
|
-
|
|
46
30
|
```
|
|
47
|
-
You are a video editor AI agent
|
|
31
|
+
You are a video editor AI agent for the "{library_name}" library. The plan below is approved direction — beats, intent, rough length, format. The specific clips are yours to find inside the library. Work iteratively, then review and refine before returning.
|
|
32
|
+
|
|
33
|
+
LIBRARY YAML: libraries/{library_name}/library.yaml
|
|
48
34
|
|
|
49
|
-
|
|
35
|
+
APPROVED PLAN:
|
|
36
|
+
{paste full plan markdown}
|
|
50
37
|
|
|
51
|
-
|
|
52
|
-
{paste relevant content from library.yaml - footage_summary, user_context, etc.}
|
|
38
|
+
EDITOR: {editor}
|
|
53
39
|
|
|
54
|
-
|
|
55
|
-
1. Read
|
|
56
|
-
2. Follow
|
|
57
|
-
3. Return
|
|
40
|
+
TASK:
|
|
41
|
+
1. Read `.claude/skills/roughcut/agent_prompt.md`
|
|
42
|
+
2. Follow the steps there in order (the plan is already approved — don't re-propose)
|
|
43
|
+
3. Return paths to the YAML and XML, plus your editorial notes (alternatives, judgment calls, plan deviations) in conversational prose
|
|
44
|
+
```
|
|
58
45
|
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
- Exported XML file for user's chosen video editor
|
|
62
|
-
- Backup created via backup-library skill
|
|
46
|
+
## 4. Context Contract
|
|
47
|
+
This sub-agent reads `library.yaml` directly — it needs the full inventory plus `footage_summary` and `user_context`. This is a deliberate carve-out from the parallel-skill contract: `roughcut` runs as a single agent (no race risk), and editorial work needs broader library context than inline-passing comfortably supports.
|
|
63
48
|
|
|
64
|
-
|
|
49
|
+
## 5. Copy XML to Desktop (if enabled)
|
|
50
|
+
Check `libraries/settings.yaml` for `save_to_desktop_after_export`:
|
|
51
|
+
1. If the key is `true`, copy the exported XML to `~/Desktop/` so it's easy to grab and import into the editor.
|
|
52
|
+
2. If the key is `false`, skip this step.
|
|
53
|
+
3. If the key is missing, ask the user whether to drop a copy of every export on the Desktop, save their answer (`true`/`false`) to `libraries/settings.yaml`, then act on it.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cp [library xml path] ~/Desktop/
|
|
65
57
|
```
|
|
66
58
|
|
|
67
|
-
|
|
59
|
+
The library copy stays as the canonical artifact; the desktop copy is a convenience drop.
|
|
60
|
+
|
|
61
|
+
## 6. Backup the Library
|
|
62
|
+
Run the `backup-library` skill. This snapshots the library (yaml, transcripts, summaries, plans, roughcuts) so progress can be restored if needed.
|
|
68
63
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
2. Confirm the rough cut is ready to import into their video editor
|
|
64
|
+
## 7. Report Results
|
|
65
|
+
Surface the agent's return message to the user — the YAML path, the library XML path, the desktop XML path (only if step 5 actually copied one), plus the editorial notes. The notes are the conversational hook for what comes next; small fixes you can do directly in the YAML, larger restructures relaunch this skill with a revised plan.
|