takumi-cli 1.2.9 → 1.3.1
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.
- package/README.md +14 -5
- package/commands/convert.sh +132 -22
- package/mcp/server.js +8 -6
- package/package.json +1 -1
- package/takumi.sh +4 -4
- package/test.sh +32 -0
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ takumi setup
|
|
|
45
45
|
|
|
46
46
|
| Command | Description |
|
|
47
47
|
|---------|-------------|
|
|
48
|
-
| `takumi convert <path> [
|
|
48
|
+
| `takumi convert <path> [--profile name]` | Convert to optimized MP4 |
|
|
49
49
|
| `takumi trim <video> <start> <end>` | Cut a clip between timestamps |
|
|
50
50
|
| `takumi cc <path> [lang] [model] [format]` | Generate captions using Whisper |
|
|
51
51
|
| `takumi thumb <path> [timestamp]` | Extract a poster image (JPG) |
|
|
@@ -60,11 +60,20 @@ All commands accept a single file or a folder (processes all videos recursively)
|
|
|
60
60
|
### Examples
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
# Convert
|
|
64
|
-
takumi convert
|
|
63
|
+
# Convert for web (default, plays everywhere)
|
|
64
|
+
takumi convert video.mp4
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
takumi convert ./videos
|
|
66
|
+
# Convert for FireTV
|
|
67
|
+
takumi convert ./videos --profile firetv
|
|
68
|
+
|
|
69
|
+
# Compressed for email or Slack
|
|
70
|
+
takumi convert video.mp4 --profile small
|
|
71
|
+
|
|
72
|
+
# High quality for portfolio
|
|
73
|
+
takumi convert video.mp4 --profile hq
|
|
74
|
+
|
|
75
|
+
# Custom: override quality and max height
|
|
76
|
+
takumi convert ./videos --crf 21 --max 720
|
|
68
77
|
|
|
69
78
|
# Trim a 75-second clip
|
|
70
79
|
takumi trim video.mp4 00:01:30 00:02:45
|
package/commands/convert.sh
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# ── Convert:
|
|
2
|
+
# ── Convert: Video conversion with profile presets ──────────────────────────
|
|
3
|
+
|
|
4
|
+
# ── Profiles ────────────────────────────────────────────────────────────────
|
|
5
|
+
#
|
|
6
|
+
# web (default) — Web-optimized MP4. Plays everywhere.
|
|
7
|
+
# firetv — FireTV-optimized H.264 MP4. High Profile, mod16 dimensions.
|
|
8
|
+
# small — Compressed for email, Slack, low bandwidth. 720p, higher CRF.
|
|
9
|
+
# hq — High quality for portfolio or client delivery. Low CRF.
|
|
3
10
|
|
|
4
11
|
SIZES_16_9=("1920:1080" "1280:720" "1024:576" "768:432" "512:288" "256:144")
|
|
5
12
|
SIZES_4_3=("640:480" "576:432" "512:384" "448:336" "384:288" "320:240" "256:192" "192:144" "128:96")
|
|
@@ -26,12 +33,66 @@ get_best_mod16() {
|
|
|
26
33
|
echo "${SIZES[-1]}"
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
apply_profile() {
|
|
37
|
+
local PROFILE="$1"
|
|
38
|
+
case "$PROFILE" in
|
|
39
|
+
web)
|
|
40
|
+
PROFILE_CRF=23
|
|
41
|
+
PROFILE_MAX_H=1080
|
|
42
|
+
PROFILE_LEVEL=""
|
|
43
|
+
PROFILE_PROFILE="main"
|
|
44
|
+
PROFILE_SUFFIX="_web"
|
|
45
|
+
PROFILE_MOD16=false
|
|
46
|
+
PROFILE_AUDIO_BITRATE="128k"
|
|
47
|
+
;;
|
|
48
|
+
firetv)
|
|
49
|
+
PROFILE_CRF=23
|
|
50
|
+
PROFILE_MAX_H=1080
|
|
51
|
+
PROFILE_LEVEL="4.0"
|
|
52
|
+
PROFILE_PROFILE="high"
|
|
53
|
+
PROFILE_SUFFIX="_firetv"
|
|
54
|
+
PROFILE_MOD16=true
|
|
55
|
+
PROFILE_AUDIO_BITRATE="128k"
|
|
56
|
+
;;
|
|
57
|
+
small)
|
|
58
|
+
PROFILE_CRF=28
|
|
59
|
+
PROFILE_MAX_H=720
|
|
60
|
+
PROFILE_LEVEL=""
|
|
61
|
+
PROFILE_PROFILE="main"
|
|
62
|
+
PROFILE_SUFFIX="_small"
|
|
63
|
+
PROFILE_MOD16=false
|
|
64
|
+
PROFILE_AUDIO_BITRATE="96k"
|
|
65
|
+
;;
|
|
66
|
+
hq)
|
|
67
|
+
PROFILE_CRF=18
|
|
68
|
+
PROFILE_MAX_H=2160
|
|
69
|
+
PROFILE_LEVEL=""
|
|
70
|
+
PROFILE_PROFILE="high"
|
|
71
|
+
PROFILE_SUFFIX="_hq"
|
|
72
|
+
PROFILE_MOD16=false
|
|
73
|
+
PROFILE_AUDIO_BITRATE="192k"
|
|
74
|
+
;;
|
|
75
|
+
*)
|
|
76
|
+
echo "Unknown profile: $PROFILE"
|
|
77
|
+
echo "Available: web, firetv, small, hq"
|
|
78
|
+
return 1
|
|
79
|
+
;;
|
|
80
|
+
esac
|
|
81
|
+
}
|
|
82
|
+
|
|
29
83
|
convert_file() {
|
|
30
|
-
local VIDEO="$1" CRF="$2" MAX_H="$3"
|
|
84
|
+
local VIDEO="$1" CRF="$2" MAX_H="$3" PROFILE="$4"
|
|
85
|
+
|
|
86
|
+
apply_profile "$PROFILE" || return 1
|
|
87
|
+
|
|
88
|
+
# Allow explicit overrides
|
|
89
|
+
CRF="${CRF:-$PROFILE_CRF}"
|
|
90
|
+
MAX_H="${MAX_H:-$PROFILE_MAX_H}"
|
|
91
|
+
|
|
31
92
|
local DIR BASENAME OUTPUT
|
|
32
93
|
DIR="$(dirname "$VIDEO")"
|
|
33
94
|
BASENAME="$(basename "${VIDEO%.*}")"
|
|
34
|
-
OUTPUT="${DIR}/${BASENAME}${
|
|
95
|
+
OUTPUT="${DIR}/${BASENAME}${PROFILE_SUFFIX}.mp4"
|
|
35
96
|
|
|
36
97
|
if [ -f "$OUTPUT" ]; then
|
|
37
98
|
echo "⏭️ Skipping (output exists): $(basename "$VIDEO")"
|
|
@@ -40,7 +101,7 @@ convert_file() {
|
|
|
40
101
|
|
|
41
102
|
echo ""
|
|
42
103
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
43
|
-
echo "🎬 Processing: $(basename "$VIDEO")"
|
|
104
|
+
echo "🎬 Processing: $(basename "$VIDEO") [${PROFILE}]"
|
|
44
105
|
|
|
45
106
|
local SRC_W SRC_H
|
|
46
107
|
SRC_W=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=p=0 "$VIDEO" 2>/dev/null)
|
|
@@ -51,18 +112,29 @@ convert_file() {
|
|
|
51
112
|
return 1
|
|
52
113
|
fi
|
|
53
114
|
|
|
54
|
-
local
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
115
|
+
local SCALE_FILTER
|
|
116
|
+
if [ "$PROFILE_MOD16" = true ]; then
|
|
117
|
+
local TARGET TGT_W TGT_H
|
|
118
|
+
TARGET=$(get_best_mod16 "$SRC_W" "$SRC_H" "$MAX_H")
|
|
119
|
+
TGT_W="${TARGET%%:*}"
|
|
120
|
+
TGT_H="${TARGET##*:}"
|
|
121
|
+
SCALE_FILTER="scale=${TGT_W}:${TGT_H}"
|
|
122
|
+
echo " ${SRC_W}x${SRC_H} → ${TGT_W}x${TGT_H} (mod16) | CRF: $CRF"
|
|
123
|
+
else
|
|
124
|
+
SCALE_FILTER="scale=-2:'min(${MAX_H},ih)'"
|
|
125
|
+
echo " ${SRC_W}x${SRC_H} → max ${MAX_H}p | CRF: $CRF"
|
|
126
|
+
fi
|
|
58
127
|
|
|
59
|
-
|
|
128
|
+
local LEVEL_FLAGS=""
|
|
129
|
+
if [ -n "$PROFILE_LEVEL" ]; then
|
|
130
|
+
LEVEL_FLAGS="-level $PROFILE_LEVEL"
|
|
131
|
+
fi
|
|
60
132
|
|
|
61
133
|
if ffmpeg -i "$VIDEO" \
|
|
62
134
|
-c:v libx264 -preset slow -crf "$CRF" \
|
|
63
|
-
-profile:v
|
|
64
|
-
-vf "
|
|
65
|
-
-c:a aac -b:a
|
|
135
|
+
-profile:v "$PROFILE_PROFILE" $LEVEL_FLAGS -pix_fmt yuv420p \
|
|
136
|
+
-vf "$SCALE_FILTER" \
|
|
137
|
+
-c:a aac -b:a "$PROFILE_AUDIO_BITRATE" -ac 2 -ar 44100 \
|
|
66
138
|
-movflags +faststart \
|
|
67
139
|
-y -loglevel warning -stats \
|
|
68
140
|
"$OUTPUT" 2>&1; then
|
|
@@ -78,25 +150,63 @@ convert_file() {
|
|
|
78
150
|
}
|
|
79
151
|
|
|
80
152
|
cmd_convert() {
|
|
81
|
-
local INPUT="
|
|
82
|
-
local
|
|
83
|
-
local
|
|
153
|
+
local INPUT=""
|
|
154
|
+
local PROFILE="web"
|
|
155
|
+
local CRF=""
|
|
156
|
+
local MAX_H=""
|
|
157
|
+
|
|
158
|
+
# Parse arguments
|
|
159
|
+
while [ $# -gt 0 ]; do
|
|
160
|
+
case "$1" in
|
|
161
|
+
--profile) PROFILE="$2"; shift 2 ;;
|
|
162
|
+
--crf) CRF="$2"; shift 2 ;;
|
|
163
|
+
--max) MAX_H="$2"; shift 2 ;;
|
|
164
|
+
--help|-h|help)
|
|
165
|
+
echo "Usage: takumi convert <file_or_folder> [options]"
|
|
166
|
+
echo ""
|
|
167
|
+
echo "Options:"
|
|
168
|
+
echo " --profile <name> Conversion profile (default: web)"
|
|
169
|
+
echo " --crf <value> Quality override (18-28, lower = better)"
|
|
170
|
+
echo " --max <height> Max height in pixels"
|
|
171
|
+
echo ""
|
|
172
|
+
echo "Profiles:"
|
|
173
|
+
echo " web Web-optimized MP4. Plays everywhere. (default)"
|
|
174
|
+
echo " firetv FireTV-optimized. High Profile, mod16 dimensions."
|
|
175
|
+
echo " small Compressed for email/Slack. 720p, smaller file."
|
|
176
|
+
echo " hq High quality for portfolio or client delivery."
|
|
177
|
+
echo ""
|
|
178
|
+
echo "Examples:"
|
|
179
|
+
echo " takumi convert video.mp4"
|
|
180
|
+
echo " takumi convert ./videos --profile firetv"
|
|
181
|
+
echo " takumi convert ./videos --profile small"
|
|
182
|
+
echo " takumi convert video.mp4 --crf 21 --max 720"
|
|
183
|
+
return 0
|
|
184
|
+
;;
|
|
185
|
+
*)
|
|
186
|
+
if [ -z "$INPUT" ]; then
|
|
187
|
+
INPUT="$1"
|
|
188
|
+
fi
|
|
189
|
+
shift
|
|
190
|
+
;;
|
|
191
|
+
esac
|
|
192
|
+
done
|
|
84
193
|
|
|
85
194
|
if [ -z "$INPUT" ]; then
|
|
86
|
-
echo "Usage:
|
|
87
|
-
echo " crf: 18-28 (default: 23, lower = better quality)"
|
|
88
|
-
echo " max_height: max output height (default: 1080)"
|
|
195
|
+
echo "Usage: takumi convert <file_or_folder> [--profile web|firetv|small|hq] [--crf N] [--max N]"
|
|
89
196
|
return 1
|
|
90
197
|
fi
|
|
91
198
|
|
|
199
|
+
# Validate profile
|
|
200
|
+
apply_profile "$PROFILE" || return 1
|
|
201
|
+
|
|
92
202
|
if [ -f "$INPUT" ]; then
|
|
93
|
-
convert_file "$INPUT" "$CRF" "$MAX_H"
|
|
203
|
+
convert_file "$INPUT" "$CRF" "$MAX_H" "$PROFILE"
|
|
94
204
|
elif [ -d "$INPUT" ]; then
|
|
95
205
|
echo "📂 Scanning: $INPUT"
|
|
96
|
-
echo "⚙️ CRF: $CRF | Max: ${MAX_H}p
|
|
206
|
+
echo "⚙️ Profile: $PROFILE | CRF: ${CRF:-$PROFILE_CRF} | Max: ${MAX_H:-$PROFILE_MAX_H}p"
|
|
97
207
|
while IFS= read -r file; do
|
|
98
|
-
convert_file "$file" "$CRF" "$MAX_H"
|
|
99
|
-
done < <(find_videos "$INPUT" "$
|
|
208
|
+
convert_file "$file" "$CRF" "$MAX_H" "$PROFILE"
|
|
209
|
+
done < <(find_videos "$INPUT" "$PROFILE_SUFFIX")
|
|
100
210
|
echo ""
|
|
101
211
|
echo "🏁 Done!"
|
|
102
212
|
else
|
package/mcp/server.js
CHANGED
|
@@ -74,16 +74,18 @@ async function main() {
|
|
|
74
74
|
|
|
75
75
|
server.tool(
|
|
76
76
|
"takumi_convert",
|
|
77
|
-
"Convert videos to
|
|
77
|
+
"Convert videos to optimized MP4. Choose a profile based on the user's intent:\n- 'web' (default): website, CMS, landing page, blog, general use, online publishing, embedding in a webpage\n- 'firetv': FireTV app, Amazon Fire tablet, streaming device, TV app assets, broadcast, set-top box\n- 'small': email attachment, Slack, Teams, Discord, SMS, quick preview, social sharing, file size matters, low bandwidth, mobile messaging\n- 'hq': portfolio, client deliverable, showreel, demo reel, presentation, archival, best possible quality",
|
|
78
78
|
{
|
|
79
79
|
path: z.string().describe("Path to video file or folder"),
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
profile: z.enum(["web", "firetv", "small", "hq"]).optional().describe("Conversion profile. 'web': website/CMS/general use/embedding. 'firetv': FireTV/streaming device/TV app. 'small': email/Slack/Teams/Discord/messaging/quick share/preview/low bandwidth. 'hq': portfolio/client delivery/showreel/presentation/archival. Default: web"),
|
|
81
|
+
crf: z.number().optional().describe("Quality override (18-28, lower = better)"),
|
|
82
|
+
max_height: z.number().optional().describe("Max height override in pixels"),
|
|
82
83
|
},
|
|
83
|
-
async ({ path: p, crf, max_height }) => {
|
|
84
|
+
async ({ path: p, profile, crf, max_height }) => {
|
|
84
85
|
const args = ["convert", p];
|
|
85
|
-
if (
|
|
86
|
-
if (
|
|
86
|
+
if (profile) args.push("--profile", profile);
|
|
87
|
+
if (crf !== undefined) args.push("--crf", String(crf));
|
|
88
|
+
if (max_height !== undefined) args.push("--max", String(max_height));
|
|
87
89
|
return run(args);
|
|
88
90
|
}
|
|
89
91
|
);
|
package/package.json
CHANGED
package/takumi.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# ============================================================================
|
|
5
5
|
# Commands:
|
|
6
6
|
# cc Generate closed captions (SRT/VTT) using Whisper
|
|
7
|
-
# convert Convert videos to
|
|
7
|
+
# convert Convert videos to optimized MP4 (web, firetv, small, hq)
|
|
8
8
|
# trim Cut a clip between timestamps
|
|
9
9
|
# thumb Extract poster image from video
|
|
10
10
|
# info Show video metadata
|
|
@@ -27,7 +27,7 @@ set -euo pipefail
|
|
|
27
27
|
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
|
|
28
28
|
|
|
29
29
|
VIDEO_EXTENSIONS="mp4|mov|avi|mkv|webm|m4v|flv|wmv|mpg|mpeg|ts"
|
|
30
|
-
CONVERT_SUFFIX="
|
|
30
|
+
CONVERT_SUFFIX="_web"
|
|
31
31
|
|
|
32
32
|
# ── Helpers ──────────────────────────────────────────────────────────────────
|
|
33
33
|
|
|
@@ -56,7 +56,7 @@ show_help() {
|
|
|
56
56
|
echo " update Update to latest version"
|
|
57
57
|
echo " mcp-config Print MCP server config"
|
|
58
58
|
echo " cc <path> [lang] [model] [format] Generate captions from video"
|
|
59
|
-
echo " convert <path> [
|
|
59
|
+
echo " convert <path> [--profile <name>] Convert to optimized MP4"
|
|
60
60
|
echo " trim <video> <start> <end> Cut a clip between timestamps"
|
|
61
61
|
echo " thumb <path> [timestamp] Extract poster image (JPG)"
|
|
62
62
|
echo " info <path> Show video metadata"
|
|
@@ -68,7 +68,7 @@ show_help() {
|
|
|
68
68
|
echo "Examples:"
|
|
69
69
|
echo " ./takumi.sh setup"
|
|
70
70
|
echo " ./takumi.sh cc ./videos ja"
|
|
71
|
-
echo " ./takumi.sh convert ./videos
|
|
71
|
+
echo " ./takumi.sh convert ./videos --profile firetv"
|
|
72
72
|
echo " ./takumi.sh trim video.mp4 00:01:30 00:02:45"
|
|
73
73
|
echo " ./takumi.sh thumb video.mp4 00:00:15"
|
|
74
74
|
echo " ./takumi.sh info ./videos"
|
package/test.sh
CHANGED
|
@@ -131,6 +131,38 @@ assert "640x480 capped 240 → 320:240" "320:240" "$OUT"
|
|
|
131
131
|
OUT=$(get_best_mod16 3840 2160 1080)
|
|
132
132
|
assert "4K capped 1080 → 1920:1080" "1920:1080" "$OUT"
|
|
133
133
|
|
|
134
|
+
# ── Convert profiles ───────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
echo ""
|
|
137
|
+
echo "🔧 Convert profiles"
|
|
138
|
+
|
|
139
|
+
source commands/convert.sh
|
|
140
|
+
|
|
141
|
+
apply_profile web
|
|
142
|
+
assert "web profile CRF" "23" "$PROFILE_CRF"
|
|
143
|
+
assert "web profile suffix" "_web" "$PROFILE_SUFFIX"
|
|
144
|
+
assert "web profile mod16 off" "false" "$PROFILE_MOD16"
|
|
145
|
+
|
|
146
|
+
apply_profile firetv
|
|
147
|
+
assert "firetv profile CRF" "23" "$PROFILE_CRF"
|
|
148
|
+
assert "firetv profile suffix" "_firetv" "$PROFILE_SUFFIX"
|
|
149
|
+
assert "firetv profile mod16 on" "true" "$PROFILE_MOD16"
|
|
150
|
+
|
|
151
|
+
apply_profile small
|
|
152
|
+
assert "small profile CRF" "28" "$PROFILE_CRF"
|
|
153
|
+
assert "small profile max height" "720" "$PROFILE_MAX_H"
|
|
154
|
+
assert "small profile suffix" "_small" "$PROFILE_SUFFIX"
|
|
155
|
+
|
|
156
|
+
apply_profile hq
|
|
157
|
+
assert "hq profile CRF" "18" "$PROFILE_CRF"
|
|
158
|
+
assert "hq profile suffix" "_hq" "$PROFILE_SUFFIX"
|
|
159
|
+
|
|
160
|
+
OUT=$(apply_profile badprofile 2>&1)
|
|
161
|
+
assert "bad profile shows error" "Unknown profile" "$OUT"
|
|
162
|
+
|
|
163
|
+
OUT=$(run convert video.mp4 --help)
|
|
164
|
+
assert "convert --help shows profiles" "Profiles:" "$OUT"
|
|
165
|
+
|
|
134
166
|
# ── Paths with spaces ──────────────────────────────────────────────────────
|
|
135
167
|
|
|
136
168
|
echo ""
|