STEP 1下のボタンでコピー
STEP 2Claude Code に貼る
STEP 3動画を入れて実行
Claude Code に貼り付けるテキスト
あなたはこれから、私のパソコンに「縦型ショート動画メーカー Lv.1」をセットアップします。
以下を上から順に、あなた自身が実行してください。私に作業を指示しないでください。
## 0. 前提の確認
まず次を確認し、足りないものは自動で導入してください(管理者権限が必要な操作だけ、私に確認してください)。
- `py --version` または `python --version` が通ること(Python 3.10以上)
- `ffmpeg -version` が通ること
- 通らなければ Windows は `winget install Gyan.FFmpeg`、macOS は `brew install ffmpeg` で導入
- `py -c "import faster_whisper"` が通ること
- 通らなければ `py -m pip install faster-whisper` で導入
## 1. 作業フォルダを作る
デスクトップに `shortvideo` フォルダを作り、その中に `input` と `output` を作ってください。
## 2. 次のスクリプトを `shortvideo/make_short.py` として保存する
```python
"""縦動画 -> 字幕焼き込み+前後カット済みショート.
usage: py make_short.py input/xxx.mp4 output/xxx.mp4
"""
from __future__ import annotations
import json, re, subprocess, sys, tempfile
from pathlib import Path
from faster_whisper import WhisperModel
W, H = 1080, 1920
MAX_CHARS = 10 # 1行の最大文字数
ROWS_Y = [1300, 1520, 1740] # 字幕の縦位置(3行まで積む)
MIN_SCREEN = 1.1 # 1画面の最低表示秒(これ未満は読めない)
HEAD_PAD, TAIL_PAD = 0.15, 0.35
PARTICLES = ("は","が","を","に","で","と","も","へ","の","から","まで")
SAFE = ("もう","もし","もの","という","ところ","とき","では","でも","には","はい")
STYLE = """[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Sub, Yu Gothic UI Semibold, 190, &H00FFFFFF, &H000000FF, &H00102040, &H1A000000, 1, 0, 0, 0, 100, 100, 4, 0, 1, 9, 4, 5, 40, 40, 0, 1
"""
LINE = ("Dialogue: 0,{s},{e},Sub,,0,0,0,,{{\\an5\\pos(540,{y})\\fad(90,60)"
"\\fscx130\\fscy130\\t(0,200,\\fscx100\\fscy100)}}{t}")
def ts(x: float) -> str:
x = max(x, 0.0)
return f"{int(x//3600)}:{int((x%3600)//60):02d}:{x%60:05.2f}"
def probe_wh(src: Path):
"""回転メタデータ込みの表示サイズ。iPhone縦動画は 1920x1080+rotation で入る。"""
st = json.loads(subprocess.run(
["ffprobe","-v","error","-select_streams","v:0","-show_streams","-of","json",str(src)],
capture_output=True, text=True).stdout)["streams"][0]
w, h = int(st["width"]), int(st["height"])
rot = 0
for sd in st.get("side_data_list", []):
rot = int(sd.get("rotation", rot))
return (h, w) if abs(rot) % 180 == 90 else (w, h)
def transcribe(src: Path):
wav = Path(tempfile.gettempdir()) / "_ms.wav"
subprocess.run(["ffmpeg","-y","-v","error","-i",str(src),"-vn","-ac","1",
"-ar","16000","-c:a","pcm_s16le",str(wav)], check=True)
model = WhisperModel("small", device="cpu", compute_type="int8")
segs, _ = model.transcribe(str(wav), language="ja", word_timestamps=True)
words = [{"s": round(w.start,2), "e": round(w.end,2), "w": w.word.strip()}
for sg in segs for w in (sg.words or []) if w.word.strip()]
wav.unlink(missing_ok=True)
return words
def group(words):
"""語を「読める単位」にまとめる。助詞で行を始めない。"""
rows, cur, st = [], "", None
for i, w in enumerate(words):
gap = w["s"] - words[i-1]["e"] if i else 0
nxt = cur + w["w"]
if cur and (gap >= 0.35 or len(nxt) > MAX_CHARS):
rows.append({"t": cur, "s": st, "e": words[i-1]["e"]})
cur, st = "", None
nxt = w["w"]
if st is None:
st = w["s"]
cur = nxt
if cur:
rows.append({"t": cur, "s": st, "e": words[-1]["e"]})
# 行頭が助詞なら前の行にくっつける(「AI」「は聞く相手」を防ぐ)
out = []
for r in rows:
if out and r["t"].startswith(PARTICLES) and not r["t"].startswith(SAFE) \
and len(out[-1]["t"]) + len(r["t"]) <= MAX_CHARS + 4:
out[-1]["t"] += r["t"]
out[-1]["e"] = r["e"]
else:
out.append(r)
return out
def silences(src: Path, a: float, b: float):
log = subprocess.run(["ffmpeg","-hide_banner","-ss",str(a),"-to",str(b),"-i",str(src),
"-af","silencedetect=n=-35dB:d=0.35","-f","null","-"],
capture_output=True, text=True, encoding="utf-8",
errors="replace").stderr
ss = [float(x)+a for x in re.findall(r"silence_start:\s*([\d.]+)", log)]
ee = [float(x)+a for x in re.findall(r"silence_end:\s*([\d.]+)", log)]
return list(zip(ss, ee))
def main():
src, out = Path(sys.argv[1]), Path(sys.argv[2])
out.parent.mkdir(parents=True, exist_ok=True)
print("1/4 文字起こし中(初回はモデルの取得で数分かかります)...")
words = transcribe(src)
if not words:
sys.exit("音声が認識できませんでした。喋っている動画を入れてください。")
rows = group(words)
print("2/4 前後と間をカット中...")
head, tail = max(words[0]["s"]-HEAD_PAD, 0), words[-1]["e"]+TAIL_PAD
keeps, cur = [], head
for a, b in silences(src, head, tail):
if a <= head or b >= tail or b-a < 0.5:
continue
keeps.append((cur, a+0.08)); cur = b-0.08
keeps.append((cur, tail))
keeps = [(a,b) for a,b in keeps if b-a > 0.05]
offs, acc = [], 0.0
for a,b in keeps:
offs.append(acc); acc += b-a
def conv(t):
for (a,b),o in zip(keeps,offs):
if t < a: return o
if t <= b: return o+(t-a)
return acc
print("3/4 字幕を作成中...")
for r in rows:
r["s"], r["e"] = conv(r["s"]), conv(r["e"])
ass = out.with_suffix(".ass")
body = [f"[Script Info]\nScriptType: v4.00+\nPlayResX: {W}\nPlayResY: {H}\n"
f"ScaledBorderAndShadow: yes\nWrapStyle: 2\n\n{STYLE}\n[Events]\n"
"Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"]
for i in range(0, len(rows), len(ROWS_Y)):
scr = rows[i:i+len(ROWS_Y)]
nxt = rows[i+len(ROWS_Y)]["s"] if i+len(ROWS_Y) < len(rows) else scr[-1]["e"]+0.8
end = max(nxt, scr[0]["s"]+MIN_SCREEN)
for k, r in enumerate(scr):
body.append(LINE.format(s=ts(r["s"]), e=ts(end), y=ROWS_Y[k], t=r["t"]))
ass.write_text("\n".join(body)+"\n", encoding="utf-8")
print("4/4 書き出し中...")
sw, sh = probe_wh(src)
if sw/sh > 1.2: # 横長は切らずに上へ置く(切ると文字が読めなくなる)
vh = int(W*sh/sw)
vf = f"scale={W}:{vh},pad={W}:{H}:0:{int(H*0.16)}:black,setsar=1"
else:
vf = f"scale={W}:{H}:force_original_aspect_ratio=increase,crop={W}:{H},setsar=1"
sel = "+".join(f"between(t,{a:.3f},{b:.3f})" for a,b in keeps)
subprocess.run(["ffmpeg","-y","-v","error","-i",str(src.resolve()),
"-map","0:v:0","-map","0:a:0",
"-vf", f"select='{sel}',setpts=N/FRAME_RATE/TB,{vf},ass={ass.name}",
"-af", f"aselect='{sel}',asetpts=N/SR/TB",
"-c:v","libx264","-preset","medium","-crf","20","-pix_fmt","yuv420p",
"-c:a","aac","-b:a","192k", out.name], check=True, cwd=out.parent)
print(f"\n完成: {out} ({acc:.1f}秒 / 元より{(words[-1]['e']-words[0]['s'])-acc:+.1f}秒)")
if __name__ == "__main__":
main()
```
## 3. 動作確認をする
`shortvideo/input` に動画が入っていれば1本処理してください。
入っていなければ「input フォルダに縦向きの動画を入れてください」と私に伝えてください。
## 4. 使い方を私に説明する
最後に、次の3点だけを日本語で簡潔に伝えてください。
1. 動画を `input` に入れる
2. 実行するコマンド1行
3. 完成品が `output` に出ること
長い解説は不要です。3行で終わらせてください。
※ Claude Code デスクトップ版で、新しいチャットに貼り付けてください。あとは Claude が環境構築から使い方の説明までやります。
これで何ができるか
- 縦動画を入れると、字幕が焼き込まれたショート動画が出ます
- 録画ボタンの前後の余白と、話の長い間を自動でカットします
- 字幕は意味の切れ目で改行します(助詞が行頭に来ません)
- 横向きの動画は、文字が切れないよう上部に配置します
- 処理はすべて自分のパソコン内で完結します(動画はどこにも送信されません)
うまく撮るための5つ
- スマホを縦のまま持って撮る
- 顔が画面の3分の1を占めるまで近づく
- 録画ボタンを押したら3秒待ってから喋り出す
- 1本で1つの話だけをする
- 喋り終わったら2秒待ってから止める
Lv.2 でできること
長い録画から、伸びる区間だけを自動で抜き出す(1本の講義から複数本)/
冒頭フックを14の型から選んでタイトル生成/強調テロップの自動配置/
話の間を詰めるジャンプカット/固有名詞の自動修正/出力の自動検証。
10分の録画に対して、AIは16本を提案してきました。私は3本に絞りました。
何を見て13本を捨てたのか——その判断基準とスキル一式は、OUTSTANDING®経営塾でお渡ししています。