マニュアル

校正・字幕改行・SNS配信の全ルールをここに集約(リポ manual/ が正本)。Claude Code エンジンはこの単一ソースを参照する。

ZFILMS ショート制作ワークフロー
目的

素材、文字起こし、字幕、カット、アングル、音声、NLE納品を別々に作らない。1つの正本から派生物を作り、各段階でhash・FPS・フレーム範囲・未解決項目を記録する。

このワークフローの証拠分類は [docs/KNOWLEDGE_CLEANUP_20260809.md](../../docs/KNOWLEDGE_CLEANUP_20260809.md) が優先する。旧成功例は教師参照、失敗例は回帰証跡、未検証値は停止条件であり、どれも自動的に本番正本へ昇格しない。

正本からのファネル
原始JSON / 原始字幕
  ↓ 内容の校正
校正JSON / 校正字幕
  ↓ 句読点・表記・話者・時間軸の確定
マスターJSON / マスター字幕(本編)
  ↓ 本編用の編集判断
縮約字幕(本編)
  ↓ 構成案の cutFrames による時間軸変換
縮約JSON / 縮約字幕(ショート各回)
  ↓ メディアRegistry・アングル・音声レーンを解決
TimelineIR / Resolve用cut plan
  ↓ コードで新規DRTを生成(教師構造を限定置換)
DRT → Resolveへインポート → DRT/DRP/API読戻し → close/reopen → スクショQC

派生SRTをマスターへ逆流させない。元ファイルが見つからない層は unknown、意味が確定していない層は candidate とし、推測で known に昇格させない。

教師DRTの使い方

教師DRTと候補DRTは、まず次の二つを分けて照合する。

  • 素材同一性: Media Storageの完全パス、Asset Registry、実測FPS、duration、必要ならhashが同じか。
  • 編集グラフ同一性: track topology、record/source range、MediaRef、multicam selector、Transform/Crop、Composition、字幕形式が同じか。

同じ収録素材を参照していても、編集グラフが違えば同じ画面にはならない。教師DRTのV2/V3を「Angle 1/2」と一般化せず、教師内で観測したangle selection、話者フォーカス、幾何プロファイルを別々に保存する。

multicamは名前、FPS、解像度、VirtualAudioTracksBAが一致しても同一とはしない。DbId、UniqueMediaPoolItemId、FieldsBlob、nested Sequence、SeqContainerのhashが異なる再生成multicamでは、CurrentSelectorIdxを移植しただけで教師のAlt+2選択へ戻るとは限らない。正本候補は、教師multicamコンテナと参照Sequenceを構造ごと保持する経路、またはDRTから読んだAngle→raw camera file / offsetを使ってraw素材を実体化する経路の二つに限定する。

Resolveの生成経路は、教師DRTを構造テンプレートとして扱い、BG / Angle / Slide / Overlay、native subtitle、Text+ / Fusionの実在トポロジーを保持したうえで、master cutのrecord/source rangeと本文だけを限定置換する。AppendToTimeline、CreateEmptyTimeline、SetSetting、SetProperty、ImportFusionCompを使って本番構造を組み直す経路は正本にしない。これらは既存成果物の読戻し・原因切り分け用の実験に限る。

DRT直接編集は、既知のXML scalar、または教師と候補の一変数差分で一意に確認した領域だけをコードで新規DRTへ出す。EffectFiltersBA、CompositionBA、MediaTimemapBA、未知ID、字幕payloadは、サイズ・圧縮フラグ・内部長さを含む構造が解読済みでない限り書き換えない。Text+やFusionをPNGへ焼き付けて代替しない。Resolve API/GUIはそのDRTをインポートし、読戻し、スクリーンショット・レンダー・差分を取得するためだけに使う。APIの戻り値やSetPropertyの読戻し成功だけで画面一致とは判定しない。

Resolve書き込み経路の固定ルール
操作正本工程で許可目的
Python/TypeScriptで新規DRTを生成○教師構造、既知の範囲、cutFrames、文言の限定置換
元DRT/DRPの上書き×教師データを保護する
AppendToTimeline等で本番タイムラインを組む×multicam selector・座標系・native generatorが落ちるため
Resolve APIでDRTをインポート○実機で解釈できるかの検証
Resolve APIでreadback、DRT/DRP出力、スクショ、レンダー○独立証跡の取得
Resolve APIでSetSetting/SetProperty/AppendToTimeline×候補構造の本番書き込みに使わない

現在のbuild_short_flat.py、bake_titles.py、apply_fusion.pyは、既存候補の監査・比較用の旧経路であり、この固定ルールの本番生成器ではない。コード側のDRT生成器で作った新規候補だけをResolveへ渡す。

各層の責任
層役割正本にしてよいか
原始JSON / 原始字幕ASR・収録時の入力。欠落や誤認を含むしない
校正JSON / 校正字幕表記、分割、誤認を直した編集入力マスター化前の候補
マスターJSON / マスター字幕本編の内容、時間、話者の正本する
縮約字幕(本編)マスターから編集候補を抽出したものしない
縮約JSONショートごとのタイトル、hook、cutFrames、構成メモ承認後のみ
縮約字幕(ショート各回)cutFramesを連結した後の記録時間に再配置したSRT縮約JSONから再生成する
TimelineIR / cut planメディア、レーン、幾何、FPS、音声、字幕を束ねたNLE入力hash付き候補
Resolve出力NLE実体。読戻し証跡が揃って初めて納品物読戻し後のみ
ショート構成のルール
  • 構成案はマスターJSONをsourceとし、sourceSha256を保存する。
  • 各ショートは本編の cutFrames を順番に連結する。未使用区間を暗黙に字幕へ残さない。
  • 連結後の字幕は本編時刻をそのまま使わず、各カットの記録時刻へ再マップする。
  • filler、重複、笑い、相づち、聞き手の反応は編集候補としてフラグ化する。自動削除を確定扱いにしない。
  • 構成案が pending または provisional の場合、生成物は候補タイムラインと表示し、本番正本へ昇格させない。
  • 字幕は合成SRTを一括投入し、Resolveの読戻しで件数・順序・start/end・位置を検証する。
メディアとタイムライン

メディアストレージ上の実ファイル、解像度、FPS、音声チャンネル、タイムコード、hashを入力Registryへ固定する。ファイル名や既存タイムライン名だけで素材を同一視しない。

ビデオ列は意味で管理する。

BG      背景・ベース
Angle   話者・全体・free のカメラ映像
Slide   スライド・挿入映像
Overlay ロゴ・黒線・装飾・補助映像

オーディオ列も意味で管理する。

Dialogue   統合マスター音声を基本とする
BGM        音楽
SE         効果音・演出音

必要な列数は素材の存在数で決める。音声の原音3ラインを加工した統合マスターを本編側のDialogueマスターとして保持し、ショートではその本編ラインをカットする。ショート全体を別音声へ再合成して置き換える設計を標準にしない。差し替え時は元ライン、派生ファイル、処理パラメータ、測定値をmanifestで紐付ける。

タイムラインFPSは素材・本編マスターの実測値を第一候補にする。24と23.976を名前だけで決めず、メディアとResolveの timelineFrameRate を照合し、変換する場合は変換比と丸め規則をmanifestへ保存する。

アングルと人物
  • マルチカムの選択情報と、選択後のPan/Tilt/Zoom/Cropを別のデータとして扱う。
  • Alt+1 等のカメラ選択はアングルID、話者を画面内に収める位置は幾何情報である。
  • 話者、顔、音声波形、カメラメタデータを組み合わせて候補を作る。3人を4人に分けた場合は、ディレクター音声か同一人物の重複かを候補として表示する。
  • 人物名はプロジェクト全体の辞書とPart固有の辞書を分ける。確定できない話者は candidate / unknown のままにする。
  • 同一カメラの中休み分割は、ファイル数ではなくメタデータ・時間軸・映像内容で同一性を判定する。
  • 連続発話中の聞き手の反応、頷き、笑い声は reactionCue として構成案へ入れ、カメラ変更候補にする。
音声とHDRのゲート

音声は原音保持、同期、EQ、ゲート、コンプレッサー、リミッター、ラウドネス測定を別工程にする。コンプを強くして音圧だけを稼がず、反響・ノイズ・ダッキングを含む測定結果を残す。未解決区間がある場合は生成を停止する。

HDRは素材がS-Log3 / S-Gamut3 / 10bitでも、自動でHDR納品へ昇格させない。Resolveの色管理、入力変換、出力色域、HDRメタデータ、HDR Light Level Report、実レンダー、close/reopen読戻し、テストチャート結果が揃って初めて適用する。推測値は使わない。

生成前後のゲート
source hash / media hash
  → master hash
  → plan hash・FPS・cutFrames
  → short SRT cue readback
  → audio unresolved=0
  → Resolve API readback
  → DRT/DRP export
  → close/reopen readback
  → representative screenshots + reference comparison

どれかが未達なら、成果物の状態は candidate または blocked と表示し、「完成」とは呼ばない。

Resolve Geometry Contract

映像をResolveへ配置するときは、Fitを名前だけで扱わず、素材寸法・タイムライン寸法・入力スケーリング・旧正典の測定値を1つの契約として記録する。

必須入力
  • 素材実寸: ResolveのGetClipProperty()['Resolution']
  • 素材FPS: ResolveのGetClipProperty()とMediaTimemap
  • タイムライン実寸/FPS: timelineResolutionWidth/Height、timelineFrameRate
  • 入力スケーリング: centerCropまたはscaleToFit
  • 基準素材寸法と基準フレーム: 旧DRP/DRTから実測した値
  • Transform: ZoomX/Y, Pan, Tilt, CropTop/Bottom/Left/Right
計算規則

centerCropのfit倍率はmax(targetWidth/sourceWidth, targetHeight/sourceHeight)、scaleToFitはmin(...)である。倍率と同時に、実際に見える素材矩形をdisplayed_source_rect()で記録する。

基準素材と実素材のアスペクト比が同じなら、3840×2160と1920×1080のような画素数の違いだけではTransform値を変えない。これをしないと、同じ16:9素材のZoom/Pan/Tiltが誤って半分になる。

アスペクト比が違う場合だけfit倍率比でZoom/Pan/Tiltを変換し、Cropは出力フレームの設計値として保持する。変換後の素材矩形・倍率・実素材寸法はResolve監査JSONへ書く。解像度が取得できない場合は推測で配置せず停止する。

実装は[geometry_contract.py](../../tools/resolve-adapter/geometry_contract.py)に集約し、プロファイルのgeometryContractで基準値とinputScalingを指定する。Part1の正本は[Part1.json](../../templates/shorts/katto/Part1.json)である。

合格条件

Transform値だけでは合格にしない。DRT再読戻しと代表フレームのスクリーンショットで、映像バンド上下境界、人物の頭部・肩、字幕安全領域、黒線、背景の露出範囲を比較する。差分が出た場合は、素材寸法・fit・表示矩形・Transformの順に原因を切り分ける。

DaVinci Resolve スクリプティング実務マニュアル
2026-08-09 cleanup: this is a readback and experiment manual. It does not authorize API timeline construction. Current evidence status is maintained in [docs/KNOWLEDGE_CLEANUP_20260809.md](../../docs/KNOWLEDGE_CLEANUP_20260809.md).

対象版: DaVinci Resolve Studio 21.0.3.0007(Windows 11)

Python 3.13 / DaVinciResolveScript(fusionscript.dll)

このファイルは実測で確かめたことだけを書く。公式ドキュメントに無い挙動が大半で、

どれも 1 変数だけ動かして画を書き出し、px を数えて確定させたもの。

推測は書かない。分からないものは「未確認」と書く。

版が変わったら測り直す。下の値はすべて 21.0.3 での実測。 別の版で違う結果が出たら、この表を上書きせず版ごとに追記すること。

関連する正本:

  • ショートの組み立て仕様 … docs/SOT.md から docs/SHORTS_CUTTER_SUBTITLES.md へ辿る
  • ネイティブファイルを書かない規則 … docs/PLAN.md §0-4-7

実行順の入口: [DaVinci Resolve 実装マニュアル](README.md) → [DaVinci Resolve 実装ワークフロー](davinci_resolve_workflow.md)。

解析で確認した事実と未確定事項: [manual/knowledge/resolve/](../knowledge/resolve/README.md)。


0-0. IMPORTANT: 設定キーが GUI のどの欄かを確かめる。反映は絵で検証する

2026-08-08 に丸一日を溶かした。原因は API ではなく、確認する場所を間違えたこと。

Timeline.GetSetting("timelineInputResMismatchBehavior") を読んで

「これが Timeline Settings > Format の Mismatched resolution だ」と決めつけた。

確かめていない。GUI を開けば 10 秒で分かったことを、値だけ見て判断した。

GUI には Mismatched resolution が 2 つある。

タブ意味正典の値
Format素材 → タイムライン`Scale full frame with crop`(= `centerCrop`)
Outputタイムライン → 出力Scale entire image to fit(= scaleToFit)

timelineInputResMismatchBehavior と timelineOutputResMismatchBehavior は

どちらを聞いても同じ値を返した(正典 4 本すべて)。

だから戻り値だけでは、どちらの欄を見ているのか判別できない。

画で測った結果(1080x1920 タイムライン、3840x2160 のマルチカム素材):

入力スケーリング映像バンドの高さ
正典(手作り)938 px
scaleToFit で組む291〜416 px
`centerCrop` で組む1037 px ← 正解

SetSetting は効く。scaleToFit / centerCrop は True を返して反映される

(scaleToFill / stretchToAll は False)。

だから、こう進める
  • 設定キーを使う前に、GUI のどの欄かを 1 度だけ目で確かめる。

値を変えて GUI がどう動くかを見る。同名・類似名のキーが複数ある前提で疑う

  • 正典から 1 フレーム書き出し、作ったものと同じ方法で書き出して px を数える。

数値の一致ではなく絵の一致を関門にする。これを最初にやる

  • `.drt` にタイムライン設定は入っていない。drt_*.py が読めるのはクリップの幾何だけ。

「学習した」と言うときは、何を読んで何を読んでいないかを明示する

「食い違い 0」を成功と呼ばない

SetProperty した値を GetProperty で読み戻して一致を確認するのはトートロジーで、

設定が効いたことしか示さない。絵がどうなったかは何も保証しない。

生成と判定が根拠を共有しているなら、それは遵守率であって正しさではない (~/.claude/CLAUDE.md 検証と報告)

正しさを測るなら独立な系統を使う。ここでは「レンダリングした画の px」。


0. 接続と、着手前の必須チェック
import os, sys
API = r"C:\ProgramData\Blackmagic Design\DaVinci Resolve\Support\Developer\Scripting"
os.environ.setdefault("RESOLVE_SCRIPT_API", API)
os.environ.setdefault("RESOLVE_SCRIPT_LIB",
    r"C:\Program Files\Blackmagic Design\DaVinci Resolve\fusionscript.dll")
sys.path.insert(0, os.path.join(API, "Modules"))
import DaVinciResolveScript as dvr
app = dvr.scriptapp("Resolve")
0-1 GetCurrentPage() が None なら、そこで止める

Resolve にダイアログ(Project Settings 等)が出ていると、書き込み系の API が全部死ぬ。

エラーにならず None / False が返るので、気づかないまま先へ進むと原因不明の失敗が延々続く。

通る通らない
読み取り全般CreateEmptyTimeline
AddTrackAppendToTimeline
ExportFusionCompSetCurrentTimecode
Timeline.ExportExportCurrentFrameAsStill
if app.GetCurrentPage() is None:
    raise SystemExit("Resolve にダイアログが出ている。閉じてから実行する")

プロジェクトマネージャ画面(プロジェクト未読み込み)でも None になる。

その場合は ProjectManager.LoadProject(名前) で開く。

0-2 プロジェクトを間違えない
pr = app.GetProjectManager().GetCurrentProject()
if pr is None or pr.GetName() != 期待する名前:
    raise SystemExit("開いているプロジェクトが違う。切り替えはしない")

スクリプトからプロジェクトを勝手に切り替えない。人が開いているものを尊重する。


1. 戻り値の落とし穴(ここで何時間も溶ける)
1-1 PyRemoteObject は falsy
r = item.ImportFusionComp(path)
if r:            # ← ダメ。成功しても False 扱いになる
if r is not None:  # ← 正しい

実測 2026-08-08: ImportFusionComp が 60/60 成功していたのに「60/60 失敗」と数えていた。

Composition オブジェクトが返っているのに bool() が False。

1-2 PyRemoteObject は hash できない
d[timeline_item] = x   # TypeError: unhashable type
d[id(timeline_item)] = (timeline_item, x)   # ← こうする
1-3 False と None を区別する

多くの API が失敗時に False ではなく None を返す。if not r: では区別できない。


2. タイムラインの作成と設定
2-1 useCustomSettings を先に立てる
tl = pool.CreateEmptyTimeline(name)
pr.SetCurrentTimeline(tl)
for k, v in (("useCustomSettings", "1"),          # ← 必ず最初
             ("timelineResolutionWidth", "1080"),
             ("timelineResolutionHeight", "1920"),
             ("timelineFrameRate", "24")):
    tl.SetSetting(k, v)

先に立てないと解像度は黙ってプロジェクト設定へ戻る。

実測: これを忘れて 13 本すべて 1920x1080 で作ってしまった。

2-2 timelineInputResMismatchBehavior は 2 値しか受け付けない
値SetSetting
scaleToFit通る
centerCrop通る
scaleToFill / scaleFullFrameWithCrop / stretchToAll / fill / crop全部 False

UI には 4 択あるが、API から入るのは 2 つだけ(21.0.3 実測)。

2-3 タイムライン設定は .drt に入らないものがある

Input Scaling / Mismatched Resolution / Resize Filter に相当する鍵は .drt のどこにも無い。

project.xml にも SeqContainer にも無い。プロジェクト設定側にある。

2-4 IMPORTANT: ImportTimelineFromFile は Project Settings の解像度を上書きする

取り込んだタイムラインが `useCustomSettings=1` でも上書きされる。 21.0.3.7 実測

(2026-08-11、使い捨てプロジェクトで因果を確認)。

手順Project Settings
1920x1080 にして保存 → 閉じて開き直す1920x1080
縦 1080x1920 の .drt を 1 本取り込む`1080x1920`
そのまま保存し直す1080x1920

だから順序が決まる。Project Settings は全部の取り込みが終わったあとに当てる。

先に当てると、次の 1 本で黙って戻される。

これに気付かないと次のように見える —— 設定して SaveProject し、閉じて開き直して

1920x1080 を確認したのに、あとで読むと 1080x1920 に戻っている。**設定が

永続化されなかったのではなく、あいだの取り込みが書き換えている。**

追従しているタイムライン(useCustomSettings != 1)が 0 本なら、Project Settings を

動かしても既存のタイムラインは動かない。先に全部を現在値で固定してから当てる

(実測: 191 本すべて解像度変化 0)。


3. クリップを置く(MediaPool.AppendToTimeline)
pool.AppendToTimeline([{
    "mediaPoolItem": item,
    "startFrame": src_in,
    "endFrame":   src_in + 尺,   # ← 排他
    "recordFrame": 置く位置,
    "trackIndex": 段番号,
    "mediaType": 1,              # 1=映像 2=音声。**省略すると両方**
}])
3-1 endFrame は排他

-1 すると尺が 1 足りず、クリップの間に 1 フレームの隙間が空く。

実測: 340 か所すべてに 1 フレームの隙間が出て、画面で見えた。

3-2 mediaType を渡さない=映像+音声

mediaType: 1 にすると音声が落ちる。実測: 13 本すべて音声 0 クリップになった。

3-3 静止画は Frames=1 なので endFrame が効かない

置くと 72 フレームに丸まる。SetClipProperty("Frames"/"Duration") は False。

item.SetMarkInOut(0, 尺 - 1, "video")     # ← マークで尺を与える
pool.AppendToTimeline([{"mediaPoolItem": item,
                        "recordFrame": 位置, "trackIndex": 段, "mediaType": 1}])
                        # startFrame / endFrame は**渡さない**
  • マークはプール項目に付くので、まとめて渡すと最後の 1 個の尺が全部に効く。1 枚ずつ置く
  • ClearClipMarkInOut() は存在しない(NoneType で落ちる)
  • 素材を再エンコードして尺を作るのはやってはいけない(浅尾却下 2026-08-07)
3-4 プールの索引は大文字小文字を区別する

bg.png(1920x1080) と BG.png(4096x2160) が別プロジェクトに同居している。

小文字だけで引くと取り違える。完全一致の索引を先に引く。


4. 幾何(Transform / Cropping)— 最重要
4-1 効き方の式(実測で確定)
見える高さ =(表示高 − cropTop − cropBottom)× zoom

表示高 = 出力幅 × 素材高 ÷ 素材幅        (scaleToFit のとき)
  • crop は zoom の前に効く。単位はフレーム px(zoom 1 のとき)。素材の上端から測る
  • tilt の移動量(px) = tilt × 表示高 / フレーム高
  • pan の移動量(px) = pan × 表示幅 / フレーム幅
  • crop は表示高で頭打ち

実測表(3840x2160 素材 / 1080x1920 タイムライン、表示高 607.5):

zoomcropTop見える範囲高さ
1.00656–1264608
1.0100756–1264508
1.0300956–1264308
3.0048–18721824
3.0300948–1872924

z=1, crop=300 で上端が 300px、z=3, crop=300 で 900px 下がる(crop は zoom で拡大される)。

tilt=300 は zoom によらず 95px 動く(= 300 × 607.5/1920)。

pan=300 は 300px 動く(横合わせなので 1:1)。

CropTop は 608 以上を入れても全部 607.5 になる。

4-2 別の素材へ値を移すときの換算

同じ数値でも素材が違えば別の絵になる。実測: 同じ CropTop 472 が、

マルチカム(縦いっぱい)では画の 25%、生ファイル(16:9 横合わせ)では 78% を削った。

zoom / tilt / pan = 元の値 × 基準フレーム高 / 表示高
crop              = 元の値 × 表示高 / 基準フレーム高

値だけを保存しない。必ず「何を基準に測った値か」を一緒に持つ。

4-3 素材の「出方」は 3 種類(実測で決める)
種別表示高例
fitToWidth出力幅 × 素材高/素材幅3840x2160 を 1080 幅 → 607.5
fillsFrameフレーム高マルチカム(1920)
native素材の画素高Fusion を載せた台(1920x1080 の台 → 1080)

native の実測: 台 bg.png(1920x1080) に comp の高さ 0.74 の矩形を無変形で載せると

799px = 0.74 × 1080。フィットなら 450、フレーム基準なら 1421 で、どちらでもない。

Fusion の台は出力と同寸の透明 PNG にする。そうすれば換算が不要になり、 comp の流し込みに失敗しても画面に何も出ない(有色の素材を台にすると全画面に出る)。
4-4 ZoomGang を外さないと非一様な拡大が潰れる
it.SetProperty("ZoomGang", False)   # ← 先に
it.SetProperty("ZoomX", 0.88)
it.SetProperty("ZoomY", 1.18)

実測: ZoomGang=True のまま ZoomY を書くと ZoomX も同じ値になり、両軸 2.0978 になっていた。

4-5 TimelineItem.GetProperty() で取れる鍵

ZoomX/Y Pan Tilt RotationAngle AnchorPointX/Y CropLeft/Right/Top/Bottom

CropSoftness CropRetain Opacity FlipX/Y Scaling ResizeFilter Distortion

CompositeMode DynamicZoomEase MotionEstimation RetimeProcess Pitch Yaw ZoomGang

カラーページの Input Sizing は取れない。別系統。


5. マルチカム
5-1 アングルは API から切り替えられない
  • AppendToTimeline で置くと常に Angle 1
  • SelectTakeByIndex / GetTakesCount は None(take として露出していない)
  • アングル名は .drt の FieldsBlob に "Camera 1" のように入っているが、書く手段は無い
  • CurrentSelectorIdx はアングルではない(全部 0)
5-2 生ファイルへ差し替えると副作用が出る
項目マルチカム生ファイル
表示縦フレームいっぱい16:9 横合わせ
crop の基準1920607.5
fps24.023.976(MediaTimemapBA にリタイムが入る)

差し替えるなら幾何の換算が必須。fps 違いも意識する。


6. Fusion と Text+
6-1 置き方は 3 通りあり、通るのは 1 つ
手段結果
Timeline.InsertFusionTitleIntoTimelineリップル挿入。全段のクリップを割る(配置が 880 → 1266 に増えた)
TimelineItem.AddFusionComp`None`。別プロセスでも None
台を置いて `TimelineItem.ImportFusionComp(.comp)`通る

.comp は Resolve 自身が ExportFusionComp で書き出すのと同じテキスト形式。

.drt / .drp のバイナリではないので、生成してよい(PLAN.md §0-4-7 の対象外)。

6-2 既存の Fusion は書き出せる
it.ExportFusionComp(path, 1)   # 1 = 何番目の comp か

読み取り操作なので、Resolve がモーダルでも通る。

ただし Sm2TiGenerator(Text+ の Rich)は GetFusionCompCount() が 0 で、書き出せない。

2-5 IMPORTANT: dev/short-plan/*.json の fps: 24.0 は実測と食い違う(2026-08-12)

実測は 23.976。 Cam1/20260411_C0355.MP4 を ffprobe で見ると

rFrameRate = 24000/1001(dev/knowledge/part11-media-fps-evidence-20260809.json)。

書き起こしの timebase.drpFrameRate も 23.976 で一致する。

いっぽう dev/short-plan/*.json は 13 本すべて `fps: 24.0`(残り 4 本は null)。

丸めた値が入っている。

出所fps素性
part11-media-fps-evidence-*.json23.976ffprobe の実測。正典
part11-condensed-*.json の drpFrameRate23.976実測と一致
dev/short-plan/*.json の fps24.0丸め。使わない

0.1% のずれは秒からフレームへ直すたびに積む。10 分地点で約 14 フレーム(0.58 秒)。

Angle の割り当ては秒で持つので、ここを取り違えると列の境界が全体に平行移動する。

被覆の検査(穴・重なり)は平行移動を検出できないので、通ってしまう。

秒 → フレームの換算に `short-plan` の `fps` を使わない。

--angle-fps の既定 24000/1001 は実測に合わせてある。

(2026-08-12 の監査でここは「実測は全部 24」と報告されたが、逆だった。

実測証跡を読み直して確認した。指摘をそのまま採らずに測って良かった例。)

6-2-1 IMPORTANT: 台の種類で決まる。Text+ 生成子には流し込めない(2026-08-12 実測)

同じ .comp・同じコード・同じタイムラインで、台を変えるだけで結果が変わる。

台GetFusionCompCount()ImportFusionComp
Sm2TiGenerator(Text+ の Rich。名前は Text)0`None`
Fusion Composition クリップ1通る

`.comp` を流し込む台は Fusion Composition クリップでなければならない。

Text+ 生成子は見た目が同じ「文字の台」でも受け取らない。

これは §6-3(session 途中で効かなくなる)とは別の原因。None が返ったときに

まず疑うのは台の種類で、再起動ではない。

6-2-2 流し込んだ Text+ は画で確かめた(2026-08-12)

Fusion Composition の台へ make_textplus_comp.py の出力を流し込み、

12 フレームだけレンダリングして 1 枚抜いた。黄色の文字が出た。

戻り値では確かめられない(§6-1 に「成功を返すのに描画されない」実測がある)。

ExportCurrentFrameAsStill も Edit ページからは真っ黒が返るので、

レンダリング → ffmpeg でフレーム抽出が唯一の確認手段。

6-2-3 動き(BezierSpline)は受理されるが描画されない(未解決 / 2026-08-12)

Text+ の Size を BezierSpline で動かそうとした。構文は正しい。描画されない。

確かめたこと:

結果
静止の Text+描画される(画で確認済み。§6-2-2)
Size に spline を付けた Text+全フレームで描画されない(先頭・中央の両方で確認)
流し込んだあと ExportFusionComp で書き戻すspline が生きている

書き戻した中身は、Resolve が自分で解釈し直した形になっていた。

TemplateSize = BezierSpline {
  KeyFrames = {
    [86400] = { 0,    RH = { 86403.33, 0.0367 }, Flags = { Linear = true } },
    [86410] = { 0.11, LH = { 86406.67, 0.0733 }, RH = { 86780.67, 0.11 }, ... },

`SizeAnim` は `TemplateSize` へ改名され、ハンドル(LH / RH)は Resolve が足した。

つまり .comp は正しく解析され、アニメーションのデータは保持されている。

それでも画に出ない。

3 通り試した。全部出ない。

試した形結果
0 起点 + Flags = { Linear = true }描画されない
絶対フレーム([86400])+ Flags描画されない
0 起点 + LH/RH ハンドル(実物と同じ形)描画されない

対照実験をした。 同じ手順で静止の comp を入れ直すと描画される。

つまりレンダリングは生きていて(キャッシュではない)、**spline を入れたときだけ

本当に出ていない。**

実物のアニメーション付き comp は自動で採れる(2026-08-12)

InsertFusionTitleIntoTimeline はリップル挿入なので本番では使えないが、

捨てて良いタイムラインなら割れて困るものが無い。 そこへ内蔵タイトルを入れて

ExportFusionComp すれば、実物のアニメーション構文が手に入る

(tools/resolve-adapter/harvest_builtin_titles.py)。

採れた実物は templates/fusion/reference/ に置いた。

ファイル中身
builtin-Scroll.comp`BezierSpline` が 8 本。動きの実物
builtin-TextPlus.comp静止の Text+。`GlobalIn` を持たない

実物から分かったこと:

  • 時間は 0 起点(RenderRange = { 0, 119 }、キーは [2] [14] [90] [117])。

一度「絶対フレーム」と読んだのは誤りで、そう見えたのはたまたま手元の書き出しが

タイムライン中ほどのクリップだったため

  • キーは Flags を使わず LH / RH ハンドルだけを持つ
  • 参照は Opacity1 = Input { SourceOp = "HeadingOpacity1", Source = "Value", }。自作と同じ
  • 内蔵タイトルは Text+ を直接動かしていない。Scroll は

InstanceOutput / UserControls を挟んだテンプレート構造になっている

6-2-4 IMPORTANT: 解決。動きは実物のテンプレートを骨格にする(2026-08-12)

自作の BezierSpline を直す方向は捨てた。 4 通り試して全部描画されない

(0 起点 + Flags / 絶対フレーム + Flags / 0 起点 + ハンドル / 開始 Size を 0 でなく微小値に)。

切り分けが答えを出した。 builtin-Scroll.comp(実物)をそのまま同じ台へ

流し込むと、動いて描画される。 つまり台もレンダリングも正常で、自作の comp が悪い。

だから静止版と同じやり方に寄せた。 静止版が line.comp(実物の書き出し)を

骨格にしているのと同じで、**動きも実物を骨格にし、文言・フォント・大きさだけ差し替える。

構造・キーフレーム・ノードには触らない。**

python tools/resolve-adapter/make_textplus_comp.py \
  --text "撮り直し不可能カット" --out out.comp \
  --animated-template templates/fusion/reference/builtin-Scroll.comp \
  --font "Noto Sans JP" --style Black --size 0.09

画で確認済み。 パワーフレーズが左から滑り込んで定位置に収まる(フレーム 8/12/16/20)。

フォントは必ず差し替える。 内蔵テンプレートは Open Sans で、

日本語のグリフを持たない(差し替えないと豆腐になる。実測)。

なぜ自作の spline が描画されないかは未解明のまま。 構造(スプラインの階層・

SourceOp / Source の書き方)は実物と同じで、Resolve は受理して書き戻しもする。

解く必要が無くなったので追っていない。

6-3 ImportFusionComp は session 途中で効かなくなる(未解決)

同じコード・同じ .comp で 60/60 成功した後、以降はどの段・どの台でも None を返すようになる。

組み直したタイムラインでも 0/60。再生ヘッド位置・Fusion ページ・上段の無効化・

台の置き直しを試したが変わらない。Resolve の再起動が要るとみられる。

対策: 台は組み立て中に全部置き、.comp の流し込みは別プロセスで 1 回だけ走らせる。

6-4 Text+ の .comp の骨格

`Composition { ... Tools = { Template = TextPlus { Inputs = { StyledText, Font, Style, Size,

Center, VerticalJustificationNew, HorizontalJustificationNew, Red1/Green1/Blue1 } },

MediaOut1 = Saver { Input = Template } } ... }`

Size はフレーム高に対する比(例 55px / 1920 = 0.0286)。

Center は {X, Y} の 0〜1。Fusion は Y 上向き(0.545 が 0.482 より上)。


7. 字幕
7-1 AppendToTimeline は SRT の空白を消す

実測: 810 件のうち 784 件がずれ、後半 7 本は字幕ゼロになった。

ショート内の隙間は保たれるが、ショート間の大きな空白だけ詰められる。

7-2 位置を直す手段が無い
  • Timeline.ImportIntoTimeline は SRT を受け付けない(3 通り試して全部 False)
  • 字幕アイテムに開始位置を書く API は無い
  • recordFrame は字幕に効かず、必ず末尾へ足される
7-3 現行の字幕経路は native ST1

過去の手作りタイムラインにText+字幕が存在しても、それを標準経路へ一般化しない。現行は合成SRTをnative subtitle track(ST1)へ一括投入し、生成DRTの再読戻しで件数・record frame・duration・ショート間隔を検証する。Text+は教師DRTで実在するタイトル/アニメーション要素に限定する。

字幕をText+やPNGへ変換して位置問題を隠さない。隙間をダミー字幕で埋めて辻褄を合わせない(docs/SOT.md の字幕owner)。


8. カラーと音声
8-1 CopyGrades は色だけ運ぶ
src_item.CopyGrades([tgt_item, ...])

サイジング(寄り)は運ばれない。実測で色は変わり、フレーミングは変わらなかった。

8-2 音量はクリップ側にしか無い
  • 音声トラック(Sm2TiTrack)にゲイン・パン・EQ・ダイナミクスの鍵は 1 つも無い
  • OutputAudioGain は 0
  • dB はクリップの `EffectFiltersBA`(種別 124 / id95)にだけ存在する
  • 「トラックごとのゲイン」に見えるものは、そのトラックの全クリップに同じ dB を書いた結果

Fairlight のクリップ FX はクリップの `FieldsBlob` の `FL::ClipFX`(zlib、68B レコード列)。

bmd:Dialogue Leveler と bmd:Fairlight EQ のパラメータが入る。

API から音量を書く手段は見つかっていない(未解決)。

8-3 最終編集タイムラインの音声正本

Historical API-era rule: the original Audio TimelineItems were preserved as the source layer and the integrated WAV was treated as a derivative. This is retained as evidence only. The current contract preserves those items muted on A1 and places the hash-verified integrated WAV on A2; API placement is allowed only as a readback experiment, never as the production writer.

現行の音声経路は、A1に元Resolve Audio TimelineItemsを保持してミュートし、A2へ未解決区間0・SHA-256一致済みの統合WAVをDialogue · Masterとして配置する。A3/BGM、A4/SEはPart profileの宣言に従う。preserve_source_audio.pyは原音保持の証跡、API配置はreadback実験に限定し、manifestまたはA2読戻しが欠ける場合は本番昇格しない。


9. .drt を読む
9-0 IMPORTANT: 何が読めて、何が読めないか

「`.drt` から学習した」と言うときは、必ずこの表を添える。

読めない項目を黙って落とすと、後段で「学習したはずなのに合わない」が起きる(実測 2026-08-08)。

項目.drt.drpResolve API
クリップの並び・尺・In○○○
クリップの幾何(EffectFiltersBA)○○○ こちらが正
Text+ の文言○○△
Fusion のノードグラフ(CompositionBA)○○○
マルチカムの中身(アングル → ファイル + オフセット)○×○
メディアプールの bin 構造×(Master に平坦化)○(zip のパス=bin パス)○
タイムライン設定(解像度・入力スケーリング・fps)×△○(§0-0 の注意)
カラーグレード×××
マルチカムのアングル選択×(CurrentSelectorIdx は壊れている)×△(GetName() が X - Angle N)
プロジェクトの色管理・書き出し設定×△○

幾何は API の実値を正とする。.drt の値は正規化されていて、

基準(フレーム幅か高さか)を推測すると外す。

API の GetProperty("Pan") などと 1 度突き合わせて、換算式を確定してから使う。

9-0-2 タイムライン項目は複数ある。名前で選ぶ

MpFolder.xml の Sm2MpTimelineClip は 1 つとは限らない。

本編の .drt には Part1 / Part1_digest / OP / LogoMotion copy が同居する。

最後の 1 つを採ると入れ子の別タイムラインを読む(実測: LogoMotion copy を読んで

「V1 に braw が 1 枚」という無関係な結果になった)。`<Name>` で選ぶ。

seq = next(s for n, s in candidates if n == "Part1")   # 名前で選ぶ
9-0-3 マルチカムは MediaFilePath を持たない

マルチカムのクリップは実ファイルを指していない。パスは空。

`MediaRef`(プール項目 ID)で追う。撮り直して別マルチカムに切り替わっていても

名前とクリップ数では見えない(実測: Part12 は 1 Part の中で C0356 と C0357 を使っている)。

実ファイルのフレーム = `srcIn` + `origin` + `offset`。

origin はマルチカム内部シーケンスの原点で、実測では 86400(= 01:00:00:00 @24fps)。

足し忘れると wav 秒が負になり、1 件も当たらない。

9-0-4 カラーグレードは `.drt` に入っている(2026-08-08 に訂正)

旧出力仕様にあった「カラーグレード。.drt に値が無い」は誤り。

`.drt` を読み込むとグレードが乗る以上、入っていないはずがない。

その指摘(浅尾 2026-08-08)から探し直して見つけた。

書いてある記述を検証せずに繰り返していたのが原因。

<pLmVerTable>
  <ListMgt::LmVersionTable>
    <pActive>…</pActive>                どのバージョンが有効か
    <Locals><Element>
      <ListMgt::LmVersion>
        <Name>Version 1</Name>          カラーバージョン名
        <HasCorrection>true</HasCorrection>
        <Body>81 28b52ffd …</Body>      **グレード本体**
        <LinkedGroup>…</LinkedGroup>    カラーグループ

`Body` の容器は `[0] 0x81` + zstd(28 b5 2f fd がマジックナンバー)。

§9-2 の容器仕様「0x81 = zstd」そのもの。解凍すると protobuf。

読める(実測 Part1_Master、104 クリップすべて解凍成功):

例
ノード名SkinTone / Wall / Table / Cloths / Glow / Halation
ResolveFX の識別子com.blackmagicdesign.resolvefx.glow(65 クリップ)/ noisereduction(6) / filmgrain(6) / halationplugin(3) / lightray(2) / `colorspacetransformv2`(1) / lensflarev2(1)
パラメータ名opacity / threshold / brightness / spread / dyeReflGamma / dyeReflSat / grainIsEnabled …

まだ分かっていないこと: protobuf のフィールド ID と数値の対応。

ノード名と FX の種類は取れるが、各パラメータの値がどれかは未解読。

色を再現するにはそこまで解く必要がある。

読む道具: tools/resolve-adapter/drt_grades.py

9-1 構造

ZIP。中身は project.xml / MediaPool/Master/MpFolder.xml / SeqContainer/<uuid>.xml。

ルートの並びの辿り方:

MpFolder.xml の Sm2MpTimelineClip → Sm2Sequence の DbId → その id を <Sequence> に持つ SeqContainer。

<ListMgt::LmVersionTable> は XML として不正。パース前に "::" → "__CC__" に置換する。

9-2 blob の容器
[0:4] uint32 BE = 2      バージョン
[4:8] uint32 BE          以降の長さ
[8]   0x80 非圧縮 / 0x81 zstd
[9:]  protobuf
9-3 EffectFiltersBA のパラメータ

field1 = フィルタ / field1 varint = 種別 / field9 = パラメータ(空でも 4a 00 で残る)

/ field1 varint = ID / field3→field1→field2(tag 0x11) = IEEE754 double LE

種別中身
4Transform(id42/43 Zoom、id40 Pan、id41 Tilt)
6Cropping(id56 CropTop、id57 CropBottom)
48Text+(id15 rich text、id17 Center、id42 は未確定)
124AudioLevels(id95 ゲイン dB、id97/98 フェード長とみられる・未確認)

バイト位置は固定ではない。省略されたパラメータがあると後ろが全部ずれるので、

ID を見ながら protobuf を歩く。

9-4 数値に | が付くことがある

In / Duration が 22329|0000f753e3a5d33f(フレーム | サブフレームの double)になる。

int() が落ちるので split("|")[0] を挟む。

9-5 Text+ の文字列は UTF-16LE

EffectFiltersBA の中に uint32 長さ(=2n+4) + UTF-16LE 本体。

読むときの罠:

  • 偶数・奇数の両方の境目から読む(開始位置が素材ごとに動く)
  • ASCII 2 バイトが漢字に化ける(Regular → 刀攀最甀氀愀爀)。

下位バイトが 0x00 の文字が 7 割を超える連続は捨てる


9-6 DRT統合デコーダーの確定範囲(2026-08-08)

tools/resolve-adapter/drt_decode.py は、DRTを編集せずに次の層を一つのJSONへ抽出する。

  • ZIPエントリ、XMLタグ、MediaPool、タイムライン名とSeqContainerの対応
  • Video / Audio / Subtitle track、clipの Start / Duration / In / Out の原値
  • FieldsBlob と SequenceSetup(1080x1920 / 24fps / input・output mismatch)
  • EffectFiltersBA の kind / slot / parameter ID / 値
  • grade Body のzstd展開とResolveFX文字列
  • trackごとのrecord-frame範囲、空白フレーム、最大空白

52本の実運用DRTを並列スキャンした結果、構文エラーは0件だった。これは「全バイト列の意味が確定した」という意味ではない。次の分類を守る。

領域現在の扱い意味の確定
EffectFiltersBAprotobufとして構文解析。Resolve 21 descriptorの EffectFilter.type / EffectParam.dbid / value・range・keyframe・Fusion field pathも付与kind 4 の40/41/42/43、kind 6の56/57、kind 124の95まで確定
kind 2 / parameter 1V11 Fusion/scrimの実データと opacity 58.78 を一対一照合Opacity(known)。kind 2自体の正式名称は未確定
kind 4 / parameter 47-90 / 180 などの角度値とTransform群との共起を記録RotationAngle候補。制御差分なしのため未確定
kind 4 / parameter 520 / ±0.1の反復値を保持未確定
kind 6 / parameter 54/5556/57(CropTop/Bottom)と同じ4辺Cropブロック内の順序を保持CropLeft/Right候補。制御差分なしのため未確定
kind 124 / parameter 97/98Audio clip上の非負フレーム値を保持AudioFadeIn/OutFrames候補。制御差分なしのため未確定
kind 48 / parameter 42Text+の未知parameterとして保持ZoomXとは呼ばない
kind 72 / parameter 137/138未知effect・parameterとして保持未確定
CompositionBA4-byte長 + zlibを展開し、Fusion .comp の版・GlobalRange・本文hashを抽出全文は drt_extract_comps.py で出力
MediaTimemapBAv1辞書のserialized key、v2 version/9・41-byte BE float64 slot/offset/reserved bytesを抽出。KeyframesBA は RetimeKeyframeProto.x/y/bezier のschema pathを付与v1のYMin/YMax/XMaxとv2 slotのUI意味・補間schemaは未確定
grade Bodyzstd展開、FX識別、protobuf wire記録。ParamValueType / OFXParams / EffectParamValueTypeを候補schemaとして出力grade root・数値fieldとFX parameterの対応は未確定

再現コマンド:

python tools/resolve-adapter/drt_decode.py input.drt `
  --timeline-name Part1_Shorts --json output.json
python tools/resolve-adapter/drt_batch_summary.py D:/path/to/export `
  --workers 8 --json dev/drt/batch.summary.json
python tools/resolve-adapter/resolve_schema_scan.py `
  --json dev/drt/resolve-embedded-schema.json

複数タイムラインを含むDRTは、検証時も必ず名前を指定する。Outは現在確認したDRTには保存されないため、終了位置は Start + Duration として扱い、素材側の終端は In と MediaTimemapBA を別層で照合する。

Resolve 21.0.3.0007 の Resolve.exe には内部protobuf descriptorが28ファイル分埋め込まれている。Sm2Filter.proto の EffectParam(dbid、value、defaultvalue、range、keyframesba)と、Sm2TiItem.proto の RetimeKeyframeProto(x、y、bezier)、依存する BtCommon / OfxParams を抽出できる。ただしこれは同一バイナリへの静的照合であり、Blackmagic公式のDRT仕様公開ではないため、未知IDのUI名は引き続き推測しない。

9-7 承認済みの実装契約(2026-08-08)
  • DRT未知IDは known / candidate / unknown で管理する。未公開numeric kind/dbidへUI名を推測で割り当てない。
  • 字幕は合成SRTを一括投入し、生成DRTを再読込して全cueのrecord frame、duration、ショート間隔を照合する。
  • マルチカムの重複区間は先勝ちで統合し、source FPSからtimeline FPSへのframe変換をAppend前に行う。
  • A1には元Resolve Audio TimelineItemsを保持してミュートし、Dialogueの編集再生正本はA2のhash検証済み統合WAVとする。未解決区間があれば生成を停止し、元音声のDRT/DRP/tl_map、TimelineItems、統合WAVのSHA-256をレポートへ残す。
  • HDRは推測設定を使わない。公式仕様、テストチャート、HDR Light Level Report、Resolve設定のread-back、close/reopen後の再確認を満たした候補だけを採用する。

タイトル段のtrack番号はグローバル規則ではなく、プロジェクト/Partプロファイルの trackLayout が正典である。Part1は templates/shorts/katto/Part1.json の logo=V8、titleLower=V9、titleUpper=V10 を使用する。タイトルmanifestは内容を供給し、track番号の正典ではない。

10. 画で確かめる
10-1 GrabStill() は使わない

ギャラリーが汚れる(浅尾の指示 2026-08-07)。

10-2 ExportCurrentFrameAsStill(path) を使う

ギャラリーを経由せず PNG を書く。画面も乗っ取らない。

tl.SetCurrentTimecode("00:02:32:15")
pr.ExportCurrentFrameAsStill(str(path))
10-3 目視で終わらせない。px を数える

2026-08-07 の遠回りは全部「別の瞬間・別のクリップを目で見比べていた」ことが原因。

  • 同じ素材フレームで比べる(タイムラインの位置ではなく、素材の何フレーム目か)
  • 映像帯の高さを数値で測る(黄色でも黒でもない行の連続区間を数える)
  • 1 変数だけ動かして 2 枚撮り、差分を計算する

10-2. 機械で通す経路(plan → DRT → 取り込み → DRP)

この節だけを見て、他人がゼロから通せること。 2026-08-12 の監査で、必要な入力が

4 つ書かれていないことが分かったので全面的に書き直した。段 0〜3 は同日に実際に走らせて

確かめた(Part11 / P1)。Resolve へ繋ぐ段 4〜6 はこのセッションでは走らせていない。

下の「未確認」を必ず読む。

IMPORTANT: DRT の実体はリポジトリに無い

git ls-files '*.drt' は 0 件。教師も素材も全部リポジトリの外にあり、

clone しただけでは 1 本も組めない。 所在は下の表(2026-08-12 に実在・SHA-256・

中のタイムライン名を実測。SHA は

[manual/knowledge/resolve/15_part11_p1_session_handoff_20260811.md](../knowledge/resolve/15_part11_p1_session_handoff_20260811.md)

の表と一致した)。

役割パス(Part11 の実例)SHA-256(先頭16桁)中のタイムライン
sourceD:\zFilms Dropbox\z Films\Directors'Katto\drp\export-20260807\reference\Part11.drt942b2d438cc602ee2 本 Part11 / Part11_Digest
master…\drp\auto-part11-20260810\master-layout\Katto_Master_Layout_Production.drtdfe0ed45248212521 本 Katto_Master_Layout_Production
layout(縦の教師)…\drp\export-20260807\reference\Part11_Shorts_old.drt4d9cdb0e1139e86e1 本 Part11_Shorts_old
字幕…\drp\auto-part11-20260810\production42-v1\raw-drt\Part11_Production42_{short}.drtP1 は 980b7995d443c1dd2 本 Part11_Production42_P1 / Part11_Content_Only_v1

字幕は 42 本(P1〜P42)が同じ階層にある。**source と字幕はタイムラインが 2 本ずつ入って

いるので、名前を渡さないと止まる**(止まるのが正しい。推測で選ばない)。

**DRT だけではない。dev/knowledge/* は .gitignore で丸ごと除外されている**(例外的に

7 ファイルだけ追跡されている)。段 0 の --fps-evidence と段 1 の --words は

その中にあり、clone した人の手元には無い。

入力版管理
.plan-drafts/PartN.compact.json / .transcripts/PartN.master.json / dev/master-srt-j/*.srt / dev/short-plan/PartN.json追跡されている
dev/knowledge/partN-media-fps-evidence-*.json / partN-condensed-*.json追跡されていない(.gitignore:62 dev/knowledge/*)
*.drt / *.drp1 本も追跡されていない(外部ストレージのみ)
必要な入力と、無いとどう壊れるか
入力どこから得るか無い/間違えるとどうなるか
--plan-json(`cutFrames` 入り)段 0 で作る。**追跡済みの dev/short-plan/*.json は使えない**(ranges しか無く cutFrames は 0 件)build_all_shorts.py が止まり、走らせるべき materialize のコマンドを出す(2026-08-12 に追加された案内)
--source-drt / --source-timeline上の表名前を省くと「2 本あるので自動で選べない」で止まる
--master-drt上の表V10/A5 のトポロジ検査で止まる。master 側は名前を渡す口が無いので、タイムラインは 1 本であること
--layout-drt / --layout-timeline上の表(Part11_Shorts_old.drt)これが今回いちばん危ない欠落。 縦の Transform/Crop はここからしか来ない。無いと1080x1920 のタイムラインに横位置のままの画が乗る。以前は静的検査も build-summary も「成功」と出て、レンダリングして画を見るまで気づかなかった。2026-08-12 に fail-close する守りが入り、いまは V2-V4 に framing が当たっていなければ止まる(ソースが既に縦のときだけ --allow-unframed で抜ける)。古い版の `build_short_drt.py` は黙って通る
--part-offset-frames書き起こしの timebase.drpOffsetFrames。Part11 は dev/knowledge/part11-condensed-23976-20260809-v3.json と .plan-drafts/Part11.compact.json が 1145、段 0 の出力は 1144(23.976 へ直した値)。1 フレーム食い違う。どちらが正かは未決取り違えると cut_fragments が空を返す。ただしここは fail-close 済みで、Angle 合計 0 / A1 0 なら「カット範囲が source と重なっていない」と言って止まる
--subtitle-drt / --subtitle-timeline上の表無ければ字幕なしで組む(映像・音声・トポロジは字幕に依存しない)。--subtitle-mode cut を短尺 DRT に使うと座標系が合わず大半が落ちる(実測 24→7)。件数は出るが終了コードは 0 のまま
--angle-plan / --angle-fps段 1 で export_angle_plan.py が作る。手で書ける形ではない渡さないと止まらず、ソースの V1-V3 をそのまま V2-V4 へ写す既定動作になる。実測では全部 V2 に乗り V3/V4 は 0 件。「列 = 人」にならない。fps を取り違えると列の境界が全体に平行移動し、被覆検査(穴・重なり)は平行移動を検出できないので通ってしまう(§2-5)
--subtitle-style-drt(任意)Resolve 上で katto_subtitle を当てた DRT--subtitle-drt が無いと受け側で使われない。build_all_shorts.py は字幕がある回だけ渡し、無ければ注意を出す
--power-word-json(任意)作る経路が無い(未確認)。 受け側は {picks:[{text,startFrame,frames}]} を読むが、dev/knowledge/part11-power-phrase-proposals-20260812.json は {proposals:[{text,score,reason,approxStartSeconds}]} で形が違う渡さなければ台を置かない。それが普通の回(正本 manual/L2_sns/16_telop_emphasis.md §1: 1 本に 0〜1 回)

秒 → フレームの fps は §2-5 が正本。 ここに数値を書き写さない。

--angle-fps の既定はそこに合わせてあるので、普通は渡さない。

段 0. cutFrames を作る

編集案(.plan-drafts/PartN.compact.json)は extracts しか持たず、

TimelineIR(dev/short-plan/PartN.json)は ranges しか持たない。**どちらもそのままでは

DRT を組めない。**

python tools/resolve-adapter/materialize_plan_cut_frames.py \
  --plan .plan-drafts/Part11.compact.json \
  --master .transcripts/Part11.master.json \
  --fps-evidence dev/knowledge/part11-media-fps-evidence-20260809.json \
  --timeline-fps 24000/1001 \
  --output dev/short-plan/Part11.cutframes.json
  • --master は .plan-drafts/*.compact.json の sourceSha256 と一致するファイル。

違えば「plan sourceSha256 does not match」で止まる(Part11 は .transcripts/Part11.master.json)

  • --fps-evidence の全 video ストリームのレートが --timeline-fps と一致しないと止まる
  • --output は既にあると上書きせずに止まる

実測(2026-08-12、Part11): 42 ショート / 476 範囲、最小テキスト類似 0.474(1 件が 0.5 未満)。

**出力の frameRatePolicy.status と cutMaterialization.productionPromotion は candidate /

blocked のまま出る。**「通った」と「本番に上げてよい」は別。

P1 の 12 範囲 [426,489] … [21703,21760](計 1105f)は、出荷済み候補 P1 v3 の cut ranges と

完全に一致した。この経路がその DRT を作った経路である。

決着(浅尾確定 2026-08-19): 系統 A(字幕 TC 起点)が正本。B は監査器へ降格。 詳細は [docs/DRT_SRT_CYCLE.md](../../docs/DRT_SRT_CYCLE.md) §3。 A がずれた原因は A の入力にあり(読んでいた SRT が最終 DRT 由来ではなかった)、A の設計ではない。 最終 DRT を読み戻して record 時刻を写した SRT を A の入力にする。 B は捨てず、A の出力を語境界で検算する相手として残す。以下は決着前の記録。 (旧・未決)`cutFrames` の作り手が 2 つある。 tools/resolve-adapter/plan_short_cuts.py も cutFrames を書く。あちらは 字幕 TC 起点で、プロジェクト CLAUDE.md と manual/L2_sns/15_shorts_composition_workflow.md §4 の「字幕 TC が上流」に一致する。いっぽう materialize_plan_cut_frames.py は extracts をマスターの語境界へスナップし、TimelineIR の `ranges` を権威として受け付けないと 明記している。同じ Part11 P1 で結果が違う(materialize 12 範囲 / 1105f、plan_short_cuts 7 範囲 / 1387f)。どちらを正本にするかは浅尾の判断待ち。 上のコマンドは、出荷済み P1 v3 と一致したほうを書いている。 なお plan_short_cuts.py は編集構成が未承認だと止まる(--allow-provisional で候補生成)。
段 1. Angle plan(話者ごとの列)を作る
python tools/resolve-adapter/export_angle_plan.py \
  --words dev/knowledge/part11-condensed-23976-20260809-v3.json \
  --out dev/angle/Part11.angle.json \
  --min-seconds 20 \
  --speaker-name speaker_0=中央 --speaker-name speaker_1=右
  • --words はその Part の本編先頭を 0 秒とする軸の、話者付き語 JSON。

生カメラの軸のものを入れない(編集で抜けた尺が階段状に効く。正本

manual/L1_subtitle/13_speaker_attribution.md)

  • --min-seconds は合計発話がこれ未満の話者を列にしない。落とした話者は dropped に残る
  • 話者名は推測で埋めない。 渡さなければ ID がそのまま列名になる

実測(2026-08-12、Part11): 語 1572 → turn 171、列 3(speaker_0 1583.9s / speaker_1 798.6s /

overview)。speaker_2 は 10.4s で落ちた。列 1 が V2 に乗る。

段 2. 全ショートの DRT を組む

1 本落ちても残りは続く。 落ちたものは最後に必ず出る。

python tools/resolve-adapter/build_all_shorts.py \
  --plan-json dev/short-plan/Part11.cutframes.json \
  --out-dir build/part11-shorts-20260812/ \
  --name-template "Part11_{short}_20260812" \
  --source-drt "D:/zFilms Dropbox/z Films/Directors'Katto/drp/export-20260807/reference/Part11.drt" \
  --source-timeline Part11 \
  --master-drt "D:/zFilms Dropbox/z Films/Directors'Katto/drp/auto-part11-20260810/master-layout/Katto_Master_Layout_Production.drt" \
  --layout-drt "D:/zFilms Dropbox/z Films/Directors'Katto/drp/export-20260807/reference/Part11_Shorts_old.drt" \
  --part-offset-frames 1145 \
  --subtitle-drt "D:/zFilms Dropbox/z Films/Directors'Katto/drp/auto-part11-20260810/production42-v1/raw-drt/Part11_Production42_{short}.drt" \
  --subtitle-timeline "Part11_Production42_{short}" \
  --angle-plan dev/angle/Part11.angle.json

{short} は P1…P42 に置き換わる(--name-template / --subtitle-drt /

--subtitle-timeline / --layout-* / --angle-* などで使える)。

`--angle-fps` は渡さない(既定が §2-5 の実測に合っている)。

build_all_shorts.py は --allow-unframed を持たないので、**--layout-drt を忘れると

42 本すべてが失敗として出る。**

1 本だけ組むなら build_short_drt.py を直接呼ぶ。--timeline-name と --output が要る。

python tools/resolve-adapter/build_short_drt.py \
  --source-drt ".../Part11.drt" --source-timeline Part11 \
  --master-drt ".../Katto_Master_Layout_Production.drt" \
  --layout-drt ".../Part11_Shorts_old.drt" \
  --plan-json dev/short-plan/Part11.cutframes.json --short-id P1 \
  --part-offset-frames 1145 \
  --angle-plan dev/angle/Part11.angle.json \
  --subtitle-drt ".../Part11_Production42_P1.drt" \
  --subtitle-timeline "Part11_Production42_P1" \
  --timeline-name "Part11_P1_20260812" \
  --output build/part11-shorts-20260812/Part11_P1_20260812.drt

実測(2026-08-12、P1、段 0 の 12 範囲ではなく plan_short_cuts 側の 7 範囲で確認):

timeline-origin 86400(master の V1 から導出)/ source-body-origin 87545(ソース開始

86400 + 1145)/ 出力 1387f。Angle は V2 speaker_0 8 片 1211f、V3 speaker_1 2 片 139f、

V4 overview 3 片 37f で被覆 1387/1387f。字幕は入力 24 → 出力 24。

段 3. 静的に検算する(Resolve を起動しない)
python tools/resolve-adapter/verify_resolution_contract.py \
  build/part11-shorts-20260812/Part11_P1_20260812.drt

DRT はタイムライン自身の解像度を持たない(ImgWidth/ImgHeight は -1 = 継承)。

ここで確かめられるのはメディアプール側の縦横の分離までで、

「P1 が 1080x1920 か」は実機でしか分からない。

段 4. Resolve へ取り込む(先にプロジェクトを開いておく)

import_drts.py はプロジェクトを切り替えない(正本 §11-4)。開いているものが

--project と違えば何もせず止まる。Resolve 側で先に開くこと。

python tools/resolve-adapter/import_drts.py \
  --project "Part11_Part12MasterLayout_20260810_v2" \
  --dir build/part11-shorts-20260812/ \
  --out dev/knowledge/part11-import-20260812.json
  • タイムライン名はファイル名で決まる(Resolve の仕様。--name-template がそのまま名前になる)
  • 同名が既にあれば飛ばす。--replace は無い(黙って上書きすると、どちらが新しいか分からなくなる)
  • 既定で SaveProject する(--no-save で止められる)
段 5. 解像度を決める(必ず取り込みの後)

ImportTimelineFromFile は取り込んだタイムラインの解像度で Project Settings を上書きする

(§2-4)。先に当てると次の 1 本で黙って戻される。

python tools/resolve-adapter/set_timeline_format.py \
  --expect-project "Part11_Part12MasterLayout_20260810_v2" \
  --suffix _20260812 --width 1080 --height 1920 --go

既定は DRY RUN で、--go を付けたときだけ実際に書く。useCustomSettings を先に立てないと

黙って戻る(§2-1)。Project Settings 側(1920x1080 に戻す)を書く道具はこの階層に無い。

段 6. DRP を書き出して検算する
python tools/resolve-adapter/export_drp.py \
  --project "Part11_Part12MasterLayout_20260810_v2" \
  --out dev/exports/Part11_20260812.drp

ExportProject は Bool しか返さないので、このツールは書き出した .drp を zip として

開き直してエントリ数と bin 数を数える。保存はしない。未保存の変更は DRP に入らないので、

保存は呼び出し側の仕事(段 4 が既定で保存している)。

何が機械で決まり、何を外から与えるか
機械で決まるトポロジ(V1-V10 / A1-A5)、BG(V1)、Overlay(V6-V9)、Dialogue(A1)、カット範囲の適用、外側の SequenceSetup、nested の rekey、静的検査
外から与えるカット範囲(--plan-json、段 0)、Angle の割り当て(--angle-plan、段 1)、縦の framing(--layout-drt)、字幕(--subtitle-drt)、解像度(段 5)

字幕が無いショートは字幕なしで組む。 映像・音声・トポロジは字幕に依存しない。

Part 固有の値を焼き込まない
値決め方
タイムライン名既定で自動。DRT に 1 本しか無ければそれ。複数あれば候補を並べて止まる
timeline-originmaster の V1 が持つ値から導出
source-body-originソースの開始フレーム + --part-offset-frames(書き起こしの timebase.drpOffsetFrames)

導けなければ止める。既定値で埋めない。 実測: Part11.drt には Part11 と

Part11_Digest の 2 本があり、名前を渡さないと正しく止まる。

導出版と旧・決め打ち版で counts / cutRanges / 原点 / Angle / 字幕がすべて一致する

(86400 と 87545 が導出でそのまま出る)ことを確認済み。

この節の未確認(2026-08-12)
  • 段 4〜6 を走らせていない。 Resolve へ繋いでいないので、上の 3 つのコマンドは

スクリプトの実装(引数・fail-close の条件)からしか書いていない。実機での再現は未確認。

  • `--part-offset-frames` が 1145 か 1144 か決まっていない。 段 0 の出力は 1144

(24 → 23.976 の直し)、書き起こしと編集案は 1145。出荷済み P1 v3 は 1145 で組まれている。

  • `cutFrames` の正本が 2 つある(段 0 の注記)。
  • `--power-word-json` を作る経路が無い。 受け側が読む形の JSON を出す道具が

リポジトリに見つからなかった。

  • 画で確かめていない。 --layout-drt の有無で絵がどう変わるかは、今回

レンダリングしていない。§10-3 のとおり px を数えるまでは「framing が当たった」と言わない。


11. やってはいけないこと
  • 任意の`.drt` / `.drp` / `.prproj`を推測生成しない。ただし本番Resolve経路では、教師DRTをコードで読み、既知の構造と限定置換だけから新規DRTを生成する。DRPはResolve自身のexport/readbackを正本とし、手書き生成や元ファイル上書きはしない。
  • `GrabStill()` を使わない
  • 素材を再エンコードして API の制限を回避しない
  • スクリプトからプロジェクトを切り替えない
  • 推測で数値を埋めない。測っていないものは「未実測」と出して止める

12. 障害と復旧
12-1 Quit() が効かないことがある

Resolve.Quit() も、ウィンドウへの通常の閉じる要求も無視されることがある。

ダイアログも出ていない。API からの終了手段が無い状態になる。

12-2 強制終了する前に必ずバックアップを取る
pm.ExportProject(プロジェクト名, str(出力先))   # .drp

実測: 48.8MB / 29 秒。これをせずに強制終了すると、直前に作ったタイムラインが消える。

実際に 1 本失った(SaveProject() が False を返していたのが前兆だった)。

12-3 再起動後はプロジェクトを読み込む

起動直後はプロジェクトマネージャ画面で GetCurrentPage() が None。

pm.LoadProject(名前) で開いてから作業する。


13. 版ごとの記録
版日付測った人備考
Studio 21.0.3.00072026-08-07〜08Claude(浅尾の実機)本文の全項目
14. 23.976fpsをSequenceSetupまで通す正規経路(2026-08-14)

23.976fpsは、画面のタイムライン設定だけを23.976にする話ではない。入力の実測、字幕TC、cutFrames、DRTの外側タイムライン、SequenceSetup、Resolveの保存後読戻しを同じ値で通す。

  • 生MP4/音声の実測証拠から 24000/1001 を確定する。未測定の素材へ一律に適用しない。
  • 字幕TCを上流にして、半開区間 [startFrame, endFrame) の cutFrames を作る。秒を丸めた24fpsのplanを後から23.976へ読み替えない。
  • build_short_drt.py は測定済みの23.976値で外側のショートタイムラインを生成する。現在の24.0生成物は旧証跡であり、23.976版の完成品とはみなさない。
  • 既存DRTを補正する場合は、tools/resolve-adapter/set_drt_frame_rate.py で対象タイムラインの <FrameRate> と FieldsBlob 内の SequenceSetup を同時に更新する。24000/1001 のレートコードは23である。片方だけを変更しない。
  • 新規Resolveプロジェクトへ読み込み、保存後にclose/reopenする。GetSetting('timelineFrameRate') と、プロジェクト/タイムラインの設定が23.976へ戻っていることをAPIで読み戻す。読み戻しが無い間は完成判定にしない。

render probeでは、1秒のMark In/Out計算に使うフレーム数は整数24としてよいが、証跡のframeRateは23.976の実値を保持する。23.976を24へ丸めた報告は、FPSのreadback証拠として使わない。

実行経路の最小例は次のとおり。--in-place は元DRTを上書きするため、出力先とハッシュを先に固定する。

python tools/resolve-adapter/set_drt_frame_rate.py `
  --input C:\tmp\shorts-23.976\Part1_P3.drt `
  --output C:\tmp\shorts-23.976\Part1_P3_23976.drt `
  --fps 24000/1001

この手順はSequenceSetupを書き換える道具であり、字幕TC、連続素材、Angle、音声の正しさを自動的に証明するものではない。各証跡とResolve readbackをそろえて初めて昇格する。

DaVinci Resolve 制作フロー

この手順の正本は、Master SRT → 構成案 → カット/話者/反応 → NLE読戻しである。固定FPSやAPI応答だけで完成とは判定しない。

1. 入力
  • Master SRTの時間を字幕とカットの正本にする。
  • 対象素材のFPSはffprobeまたはResolveで実測し、$sourceFpsへ入れる。24fpsや23.976fpsを全素材へ固定適用しない。
  • 構成案は dev/short-plan/Part1.json、候補の正本は .plan-drafts/<Part>.compact.json を使う。
  • 短尺の生成基準は、最新の意味選定JSON(.plan-drafts/<Part>.semantic-selection.json)の keep にある source range との一致だけとする。Score、尺、タイトル、構成案の抽出は source range を追加・削除・補間する根拠にしない。
$sourceFps = <ffprobe または Resolve の実測値>

npx tsx scripts/build-shorts-from-master.mts `
  --part Part1 `
  --master .transcripts/Part1.master.json `
  --plan dev/short-plan/Part1.json `
  --selection .plan-drafts/Part1.semantic-selection.json `
  --out dev/shorts-from-master/<run-id> `
  --timeline-fps $sourceFps `
  --source-fps $sourceFps `
  --audio-manifest dev/audio/Part1_integrated_profile_v1.report.json `
  --selection-boundaries hard `
  --allow-provisional

--allow-provisionalは候補生成だけを許可する。productionReady=false、話者・反応・Angle・音声の未解決が残る状態でResolveへ本番適用しない。

2. タイムライン構成

Videoは BG / Angle / Slide / Overlay、Audioは Dialogue / BGM / SE を基幹列とする。必要列数はPartの素材構成から決め、空列を無目的に増やさない。

  • Angleはマルチカムの切替情報、話者フォーカス、位置・ズームを別々に記録する。
  • 同じカメラが分割ファイルになっている場合はメタデータと時間で統合候補を作る。
  • 話者、reaction、聞き手カット、笑い、字幕表示尺は構成案のカット単位へ付ける。
  • SlideがないPartではSlide列を作らず、必要な挿入素材だけをOverlayまたはSlideとして登録する。
3. Resolve適用と確認

生成DRTは候補であり、次の順番で確認する。

  • --selection に渡した最新意味選定JSONの keep と、生成された各短尺の source range が完全一致していることを確認する。欠落、追加、補間、境界の相違があれば停止する。
  • DRTをResolveへ読み込む。
  • Native subtitle、Text+、Fusion、MediaRef、Angle、音声列を構造として確認する。
  • close/reopen後に同じ内容を読戻す。
  • Master SRTの時間、実測FPS、字幕表示尺、カット数、話者・反応フラグを比較する。
  • 音声マニフェストがpass、productionReady=true、読戻し証跡が揃ったものだけを本番候補に昇格する。

APIのAppend/Buildだけで構造保持を証明したことにはしない。Premiere、CapCutも同じTimeline IRを入力にし、各NLEの読戻し検証が終わるまで互換性を「完了」と表示しない。

DRTの構造と未知ID
known
  • DRTはZIP/XMLを含むResolveのタイムライン交換ファイルである。
  • timelineとSeqContainerの対応、Video / Audio / Subtitle track、clipのStart / Duration / In / Outを読み取れる。
  • SequenceSetup、FieldsBlob、EffectFiltersBA、grade Body、CompositionBA、MediaTimemapBAを解析対象にできる。
  • CompositionBAは4-byte lengthとzlib展開でFusion本文を抽出できる。
  • MediaTimemapBAにはv1辞書構造とv2の9/41-byte layout、float64 offset、reserved bytesがある。
  • Resolve 21.0.3.0007から埋込みprotobuf descriptorを抽出し、field pathを記録できる。
candidate
  • kind=4 / param=47 → RotationAngle
  • kind=6 / param=54/55 → CropLeft / CropRight
  • kind=124 / param=97/98 → AudioFadeIn / AudioFadeOutFrames

これらは単一操作のResolve差分が不足しているためcandidateのままにする。

unknown
  • Resolveが公開していないnumeric kind / dbidの意味名
  • MediaTimemapのYMin / YMax / XMaxと一部slotのUI上の意味
  • grade Bodyのnumeric leafと実際のGUI項目の完全な対応
  • DRTだけに存在するIDを、公式APIの項目名へ推測で割り当てること
  • Part1旧DRTのmulticam selector値を、Angle 1/2や人物名へ直接割り当てること
実機から読めるもの(2026-08-22 実測 / Resolve 21.0.3.7 Studio)

幾何は Resolve の API から直に読める。DRT を復号する必要は無い。

  • `TimelineItem.GetProperty('ZoomX' | 'ZoomY' | 'Pan' | 'Tilt' | 'RotationAngle' |

'CropLeft' | 'CropRight' | 'CropTop' | 'CropBottom' | 'Opacity' | 'AnchorPointX' | 'AnchorPointY')`

  • Part1 の composite 30 本で実測した値(トラックごとに固定):
トラック役割ZoomXPanTiltCropTop / CropBottom
V3Fujii0.91+274−59454.4 / 449.2
V4Nabe0.95−286−52476.0 / 462.0
V5Asao0.95−129−62472.0 / 477.0

3 本とも同じ素材(Part1 - Angle 1 / Angle 2)を横位置だけ変えて切っている。

読めないもの(この日の実測)

  • UI 系(OpenPage / SetCurrentRenderFormatAndCodec / AddRenderJob)は

背景から起動した Resolve では全部 `None` を返す。描き出しには対話セッションが要る

  • GetProperty も時間が経つと None を返し始めた。読めた値と読めなかったことを分けて数える

`Pan` の換算式は未確定。符号がどちらへ動かすかを実機の描き出しと 1 度突き合わせるまで、

クロップを別の道具(ffmpeg 等)で再現してはいけない(正本「正規化された値は基準を推測しない」)。

宣言とのズレ(2026-08-22 実測)

templates/shorts/katto/Part1.json は角度ごとに幾何を並べているが、

実機は人ごとのトラックで持っている。そのため片方から他方を引けない。

宣言 angle 1 → trackIndex 2(幾何を 2 つ持つ: pan −129 と pan 274)

angle 2 → trackIndex 3(pan 64 / cropTop 846)

実機 V3 pan +274 / V4 pan −286 / V5 pan −129

`pan −286`(Nabe)は宣言に無い。trackIndex も一致しない。

scripts/audit-template-vs-resolve.py がこれを全数で検算する(Resolve の実機が要る)。

実装ルール

drt_decode.pyは解析値と意味付けを分離する。意味名が未確定の値はunknownとして保存し、検証器がunknownを正解として通さない。新しい意味名を追加するには、公式仕様または1変数だけを変えたResolve実機差分とread-backを残す。

参照:

  • scripts/audit-template-vs-resolve.py(宣言と実機の全数突き合わせ)
  • tools/resolve-adapter/drt_decode.py
  • tools/resolve-adapter/drt_control_diff.py
  • docs/archive/DRT_CONTROL_DIFF_REPORT.md(2026-08-10 時点の調査報告。規則ではない)
  • manual/L4_tools/davinci_resolve_api.md §9
時間軸・字幕・マルチカム
known
  • Part1の候補は30本のショートで構成される。
  • ショート間の空白は時間値を正本とし、対象timelineのFPSでframe化して検証する。Kattoの現行素材候補は23.976023976fpsであり、旧24fps DRTはhistorical candidateとして扱う。360Fは24fps固定のグローバル値ではない。
  • 字幕は合成SRTの一括投入が必要で、cue単位のAppendは記録位置を壊す可能性がある。
  • WaveformFocus候補は字幕810/810、start/duration差0F、verify_short.py 17 passed / 0 failedだった。
  • マルチカムの重複は同一target区間の先勝ちで統合する。
  • 素材FPSはAppend前にtimeline FPSへ変換する。
  • 話者ASRラベルとカメラAngleは別情報であり、片方からもう片方を推測しない。
candidate
  • 波形照合による話者フォーカスは、known区間だけを自動適用する。
  • candidate / unknown区間はwideへ戻す。
  • 強い波形不一致だけで話者や笑いを確定しない。
検証順
  • timeline名、FPS、resolution、track数
  • short数、開始位置、360F gap
  • subtitle count、start、duration
  • source frame / record frame
  • スクリーンショットによる画の確認

参照:

  • tools/resolve-adapter/verify_short.py
  • tools/resolve-adapter/verify_subtitle_timing.py
  • dev/resolve-live-20260808/*subtitle-verify.json
  • [docs/SOT.md](../../../docs/SOT.md) から辿れるTimelineIR・Project profile

素材FPSはProject Masterのsource-derivedを既定とし、Media Registryで実測したCFRの分数をTimelineIR・編集プリセットへ渡す。実測前の24や23.976を推測で固定しない。例外的にexplicitを選ぶ場合は、DRP/メディア証跡と一致するFPSを必須入力にする。

Audio authority and derived DSP
Current contract

The current production contract is:

  • A1 Source · Dialogue (muted): original Resolve Audio TimelineItems retained for Fairlight and source-range audit.
  • A2 Dialogue · Master: one hash-verified integrated WAV generated from the raw microphone sources.
  • A3 BGM: original music items with source and record ranges.
  • A4 SE: sound-effect and theme items with source and record ranges.

A short cuts A2–A4. It does not regenerate or replace the original source

audio. A1 remains available but muted so the raw dialogue is not double-played.

Promotion gate

Audio generation and Resolve promotion stop when any item is missing:

  • authoritative part-level raw microphone map;
  • source file existence and SHA-256;
  • synchronization and speaker mapping with unresolvedCount=0;
  • completed integrated-audio report;
  • integrated WAV SHA-256;
  • A2 DRT readback pointing to that exact WAV;
  • DRT / DRP / tl_map hashes recorded in the run manifest.

The read-only gate is

[audit_drt_audio_authority.py](../../../tools/resolve-adapter/audit_drt_audio_authority.py).

The overlay probe cannot use --production unless this gate passes.

Verified and blocked parts
  • Part1 has an authoritative three-microphone map in

dev/audio/tl_map.json. Its completed report is

dev/audio/Part1_integrated_profile_v1.report.json, and the output is

dev/audio/Part1_integrated_profile_v1.wav (-14.0 LUFS / LRA 2.1 / TP -1.0).

This is an audio-derivative verification only; it does not certify the full

visual timeline.

  • Part11 has teacher/reference audio topology, but no verified raw-mic map and

no completed integrated-audio manifest. Part11 integrated audio is therefore

unverified/unknown; generation must stop instead of reusing OP/camera

fragments or guessing the microphone mapping.

DSP policy

EQ, gate, leveling, compression, limiting, and loudness normalization are

derived processing. Their profile, measured I/LRA/TP, input hashes, output hash,

and comparison result are stored separately. -14 LUFS is a delivery target,

not proof that the source dynamics were preserved. A/B listening and numerical

comparison are both required before choosing a less-compressed profile.

Relevant implementation:

  • tools/audio/integrated_audio.py
  • tools/audio/integrated_master_audio.py
  • templates/audio/katto-v1.json
  • templates/audio/katto-v2-open.json
  • tools/resolve-adapter/apply_master_audio_lane.py

Historical claims that A1 original audio alone is the current editable master

are retained in docs/AUDIO_SOURCE_PRESERVE.md as historical evidence. They do

not override the A1-muted / A2-integrated contract above.

映像・Fusion・話者フォーカス
known
  • 1080×1920の縦型タイムラインを基準にする。
  • 素材→タイムラインのスケーリングと、タイムライン→出力のスケーリングは別設定である。
  • 正典の入力スケーリングはcenterCrop相当で、GUIの実測フレームを基準に判定する。
  • Part1のタイトル段はtrackLayoutを正典とし、V8/V9/V10を確認する。
  • Fusionのline、scrim、Text+は.compとして保存し、DRTのComposition本文と画で確認する。
  • dev/fusion/titlesにはPart1〜Part30のタイトル候補がある。
candidate
  • .master.jsonとraw audio波形を照合した話者フォーカス。
  • waveform overrideは候補として保存し、global speaker mapを上書きしない。
  • 明示的な笑いマーカーがない場合、音量や無音だけからreaction cutを作らない。
  • Part1旧DRTのV2 45区間は、話者フォーカス列の素材としては使えるが、V2のopaque selectorをAngle 1と断定する根拠にはしない。
  • V2〜V5のPart1プロファイルはframingColumn(wide/Fujii/Nabe/Asao)であり、multicamのcameraAngleSelectionとは別の証拠として扱う。
camera selection / framing / timeline role
  • cameraAngleSelection: Resolve multicamのAlt+1/Alt+2に相当するselector。DRTのnumeric値がopaqueならunknown。
  • framingColumn: 話者・全体・freeのフォーカス、crop、position、zoomを持つ出力列。
  • timelineRole: BG / Angle / Slide / OverlayというNLE共通の意味列。番号はPartごとに異なる。

この3層を一つのV2/V3番号へ圧縮しない。自動でspeaker→cameraを昇格するには、元multicam定義、波形、代表フレームの三系統を揃える。

目視検証
  • 中央フレームだけでなく、Asao / Fujii / Nabeの代表フォーカスフレームを保存する。
  • 背景、映像帯、クロップ、字幕、Fusion line、logo、Text+ titleを同一フレームで確認する。
  • 数値の読戻しだけでなく、独立したPNGを比較する。

参照:

  • tools/resolve-adapter/apply_fusion.py
  • tools/resolve-adapter/learn_angle_geometry.py
  • tools/resolve-adapter/learn_multicam_angles.py
  • dev/frames/waveform-focus-final/
  • templates/shorts/katto/fusion/
HDRの根拠と未完了ゲート
known
  • HDRの正本はタイムライン単体ではなくResolveプロジェクト設定である。
  • Sony S-Log3 / S-Gamut3 / 10-bit素材を前提に調査している。
  • HLG候補ではRec.2020、Rec.2100 HLG、1000 nit作業輝度、203 cd/m² reference whiteを候補値として記録している。
  • ITU-R BT.2100、BT.2408、BT.2166、Blackmagic公式資料を根拠として整理している。
  • verify_hdr_evidence.pyは公式資料、チャートhash、実レンダーmetadata、MaxCLL / MaxFALL、HDR Light Level Report、Resolve close/reopen read-backを要求する。
candidate
  • Katto-Codex-HDR-Candidate.drp
  • Katto_Codex_HDR_HLG_Candidate_20260808_v4.drp
  • Part1_Shorts_Final_Integrated_HLG_Candidate_20260808_v4.drt

これらは旧候補であり、存在するだけでは最終HDRとは呼ばない。

未完了 / unknown
  • HDR Light Level Reportの実機PDFとhash
  • 実HDRレンダーのRec.2020 / HLG / 10-bit metadataの完全なread-back
  • Resolve close/reopen後のプロジェクト設定の完全一致
  • PQを採用する場合のマスタリングディスプレイ、MaxCLL、MaxFALL

上記が揃うまで、HDRはcandidateまたはblockedとして扱う。推測値でDRP/DRTを完成版に昇格させない。

参照:

  • docs/HDR_PROJECT_CANDIDATE.md
  • docs/HDR_RESOLVE_PROCEDURE.md
  • tools/hdr/verify_hdr_evidence.py
  • resolve_hdr_project_apply.py
Official workflow research — Resolve, audio, interchange, and publishing

Research date: 2026-08-09 JST

This note separates vendor requirements from project policy. A candidate is not promoted to verified merely because a setting exists in a DRP/DRT.

DaVinci Resolve HDR

The official Resolve workflow is based on color management with explicit input, timeline, and output color spaces. The production candidate for S-Log3 / S-Gamut3 / 10-bit material is:

camera input -> RCM / DaVinci Wide Gamut Intermediate -> HDR output (PQ or HLG) -> tagged render -> report and playback evidence

  • HDR10 uses PQ / SMPTE ST.2084 with an absolute luminance target.
  • HLG uses Rec.2100 HLG and does not rely on HDR10 static metadata in the same way.
  • The project must record input color space, timeline color space, output color space, transfer function, mastering luminance, bit depth, codec, and file tags.
  • HDR scopes must be inspected in nits. An HDR Light Level Report is a PDF QC artifact; it is not itself the file's HDR metadata.
  • A calibrated external HDR display through a supported video I/O device is the color-critical check. A PC preview is not sufficient evidence.
  • Test charts and HDR Light Level Reports are project QC requirements here, not claims that Blackmagic makes every one of them universally mandatory for every delivery.

Sources: [Resolve 21 Reference Manual](https://documents.blackmagicdesign.com/UserManuals/DaVinci_Resolve_21_Reference_Manual.pdf), [Resolve 21 New Features Guide](https://documents.blackmagicdesign.com/SupportNotes/DaVinci_Resolve_21_New_Features_Guide.pdf), [DaVinci Wide Gamut Intermediate](https://documents.blackmagicdesign.com/InformationNotes/DaVinci_Resolve_17_Wide_Gamut_Intermediate.pdf).

Audio master policy

SOURCE_RAW is immutable. EQ, microphone gate, compression, limiter, loudness normalization, and the integrated mix are derived artifacts with their own hashes. The derived file is never used as a replacement for the original timeline source.

  • Measure with an ITU-R BS.1770-compliant meter and keep integrated, momentary, short-term, LRA, sample peak, and true peak separate.
  • EBU R 128 gives -23 LUFS and -1 dBTP as broadcast-oriented references. It says LRA is not recommended for programmes shorter than one minute because the sample count is too small.
  • -14 LUFS / -1 dBTP is a selectable delivery profile, not a universal YouTube rule. YouTube's upload guidance does not define a single LUFS target.
  • Gate for microphone bleed and the loudness-meter relative/absolute gate are different mechanisms and must remain separate in the profile and report.
  • Re-measure after AAC/Opus export when the delivery path uses lossy encoding.

Sources: [ITU-R BS.1770-5](https://www.itu.int/rec/R-REC-BS.1770-5-202311-I/en), [EBU R 128-2023](https://tech.ebu.ch/files/live/sites/tech/files/shared/r/r128.pdf), [YouTube recommended upload encoding settings](https://support.google.com/youtube/answer/1722171).

Interchange boundary

OTIO is the editorial cut interchange layer: clips, timing, tracks, transitions, markers, metadata, and external media references. It does not embed the media and it is not a lossless representation of Resolve Fusion/Text+, Premiere MOGRTs, plug-ins, or application-specific easing.

The project therefore keeps three layers:

  • TimelineIR / OTIO for editorial decisions.
  • Caption IR plus SRT for text and timing; SRT import is followed by Resolve readback verification.
  • Motion IR for text styling, keyframes, and easing, converted by an NLE-specific adapter or baked to alpha video when fidelity cannot be guaranteed.

DRP/DRT are handled only through Resolve's own import/export APIs. Their binary internals are not a write target.

Sources: [OpenTimelineIO documentation](https://opentimelineio.readthedocs.io/en/latest/index.html), [Adobe export to Final Cut Pro XML](https://helpx.adobe.com/premiere/desktop/render-and-export/export-files/export-a-project-as-a-final-cut-pro-xml-file.html), [CapCut subtitle import](https://www.capcut.com/help/how-to-import-subtitles).

Publishing API boundary

Upload, processing, publish, and public readback are separate states. The queue stores a platform-neutral pathname and immutable source/fix hashes; it creates a short-lived signed URL only when a platform requires one.

  • YouTube supports resumable upload and status.publishAt; readback must check processing status and privacy/publication state.
  • X, Threads, Instagram, and TikTok have different asynchronous media flows; do not model them as a single synchronous publish() call.
  • For platforms without an official API schedule field, the scheduler calls the publish API at the due time and records the provider receipt.
  • note and Spotify remain UI/RSS boundaries until an approved official write API is available.

Sources: [YouTube videos.insert](https://developers.google.com/youtube/v3/docs/videos/insert), [YouTube video processing status](https://developers.google.com/youtube/v3/guides/implementation/videos), [X Create Post](https://docs.x.com/x-api/posts/create-post), [Threads publishing](https://developers.facebook.com/docs/threads/reference/publishing), [TikTok Direct Post](https://developers.tiktok.com/doc/content-posting-api-reference-direct-post).

Implementation gates
  • known / candidate / unknown is mandatory for unresolved Resolve IDs and NLE capabilities.
  • No audio output is accepted with an unresolved source interval, and the run records DRT/DRP/tl_map/profile hashes.
  • HDR is blocked until settings, tagged render, chart evidence, HDR Light Level Report, and close/reopen/readback evidence are present.
  • A successful compile or API response is not a completed production run; the final gate requires structure readback, render evidence, and the appropriate platform receipt.
Resolveパラメータ工学:公式APIとDRT/DRP逆解析
目的

Resolveの設定値を「触れるもの」と「意味がまだ確定していないもの」に分け、アプリから編集要求を作り、Resolve実機で適用して、API読戻し・DRT/DRP・スクリーンショットの三方向で検証する。

教師データは、成功実績のある次のDRTを基準にする。

  • D:\zFilms Dropbox\z Films\Directors'Katto\drp\export-20260807\reference\Part10_Short_old.drt
  • D:\zFilms Dropbox\z Films\Directors'Katto\drp\export-20260807\reference\Part11_Shorts_old.drt
  • D:\zFilms Dropbox\z Films\Directors'Katto\drp\export-20260807\reference\Part12_Shorts 2_old.drt

同じファイル名のDownloads版はバイト同一ではないため、教師データとは別の候補として扱う。

二つの判定軸
軸正本判定できることできないこと
正攻法Resolve Developer Scripting README(インストール済み公式資料)Project.SetSetting、Timeline.SetSetting、TimelineItem.SetProperty、Fusion composition APIの公式キーと型DRT内部protobufのフィールド番号を自動的に意味付けること
逆解析Part10/11/12のDRT、Part1差分、controlled one-variable diffXML構造、トラック、時刻、既知のEffectFilters kind/id、opaque payloadの変化未検証IDの名称推測、opaque protobufの直接書換え

両軸は同じ意味に潰さない。公式キーがknownでもDRT raw IDがcandidateなら、公式APIで適用し、raw IDはcandidateのまま追加証跡を要求する。unknownはアプリから編集できない。

現在のパラメータ台帳

機械可読な正本は [lib/nle/resolve/parameter-registry.ts](../../../lib/nle/resolve/parameter-registry.ts) である。主なknown項目は次の通り。

  • Project/Timeline: timelineFrameRate、解像度、timelineInputResMismatchBehavior
  • TimelineItem: Pan、Tilt、ZoomX、ZoomY、ZoomGang、RotationAngle、4辺Crop、Opacity
  • 逆解析で既知だが公式編集面が未確定の音声gain/fadeはaudit-only
  • Text+のraw protobuf IDはunknown。Text+は.compとFusion compositionの公式面で編集する

値の範囲は、公式資料に記載された範囲をマニフェスト生成時に検証する。素材サイズ依存のPan/Tilt/Cropは、タイムライン解像度を明示しない要求を拒否する。

アプリからResolveへ渡す経路
  • /projects/{slug}/settings/resolve-parametersでタイムライン、対象トラック、開始フレーム、値を入力する。
  • アプリが公式status、reverse status、型、範囲、対象クリップを検証する。
  • immutableなresolve-parameter-manifest/1をprivate artifactとして保存し、SHA-256を返す。
  • tools/resolve-adapter/apply_parameter_manifest.py --manifest ...をdry-runする。
  • 本番の書き込みはコード側DRTを正本とする。apply_fusion.py、apply_geometry_contract.py、apply_master_audio_lane.py、apply_dialogue_textplus.pyなどのResolve API writerは通常の--goでは停止する。比較実験だけ、使い捨てタイムラインに対して--lab-only-api-writer --goを明示する。
  • 各SetSetting/SetPropertyの直後にGetで読戻し、SaveProject後にDRTを書き出す。
  • Resolveをclose/reopenし、同じtimelineを再読戻しする。DRT/DRP hash、tl_map、代表フレームのスクショをrunへ記録する。
  • API読戻しだけではverifiedにしない。DRT構造と見た目が教師データ/正典スクショと一致した場合のみ昇格する。
逆解析の使い方
python tools/resolve-adapter/drt_teacher_diff.py `
  --teacher "D:\zFilms Dropbox\z Films\Directors'Katto\drp\export-20260807\reference\Part11_Shorts_old.drt" `
  --candidate "candidate.drt" `
  --out dev/resolve-roundtrip-20260809/teacher-vs-candidate.json

known / candidate / unknownを出力し、未知IDをrenameしない。CompositionBA、MediaTimemapBA、FieldsBlob、EffectFiltersBAなどのopaque payloadはcandidateとして記録する。教師DRTをコピーして差分0になることは、コピーの完全性証明であって、Part1が正しい証明ではない。

完成条件
  • 公式APIの対象・型・範囲が台帳にある
  • unknown raw IDを推測していない
  • 対象トラック・開始フレーム・素材解像度がマニフェストにある
  • API読戻しが一致する
  • DRT/DRPとhashを保存する
  • close/reopen後も値・字幕・音声・Fusion/Text+が残る
  • 教師スクショと代表フレームのクロップ、タイトル台、字幕位置、グレーディングを比較する
参照
  • ローカル公式資料: C:\ProgramData\Blackmagic Design\DaVinci Resolve\Support\Developer\Scripting\README.txt
  • [DaVinci Resolve公式マニュアル](https://www.blackmagicdesign.com/welcome/en/W-DRE-03)
  • [Blackmagic Design公式トレーニング](https://www.blackmagicdesign.com/products/davinciresolve/training)
Part11教師ラウンドトリップ

Part11を最初の教師ラウンドトリップにする場合は、次を実行する。

python tools/resolve-adapter/resolve_teacher_roundtrip.py `
  --project __Codex_Reference_Visuals_20260809 `
  --timeline "Part11_Shorts_old 2" `
  --teacher-drt "D:\zFilms Dropbox\z Films\Directors'Katto\drp\export-20260807\reference\Part11_Shorts_old.drt" `
  --teacher-analysis dev/resolve-roundtrip-20260809/teacher-Part11_Shorts_old.json `
  --out dev/resolve-roundtrip-20260809/Part11_teacher_api_roundtrip.json

officialTimelineItemPropertyPresenceはResolveのGetProperty()で見えている値、reverseTeacherAnalysisのEffectFilters statusはDRT側のknown/candidate/unknownである。両者が異なることは失敗ではなく、直接protobufを書き換えず、公式APIで操作してDRTとスクショで確認するための境界である。

Master JSON to Resolve
Source funnel
original transcript JSON
  -> proofread master JSON / master subtitle
  -> approved master cut plan
  -> condensed main-program subtitle
  -> condensed short subtitle per episode
  -> TimelineIR / media registry / audio manifest
  -> code-generated teacher DRT candidate
  -> Resolve import and readback
  -> close/reopen, DRT/DRP hash, screenshot QC

The master JSON and master subtitle are the content and timing authorities.

Condensed subtitle layers are derived outputs. A short SRT is generated from

the short cut's record-time frames; it is never used to invent the cut range.

What the Part12 teacher actually proves

C:/Users/akihi/Downloads/Part12_Shorts 2_old.drt is a useful historical

teacher because it preserves BG / Angle / Slide / Overlay topology, native

subtitle/Text+ structure, Fusion carriers, and measured geometry better than a

generic API-built timeline. The independent check was 15 pass / 2 fail,

including a missing V10 result and an A2 link issue. It is therefore

historical-success/candidate, not a complete production reference.

The similarly named Part12_Shorts.drt is a different artifact and must not be

merged into the teacher profile without identity and graph-hash checks.

Part11 current state

Inputs:

  • .transcripts/Part11.master.json
  • .plan-drafts/Part11.compact.json
  • dev/master-srt-j/Part11.master.srt
  • dev/master-srt-j/Part11.shorts.srt

The current Part11 plan has 42 shorts and 360-frame spacing, but it remains a

candidate until the source-to-cut mapping, approved speaker/angle mapping,

audio authority, and Resolve readback gates all pass.

The file

dev/resolve-live-20260809/Part11_candidate_teacher_overlay_probe_20260809_v12_teacher_text_control.drt

is failed evidence, not the final file. Its decoded audio has A2 with 65 OP or

camera fragments and no integrated Part11 WAV authority. The screenshot from

that timeline must not be used as a completion reference.

Production writer rules
  • Verify master JSON, master subtitle, plan, media registry, and audio manifest

hashes before writing.

  • Copy a teacher DRT in code. Replace only measured known regions: record and

source ranges, approved text payloads, and explicitly verified geometry.

  • Preserve native ST1 subtitle structure. Do not convert captions into PNG or

Text+ unless the teacher itself uses Text+ for that specific design layer.

  • Preserve the teacher's BG / Angle / Slide / Overlay topology and the

angle-selector plus per-angle geometry as separate fields.

  • Resolve API is limited to import, readback, render, screenshot, diff, and

close/reopen verification. It is not the production timeline constructor.

  • Stop before writing when audio has an unresolved interval, when A2 does not

point at the manifest WAV, or when a DRT parameter is candidate or

unknown.

The overlay probe supports lab investigation without an audio manifest, but

its report is probe-only-unverified-audio. --production fails closed until

the A2 gate passes. Passing that gate still does not replace Resolve visual,

subtitle, close/reopen, or screenshot QC.

Promotion definition

masterToShortPlan, short SRT completeness, audio unresolvedCount=0, Resolve

readback, close/reopen readback, DRT/DRP hashes, and representative screenshot

comparison must all be present in the same run manifest. Until then the result

is candidate or blocked, never complete.

Resolve教師データの学習契約
目的

Part10・Part11・Part12のDRTは、数字をコピーするためのテンプレートではない。

成功したタイムラインが成立した条件を、次の4層に分けて再現するための教師データである。

  • 素材同一性: 同じ実ファイル、解像度、FPS、音声源が使えること。
  • 編集グラフ: BG / Angle / Slide / Overlay、各トラック、カットの順序、マルチカム選択が成立していること。
  • 表現形式: native subtitle、Text+、Fusion、Grade、EffectFiltersBAが教師と同じ役割で存在すること。
  • 実機証跡: DRT読戻し、Resolve close/reopen、代表フレーム、音声A2、ハッシュが一致すること。
Part12がそのままPart11にならなかった理由

Part12の参照版とDownloads版はバイトが異なるが、素材・編集グラフ・トラック構造は一致した。

これはResolveがID、コンテナ名、時刻、MediaPoolメタデータを再生成するためであり、バイト差分だけで教師失敗とは判定できない。

一方、Part11候補は素材集合、内容選択、編集グラフ、トラック本数が教師と一致しない。

さらに候補はA2にカメラ/OP断片を持ち、統合WAVを音声正本として指していない。

したがって、Part12のTransform値やText+の一部を移植しても、成立条件を再現したことにはならない。

1変数実験の規則
  • 1回の実験で変更する宣言領域は1つだけにする。
  • DbId、UUID、SeqContainer名、アーカイブメンバー、opaqueなFieldsBlob / CompositionBA / MediaTimemapBA / EffectFiltersBAを、意味名を推測して書き換えない。
  • 差分に複数のフィールド族、アーカイブ構造変更、unknown IDが出たら自動で blocked にする。
  • バイト同一は「コピーが完全」の証明であり、別シーンの正しさの証明ではない。
  • APIの成功戻り値は証跡ではない。構造生成は本番経路にせず、APIはimport/readback/render/screenshot/close-reopenに限定する。
自動判定の分離

tools/resolve-adapter/teacher_graph_contract.py は、教師と候補を次の2つに分けて返す。

  • semanticStatus: トラック集合、native字幕、映像ジェネレータ、Geometry payloadの文脈が維持されているか。
  • productionStatus: 上記に加えて、統合音声manifestとResolve実機の再読戻しがあるか。

この判定器は dev/knowledge/production-route-policy-20260810.json の

drt-teacher-graph-contract として登録された監査専用ルートである。

semanticStatus=passでもproductionStatus=blockedになり得る。これは正常な安全側判定である。

音声未解決、字幕のPNG化、Angle selector欠落、素材FPS未確定、close/reopen未確認のいずれかがあれば本番昇格しない。

実行例
python tools/resolve-adapter/teacher_graph_contract.py `
  --teacher "D:\zFilms Dropbox\z Films\Directors'Katto\drp\export-20260807\reference\Part12_Shorts 2_old.drt" `
  --candidate candidate.drt `
  --out dev/knowledge/teacher-graph-contract.json
Code-side DRT route (current canonical boundary)

tools/resolve-adapter/code_side_teacher_drt.py is the only code-side DRT

patch route currently admitted to the cleaned knowledge base. It copies the

teacher ZIP without XML reserialization and changes only declared safe scalar

fields. Each clip patch requires the exact DbId and the exact old value.

The following are blocked in this route: FieldsBlob, EffectFiltersBA,

MediaTimemapBA, CompositionBA, CurrentSelectorIdx, MediaRef, grade,

Fusion, subtitle payloads, and audio graph fields. These are the fields that

previous API/probe routes changed too broadly and they are the reason a

Part11 candidate can look structurally complete while being visually wrong.

The Part12 copy proof is only a byte-level proof. It is classified as

candidate/unverified until Resolve import, DRT readback, close/reopen, and

screenshot comparison pass. A same-material Part12 graph may pass the

semantic contract; a Part11 graph with different observed video materials is

blocked unless an independent geometry/source-context contract is supplied.

このレポートはDRTを書き換えない。productionStatus=passにするには、別途audio manifestとResolve close/reopen証跡を同じRun Manifestに記録する必要がある。

Part11 source-specific teacher learning

Part12の成功例をPart11へ数値コピーしてはいけない。Part12で確認できたのは、同一タイムラインの構造をコード側で保ったまま複製できることだけである。Part11は素材・カット数・字幕数・音声レーンが異なるため、教師から再利用するのは検証済みの表現だけに限定する。

Part12のコード側コピーが成立した条件は、ZIPの変更がMediaPoolのタイムライン名だけで、MediaRef、selector、geometry、Fusion、grade、subtitle、audio、不透明payloadを変更しなかったことである。これは完全コピーの証明であり、別Partへの移植可能性の証明ではない。

Part11では、教師が8ショート・ST1 191・A1 97+A2 8なのに対し、候補は42ショート・ST1 1,091・A2 65+A3 6である。同名のC0355 MulticamでもDbId、UniqueMediaPoolItemId、FieldsBlob、nested Sequence、SeqContainer hashが異なる。したがって、素材名一致だけではAlt+1/Alt+2 selectorの同一性を証明できない。

現在のコード側ラボ経路

tools/resolve-adapter/drt_teacher_overlay_probe.py は候補側のカット範囲、MediaRef、マルチカムselector、音声、ST1字幕を保持し、教師の装飾表現を限定移植する。

  • V2(Angle 1)には教師V2、V3(Angle 2)には教師V3を別々に適用する。同じマルチカム素材でもV2/V3のTransform値を混ぜない。
  • V2/V3はEffectFiltersBAのkind/parameterスキーマが完全一致しない限り停止する。素材解像度・input fit・座標系が違う場合、同じ数字を入れても同じ画面位置にならないためである。
  • V1/V6/V7の固定装飾とV8/V9のRich generatorは、教師の表現を候補の時間・本文へ限定移植する。
  • ST1の字幕はnative Sm2TiGenerator、PrettyType=Subtitle、RenderTextEnabled=trueを維持する。字幕をText+やPNGへ変換しない。
  • MediaRef、FieldsBlob、CompositionBA、MediaTimemapBA、selector、音声は候補側の正本を保持し、未照合の置換をしない。
生成後の読戻し前ゲート

tools/resolve-adapter/verify_teacher_overlay_probe.py は次を読み取り専用で検証する。

  • V2/V3等に教師EffectFiltersBAが適用されていること。
  • 候補のStart/Duration/In/Out/MediaStartTime/MediaRef/selector/FieldsBlob等が変わっていないこと。
  • ST1がnative字幕のままであること。
  • DRTの対象XMLとMediaPool以外のopaque archive memberが変わっていないこと。

このゲートがpassしても、生成物はcandidateのままである。統合A2 WAVのhash、Resolve import/save/close-reopen、DRT読戻し、クロップ境界を含むスクリーンショット比較が揃うまでproductionへ昇格しない。

今回のPart11候補は、V2/V3のAngle別ジオメトリ適用と不変領域検証までは通過した。しかし教師と候補でV4/V8/V9の素材・生成器、字幕件数、音声レーンが異なるため、全体のteacher graph contractは引き続きblockedである。

Feedback cycle

Each round is recorded by tools/resolve-adapter/teacher_feedback_cycle.py.

It keeps the narrow DRT verifier, semantic graph, teacher applicability, audio

authority, and Resolve round-trip as separate gates. A passing narrow verifier

never promotes a candidate by itself; the blocked reasons and next actions are

written to dev/knowledge/part11-feedback-cycle-*.json.

Current gate correction (2026-08-10)

The current record is dev/knowledge/part11-feedback-cycle-20260810-v4.json.

The v1 record is historical and superseded. A Resolve round-trip gate is not

passed by a screenshot, a DRT file, or file existence alone. Accepted evidence

must be structured native readback with verified import/save, close/reopen,

stable-field comparison, DRT/DRP identity, and screenshot comparison. The

current Part11 candidate has no such evidence and remains blocked from

production.

全Part教師DRTの標準フォーマット

更新: 2026-08-10

目的

Partごとの新規ショートは、空のタイムラインをAPIで組み立てない。まず同じ素材系統の教師DRTを選び、アーカイブをコード側でコピーする。今回の標準処理では、教師DRTの構造を保持したまま、Resolveで識別できるタイムライン名だけを変更する。

旧batch manifestは現行treeに無く、再び正本へ戻さない。採用中の証跡だけを

[resolve-evidence-registry-20260809.json](../../../dev/knowledge/resolve-evidence-registry-20260809.json)

から辿り、manifestが無い実績は未測定として扱う。

実行
python tools/resolve-adapter/format_teacher_drt_batch.py `
  --manifest dev/knowledge/teacher-drt-batch-manifest-20260810.json `
  --out-root dev/knowledge/teacher-baselines/20260810 `
  --report dev/knowledge/teacher-drt-batch-format-20260810-v1.json `
  --workers 8

2026-08-10の実績は 12/12 formatted、DRT read-only batch decode は 12 files / 0 errors である。各出力は dev/knowledge/teacher-baselines/20260810/PartN.teacher-baseline_20260810.drt に置く。

保持するもの
  • FieldsBlob、CompositionBA、MediaTimemapBA、EffectFiltersBA
  • MediaRef、multicam selector、grade、Fusion、native ST1字幕
  • Dialogue / BGM / SEを含む教師側の音声トラック構造
  • BG / Angle / Slide / Overlay / Text / Logoのトラック順
  • ZIP内の上記以外の全メンバーのペイロード

変更するのは MediaPool/Master/MpFolder.xml の明示したタイムライン名だけである。クリップのDbId、Transform、Crop、Zoom、FPS、MediaRef、字幕文言、音声参照、未知DRT IDはこの処理で変更しない。

ステータスの意味

formatStatus=pass は「教師アーカイブの名前だけを安全に整え、他のメンバーが保持された」という意味である。完成品・本番正本を意味しない。今回のPart1〜12の選定はすべて teacherStatus=candidate とし、次の確認が終わるまで昇格させない。

  • ResolveへDRTをimport-onlyで投入する
  • ST1、Text、Logo、Fusion、BG、Angle、音声トラックを画面とreadbackで確認する
  • close/reopen後に同じタイムラインとpayloadを再読戻しする
  • Master JSONのcut、素材MediaRef、実測FPS、話者↔Angle、source座標系のGeometry、統合Dialogue Master WAVを別ゲートで照合する
  • 教師スクリーンショットとの比較が通ったものだけ known / production候補へ進める

DRTの容量は判定基準にしない。Part12の教師は約326KB、Part11は約382KBであり、候補側が1MB超でも容量だけでは失敗原因にならない。重要なのはSeqContainerのクリップ密度、ST1件数、MediaRef/selector、opaque payload、Resolveの応答性である。Resolveが応答しない場合は再投入せず、該当候補を隔離する。

禁止事項
  • CreateEmptyTimeline、AppendToTimeline、SetSetting、SetPropertyで本番タイムラインを作らない
  • APIで教師構造を再構成しない
  • native字幕をPNGやText+へ変換しない
  • 数値Transformを別解像度・別fit座標系へそのまま移植しない
  • unknownを推測して名前付けしない
  • 出力DRTを上書きしない。再実験は新しい日付・revisionで出す

このバッチは「全Partを同じ入口に揃える」ための教師フォーマット層であり、字幕縮約、話者判定、Angle精査、音声統合、HDR、最終Resolve QCを置き換えない。

生成DRTのResolve読込みゲート

更新: 2026-08-10

結論

生成DRTは、コード側のdecode/narrow verifierがpassしても、Resolveで起動・読戻しできるまでは実行可能な成果物ではない。教師DRTだけが開いていたのは、教師を正本フォーマットとして先に確認し、生成候補はこの実機ゲートを通っていなかったためである。

APIと画面操作の境界
  • Resolve API: 読み取り専用のtimeline名、track、clip、media、setting、item属性の構造QCに使う。
  • コード側DRT: バイト保持コピーと、測定済みの最小パッチに使う。CreateEmptyTimeline、AppendToTimeline、SetSetting、SetPropertyは本番経路で使わない。
  • UI/OS操作: DRTをResolveへ投入し、Viewerの実ピクセル、ST1、Text、Fusion、BG、Angle、黒帯、クロップ、音声波形を確認するためだけに使う。
  • したがって、スクリーンショットはAPIの代替ではなく、APIで表現できない見た目の証跡である。
2026-08-10 Part11 v4の失敗

Part11_MasterJSON_TeacherGeometry_v4.drt はコード側の狭い検証にはpassしたが、Resolve 21.0.3.0007を空の状態から起動しても、生成タイムラインを表示する前にResolveプロセスが終了した。教師ベースラインは4 ZIP entries/1 timeline/673 unique blobsで読戻しできたのに対し、候補は5 ZIP entries/2 timelines/2668 unique blobsであった。

この差は、単にTransform値を間違えたという段階ではない。候補が教師と異なるタイムライン・opaque payload・メディアグラフを持つため、narrowVerifier=passをResolve互換性の証明として扱ってはいけない。旧個別JSONは現行treeに無いため、採用状態は [resolve-evidence-registry-20260809.json](../../../dev/knowledge/resolve-evidence-registry-20260809.json) だけから辿る。

再投入条件
  • 同じ素材系列の、Resolveで読戻し済み教師DRTを1タイムラインの基底にする。
  • ZIPをXML再シリアライズせずコピーする。
  • 変更は旧値・DbId・track・新値が測定済みのフィールドだけに限定する。
  • 候補のtimeline数、SeqContainer数、MediaRef、selector、FieldsBlob、CompositionBA、MediaTimemapBA、EffectFiltersBA、native ST1、Fusion、grade、audio graphを教師との差分として記録する。
  • 1ファイルだけResolveへ投入し、プロセス応答、timeline readback、Viewerスクリーンショット、close/reopenを記録する。
  • どれか一つでも失敗した候補は blocked のまま隔離し、productionへ渡さない。
Part11 マスターレイアウト運用と手動差分フロー(2026-08-10)
目的

Part11は、1本の外側タイムラインに42ショートを順番に配置する。各ブロックはカット、アングル、音声、タイトル、ST1字幕を持つ。P1〜P42の別タイムラインや、Part11_Content_Only_v1のコンパウンド化は完成形にしない。

レイアウトの正本

Katto_Master_Layout_Productionが全Part共通のレイアウト正本である。V1 bg.png、V6 Fusion Composition、V7 Logo_Black.png、V8/V9のRich/Text generatorは、XML/Fusion payloadを再生成せず、そのまま複製する。変更してよいのは各ブロックのStart/Duration、Part固有の映像カット、音声、タイトル文言、ST1文言とタイミングだけである。

今回の安全な基準

自動DRTマージは構造readbackを通っても、Fusion payloadの描画がマスターと一致しないケースがあった。そこでResolveの完成マスターをDuplicateTimelineで複製し、手動修正用の基準を作成した。

  • 基準タイムライン: Part11_MasterCopy_20260810_v1
  • DRT: build/part11-manual-baseline-20260810/Part11_MasterCopy_20260810_v1.drt
  • 共有DRT: D:/zFilms Dropbox/z Films/Directors'Katto/drp/auto-part11-20260810/manual-baseline/Part11_MasterCopy_20260810_v1.drt
  • 範囲: 86400–87822 / 1080x1920 / 24fps
  • 正本トラック: V1/V6/V7/V8/V9、各1 native item。placeholder 0
  • Resolve検証: Save → close → reopen後も構造一致。レンダーで黄色テクスチャ、区切り線、タイトル、ロゴを確認済み
  • 旧版参照: 同じResolveプロジェクトにPart11_Shorts_oldを保持。canonical DRTはD:/zFilms Dropbox/z Films/Directors'Katto/drp/export-20260807/reference/Part11_Shorts_old.drt。比較用コピーはD:/zFilms Dropbox/z Films/Directors'Katto/drp/auto-part11-20260810/manual-baseline/Part11_Shorts_old.drt
手動差分の受け渡し
  • Part11_MasterCopy_20260810_v1を開き、同じ1本の外側タイムラインへPart11の42ブロック、496カット、アングル、A1音声、タイトル、ST1字幕を反映する。
  • V1/V6/V7/V8/V9のレイアウト・Fusion payload・generator設定は触らない。
  • 編集後にDRTをexportし、基準DRTと一緒に保存する。
  • 編集後DRTを受け取ったら、カット/アングル/音声/タイトル/ST1だけを許可差分として比較し、次の自動生成器へ反映する。
  • 編集後DRTが戻るまでは、自動生成物を完成版と呼ばない。この時点で自動側の作業はいったん停止し、手動編集後のDRTを次の入力にする。
P1 importの注意

Part11_Production42_P1 importはResolveが受理した検証用timelineだが、V2とA1がPart11_Content_Only_v1 import 1というTimeline/compound clipになっている。これはflatなカット配置ではないため、そのまま教師データにしない。手動で使う場合は複製後にV2/A1をDecompose in Place(またはnested timelineを開いて外側へ実クリップを移動)し、V2/V3/V4とA1が直接見える状態にしてからDRTをexportする。V1/V6/V7/V8/V9とST1のnative layoutは触らない。

失敗した経路の扱い

以前のv3〜v5はV1/V6/V7/V8/V9やST1の件数が揃っていても、直接レンダーでマスターと違う黒帯・中央映像・崩れたタイトルが出た。したがって、構造件数だけで合格にせず、マスターと同じフレームの直接レンダーを必須ゲートとする。

DRT / DRP 構造と Part11 import gate(2026-08-10)
2026-08-11訂正: 本文中のPart11_MasterCopy_ContentOnly_20260810_v3.drt安全評価は撤回する。MediaPool/Master/MpFolder.xmlがnested EmbeddedAudioVec/<Element>の正規表現切断で未閉鎖だった。現行の実機再開手順は [15_part11_p1_session_handoff_20260811.md](./15_part11_p1_session_handoff_20260811.md) を正とする。
結論

DRT/DRPは特殊な独自圧縮形式ではない。どちらもZIP + DEFLATEで、Resolve固有のXMLとopaqueなバイナリblobを格納する。

  • DRT: 1タイムラインの縮約スナップショット。project.xml、MediaPool/Master/MpFolder.xml、SeqContainer/*.xmlを含む。
  • DRP: プロジェクト全体のスナップショット。多数のSeqContainerとMediaPoolを含む。DRTをDRPへ変換する形式ではなく、別プロジェクトとして開く。
  • 圧縮を解凍できても、Resolveが参照グラフを解決できなければ黒画面・空タイムライン・既存タイムライン返却になる。
何が読めて、何がResolve依存か
層主な内容コード側で扱える範囲
MediaPool/Master/MpFolder.xml素材、Multicam、音声、タイムラインclipMediaRefから必要なMediaPool要素をclosureとして収集する
SeqContainer/*.xmlTrack、clipのStart/Duration/In/Out、字幕、音声数値と順序の監査・正本順の差し替え
FieldsBlobTransform、crop、text等の設定既存blobの保持・限定的な読出し。再合成は禁止
EffectFiltersBATransform/Crop/Text+/Audioなど既存native itemをbyte保持。未知IDの意味を推測しない
CompositionBAFusion node graph既存Fusionを保持するだけ。新規Fusionの合成・移植はResolveで確認必須
ST1native字幕generatorSRTを正本にし、既存字幕trackの型・配置を維持して差し替える
なぜPart11で失敗したか

Part12はResolveが書き出した完成グラフをほぼbyte-preservingで複製した。Part11の旧writerは、42本のnative装飾を再構成し、placeholder Fusionと大量のSeqContainerを追加したうえ、Multicamのnested SeqContainerと4カメラ素材の参照を落とした。

確認済みの旧候補:

  • Part11_FlatNativeMasterFromClone_20260810_v2.drt: 681 SeqContainer、697 MediaPool要素、placeholder 360件。
  • canonical one-block master: V1/V6/V7/V8/V9各1、placeholder 0。
  • 正しいPart11 content closure: Multicam、Part11.mp3、raw MOV、4カメラ素材の計7 MediaRef + nested SeqContainer/1905b9af-4e3f-4795-b887-0c3128f3a262.xml。

つまり形式が読めないのではなく、MediaRef → MediaPool → nested SeqContainerのclosureを欠いたsynthetic graphがResolveの受理条件を満たしていなかった。

Part11の安全な土台

build/part11-master-content-only-20260810/Part11_MasterCopy_ContentOnly_20260810_v3.drt は、canonical masterのV1/V6/V7/V8/V9を各1つだけ保持し、V2/V3/V4=355/153/47、A1=496、ST1=1091を追加した静的候補である。placeholderと追加Fusionは0、closure SeqContainerは1、MediaPool追加は7。

ただし、API importが「新timeline名」を返すことだけでは合格にしない。Resolve画面で次を確認する。

  • 新規timeline名が作成される(既存名の返却は失敗)。
  • MediaPoolにplaceholderがない。
  • Timeline先頭・中盤・末尾をスクリーンショットで確認する。
  • V1/V6/V7/V8/V9はnative masterの1ブロック、V2/V3/V4/A1/ST1は正本順。
  • Save → Close → Reopen後も同じtrack count、clip count、字幕表示である。

今回の実機ゲートは未合格である。v3 importは新規timelineを返さず既存timelineへフォールバックし、Resolve画面の自動取得もsky.list_windows()のspawn EPERMで停止した。ユーザー提供スクリーンショットの「応答なし」状態は、失敗候補を完成証跡に使ってはいけないことの実機証拠として記録する。

APIは構造を新規生成するために使わない。import、readback、export、close/reopen、画面確認だけに使う。根拠は [manual/L4_tools/davinci_resolve_api.md](../../L4_tools/davinci_resolve_api.md)、実装は [tools/resolve-adapter/build_part11_master_content_only.py](../../../tools/resolve-adapter/build_part11_master_content_only.py)、closure/rekeyは [tools/resolve-adapter/merge_part11_flat_st1_drt.py](../../../tools/resolve-adapter/merge_part11_flat_st1_drt.py) を参照する。

Premiere Proへ渡す場合

DRPをXMLに変換しても、Fusion、Color、Text+のopaque payload、Project Settings、native subtitle representationは完全には移植できない。Premiereへ渡すのはカット・素材・音声・SRTを中心にし、レイアウト/エフェクトの正本はResolveのDRT/DRPとする。

現行の関連仕様は [docs/SOT.md](../../../docs/SOT.md) からTimelineIR、NLE receipt、Project profileのownerへ辿る。

実装判断表
対象DRT/DRPから読めるもの安全な変更方法担当コード
カット/アングルTrack、Start、Duration、In/Out、MediaRef、Multicam参照既存clipの測定済みscalarと順序だけ変更drt_learn_short.py、build_part11_master_content_only.py
素材MediaPoolのpath、UniqueMediaPoolItemId、Multicamのnested参照MediaRef→MediaPool→nested SeqContainerのclosureを丸ごと保持drt_inspect.py、merge_part11_flat_st1_drt.py
SRT/字幕SRTは外部正本として完全に読める。DRTのST1はgeneratorと圧縮payloadSRTからResolveのSubtitle trackを作成し、DRTのST1はResolve readbackで確認。既存ST1のStartだけの変更は限定可align_subtitles.py、shift_native_subtitle_drt.py、verify_subtitle_timing.py、restore_native_subtitles.py
Text+タイトル既存Text+の文字列、font、Transformの一部既存EffectFiltersBA/FieldsBlobを保持し、文字列差分だけを測定して変更drt_text_strings.py、drt_fieldsblob.py
Transform/Crop/音量既知のEffectFiltersBA項目既存blobの既知scalarのみ。未知IDを新規合成しないdrt_effectfilters.py
FusionCompositionBAの圧縮graphを保存・展開できるgraphを再生成せずnative itemをbyte保持し、Resolveで画面/render確認drt_extract_comps.py、render_probe.py
Project/Timeline設定project.xml、FieldsBlob内の解像度・fps・fitSequenceSetupなど既知フィールドのみ読出し。Project Settings全体の移植はDRP+Resolveで確認drt_fieldsblob.py、timeline_audit.py
なぜ「素材パスだけ取れる」ように見えるのか

素材パスはMediaPool/Master/MpFolder.xmlの通常XMLにあり、MediaRefの文字列を追えば読める。一方、レイアウトやエフェクトは同じclipの中にあるFieldsBlob、EffectFiltersBA、CompositionBAへ分散している。さらに、clipのDbId、MediaRef、UniqueMediaPoolItemId、nested SeqContainerは相互参照であり、1つをコピーしても閉じたグラフにならない。したがって、パス抽出が成功しても、同じ見た目の編集可能timelineが得られたことにはならない。

Part12とPart11の差

Part12はResolve-authoredの完成グラフをほぼそのまま複製したため、opaque payload、track Sequence、MediaPool closureが一致した。Part11の旧writerは42ブロックをコードで合成し、native itemのDbId重複、placeholder Fusion、Multicam closure欠落、outer/nested Sequenceの混在を起こした。これはDRTが一般的でないからではなく、再構成側がResolveの内部グラフ契約を破ったためである。

手動教師なしで進める場合の正しい実装ルート

Resolveが実際に受理したPart11_Production42_P1 importをグラフの入口にする。ただし、現在のP1 importはV2/A1にPart11_Content_Only_v1というTimeline clipを持つcompound構造である。次のwriterはこの構造をそのまま再利用し、外側のnested Sequence(MediaPoolの<Sm2Sequence DbId>とSeqContainerのroot)を維持したまま、以下だけをflat置換する。

  • V2/V3/V4へPart11の実clip、A1へ496音声clip、ST1へ42 Partの字幕generatorを挿入する。
  • compoundのTimeline itemだけを除去する。MediaPoolのcontent/multicam closureは参照が残る限り削除しない。
  • V1/V6/V7/V8/V9はmasterのnative payloadを42回使うが、各clipのDbIdだけを新規化する。EffectFiltersBA、FieldsBlob、CompositionBA、MediaRef、UniqueMediaPoolItemIdは保持する。
  • 全trackの<Sequence>を外側Containerではなく、MediaPool timeline clipのnested Sequence IDへ統一する。
  • 新しいtimeline identityでimportし、返却名が既存timelineでないこと、V2/V3/V4/A1/ST1の数、placeholder=0、画面、close/reopenを確認する。

現行のbuild_part11_master_content_only.pyはcanonical one-block rootへ直接差し込む方式で、静的には整合してもResolveの受理グラフにならない場合がある。build_part11_flat_native_from_accepted_drt.pyをaccepted P1 root対応へ拡張するのが次の実装箇所であり、手動教師を作ることが本質的な解決策ではない。

Part11 P1 セッション引き継ぎ(2026-08-11)
結論

次セッションは42本へ進まず、まずP1の1本を完成させる。

現在の最良候補は Part11_P1_FromHorizontalSource_20260811_v3 である。正本の横長Part11素材から映像・音声を切り出し、縦長マスターレイアウトと旧Shortsの画角だけを重ねた。Resolveへの新規importと実映像レンダーまでは通った。ただし、以下が未完了なので完成DRTではない。

  • live Project Settingsが1920x1080であることの実機確認
  • 映像と音声の同期確認
  • katto_subtitle相当のST1表示・スタイル確認
  • source側のカラー/Multicam効果が維持されていることの確認
  • Save → Close → Reopen後の同一性確認
  • ResolveからのDRT/DRP再書き出し

最終目標はPart11の1本のまとめタイムラインに42ショート・496カットをflat配置することだが、P1の上記ゲートが通るまでP2以降へ展開しない。

画面と設定の絶対条件

Project Settings、short timeline、source Multicamを混同しない。

対象正しい設定用途
Resolve Project Settings1920x1080 / 24fpsPart11本編を保持するプロジェクトの基準
完成Short timeline1080x1920 / 24fps縦長Shortsの外側タイムライン。タイムライン個別設定で上書きする
20260411_C0355 Multicam1920x1080 / 24fps元カメラ、カラー、Multicam angleの正本
マスターレイアウト1080x1920V1/V6/V7/V8/V9だけを複製する。Project Settingsの入力にはしない

縦長マスターのproject.xmlをProject Settingsの正本にすると、Multicamまで1080x1920として解釈され、crop、scale、カラー、音声同期が連鎖して崩れる。縦長にするのは完成Short timelineだけである。

DRT内の設定が正しくても、既存のResolveプロジェクトへimportしただけではlive Project Settingsが直った証明にならない。次セッションではProject Settings画面またはproject.GetSetting()のreadbackとスクリーンショットを必ず残す。

正本入力
役割ファイルSHA-256使用範囲
Part11横長本編D:\zFilms Dropbox\z Films\Directors'Katto\drp\export-20260807\reference\Part11.drt942b2d438cc602eee1043dafb1eb92cb6f4c89955938b16570893eee5b8ceb93映像、音声、Multicam、MediaRef、カラー、timemap、Project shell
完成レイアウトD:\zFilms Dropbox\z Films\Directors'Katto\drp\auto-part11-20260810\master-layout\Katto_Master_Layout_Production.drtdfe0ed45248212dfc51482ee43442b37e162b27b2c6d3e5f1034bf61c930a235V1/V6/V7/V8/V9と外側Short timelineのSequenceSetupだけ
Short画角教師D:\zFilms Dropbox\z Films\Directors'Katto\drp\export-20260807\reference\Part11_Shorts_old.drt4d9cdb0e1139e86e0cbd5e16405284a53ea067ed0018be8c7e1c7a3f2bc6433cV2/V3のResolve-authored framing/crop blobだけ
P1構成dev/short-plan/Part11.json-P1の12 cut ranges
ST1 item候補D:\zFilms Dropbox\z Films\Directors'Katto\drp\auto-part11-20260810\production42-v1\raw-drt\Part11_Production42_P1.drt980b7995d443c1dd31cb8c99da1acccfccf4165ab6ae8a2be5bcc1dc9561c8c4P1字幕item。映像・音声・compoundは使わない
手動比較資料C:\Users\akihi\Downloads\Part11_Production42_P1 import.drt3c648579f6f83ee69703d7e277517aa1531b5316245aced8a749abd79677c0fakatto_subtitle適用前後の差分確認だけ。映像・音声・angleの正本にしない

注意: C:\Users\akihi\Downloads\Part11.drt(SHA-256 9f5a54f3ba028765e7632da2bd8fbf4124b2a4d5600314c4073b7b8c3ee9ebca)は、現物解析ではPart11と20260411_C0355 Multicamが1080x1920になっている。横長正本としては使わず、上表のexport-20260807/reference/Part11.drtを使う。

現在の最良候補と証跡
P1 v3
  • DRT: build/part11-full-20260811/Part11_P1_FromHorizontalSource_20260811_v3.drt
  • SHA-256: 666b3dc162dc7b191c732eba1b7b57771f4645e17a0616fbe89b55574dbb647e
  • manifest: build/part11-full-20260811/Part11_P1_FromHorizontalSource_20260811_v3.manifest.json
  • decode: build/part11-full-20260811/Part11_P1_FromHorizontalSource_20260811_v3.decode.json
  • Resolve import report: build/part11-full-20260811/p1-horizontal-v3-import.json
  • Resolve project: Part11_Part12MasterLayout_20260810_v2
  • imported timeline: Part11_P1_FromHorizontalSource_20260811_v3
  • render: build/part11-full-20260811/render-p1-v3/unique/zfilms-probe-unique.mov
  • render SHA-256: 19e65396111851d38daa22d24427d8fd160ffbf9869a00fc17ecfaf7501c9ed6
  • frame: build/part11-full-20260811/render-p1-v3/p1-v3-frame-025.png

静的構造とimport readback:

  • 外側timeline: 1080x1920 / 24fps / [86400,87505) / 1105F
  • source nested 20260411_C0355 Multicam: 1920x1080 / 24fps
  • V1=1、V2=12、V3=0、V4=0、V6=1、V7=1、V8=1、V9=1
  • A1=12、ST1=7
  • V2/A1はtimeline clipやcompoundではなく、外側timelineへ実clipを直接配置
  • project.xmlは横長Part11正本からbyte保持
  • V1/V6/V7/V8/V9はcanonical masterから保持
  • V2 framingだけPart11_Shorts_oldの既存EffectFiltersBAを適用
  • real videoのResolve renderは成功
P1 cut ranges

dev/short-plan/Part11.jsonのP1を使用した。

[426,489] [498,591] [632,733]
[20224,20368] [20384,20546] [20561,20688]
[20918,20965] [21095,21169] [21187,21296]
[21310,21390] [21492,21540] [21703,21760]

現在のwriterはsourceBodyOrigin=87545、timelineOrigin=86400として同じsource rangeからV2とA1を切り出す。コード上は映像・音声のrecord Startが一致するが、実再生での同期とcut境界の1〜2F丸めはまだ合格していない。

未完了の問題
1. live Project Settings

現行Resolve project Part11_Part12MasterLayout_20260810_v2は、ユーザー確認時にProject Settingsが1080x1920だった。P1 v3のDRT archiveは横長sourceのproject.xmlを保持するよう修正したが、既存projectへimportしただけではlive Project Settingsは変わらない可能性がある。

次セッションは、共有projectをさらに変更する前に複製または検証専用projectを用意し、次を別々にreadbackする。

  • Project Settings = 1920x1080
  • P1 short timeline = 1080x1920
  • nested C0355 Multicam = 1920x1080
2. 映像と音声の同期

P1 v3はV2/A1を同じ横長Part11 source clipと同じcut rangesから生成した。旧候補より構造は正しいが、音声を含むレンダーの視聴、cutごとのStart/Duration/In比較、cut境界の波形確認が未完了である。

3. ST1字幕

ST1 trackは7 itemとしてimportされたが、1秒probe renderでは字幕が見えなかった。katto_subtitleのtrack presetを手動版から一時的に参照しているが、完成条件にはしていない。

次セッションでは次の順で切り分ける。

  • ST1 cueが存在するフレームへplayheadを置き、Edit pageで字幕が表示されるか確認する。
  • ST1 track自体にkatto_subtitleをResolve上で適用し、preset適用後DRTを差分取得する。
  • subtitle burn-inを明示したレンダーで表示を確認する。
  • Save → Close → Reopen後にも同じstyle、位置、本文が残ることを確認する。

ユーザー手動DRTは差分教材であり、映像・音声・angleをコピーして完成扱いにしない。

4. タイトルとカラー

現在のV8/V9はcanonical masterの既存native payloadを保持しており、P1固有タイトルの最終反映は未完了である。過去にText+ opaque payloadを直接書換えてResolveをクラッシュさせたため、推測書換えを再開しない。Resolve-authored one-variable diffを取得してから限定変更する。

カラーはsource Multicam closureとclip payloadを保持しているが、live Resolve上でsource本編とP1の同一フレームを比較して最終確認する。

明確になった失敗原因
  • Project Settingsとtimeline settingsを混同した。 縦長masterのproject shellを使ったため、source Multicamまで1080x1920扱いになった。
  • 違うPart11 DRTをsourceにした。 Downloads版は横長authorityとhash・SequenceSetupが違った。
  • MediaRefだけを差し替えた。 旧short clipのMediaStartTime、MediaTimemapBA、selector、effectを残したまま本編素材へrebindし、映像・音声・angleを壊した。
  • MediaPool XMLを正規表現で切った。 nested EmbeddedAudioVec/<Element>を最初の</Element>で切断し、ContentOnly v1/v2/v3/FromAcceptedのMpFolder.xmlを壊した。
  • native itemを42回同一DbIdで複製した。 placeholder 360件、SeqContainer 681件、outer/nested Sequence不一致を生んだ。
  • compoundを完成形にした。 Part11_Content_Only_v1 import 1をV2/A1へ1clipで置いたため、外側からcutを編集できなかった。
  • 字幕itemとtrack styleを同一視した。 raw ST1 itemを置くだけではkatto_subtitle presetは付かない。

DRT/DRPの外側はZIP + DEFLATEであり、形式が解凍できないことが原因ではない。問題はXML内のMediaRef closure、nested Sequence、DbId、FieldsBlob、EffectFiltersBA、CompositionBAを異なる正本から混ぜたことにある。

使用禁止/参照限定

次の成果物をproduction inputに戻さない。

  • Part11_MasterCopy_ContentOnly_20260810_v1/v2/v3/FromAccepted
  • MediaPool/Master/MpFolder.xmlが未閉鎖。Resolve基盤に使わない。
  • Part11_MasterBased_42x496_Candidate*
  • Part11_FlatNativeMasterFromClone*
  • duplicate DbId、placeholder、参照不整合がある。
  • Part11_Production42_P1〜P42
  • V2/A1がnested/compoundの検証物。
  • build_part11_full_from_horizontal_source.pyで作った42本v4候補
  • importしてもvideoがblank。旧aggregate metadataを残したMediaRef rebind経路。
  • FCPXML、PNG字幕、__zfilms_fusion_placeholder.png
次セッションの実行順
0. リポジトリとSOT
Set-Location 'C:\Users\akihi\OneDrive\ドキュメント\ZFILMS\zfilms-subtitles-part11-handoff'
git status --short --branch

branchはCodex/part11-claude-handoff-pr-20260810。dirty worktreeの変更を消さない。計画の正本はdocs/PLAN.md、この文書は実機再開手順である。

1. 既存P1 v3を先に検証

再生成より先に、検証専用projectでProject Settingsを1920x1080にしたうえで既存v3をimportする。Resolveエラー時に別window/Untitled Projectへ移ることがあるため、毎回--expect-projectでfail-closeする。

python tools/resolve-adapter/import_drt.py `
  --expect-project '<1920x1080の検証project名>' `
  --drt 'build\part11-full-20260811\Part11_P1_FromHorizontalSource_20260811_v3.drt' `
  --expect-timeline 'Part11_P1_FromHorizontalSource_20260811_v3' `
  --report 'build\part11-full-20260811\p1-v3-clean-project-import.json' `
  --go

同名timelineが既にあるprojectではfresh importにならないため、clean projectか新しいtimeline identityで再生成する。

2. 必要な場合だけP1を再生成

build_short_drt.pyだけを現行writerとして使う。出力は上書き拒否なので、v4など新しい名前へ出す。

python tools/resolve-adapter/build_short_drt.py `
  --source-drt "D:\zFilms Dropbox\z Films\Directors'Katto\drp\export-20260807\reference\Part11.drt" `
  --master-drt "D:\zFilms Dropbox\z Films\Directors'Katto\drp\auto-part11-20260810\master-layout\Katto_Master_Layout_Production.drt" `
  --layout-drt "D:\zFilms Dropbox\z Films\Directors'Katto\drp\export-20260807\reference\Part11_Shorts_old.drt" `
  --subtitle-drt "D:\zFilms Dropbox\z Films\Directors'Katto\drp\auto-part11-20260810\production42-v1\raw-drt\Part11_Production42_P1.drt" `
  --plan-json 'dev\short-plan\Part11.json' `
  --short-id P1 `
  --timeline-name 'Part11_P1_FromHorizontalSource_20260811_v4' `
  --output 'build\part11-full-20260811\Part11_P1_FromHorizontalSource_20260811_v4.drt' `
  --manifest 'build\part11-full-20260811\Part11_P1_FromHorizontalSource_20260811_v4.manifest.json'

--subtitle-style-drtは現時点では手動比較資料への依存になる。最終候補では、Resolve上のkatto_subtitle適用済みtrackを正規のstyle authorityとしてexportできるまで省略または検証専用とする。

3. 実機ゲート

ユーザーはResolveのSave、close/reopen、必要な再起動、スクリーンショットによる自動確認を許可済みである。ただしResolveエラーで別windowやUntitled Projectが開くことがあるため、操作の前後で対象project名とtimeline名を必ず再確認する。

  • Project Settings、P1 timeline settings、nested Multicam settingsを別々にreadbackし、スクリーンショットを保存する。
  • source Part11とP1の同じ発話点を並べ、映像・音声同期、angle、crop、カラーを確認する。
  • ST1 cue上でkatto_subtitleの本文、位置、font、stroke、shadowを確認する。
  • subtitle burn-inを含む全尺レンダーを作る。先頭だけでなくcut後半も見る。
  • verify_close_reopen_counts.pyでSave → Close → Reopenを実行する。
  • export_timelines.pyでResolve-authored DRTとDRPを書き出し、再importして同一性を確認する。
python tools/resolve-adapter/verify_close_reopen_counts.py `
  --project '<検証project名>' `
  --timeline '<合格させるP1 timeline名>' `
  --out 'build\part11-full-20260811\p1-close-reopen.json'

python tools/resolve-adapter/export_timelines.py `
  --expect-project '<検証project名>' `
  --out-dir 'build\part11-full-20260811\resolve-export' `
  --timeline '<合格させるP1 timeline名>' `
  --drp `
  --drp-path 'build\part11-full-20260811\resolve-export\Part11_P1_verified.drp'
P1完成ゲート

次の全項目を満たして初めてP1完成とする。

  • [ ] live Project Settings 1920x1080 / 24fps
  • [ ] target timeline 1080x1920 / 24fps
  • [ ] nested C0355 Multicam 1920x1080 / 24fps
  • [ ] V2/A1がflatな12実clipで、Start/In/Durationと再生が同期
  • [ ] 正しいangle、crop、scale、カラー
  • [ ] V1/V6/V7/V8/V9がcanonical masterと同じ描画
  • [ ] P1固有タイトル
  • [ ] ST1本文とkatto_subtitle styleが表示
  • [ ] placeholder 0、compound/nested content clip 0
  • [ ] 全尺レンダーの目視合格
  • [ ] Save → Close → Reopen後に構造・描画一致
  • [ ] Resolve-authored DRT/DRPを書き出し、再import合格

P1合格後だけ、同じ構造を1本のproduction timeline上でP2〜P42へ順に拡張する。

Git状態
  • worktree: C:\Users\akihi\OneDrive\ドキュメント\ZFILMS\zfilms-subtitles-part11-handoff
  • branch: Codex/part11-claude-handoff-pr-20260810
  • 既存dirty変更がある。git reset --hard、git checkout --、無差別削除をしない。
  • 今回の実装入口: tools/resolve-adapter/build_short_drt.py
  • 古い42本writerを修正し続ける前に、P1 writerと実機ゲートを完成させる。
L1-00 字幕の三層(オリジナル/校正/マスター)

この分類が字幕の統制の起点。以降のすべての工程が、どの層を入力にし、どの層を出力にするかを明示する。層を混ぜたことが、これまでの不具合の大半の原因だった。

浅尾確定(2026-08-03)。


1. 三層
層名前中身作り方
1オリジナル字幕収録したものをカットせずに全部書き起こしたものElevenLabs にそのまま当てる。人手を入れない
2校正字幕オリジナルの誤変換・固有名詞を直したものClaude が正本と辞書で校正。タイムコードは動かさない
3本編字幕(マスター字幕)校正字幕から、テクニカルトラブル等を軽くカットしたもの本編そのもの
4縮約字幕マスターから、意味を変えずに字数を減らしたもの10_condensation.md。これが組版の入力

ショートは縮約字幕からのみ作る。 他の層からは作らない。

収録
 └→ オリジナル ──校正──→ 校正字幕 ──軽いカット──→ マスター ──縮約──→ 縮約字幕
                                                                        ├→ 本編(16:9)
                                                                        └→ ショート(9:16)

層4 は 2026-08-07 に足した。 それまで層が 3 つしか無かったため、本文が逐語のまま

組版へ入り、CPS が話速そのものになっていた(全 error の 75% がこれ)。

何が減る時間
層3 カット区間ごと落とす縮む
層4 縮約文字だけ減らす変わらない

縮約は Part 単位で 1 回。本編用とショート用に分けない。 分けた瞬間に本文が 2 つになり、

下の §2 が壊れる。幅が変えるのは改行位置であって、字数の密度ではない。


2. なぜマスターのみからショートを作るのか

ショートは本文のコピーを持たない。マスターへの参照だけを持つ。

コピーを持たせると、こうなる(2026-08-03 に全部実際に起きた)。

コピーを持つ場合参照だけ持つ場合
校正で語が直ったら 610 本に伝播させるマスターを直すだけ。ショートは無変更
引用が正しいか毎回検算が要る検算が要らない(ずれようがない)
時間軸がずれたらショート側の tc を動かすマスター 1 本だけ
検算先が同一素材だと循環する起きない

マスターが確定すれば、ショートは二度と校正しなくてよい。 それがこの分類の目的。


3. 今後の作り方

カットなしのオリジナルレベルのものを、まず ElevenLabs に当てる。

編集後の音声を書き起こすと、その書き起こしはオリジナルではなくマスター相当になる。層が 1 つ潰れ、どこを削ったのかが永久に分からなくなる。


4. 過去分の注意(重要)

校正字幕からマスターへ移る際のルールを、これまで持っていなかった。

そのため、マスター時点の編集後に ElevenLabs 書き起こしをしているものが混ざっている。 その回では:

  • 手元の「書き起こし」は実質マスター相当であって、オリジナルではない
  • オリジナル字幕が存在しない
  • 削った区間が分からないので、マスターとオリジナルの差分が取れない

回ごとに、その書き起こしがどの層なのかを判定してから使うこと。 判定せずに「オリジナル」として扱うと、時間軸が本編とずれる。

実際に起きた例(2026-08-03 実測):

回症状
Part5SRT が本編より 27.3 秒早い(ダイジェスト追加前の版)
Part7同 27.0 秒
Part8同 34.71 秒
Part6書き起こしが本編に対し 20〜30 秒ずれ

いずれも「どの層のどの版か」を明示していなかったために起きている。

判定済みの一覧(2026-08-04 実測)

13 Part すべてが編集後の音声に ElevenLabs を当てたもので、オリジナル字幕は 1 本も無い。

測り方と根拠の全文は docs/PLAN.md「2026-08-04 手元の書き起こし 13 Part が字幕の三層のどれかを 1 話ずつ判定した」。

Part層ダイジェストオリジナル音源
Part1〜8マスター相当書き起こしに入っている(Part1 20.3s / Part2 20.5s / Part3 20.9s / Part4 20.3s / Part5 28.3s / Part6 23.3s / Part7 26.8s / Part8 34.9s)無い
Part9未判定Final/Part9.mp4(映像+AAC音声)はある。DRP の最終タイムラインには Part9_Digest 30.67s と OP 20.46s がある動画音声3835.221sとproofread末尾3719.574sに115.647s差があり、書き起こしの層・時間軸は未判定。DRP上の本編開始は origin + 1,227 frames(51.125s)
Part10・11・12・Special1マスター相当(Special1 の DRP は未確認)書き起こしには無い。DRP は Part10/11 にダイジェスト+OP、Part12 に無しを確認Clips_Raw/Audio/*_tempAudio.mp3(本編より 84〜441 秒以上長い)

.transcripts/<Part>.{corrected,proofread,repaired}.json は Part ごとに先頭・末尾が完全一致で、

校正で時間軸は動いていない。 三層の定義どおりに動いている。

Part12 だけ、冒頭 9〜25 秒にマイク調整の生のやり取りが残っている。 マスター化で落とす候補だが、

§7 の規則が空なので機械で落とさない。


5. 層を記録する

各成果物に、どの層か・何から作られたかを必ず持たせる。

layer          original | proofread | master
derivedFrom    元にした成果物の識別子とハッシュ
rulesVersion   校正なら辞書と規則の版、マスターならカット規則の版

lib/subtitle/rules-hash.ts が字幕の数値ルールで同じことをやっているので、その作法に揃える。

2026-08-04 に scripts/materialize-master-subtitles.mjs を追加し、13 Part+Special1について

.transcripts/<Part>.master.json を生成した。校正本文をそのまま保持し、layer: master、

derivedFrom、rulesVersion、DRP offset、保留理由を成果物へ付けている。未確定の区間は削除していない。


6. 校正とマスター化は規則で持てる

校正字幕とマスター字幕は、外部ツールなしで Claude Code の規則だけで作れる。

  • 校正 → lib/katto/corrections.ts lib/katto/dictionary.ts と manual/L1_subtitle/08_multipass_correction.md
  • マスター化 → 何をテクニカルトラブルとみなして落とすかの規則。ここはまだ書かれていない(本ファイルの §7)

7. マスター化の規則(未確定)

落とす対象をここに列挙する。確定するまで、マスター化で機械削除しない。

初期 materialization rule は「proofread本文を保持し、未確定区間を pendingReview に記録する」である。

これは最終的な編集済みマスターの完成を意味しない。

未記入。浅尾の判断を待つ項目:

  • 機材トラブル・撮り直しの宣言
  • 長い沈黙(何秒から落とすか)
  • 収録の中断・再開の前後
  • 進行上の打ち合わせ(本編に不要なもの)
  • 言い直しのうち、前半を落とすか両方残すか

現時点の保留:

  • Part9: Final/Part9.mp4はあるが、動画音声と本文に115.647秒差があり、DRP offsetと本文の突合が未完了
  • Part12: 冒頭9〜25秒のマイク調整を残すか削るか未決定
  • Special1: DRP上のダイジェスト/OP構造が未確認

規則が空のまま機械で落としてはいけない。 何を落としたかが分からなくなる。


関連
  • 05_transcription_pipeline.md — 書き起こしの実行手順
  • 07_elevenlabs_workflow.md — ElevenLabs の当て方
  • 08_multipass_correction.md — 校正の多段処理
  • manual/L2_sns/02_shorts_planning_framework.md — ショートの構成(マスターから作る)
L1-01 日本語字幕フォーマット規則
あらゆる日本語字幕の共通ルール。1 行の字数と行数は案件ごとのパラメータで、 正本が持つのは動かしてよい範囲である。

1. 文字数
IMPORTANT: 1 行 6〜16 字 / 最大 3 行。どちらもパラメータ(浅尾確定 2026-08-11)

単一の正解値は無い。 案件・画面比・テロップ設計で変わるので、正本は範囲を持ち、

実際の値はプロジェクト設定から渡す。

項目可変域実装
1 行の字数6〜16 字maxCharsPerLine
行数1〜3 行maxLines
1 枚の容量既定は 字数 × 行数maxTotalChars(明示すれば据え置ける)

範囲の外は例外にする。黙って丸めない(SubtitleLayoutOutOfBoundsError)。

丸めると、頼んだ値と焼き込まれた値が違うのに誰も気付かないまま出荷される。

行数を増やしたら容量も増える。 maxLines だけ 2→3 にして容量が据え置きだと、

3 行目は一度も使われないまま「3 行に対応した」ことになる。容量を別に決めたい案件は

maxTotalChars を明示する。実装は lib/subtitle/thresholds.ts の JA_LAYOUT_BOUNDS、

検証は tests/lib/subtitle-layout-bounds.test.ts。

この決定は「正本 10 字 / 実装 13 字」の食い違いを閉じたもの。 どちらか一方を選ぶ

問いではなく、両方とも範囲の内側の妥当な既定値だった。

既定値(範囲の内側)
プリセット1 行行数合計
episode-subtitle-v1 本編(16:9)13 字2 行26 字
katto-shorts ショート(9:16)10 字2 行20 字
参考: 業界の一般値

範囲を決めた根拠。そのまま使う値ではない。

種別1行最大行数合計
劇映画 / トーク本編13文字(最大16)2行26文字
ビジネス動画最大20文字2行40文字
バリアフリー字幕 (SDH)16〜17文字2行32〜34文字
シネマスコープ11文字2行22文字
縦字幕(標準)10〜11文字2列—

ラテン文字には当てない。 6〜16 は全角基準で、半角は幅が約半分。英語字幕は

1 行 42 字(Netflix / AVTpro 系)で、可変域も別に持つ(LATIN_LAYOUT_BOUNDS)。

カウント方法: 全角=1、半角=0.5。


2. 読み取り速度(CPS)

下の表は業界の目安であって、この案件が使う値ではない。

実際に出荷している CPS は本文末尾の「規則値(自動生成)」が持つ(lib/rules/registry.ts が正本)。

コンテンツCPS 上限備考
劇映画字幕4文字/秒田村幸彦 (1931) 由来の業界標準
同言語トーク番組8文字/秒(理想) / 10文字/秒(許容)YouTube ロング
ビジネス・教育5〜6文字/秒専門用語多めも許容
バリアフリー (SDH)最大7文字/秒ほぼ逐語的

目安:

  • 4 CPS = 快適
  • 6 CPS = 許容上限
  • 7+ CPS = 読み取り困難(特別な配慮が必要)

3. 表示時間
項目基準
最小表示時間500ms(24fps で 12 フレーム)。葛藤は 0.4 秒へ上書きする → §最小表示時間の上書き
最大表示時間6.5秒(ドラマ)/ 7秒(ドキュメンタリー)
IN点音声開始の 1〜2 フレーム前
OUT点音声終了後 +0.5 秒。上限は下の「余韻の上限」1.0 秒(旧記述の「最大12フレーム」は誤り。3 行下と両立しない)
字幕間ギャップ最小2フレーム(≈ 83.3ms @24fps)
余韻言い終わり +0.5秒(12フレーム @24fps)
余韻の上限1.0秒。最短表示時間に届かないときだけここまで伸ばす
余韻の決め方(順に見る)

実装は lib/subtitle/cueTiming.ts の applyLeadOut。

  • 次のキューが来るならそちらが優先。 次の入り − 最小ギャップを超えて伸ばさない
  • 次が無ければ言い終わり +0.5秒
  • それでも最短表示時間(500ms)に届かなければ 1.0秒まで
  • 最大表示時間で頭打ち

伸ばすのではなく、切り詰めるのが主な仕事。 出荷済みは「言い終わってから次の人が

喋り出すまで」を 1 枚が抱えていて、末尾の無音が最大 3.48 秒、尺が最大 18.44 秒あった。

この規則を通すと 0.50 秒 / 7.00 秒で頭打ちになる。

0.5〜1.0 秒を超える余韻は業界標準を外れる。 意図して外すとき以外は使わない。

尺が伸びる原因は余白ではなく切らなすぎ

実測すると、句読点だけで切った生成は 4 秒超のキューが人手の 4 倍出た。

上限を超えたら文節境界で割る(§4「1 枚に収まらないとき」)。

合格ライン

scripts/compare-cue-timing.mts で測る。

指標合格
末尾の無音0.5 秒以下(超過 0 件)
最大表示時間上限以下(超過 0 件)
最小表示時間未満 0 件

4. 改行ルール
切ってよい位置のレベル(L0 強調フレーズ / L1 話者交代 / L2 意味のまとまり / L3 改行ポイント / L4 音節)は、 グローバル正本 `docs/SHORTS_CUTTER_SUBTITLES.md` が持つ。 レベルを付けるのは LLM、禁則の検算と幅の適用は機械(lib/subtitle/break-levels.ts)。 以下の禁止事項と罰点は今も有効で、機械側の検算がそのまま使う。 工程の順番は 09_break_priority.md(段0〜段4)。段は工程、L0〜L4 は位置の種類で別物。
基本原則

意味のまとまり(文節)で区切る。絶対に助詞から始めない。

行の文字数は均等である必要なし。意味的完全性 > 視覚的バランス。

均等に割れるからという理由で文節の途中を選んではいけない。

切ってよい位置と罰点

実装は lib/subtitle/breakPoints.ts。切ってよい位置を全部出し、それぞれに罰点を付ける。

行幅を決め打ちして改行位置を焼き込まない。 位置と罰点だけ先に出しておけば、

10 字でも 13 字でも同じデータから組み直せる。罰点は小さいほど良い切れ目。

罰点位置備考
0句点「。?!」の直後文の終わり。最優先
2半角スペースの直後
8読点「、」の直後節の切れ目
12接続詞・接続助詞の直前でも / だから / つまり / なので / けど
16連用形・て形の直後〜して / 〜くて / 〜んで。引用の「って」は除く
18格助詞の直後1 文字の助詞は直前が体言のときだけ
20語頭・語末固有名詞と形式名詞の前後、L3 改行ポイント(LLM 付与)
35文字種が変わるところ漢字→ひらがなは送り仮名なので除く
90語の内側(音節境界)1 語が 1 行に載らないときだけ立てる。最後の手段
90て形 + 補助動詞の直後やって|おく 変えて|くれる。浅尾確定 2026-08-14(下)
902 文字以上の付属語が行頭する|しかないで クランクイン|ですよ

補助動詞の直後は「切ってよい」ではない。

補助動詞は基本切りたくないな。完全にどうしても切れない場合だよ。(浅尾確定 2026-08-14)

独立した語なので禁則にはしない(候補には立つ)。**罰点を音節改行と同じ 90 にして、

他に候補があるかぎり選ばれないようにする。** 縮約形(〜てた 〜てる 〜てへん 〜てく

〜てまう 〜とく)は 1 語なので候補にすらしない(禁則)。みる は 〜て、見る と

字面で区別できないので対象外(罰点 16 のまま)。詳細と実測は

09_break_priority.md §2「補助動詞の直後は『切ってよい』ではない」。

句読点は表示しないが、切る判断には使う。 先に落とすと罰点 0 と 8 のいちばん強い切れ目が

消え、合法率が 54% で止まる。組んでから落とす。

罰点 55「カタカナ語の音節境界(N 字以上続く語)」は 2026-08-11 に撤去した。

候補を出す側(findBreakPoints)は行幅を知らない。幅を知らないまま「6 字以上なら割ってよい」と

言うと、その行に載る語まで割れる。 実測 2026-08-11(全 13 Part / 34,000 キュー / 本番経路

build-master-srt): katakana_break 405 件のほぼ全部がこの段の出力で、

タイムラ|イン(6 字・上限 13)キャラク|ター プロジェ|クト サラ|リーマン のように、

割る必要のない語が割れていた。下の「改行してはいけない位置」が名指しで禁じている

「カタカナ複合語の途中」そのものである。

いまは幅を持つ syllableBreakPoints(罰点 90)だけが受け持ち、

候補と候補のあいだが 1 行に収まらないときにしか立たない。字種は問わないので、

カタカナ以外の長い語も同じ経路で割れる(浅尾指示 2026-08-11:「6文字以上の長い単語やカタカナ語」)。

全部ひらがなの区間(レベル付与は LLM)

全部ひらがなの区間には字種の変わり目も助詞の直後も無い。罰点モデルだけでは

「映画これから撮りたいよみた|いな人がいた時に」のように語の途中で折れる。

そこを埋めるのは LLM の L3 改行ポイント付与である(lib/subtitle/break-levels.ts)。

文節モデル(BudouX 等)は使わない(2026-08-16)。 実測で

ぜひよろしくお願いしますはいじゃあ に対し はい を は|い と割る位置を優先候補として返し、

本当の切れ目 します|はい は候補にすら立たなかった。

LLM が語頭と申告した位置は、1 文字助詞の禁止を解除する。

字面で助詞を判定すると語頭が同じ字の語を巻き込む。やばい『やっぱ』は「や」、

ところ『という』は「と」で始まるため、解除しないと行頭に置けない。

実害として 倫理的に|やばいやろって は候補が 1 件も立たなかった。

申告が効いた回数を数えて出す(全部に付けて回る応答はそこに出る)。

改行してはいけない位置
  • 動詞活用の途中(撮り|たい、やっ|たら、ご飯食べ|ましょう)
  • 活用語尾の途中(関係な|い、です|、〜な|かった)

1 文字の助詞を字面で見ると活用語尾を取り違える。関係 が漢字なので「な」を

格助詞と判定し 関係な|い を候補に出してしまう。禁則として持ち、

候補の生成だけでなく機械折り返しにも効かせる

  • LLM が 1 語と申告した短い塊の内側(6 字以下の塊)。

長い塊には掛けない。「空気変えてくれる」(8 字)を丸ごと守ると

変えて|くれる が使えなくなり、他に候補が無い本文で詰む

  • 送り仮名の途中(漢字→ひらがなの境界は候補にしない)
  • カタカナ複合語の途中(タ|イミング、ワンシチュ|エーション)
  • 格助詞が次行の先頭に来る位置
  • 形式名詞の途中(ところ / こと / もの / という / みたいな / っていう)
  • 連語(複合助詞)の途中(と|して、に|おいて、に|よって、に|関して)。

ただし意志形と擬態語は別(作ろうと|して、ピリッと|して は と までが前の語)

  • 名前と敬称のあいだ(齋藤|君、デューク|氏)。左が漢字・カタカナ・英数字のときだけ
  • 固有名詞の内部(辞書の語は範囲ごと保護する)
  • 数字と助数詞のあいだ、英数字の途中
  • 行頭禁則(小書き仮名・長音・約物)、行末禁則(開き括弧)
上下のバランス

案件ごとに決める。 実装は pyramidWeight(既定 0 = 掛けない)。

方針値使うとき
均等寄り0上下の差を問わない。ディレクターズ葛藤はこちら(2026-08-03 浅尾確定)
逆ピラミッド1.5上の行 ≦ 下の行。教科書の推奨

2 行目に 1 文字だけ残さない。

1 枚に収まらないとき

規則違反は出さない(浅尾確定 2026-08-10)。順に試す。この 3 つで必ず解ける。

  • 改行。 候補(下の罰点表)から組む。1 行の字数は案件ごとのパラメータで、

13 字も 10 字も同じ実装(layoutLinesDetailed)が扱う。幅を決め打ちした改行器を別に作らない。

  • カット。 どう組んでも 1 枚に入らない本文がある。

「映画これから撮りたいよみたいな人がいた時に」(24 字)は合法な切れ目が 6 / 15 / 17 字目で、

どれで割っても片側が 13 字を超える。候補を増やしても解けない。

1 枚に詰め込まず、カットを入れて次へ続ける(映画字幕と同じ扱い)。

  • 音節改行。 1 語が 1 行に載らないときだけ。

コミュニケーション を 5 字幅に置くようなときは、その語の中をモーラ境界で割る。

行頭に来られない字(小書き仮名・長音・撥音・約物)と活用語尾は避ける。

1 行に載る語は割らない。字種は問わない(カタカナ限定ではない)。

この 3 段は layoutLinesDetailed が outcome: 'fit' | 'cut' | 'syllable' で返す。

②を③より先に置く。 逆にすると、カットで解ける本文を音節で解いてしまい②が飛ばされる。

カットできる呼び出し側には、音節へ降りる前に fits: false / outcome: 'cut' を返す。

幅で機械的に折り返す経路は持たない。 そこには禁則もモーラ規則も無く、

消え|る、 関係な|い めちゃめ|ちゃ はすべてそこから出ていた(docs/PLAN.md 2026-08-10)。

2026-08-11 に等分割(splitByLength)を削除して経路ごと閉じた。

1 枚に収まらない本文は切れ目候補でだけ割り、どの候補でも収まらないときは

はみ出しがいちばん小さい合法な位置を選ぶ。幅を超えるほうが、語を割るよりよい。

カットを入れられない出口(焼き込みタイトルなど)は、組めなかったことを報告して出さない。

それでも出す場合、はみ出すのは幅であって切れ目ではない(切れ目は常に合法)。

はみ出しの許容(旧 `overflowTolerance`、既定 2 字)は 2026-08-11 に型ごと削除した。

上限を超えた行が出るのは「はみ出しを許した」のではなく「組めなかったのに出した」ときだけなので、

検証側は 1 字でも `char_limit`(error)にする。warning へ落とす階層は無い。

合格ライン

scripts/check-line-breaks.mts で測る。改行位置が上の候補に入っているかを数える。

指標合格
改行が候補に入っている99% 以上
行数超過0 件
上限を超える行0 件(許容は無い。1 字でも違反)
活用語尾(です / ない)が行をまたぐ0 件

上限超過は 0 件。 本番経路 scripts/build-master-srt.mts / 全 13 Part / 33,556 キュー

(2026-08-12 実測)。行数超過・総字数超過・句読点・重なりも 0 件で、

同じ SRT を独立実装で数え直しても同数だった。

検出器そのものの監査(2026-08-12)

違反件数を成果として読む前に、その件数が本物かを確かめる。 全 error 1,374 件を

種別ごとに読んだ結果、過検出が 174 件(12.7%)あった。原因は 1 つに集約できる。

検証側だけが句読点を見ていなかった。 表示では 、。 を落とすので、出力の行だけを見ると まあしゃあないよな。|いや。 が な+い = ない の分断に見える。 §4 の罰点表は句点の直後を 0 点(最良の切れ目)と定めているのだから、 そこを違反に数えるのは検出側の誤りである。

直し方は、生成に使った句読点つき本文を検証にも渡すだけ

(validateSubtitles の bodies、cuesFromSource が前から返している)。

種別監査前読んだ本物過検出監査後
reading_speed1,14540(等間隔)+全件を独立実装で再計算1,14501,147
cue_break93930930
katakana_break78783344(判定不能 1)39
particle_start4343162716
verb_break1111011(うち 1 は ASR の句読点誤り)0
proper_noun_banned44224
min_gap(警告)15,202全件を数値で015,2020

min_gap は日本語の問題ではない。生成側が start = 前の end + minGapSec と置いた値を

検証側が引き算し直すと、浮動小数の丸めで 1 ナノ秒だけ下回る。

規則どおりに置いた位置が全部違反になっていた。

**残り 55 件(katakana_break 39 / particle_start 16)は 1 件ずつ読んで

「正本上そうなるしかない」ことを確かめてある。** うち 6 件は

lib/subtitle/compound-lexicon.ts に継ぎ目が無いだけの過検出

(キャスティング|コール 革ジャン|アメリカ ウェスタン|ベース

スペース|マーケット キャンディ|チューン フォーカス|マン)で、

語彙を足すかは人が決める(L1-09 §9 のループ)。残る 49 件は本物である。

見逃し(recall)は測ってある。低い。

過検出だけ直すと「検出器が甘くなった」だけになる。 全長から等間隔に 300 箇所

抜いて目で読み、検出器が拾えていない違反を数えた。

1 回目(監査前の出力)2 回目(監査後の出力)
読んだ行境界300300
検出器が拾った00
目で見つけた違反76

1 回目に見つけた 7 件のうち 3 つの型は表に無いだけだったので足した(実測 193 件が消えた)。

足した型例実測
て + た / へん覚悟決まって|た 金かかって|へんけど135
未然形 + へん(関西弁の否定)わから|へん 人おら|へん52
連用形 + たい(本書 §4 の名指し例)映画作り|たい 頑張ろうみ|たい6

2 回目に残った 6 件はまだ表に無い型である。

型例候補数
数字と助数詞のあいだ引っ越し3|件分 4|リットル17
例外表が広すぎて免除している格助詞パトロンで|はないから(はない が例外表にある)9
形式名詞 とこ の途中細かいと|こも全部4
複合語の継ぎ目が語彙に無い段|ボール ジャイアントパン|ケーキ未計測

推定 recall は約 13%(検出 55 件 / 推定総数 55 + 300 分の 6 × 18,031 ≒ 416)。

「規則違反 0」と書けるのは、この recall が上がってからである。

反復して測った(2026-08-13。3〜12 周目)

器は [scripts/audit-recall.mts](../../scripts/audit-recall.mts)。**出荷 SRT と 1 バイト一致を

照合してから数える**ので、測るものと出荷されるものが同じ経路から出ている。

抜く位置の位相は van der Corput 列で周ごとにずらす(同じ 300 箇所を読み続けても新しい型は出ない)。

記録は dev/knowledge/recall-rounds-20260813.json。

周読んだ目で見つけた検出器が拾えていた足した型消えた箇所誤検出の増加
1(08-12)3007031930
2(08-12)30060000
33005261010
4300916750
5300735510
63008171010
7150302170
8300808600
9300325230
10300414360
11300506320
1230043450

「消えた箇所」の合計は 12 周で 690。「誤検出の増加」は 12 周とも 0

(出荷される SRT の particle_start は 16 のまま、katakana_break は 38 のまま、

verb_break と char_limit の新規はいずれも下で説明した 1 件だけ)。

減り方の曲線は 2 通りに測れて、答えが違う。

① 出力に残っている違反の数は落ちる。ただしどの検出器で数え直したかで値が変わる。

7 周目の検出器で数えた:  監査前 250 → 3周後 194 → 4周後 129 → 5周後 92 → 6周後 14 → 7周後 0
12 周目の検出器で数え直す: 7周後 150 → 8周後 93 → 9周後 70 → 10周後 37 → 11周後 5 → 12周後 0

「7 周後に 0」は「7 周目までに知っていた型が 0」という意味でしかなかった。

同じ出力を 12 周目の検出器で数え直すと 150 件ある。

曲線の 0 を「違反が無い」と読んではいけない。

② 目の当たり率(300 箇所あたり)は 7 / 6 / 5 / 9 / 7 / 8 / 6 / 8 / 3 / 4 / 5 / 4。

1〜8 周の率は 7.07 / 300 で、そのポアソン帯(両側 95%)は [2, 13]。

9〜12 周の 3・4・5・4 はどれも下限を割っていない。

ただし率そのものは下がった。 9〜12 周は 1,200 箇所で 16 件。

1〜8 周の率なら期待値 28.3 件で、これ以下になる確率は 0.9%。

族の出方も変わった。 3〜8 周は毎周ちがう族が出ていたが、

9・11・12 周は既知の族の再出だけ(新しい族が出たのは 10 周目の 2 つだけ)。

頭打ちの判定は「まだ下がっていない」。 判定の条件は

①2 周連続でポアソン帯の下限を割る かつ ②新しい族が出ない の両方で、

②は 11・12 周で満たしたが、①は 12 周とも満たしていない。

推定残存は 241 箇所(95% 区間 138〜391)。 9〜12 周の率 × 母集団 18,082 箇所。

残っているのは族ではなく語の裾である。 8〜12 周で語彙に足した 21 語の

1 語あたりの件数は中央値 2、最大 19(じゃあ)、最小 1。

この裾は機械化できない。 正本 §4 が名指ししている「1 語と見た

短い塊(6 字以下)の内側」を検証側にも入れて測ったが、母集団に当たる 98 件の大半が

〜あんねん|けど(罰点 12「接続助詞の直前」=合法)で、塊 4 字以下まで絞ると 4 件中 3 件が合法だった。

足した型(3〜12 周目、計 51 型 / 690 箇所)

周型例実測
3語幹+すぎる関係者が少な|すぎる3
3て+まう(てしまう の関西縮約)衝突して|まう2
3て+く(ていく の縮約)持って|くのかな8
3とく+いた/いて(ておく の縮約)用意しと|いたら16
3複合動詞の内部送り|込みます48
3数字+助数詞引っ越し3|件分24
4全部ひらがなの 1 語めちゃ|くちゃ わ|からん25
4未然形+ひん(へん の変種)起き|ひん8
4接頭辞 おお|願いします11
4接尾辞 やすい願いしや|すかった1
4例外表が広すぎた(はい はない になる …)して|はいけない29
4漢字+カタカナで 1 語段|ボール1
5いただく の分断見てい|ただいて22
5形式名詞 とこ細かいと|こも全部4
5短いかな語(もう どんどん 〜たち)も|う さんた|ち18
5接頭辞 ぶっぶっ|殺してやる4
6て+ない の活用形出せて|なかった26
6て+ますジョークして|ますね31
6て+いる の活用形(いただく は除く)して|いたんですね9
6`で`(撥音便・イ音便)も て形死んで|るのよ17
6例外表 もし が もしれない を免除だってか|もしれない13
7て+て(〜てて=〜ていて の縮約)決まって|てで16
7形式名詞 ため出すた|めって1
8語幹+る/れ(行頭に活用語尾だけが残る)いけ|るんちゃう かもし|れない15
8お段+う(意志形・長音の 1 語)しよ|うぜ そ|うせんかったら14
8じゃあ(接続詞)もう最後のじゃ|あ19
8連体詞 こういう/そういう/どういうキャラクターこう|いうやつ9
8より / 絵の具ってよ|りかは 赤の絵の|具3
8継ぎ目 ジャイアント+パンケーキ / スタートジャイアントパン|ケーキ2
9ぐらい/くらいこんぐら|いついてる8
9どんだけ じゃんけん ほんま すごい さっき ならへんじゃん|けんで11
9数量に 何 と Nヶ を足すあれ何|リットル 2ヶ|月4
10語幹+る/れ にあ段(未然形)を足すやら|れてる かか|るのか16
10複合動詞の後項 立終尽締忘訳来室方成り|立たない 撮り|終わって14
10漢語の内部 多分 分担 出来上がめっちゃ多|分 役割分|担5
10分数2分の|11
11ラ行を 4 つまとめる(る れ に ら ろ を足す)い|られへん やった|らあかん21
11間に合 / でかい契約間に|合わへん9
11えへん(サ変の関西否定)/ 序数 目成立せ|えへん 一発|目2
12お伝え やつ すげえ 凄まじちゃんとお|伝え や|つつったら5

過検出は 12 周とも 1 件も増えていない。 出荷される SRT の particle_start は 16 のまま、

katakana_break は 38 のまま、verb_break は 0 のまま

(particle_start は 2026-08-14 の提案 4 型で 9 になった。下の表)。

`char_limit` が 1 件だけ増えた。 2ヶ月ぐらいかかってんねん(12.5 字 / ショート上限 10)に

切れ目候補が 1 つも立たない(breakPoints.ts の助詞表に ぐらい が無く、月|ぐらい は

漢字→ひらがなとして字種の段から外れ、かかっ|て は促音便の禁則)。

正本 §4「幅を超えるほうが、語を割るよりよい」に従えばこの 12.5 字の行のほうが正しい

(それまでは 月ぐら|い と語を割っていた)。検出器の過検出ではない。

禁則は族ごと塞がないと、同じ語の中で 1 字ずれるだけになる。 11 周目にラ行を

る/れ だけで塞いだら やら|れてる が や|られてる へ、

プロダクションや|ろう が プロダクショ|ンやろう 側へ動いた。ら/ろ も同時に入れて解消した。

提案 7 型のうち 4 型を入れた(2026-08-14。浅尾承認)。送り仮名は入れない。

残りは dev/knowledge/recall-rounds-20260813.json の remaining.proposals。

型変更前の件数目視した過検出変更後の件数扱い
て形+補助動詞の直後1790 / 2070罰点 90(最後の手段)。他に候補があれば違反
終助詞・副助詞が行頭910 / 45—罰点 90。 コピュラと合わせて 177 → 125
コピュラが行頭860 / 28(全件。やってん と さ を外して 3 → 0)—同上
連語(複合助詞)500 / 20(意志形・擬態語を外して 3 → 0)0段0 禁則
敬称が行頭130 / 13(全件)0段0 禁則
擬態語の同語反復70 / 7(全件)7未決。表どうしの矛盾(下)
~~送り仮名の途中~~16220 / 20(当たり 0)—入れない(下)

出荷される SRT はこうなった(本番経路 scripts/build-master-srt.mts / 全 13 Part。

SRT と 1 バイト一致を照合してから数えた)。

変更前変更後
キュー33,63233,645
error 合計1,2161,211
particle_start169
katakana_break3838
verb_break00
cue_break11
char_limit11

規則違反を 231 箇所ぶん直して、検出される error は 5 件減った。

particle_start の −7 は連語の頭の に を格助詞と数えていた過検出

(〜|に対しての不満ってさ)。+1 は のみで の位置が動いたもので、これも

のみ を副助詞として扱って解消した。

送り仮名は仕分けが済んだ。入れてはいけない。 「漢字→ひらがなの境目 231 箇所が未仕分け」

としてあったものを 12 周目の出力(162 箇所)で等間隔に 20 件読んだところ、

興行収入|ありきで かませ犬|みたいな 生活|できひんねんな のように全部が名詞+別の語だった。

生成側は findBreakPoints の字種の段でも syllableBreakPoints でも漢字→ひらがなを外しており、

この型は既に塞がっている。残る 162 箇所は助詞・文節・語頭の段から立った合法な切れ目である。

`て形+補助動詞` は浅尾の判断で決着した(2026-08-14)。

補助動詞は基本切りたくないな。完全にどうしても切れない場合だよ。

正本 L1-09 §2 が「独立した語(くれる もらう いただく ある くる)なら切ってよい」と

書いていたのを取り消し、音節改行と同じ「最後の手段」(罰点 90)にした。

名指しの有無で分けていた 341 件の区別は要らなくなる。

変更前変更後扱い
て+もらう3118最後の手段(罰点 90)
て+くれる5117同上
て+いく3615同上
て+くださる169同上
て+いただく85同上
て+ある123同上
て+くる182同上
て+おく71同上
合計 / うち他に候補あり(=違反)179 / 10870 / 0
て+みる——対象外。〜て、見る と字面で区別できないので数えていない
て+いる の活用形00段0 禁則のまま(「最後の手段」より厳しい)

`で` は て形のときだけ。 撥音便(死んで)とイ音便(急いで)だけが て形で、

格助詞の で は違う。緩めた版を測ったら違反 22 件のうち 7 件が全部この過検出だった

(に一発で|いくとい な感じで|いきまし 最後まで|いけねえ はどれも本動詞の 行く)。

擬態語は表どうしが矛盾している。 ATOMIC_KATAKANA は擬態語(ギリギリ ノロノロ

ネチョネチョ)を「繰り返しに意味があるので割らない」として入れているのに(2026-08-07)、

repetitionSeams は同語反復を「割ってよい継ぎ目」として免除する(2026-08-12)。どちらが正か。

上限超過をどう 0 にしたか(2026-08-11 → 08-12。32 → 4 → 0)

どれも規則を変えたのではなく、規則が要求する候補が無かっただけ。

直し減った件数中身
複合語の継ぎ目を 3 語登録25じゃんけん+デスゲーム(23 件)/ インディー+映画 / ジャン・K・+ポール
行頭の 1 文字助詞の例外に やっ して3`プロダクション\やったと 型。や し` で始まる用言を助詞と取り違えていた
(上の副作用でキューの割れ方が変わった)4
残り 4 件(下表)は縮約と再分割で解消4

継ぎ目を登録した語は、辞書の語でもそこで割れる。 これは新しい例外ではなく

breakPoints.ts が最初から持っている規則で、パラノーマル\|アクティビティ

ブレアウィッチ\|プロジェクト は前からこう振る舞う。上書きしてよいのは

継ぎ目の語が保護範囲そのものと一致するときだけで、じゃんけんデス\|ゲーム

(内側の デス+ゲーム の継ぎ目)は依然として違反である。

2026-08-11 に残っていた 4 件。改行・カット・音節改行のどれでも解けなかった。

(すべてショート 10 字幅。2026-08-12 現在は 0 件)

本文字数なぜ解けないか
シノプシスっていうんや11シノプシス は 1 語(分割不可)。5 字目は っ、9 字目は ん で行頭禁則。10 字目は助詞 や。候補 0 件
プロダクションっていうのを13プロダクション は 1 語。7 字目は っ で行頭禁則。唯一の候補が 11 字目(準体助詞 のを)で、そこで割ると 1 行目が 11 字
いかんかったっていうか11全部ひらがな。意味の切れ目は 6 字目だけで、そこは っ で行頭禁則。当時の文節モデルも候補を出さなかった
12個やったかっていうと11切れ目は 3 字目(`12個\やったか…`)だが、漢字→ひらがなは送り仮名なので候補にしない規則がある。音節改行も同じ理由で塞がる

共通の形は「7〜9 字の 1 語+語尾」で、語の直後が行頭禁則の字か助詞。

この 4 件は改行では解けなかった。残る手は縮約(L1-10)で語尾を落とすことで、

縮約は本文を変えるので、字幕フォーマットの側で勝手に決めない。

⚠️ 「候補に入っているか」だけでは足りない。 指標は罰点モデルと同じ判定を使うので、

モデル側の取り違えは合法と数えられる。関係な|い は 100% の中に隠れていて、

実物を目で見て初めて見つかった。 数値を通したうえで、全長から等間隔に 20 件ほど抜き出して読む。

実測(ディレクターズ葛藤 全 13 Part / 18,363 キュー / 2 行 6,840 件): 99.91%、行数超過 0。

配線前の出荷済み字幕は 19〜25% だった。


5. ショット チェンジ
状況処理
対話がショットチェンジ時 or 5フレーム以内に始まるIN点をショットチェンジの 2フレーム後
音声がショットチェンジ近くで終わるOUT点をショットチェンジの 2フレーム前
字幕がシーン変更を跨ぐ原則禁止(話者が継続する場合のみ可)

6. ショート(縦型 9:16)の上書き値

1 行の文字数はプロジェクト設定のパラメータ(6 以上。§1)。 下は

katto-shorts(ディレクターズ葛藤)の設定値で、固定値ではない。

項目フル尺ショート
1行最大13文字10文字
合計最大26文字20文字
CPS 理想 / 上限8 / 128 / 12

この案件では 10 字 × 最大 2 行(浅尾確定 2026-08-07)。

かつてここには「本書 10 字 / 実装 13 字」の食い違いが残っていたが、

実装(lib/subtitle/thresholds.ts の KATTO_SHORTS_JA)は 2026-08-07 に 10 字へそろえてある。

設定値と実装が一致している。

13 字の根拠だった「手仕上げ Part10 の行長 p95=13 / max=17」は

機械の折り返し幅(1 行目が常に上限値)で、人の判断ではなかった。

10 字に絞ると手仕事版の約 3 分の 1 の行が組み直しになりキュー数は増えるが、正本を採る。

CPS 上限 12 も実測で決めた。10 では浅尾自身の手仕事の 17.8% が違反する(12 なら 3.6%)。

実測 p90=11.08 / p95=11.69。測り方は scripts/measure-human-cps.mts。

数値を動かすと rulesHash が変わり、生成済みのプランと spec は作り直しになる。


7. ファイルフォーマット
形式用途
SRT (SubRip)最も一般的・汎用
WebVTTWeb ストリーミング
Timed Text (TTML)Netflix / 配信プラットフォーム
STLプロ放送用

出典
  • Netflix 日本語 Timed Text スタイルガイド
  • AVTpro 日本語字幕スタイルガイド (2024-03-20)
  • NHK / 民放慣行(田村幸彦 1931 由来)
  • 学術: 古後・岸 (1996), Sasaki (2017)
  • 参照: [03_japanese_typography.md](03_japanese_typography.md), [05_transcription_pipeline.md](05_transcription_pipeline.md)

最小表示時間の上書き(葛藤 / 本編・ショート共通)

最小表示時間は 0.4 秒を採る。全体の既定 500ms からの上書きで、このマニュアルがそれを認可する。

根拠(浅尾確定 2026-08-18): Part1 の実測で 本編 5 件・ショート 16 件が 0.5 秒未満、最小 0.196 秒。

0.5 のままだと、次の発話に押されて規則を守れない箇所が残る。**守れない規則を掲げるより、

守れる値へ下げて違反を実数で数えられるようにする。**

CPS 上限の上書き(葛藤 / 本編・ショート共通)

CPS 上限は 18 を採る。上の業界目安(8/10)からの上書きで、このマニュアルがそれを認可する。

根拠(浅尾確定 2026-08-18): 本編で実測した。Part1 本文 10,422 字、上限超過 28 件の分布は

12.1〜17.3 / 中央 14.0。いちばん速いのは ワンシチュエーション の 10 字 0.58 秒 = 17.3 字/秒で、

28 件すべて 12 字以下の短いキュー。発話がそこだけ速いという素材の事実で、縮約では減らない

(文字が少ないので削る余地が無い)。

閾値を上げても出力は 1 バイトも変わらない。12 / 14 / 15 / 16 / 18 の 5 通りで SRT の長さが

31,122 / 37,541 のまま同一だった。本編の経路では検証の閾値としてしか使われない。

測る順序に注意。時刻を按分から実測へ直したあとで測ること。按分のままだと分母が水増しされ、

違反が甘い方向に隠れる(実測 2026-08-17: 同じ Part1 で reading_speed が按分 20 件 / 実測 28 件、

CPS の最大が按分 32.0 / 実測 17.3。32.0 は 4 字を 0.125 秒に押し込んだ按分の産物で、素材の速さではない)。


規則値(自動生成)

この節の表は `lib/rules/registry.ts` から生成される。手で書き換えると `yarn rules:docs --check` が落ちる。

値を変えるときは実定数を変え、yarn rules:docs で吐き直す。

全体の既定から外す場合は、このマニュアルに根拠を書いてから authorizedBy を埋める(正本 §7)。

<!-- generated:rules:subtitle-shorts-ja -->

字幕(ショート・日本語 / `SHORTS_JA_DEFAULT`) — この表は lib/rules/registry.ts から生成される。手で書き換えない(yarn rules:docs)。

値いま全体の既定認可根拠
maxCharsPerLine10 字6〜16 の範囲manual/L1_subtitle/01_subtitle_format_jp.md §1浅尾 2026-08-20 — 13 へ広げる案を測って却下。Part1 で幅超えは 10 字 31.4% / 13 字 5.7% だが、幅超えは全部「縮約字幕 1 件が単独で 2 行に組めない」もので、繋ぎすぎではない(縮約が本編の 13 字幅で書かれているため)。9:16 の可読性を優先し 10 字を維持する。溢れは規則どおりの出力で、欠陥ではない。画面はその場に超過を出す
maxLines2 行1〜3 の範囲manual/L1_subtitle/01_subtitle_format_jp.md §1—
maxTotalChars20 字maxCharsPerLine × maxLines—行幅か行数を動かすと連動する
cpsMax18 字/秒12 字/秒manual/L1_subtitle/01_subtitle_format_jp.md §CPS 上限の上書き浅尾 2026-08-18 — Part1 本文 10,422 字。上限超過 28 件の分布 12.1〜17.3 / 中央 14.0。閾値を 12/14/15/16/18 の 5 通りで振っても SRT の長さは 31,122 / 37,541 で同一
minDuration0.4 秒0.5 秒manual/L1_subtitle/01_subtitle_format_jp.md §最小表示時間の上書き浅尾 2026-08-18 — Part1 実測: 本編 5 件・ショート 16 件が 0.5 秒未満、最小 0.196 秒。0.5 では次の発話に押されて守れない箇所が残る
maxDuration7 秒7 秒—ドキュメンタリー側。ドラマは 6.5
softMaxDuration4.5 秒——根拠が無い。 正本にも manual にも docs にも記載が無いのに lib/subtitle/shortCueRefine.ts:58 が出荷経路で使っている
minGapSec0.083417 秒2 フレーム—23.976fps 基準。24fps へ丸めない
flickerGapSec0.2 秒最小ギャップ—正本 §9 は「0 か最小ギャップ以上」。0.0834〜0.2 が正本では合法・実装では違法になる帯
leadInSec0.083417 秒2 フレーム——
leadOutSec0.5 秒0.5 秒——
maxLeadOutSec1 秒1 秒——
fps23.976024 fps23.976024 fps——
  • maxCharsPerLine: 正本は範囲を持つ。10 は葛藤ショートの既定値
  • cpsMax: 業界の目安(8/10)は劇映画・ロング向け。この案件は実測で 18 を採る
  • minDuration: 守れない規則を掲げるより、守れる値へ下げて違反を実数で数える

<!-- /generated:rules:subtitle-shorts-ja -->

L1-02 英語字幕フォーマット規則
Netflix / BBC / TED / EBU 基準に準拠した英語字幕の共通ルール。

1. 文字数
基準1行最大行数合計
Netflix / TED42 chars284
BBC (Teletext, 等幅)37 chars274
BBC (proportional, 16:9)動画幅の 68%2—

Best practice: 可能なら 1 行に収める。2 行にする場合は bottom-heavy pyramid(上短・下長)。


2. 読み取り速度
プラットフォーム成人向け子供向け備考
Netflix20 CPS17 CPS—
TED21 CPS—専門用語多い場合は下げる
BBC15 CPS—標準
6秒ルール(伝統)12 CPS—歴史的基準

WPM 換算:

  • BBC: 一般 160〜180 WPM / 成人 250 WPM
  • 180 WPM ≈ 15 CPS

3. 表示時間
項目NetflixTED
最小5/6 秒 (0.833s) ※24fps で 20F≈ 1.12 秒
最大7 秒7 秒

字幕間ギャップ (Netflix, 24fps):

  • 最小 2 フレーム / 3〜11 フレームは禁止(2F に詰める) / 12F (0.5s) 以上は OK

4. 改行ルール
区切ってよい位置(優先順)
  • 句読点の後(period, comma, semicolon)
  • 接続詞の 前(and, but, or, so, yet)
  • 前置詞の 前(at, by, for, from, in, of, on, to, with)
  • 自然な節境界
  • 発話の自然な区切り
区切ってはいけない位置
  • 冠詞(a, an, the)の後
  • 冠詞と名詞の間
  • 形容詞と名詞の間
  • 動詞と主語の間
  • 動詞と助動詞の間
  • 代名詞と動詞の間
  • 所有代名詞の後
  • 人名(first / last の間)
TED 特有ルール
  • "linguistic wholes" を保つ
  • 2 行は可能な限り視覚的バランスを取る

5. 句読点・大文字
大文字化
  • 通常の文 = Sentence case
  • 固有名詞・人名・地名 = 大文字
  • Netflix: "Black", "Deaf", "Indigenous" は人種・属性を指すとき大文字
  • ALL CAPS は禁止(強制的な画面テキスト等の例外を除く)
句読点
記号ルール
Period (.)通常の文末
Comma (,)標準英文法に従う・改行候補
!Netflix: 叫び / 驚きのみ。乱用禁止
?標準。?! interrobang は許容
Em dash (—)Netflix では 使用禁止
中断Double hyphen (--) で表現。行末か文中、行頭は禁止
Ellipsis (…)2秒以上の間 / 言い淀み / 尻すぼみ。U+2026 を使用、3ドット連打は不可
連続2字幕の継続ellipsis 不要
ダブルスペース禁止
引用符
  • 米: "..."、内側 '...'、ピリオド・カンマは閉じ引用符の内側
  • 英: '...'、内側 "..."

6. 話者識別
方式形式主用途
ダッシュ- I told you.\n- Did you?北米一般
ブラケット名 (Netflix)[sarah] Don't go.配信標準
コロンDOCTOR: The results are in.クラシック
色分け (BBC)白 / 黄 / シアン / 緑UK / EU

Netflix 規則:

  • ブラケットは 小文字(固有名詞は除く)
  • 名前を優先。不明話者は [man], [woman on phone], [man 1] [man 2]

7. SDH(聴覚障害者向け)

含めるもの:

  • 全対話
  • 話者識別(明らかでないとき)
  • 意味のある効果音
  • 音楽説明 / 歌詞
  • トーン(皮肉・叫び等、文脈に必要なとき)
  • オフスクリーン音
  • 重要な沈黙

書式:

  • 効果音: [door slams], [phone ringing]
  • 音楽: [suspenseful music playing] または ♪ *Lyrics here* ♪
  • トーン: [sarcastically], [whispering]
  • 沈黙: [silence], [long pause]

8. イタリック

イタリック化する:

  • ナレーション・ボイスオーバー
  • 内心独白
  • 歌詞
  • 電話 / TV / ラジオ / AI の声(話者が見えないとき)
  • 作品タイトル(書籍・映画・番組・新聞)
  • 馴染みのない外国語
  • 強調(句読点で足りないとき)

しない:

  • 話者ブラケット [John]
  • 効果音ブラケット [door slams]
  • 辞書登録済み外来語(rendezvous, café, déjà vu)

Note: Netflix の言語ガイドの 25% 以上(韓・中・希・印・尼・タイ)は イタリック非推奨。


9. 数字フォーマット
範囲推奨例
1〜9スペルアウトthree
10+数字12, 345
時刻・日付・年数字8:30 PM, 2026, March 5
通貨数字+記号$50, €25

10. アスペクト比 / フォント
  • フォント: Arial, Helvetica, Verdana 系 sans-serif、白文字+黒縁
  • 配置: 画面下部・中央揃え
  • TTML: ピクセル値禁止、% 指定のみ

出典
  • Netflix Timed Text Style Guide (English)
  • BBC Subtitle Guidelines v1.2.3 (2024-06)
  • TED Subtitle Guidelines
  • ISO/IEC 20071-23:2018
  • EBU Tech 3264 (EBU-STL)
  • Code of Good Subtitling Practice (Ivarsson & Carroll, 1998)
L1-03 日本語タイポグラフィ規則
字幕中の文字種・句読点・数字・記号の扱い。フォーマット規則は [01_subtitle_format_jp.md](01_subtitle_format_jp.md) を参照。

1. 句読点
基本原則: 字幕に伝統的句読点を使わない

理由: 句読点は「テキストを読んでいる」感覚を生み、字幕の理想(直接発話を聞いている錯覚)を破る。

代替
元代替
句点(。)全角スペース
読点(、)半角スペース
疑問符全角(?)+ 新文開始時は全角スペース
感嘆符全角(!)+ 新文開始時は全角スペース
例外
  • バリアフリー字幕(SDH)では使用可
  • 書面引用・画面内テキスト翻訳では使用可

2. 漢字・ひらがな・カタカナのバランス
推奨比率
  • 漢字 20〜30% : ひらがな 70% : カタカナ 0〜10%
  • 別ガイド: 6:3:1(ひらがな:漢字:カタカナ)
漢字ルール
  • 常用漢字のみ 使用
  • 一般に読めない難しい漢字は避ける
  • 参考: NHK「新用語辞典」/「ことばのハンドブック」、共同通信「記者ハンドブック」、朝日「用語の手引き」
ひらがな化すべき品詞
  • 助動詞
  • 副詞
  • 抽象名詞:
  • 時 → とき
  • 事 → こと
  • 物 → もの
  • 為 → ため
例
評価例
✅ 良いバランス彼はとても速く走ることができる
❌ 漢字過多彼は非常に速く走る事が出来る
❌ ひらがな過多かれはとてもはやくはしることができる

3. 数字
横字幕
桁表記例
1桁(1〜9)全角1、2、3
2桁以上半角12、345、2023年
小数点半角ピリオド3.14
4桁超カンマ避けて漢数字一万、十万
縦字幕
桁表記
1桁全角
2桁半角・横向き配置
3桁以上全角・縦向き配置
小数点中黒(・)を縦書きで使用

4. スペース
状況スペース
文節の区切り半角スペース
文の区切り全角スペース
語句の途中挿入禁止
単語境界必要なし(日本語特性)

5. 特殊文字 / 記号
記号用途
⸺ (U+2E3A 二分ダッシュ)複数字幕にまたがる文の継続
""(半角ダブル)引用、内側スペースなし
〈〉 ギュメプロット関連の外国語対話
( ) 全角括弧SDH の話者識別
♪SDH の歌マーク、半角スペースを挟む
イタリック歌詞・朗読詩・画面内テキスト翻訳(Netflix基準)

6. ふりがな・ルビ

映画字幕では原則使用しない。

使用が許容される場面
  • 混乱を招く読み(曖昧な漢字読み)
  • 固有名詞(珍しい読みの人名・地名)
  • 専門用語(発音ガイダンスが必要)
SDH では
  • 対象視聴者の読解レベルに応じて使用可
  • 学習補助としても機能

7. 数字以外の表記ゆれ統一

字幕プロジェクト全体で一貫させる:

揺れる対象統一の方針例
人名(漢字 vs かな)プロジェクト辞書で固定
業界用語(プレプロ / プリプロ)正式形を辞書で固定
カタカナ vs ひらがな(あさお vs アサオ)プロジェクト辞書で固定
数字(3 vs 3)上記桁ルールに従う

→ 辞書管理は各プロジェクトの DICTIONARY.md で行う(雛形 _project_template/ はこのリポジトリへは未移植)。


出典
  • Netflix 日本語 Timed Text スタイルガイド
  • NHK ことばのハンドブック
  • 共同通信 記者ハンドブック
  • 日本字幕業界慣行(1931 田村幸彦以降)
L1-04 翻訳原則(汎用)
言語ペアを問わず適用する翻訳の基本原則。トーク番組・ドキュメンタリー・字幕翻訳すべての共通土台。 映画 / ドラマ特有のキャラクター訳・ジャンル別スタイルは [04b_film_translation_subs.md](04b_film_translation_subs.md) を参照。

1. 三原則
  • 簡潔性 (Brevity): 限られた文字数で本質を伝える
  • 自然性 (Naturalness): 翻訳臭のない自然な表現
  • 等価性 (Equivalence): 原文の意図・感情・トーンを等価に再現

2. 意訳 vs 直訳
状況推奨理由
一般的な会話意訳自然な日本語で視聴体験を保つ
専門用語・固有名詞直訳 or 原語保持正確性最優先
ジョーク・言葉遊び等価効果笑いのタイミングを逃さない
文化的参照文脈に応じて判断視聴者の理解度を考慮
感情表現意訳ニュアンス最優先

3. 凝縮テクニック
原則: 削らず、凝縮する
優先順位
  • 冗長表現を削除(「〜という風に」→「〜と」、「させていただいております」→「しております」、「っていうところ」→「って」)
  • 自明な情報を省略(視覚で分かる情報、文脈で明らかな主語)
  • 修飾語を簡略化
  • 複文を単文に分割
例
原文: "I really, really need you to understand that this is extremely important."
直訳: 「本当に本当に これが極めて重要であることを理解してほしいんだ」
凝縮: 「頼む これは本当に重要なんだ」

4. イディオム
処理場面例
等価な日本語慣用句類似表現あり"piece of cake" → 朝飯前
意味で翻訳等価表現なし"break a leg" → 頑張って
原語保持+意訳有名な表現原語を活かしつつ意味を伝える

5. 文化特有概念
1. 同等概念あり → 置換
2. 説明なしで理解可能 → そのまま
3. 文脈で推測可能 → 軽い補足
4. 理解不能 → 意訳 or 注釈(最終手段)

例:

原文: "She threw a baby shower."
処理: 「出産祝いのパーティーを開いた」(日本では馴染みが薄いため説明的に)

6. 言語ペア特有の課題
英 → 日
  • 主語省略: 文脈で明らかなら省略("I went... I bought... I came home." → 「店に行って牛乳を買って帰った」)
  • 時制: 厳密でなくて良い。現在形で済むなら現在形
  • 受動 → 能動: 推奨(「ジョンがドアを開けた」)
日 → 英
  • 省略主語の補完: 英語では明示が必須
  • 敬語: フォーマルな語彙選択 / "sir", "ma'am"
  • オノマトペ: 「ドキドキする」→ "My heart is pounding" / 「シーン」→ [silence]

7. 品質チェックリスト
技術
  • [ ] 文字数が制限内(日: 13/行、英: 42/行)
  • [ ] CPS 適切(日: 4、英: 20)
  • [ ] 表示時間 0.833〜7秒
  • [ ] 改行位置が自然
翻訳品質
  • [ ] 原文の意味が正確
  • [ ] 自然な日本語/英語
  • [ ] 感情・ニュアンスが保持
  • [ ] 文化的誤解がない
一貫性
  • [ ] 専門用語が統一
  • [ ] 固有名詞処理が統一

→ 用語統一は各プロジェクトの DICTIONARY.md で管理。

→ キャラクター口調・ジャンル別スタイル(映画/ドラマ用)は [04b_film_translation_subs.md](04b_film_translation_subs.md) を参照。


出典
  • Netflix Timed Text Style Guide (各言語)
  • TED Translators Wiki
  • 学術: 通訳翻訳研究 (JAITS), Sasaki (2017)
L1-04b 映画 / ドラマ字幕用 翻訳サブカテゴリ
適用範囲: 劇映画・ドラマ・アニメの字幕翻訳。 トークドキュメンタリー / 教育動画 / SNS ショートには通常 適用しない(必要に応じて部分的に参照)。 汎用の翻訳原則は [04_translation_principles.md](04_translation_principles.md) を参照。

1. キャラクター訳
1-1. 一人称(日本語)
一人称キャラタイプニュアンス
私ニュートラル / 女性 / フォーマル男性標準
僕若い男性 / 知的柔らかい
俺成人男性カジュアル男性的
オレ若者・ヤンキー粗野
わたくし上流階級・執事格式
あたし女性カジュアル親しみ
わし老人・武将威厳
拙者侍古風
余王族高貴
我神 / 超越者 / 中二病荘厳
自分軍人 / 体育会系規律
1-2. 二人称
二人称用途ニュアンス
あなた標準・フォーマル距離
君 / きみ同僚 / 恋人(男→)親しみ
お前友人 / 目下カジュアル
てめえ / 貴様敵対攻撃的
あんたカジュアル / 関西ぞんざい
そなた / お主時代劇古風
1-3. 語尾パターン
  • 敬語: 〜です / ます、〜でございます、〜ですわ(お嬢様)、〜ですの(軽め)
  • カジュアル: 〜だ / だよ、〜よ / わ(女性)、〜だぜ / ぞ、〜じゃん / っしょ(若者)、〜っす(体育会系)
  • 特殊: 〜じゃ(老人・博士)、〜のだ(解説・中二病)、〜でござる(侍)、〜なのです(丁寧な解説)、〜にゃ / 〜なの(萌え系)
1-4. 一貫性チェック
  • [ ] 一人称が統一されている
  • [ ] 二人称が関係性に適切
  • [ ] 語尾パターンが一貫
  • [ ] 感情変化時の口調変化が自然
  • [ ] 敬語/タメ口の使い分けが適切

2. ジャンル別スタイル
2-1. コメディ
  • テンポ重視(長い字幕は避ける)
  • ボケとツッコミのタイミングを逃さない
  • 言葉遊びは等価効果を追求

NG:

  • 説明的すぎる翻訳
  • ジョークの解説
  • 笑いのピークを過ぎた字幕表示
2-2. サスペンス・スリラー
  • 緊張感を維持する短い文
  • 情報の出し方に注意(ネタバレ厳禁)
  • 不穏な雰囲気を言葉で演出
原文: "Someone is watching."
標準:    「誰かが見ている」
サスペンス調: 「…見られている」
2-3. ロマンス
  • 感情表現を豊かに
  • 詩的な美しさを保持
  • 過剰にならない自然さ
  • "I love you" → 状況に応じて「好きだ」「愛してる」を使い分け
2-4. アクション
  • 短く、インパクトのある表現
  • 一言で状況を伝える
  • 叫び声は視覚効果と調和
原文: "Get down! They're shooting at us!"
字幕: 「伏せろ!」(状況は視覚で伝わる)
2-5. SF・ファンタジー
  • 専門用語・造語の統一
  • 世界観を損なわない表現
  • 用語集(DICTIONARY.md)の徹底管理
パターン処理例
一般 SF 用語既存の訳語warp → ワープ
作品独自の造語音訳 or 意訳作品内で統一
複合語組み合わせて翻訳hyperdrive → ハイパードライブ
2-6. ホラー
  • 不気味な雰囲気を文体で演出
  • 曖昧さを残す(説明しすぎない)
  • 静寂の効果を活かす

テクニック:

  • 体言止め: 「…誰かが、いる」
  • 省略: 「まさか…」
  • 不自然な間: 文を短く区切る

3. スラング・俗語
年代別マッピング
年代英語日本語等価
現代若者"lit", "slay", "no cap"やばい、まじ卍、ガチで
2010 年代"yolo", "bae"リア充、推し
クラシック"groovy", "rad"ナウい、イカす

注意: 流行語は陳腐化が早い。作品の時代設定に合わせる。


4. 視聴体験のチェック

字幕翻訳の品質チェック(映画/ドラマ用):

  • [ ] キャラクターの口調が一貫しているか
  • [ ] 感情変化時の口調変化が自然か
  • [ ] ジョークのタイミングが適切か
  • [ ] ネタバレになっていないか
  • [ ] 視覚情報と矛盾していないか
  • [ ] 音楽・SE との調和が取れているか

このサブを使うべきとき
  • 劇映画字幕(特に翻訳作業)
  • ドラマ字幕
  • アニメ字幕
このサブを使わないとき
  • 実在人物のトーク番組(出演者は本人なので口調は変えない)
  • ドキュメンタリー(証言は元発話を尊重)
  • 教育動画 / 講演(中立的なナレーション)
  • SNS ショート(短すぎてキャラ口調が意味を持たない)

→ これらは [04_translation_principles.md](04_translation_principles.md) のみで足りる。

L1-05 ASR → SRT 共通パイプライン
音声書き起こし(ElevenLabs / Whisper 等)から放送品質 SRT までの汎用パイプライン。 プロジェクト固有のスクリプトパスや辞書は各プロジェクトの LOCAL_PATHS.md / DICTIONARY.md を参照。

1. 入力ソース
種別用途必要度
ASR SRT(ElevenLabs / Whisper)書き起こしテキスト必須
ASR JSON(word-level / character-level)精密タイムスタンプ・gap 検出セマンティック分割に必須
編集 EDL / DaVinci SRTカット点参照あれば品質向上
元音声 / 映像検証用推奨

1.5 実行順とゲート(他案件でも同じ順で回す)

各段に機械で測れる合格条件を置く。 散文で守るのではなく、通らなければ止める。

実装は scripts/ 配下。既定は DRY RUN で、--go で書き込む。

#段コマンド合格条件
0素材の確認—語単位 JSON が全尺ぶんある。無ければ ASR をやり直す
1時刻の潰れを直すrepair-transcript-collapse.mts話速 20 字/s 超の語が 0(音イベントのタグは長さから除く)
2辞書で機械補正correct-transcripts.mts辞書にある誤変換の残存 0
3文脈校正(Claude)audit-plan-proper-nouns.mts ほかconfident のみ適用。unsure は人へ回す
4校正の適用apply-proofread.mts該当箇所が見つからなかったもの 0
4.5校正の精度と再現率下記precision 95% 以上 / 残存誤り率 1% 未満
5品質監査audit-transcript-quality.mts二重出力 0 / 時刻の巻き戻り 0
6キュー生成cuesFromSource下記の改行と尺のゲート
7改行の検査check-line-breaks.mts合法率 99% 以上 / 行数超過 0
8尺の検査compare-cue-timing.mts末尾無音 0.5 秒超 0 / 最大表示時間超過 0
なぜ段 1 が最初か

ElevenLabs は時刻を潰すことがある。 何十文字もが同じ時刻に貼り付き、

時間情報がまったく無い範囲が出る。しかもその本文は数秒後に正しい時刻で再出力される。

これを直さずに先へ進むと、二重の本文と使えない時刻が全段に流れる。

直すのは生データの段で。 補正済みファイルでは潰れた文字が 1 語へ連結され、

重複した部分と固有の部分が同じ塊に混ざって境界が取れなくなる。

見つけ方と直し方は L1-08 §「時刻の潰れ」を参照。

段 4.5 校正の精度と再現率(「N 件直した」は成果ではない)

直した件数は品質を表さない。 2 つを別に測る。

precision(直したもののうち正しかった割合)

適用した修正から無作為に 20〜30 件抜き、独立な根拠で 1 件ずつ検証する。根拠は別系統の ASR、校正済み字幕、クレジット、キャスト表、脚本。実測では提案 160 件のうち 5 件(3%)が「原文が正しく、提案が誤り」だった。 precision を測らないと、この 3% が全長へ広がる。

recall(残っている誤りのうち見つけた割合)

無作為に区間(合計 3,000 字程度)を抜き、独立な系統でもう一度誤りを探し、1 回目が見落とした数を数える。recall を測っていないなら「校正できた」と言わない。

系統間の不一致率が残存誤りの上限。 同じ音源の書き起こしが複数あるなら、全系統が一致する箇所は実発話とみなせる。割れている箇所の割合が、まだ残っている誤りの上限になる。これは全長に対して機械的に出せるので、抜き取りより強い。

実測(ディレクターズ葛藤 全 13 Part、2026-08-03)
段結果
1 潰れの修復620 字を削除、142 字の時刻を割り直し。話速違反 0
2 機械補正603 箇所
3+4 文脈校正580 件指摘 → 126 件適用(未適用 0)
7 改行6,834 / 6,840 = 99.91%(配線前 19〜25%)
8 尺末尾無音 最大 0.50 秒(出荷済み 3.48 秒)、尺 最大 7.00 秒(同 18.44 秒)

2. 標準パイプライン
ASR JSON (character-level)
    ↓ ① セマンティック分割
  gap 境界でチャンキング
  → 意味単位の自然な切り位置を保証
    ↓ ② 固有名詞補正
  プロジェクト辞書を適用
  人名表記揺れ / 業界用語の統一
    ↓ ③ 話者ID付与
  speaker_N → 人物名マッピング
    ↓ ④ ブロック分割
  word-level タイムスタンプで
  5秒超 or 字数超ブロックを分割
    ↓ ⑤ ブロック結合
  同一話者・短ギャップ・合計上限以下 → 結合
    ↓ ⑥ カット点参照分割
  編集カット点でさらに細分割
    ↓ ⑦ 時刻補正
  短テキスト長尺ブロックの開始時刻を補正
    ↓ ⑧ 出力整形
  極短(<300ms)ブロックを前ブロックに吸収
  smart_linebreak(行字数 × 最大2行)
  話者ラベル除去(出力ポリシーに応じて)
  余韻 +0.5秒(次ブロックが来るならそちら優先。L1-01 §3)
    ↓
  output.srt + review.md

3. セマンティック分割の主要パラメータ
パラメータ推奨値意味
GAP_HARD_MS200ms必ず分割
GAP_SOFT_MS80ms8文字以上蓄積で分割
MAX_CHARS34文字上限(2行 × 17 字)
ECHO_MS300ms余韻
MIN_DURATION_MS300msこれ未満は前ブロックに吸収
MERGE_GAP_MS600ms同一話者・このギャップ以下で結合

→ プロジェクト側で文字数上限など上書き可(例: Katto 本編 = 26字, ショート = 20字)。


4. 固有名詞補正

各プロジェクトの DICTIONARY.md に記載された:

  • 誤変換 → 正表記マッピング
  • カタカナ ⇄ ひらがな統一
  • 作品名・人名・業界用語の正式形

を 全置換 で適用。

ASR 誤変換の典型パターン(日本語)
実音誤出力説明
せ [se]て [te]歯茎: 摩擦 → 破裂
まーけまがき長母音 → 有声軟口蓋+母音シフト
ひらがな名カタカナ名あさお → アサオ 等

辞書更新は 処理のたびに育てる こと([06_quality_review_workflow.md](06_quality_review_workflow.md) 参照)。


5. 出力フォーマット
SRT 命名規則
  • 最新版: <name>_claude.srt(常にこのファイル名が最新)
  • 退避: <name>_<日付>_claude.srt にリネームして _archive/ へ
review.md

レビューファイルに以下を記録:

マーク意味
✅ 自動補正済み辞書で自動修正した箇所
🔴 話者矛盾自己紹介テキストと話者ID不一致
⚠ 長尺ブロック4.5〜5秒超(スロー発話は許容)
✂️ 切り位置フラグ断片テキスト / 長尺ブロック
🗣 話者交代フラグブロック内で話者交代
🤖 AI意味チェックセマンティック問題
review.md の典型フラグ
マーク意味対処
start_frag単語の途中から始まる前ブロックと結合 or start_ms 調整
end_frag接続助詞で途中終わり次ブロックと結合 or end_ms 調整
long_block4.5秒超手動で分割点を探す
short_text3文字以下の孤立前後と結合
speaker_changeブロック内で話者交代交代点で分割
ai_mid_startAI が途中開始と判定前ブロックを確認
ai_mid_endAI が意味的途中切れと判定次ブロックを確認

6. AI レビュー(Claude 等)

ルールベースでは検出できない 言語としての自然さ を AI で修正。

検出対象
  • 噛み・繰り返し(「8月8月25日」→「8月25日」)
  • 数字補完(「4月1から」→「4月1日から」)
  • 単語分断(活用が改行を跨ぐ)
  • 不自然なスペース(語句途中のスペース)
  • STT アーティファクト(意味不明な英語断片)
  • 冗長表現の圧縮(CPS 過大エントリ)
使用モデル
  • Haiku: コスト効率重視(バルク処理向け)
  • Sonnet / Opus: 重要パートのみ

7. 既知の制限と許容事項
長尺ブロックが残る場合

ASR がブロック全体を長い単一セグメントとして認識し、かつ:

  • JSON 単語タイムスタンプも該当区間に存在しない
  • カット点も無い

→ 分割根拠なし。スロー発話 / 沈黙区間として許容。

テキストが途中から始まる

文章を時間比例で分割する副作用(形態素境界と一致しない)。

DaVinci カット点 / セマンティック gap で補正するが限界あり。

ASR 仕様による品質差

ASR が word-level / character-level JSON を出さないケース(短尺ショート等)は、SRT 単独依存となり長いブロックが残りやすい。


8. 品質基準(出力の合格ライン)
項目基準
1行の文字数プロジェクト規定値以下(日: 13, 英: 42 等)
行数最大2行
表示時間≥ 300ms(最小は 500ms 推奨)
ゼロ秒ブロック0 件
5秒超ブロック0 件が理想(スロー発話は許容)
タイムスタンプ重複0 件
句読点(、。)プロジェクト規定に従う(字幕では除去が標準)
話者ラベル出力ポリシーに従う(YouTube 本編: 除去、Podcast: 残す等)

→ 検証手順は [06_quality_review_workflow.md](06_quality_review_workflow.md) 参照。

L1-06 品質検証 / AI レビュー ワークフロー
パイプライン出力後の検証フロー。validator → AI レビュー → 再検証 の 4 ステップ。

着手前に必ず読む: 教師データを信じる前に確かめる

「実測」「完成版」と呼ばれているデータが、実は機械の出力であることがある。

そこへ寄せると品質が上がったように見えて、機械の癖を再現しているだけになる。

署名を探す
対象機械の出力である印
改行1 行目が ceil(n/2) ちょうど / 1 行目が常に上限値 / 2 行目が 1 文字 / 語の途中で割れている
タイミング値が定数の倍数に揃っている / 尺が文字数に正比例している
本文同じ並びが数秒差で 2 回出る / 話速が 20 字/s を超える

どれかが当たれば教師データではない。

実例(ディレクターズ葛藤、2026-08-03)

完成ショートの 2 行キュー 142 件は 73% が `ceil(n/2)` ちょうど、

本編 2,624 件は 1 行目が常に 13 字(2 行目が 1 文字のものまである)。

めちゃめ|ちゃ『タ|イミング』ご飯食べ|ましょう と語が割れており、どちらも人の改行ではなかった。

さらに悪いことに、過去のセッションが同じ罠にはまった結論をコードのコメントに残していた——

「熟語・送り仮名・数字+助数詞を割らない」を入れたら一致率が 97% → 95% へ落ちたので外した、と。

一致率を上げるほど機械的になる指標だったので、指標ごと捨てた。

教師データが無いと分かったら

正本(本マニュアル)を根拠にし、測るのは「規則違反の数」に切り替える。

「人と同じか」ではなく「規則上ゆるされる位置か」なら教師データ無しで測れる。

実装は scripts/check-line-breaks.mts。


4 ステップ
Step 1: パイプラインで SRT 生成(機械処理)
Step 2: validator で 0 errors を確認
Step 3: Claude AI レビューで日本語/英語品質を修正
Step 4: validator 再確認 → 0 errors で完了

Step 1: パイプライン実行

→ [05_transcription_pipeline.md](05_transcription_pipeline.md) のフローを実行。

プロジェクト固有のコマンドは各プロジェクトの LOCAL_PATHS.md を参照。


Step 2: バリデーション
チェック項目(自動)
項目基準
行字数プロジェクト規定値以下
行数2 行以下
表示時間min 〜 max 範囲内
ゼロ秒ブロック0 件
タイムスタンプ重複0 件
句読点規定に従う
話者ラベル出力ポリシーに従う
CPS上限以下
改行位置助詞先頭 / 単語途中の禁止位置に違反していないか
出力例
Part1: 0ms=0 <300ms=0 行>13=0 5s+=0 ラベル=0  ✅

errors があれば Step 1 のスクリプトを修正して再生成。SRT を直接いじらない(次回パイプライン実行で消える)。


Step 3: Claude AI レビュー(最重要)

Python では検出できない 言語としての自然さ を LLM が直接チェック・修正。

推奨フロー

50 エントリずつ SRT を読み、Edit ツールで直接修正。

チェック項目
1. 噛み・繰り返しの除去
例修正
8月8月25日8月25日
概要欄概要欄概要欄
人気店人気店人気店
2. 日付・数字の補完
例修正
8月29で8月29日で
4月1から4月1日から
3. 単語分断の修正(改行/エントリ跨ぎ)
例修正
撮りた / いよ撮りたいよ / みたいな
いただきた / いないただきたいな
と / ころところ
4. 不自然なスペースの削除
例修正
形に はなったで形にはなったで
思って ますと思ってますと
5. STT アーティファクトの除去
例修正
sorryすみません
porque(削除)
意味不明な英語断片削除 or 日本語に置換
6. 冗長表現の圧縮(CPS 上限超過時)
元圧縮
させていただいておりますしております
っていうところって
〜という風に〜と

Step 4: 最終検証

Step 2 と同じ validator を実行し、0 errors を確認して完了。


レビュー出力(review.md)の運用

[05_transcription_pipeline.md](05_transcription_pipeline.md) §5 のフラグを参照。

再実行時の動作
  • ルールベース検出セクション(✂️ 切り位置 / 🗣 話者交代)は 上書き(重複しない)
  • AI レビュー(🤖)も上書き
  • 手書きの修正メモは別セクションを設けて保存

レビューレベル
Lv内容自動化
L1改行 3 行以上 → 2 行に強制修正完全自動(SRT 上書き)
L2a断片テキスト検出検出のみ → 人手修正
L2b話者交代がブロック内に混在検出のみ → 人手修正
L3Claude API で意味・文脈問題を検出検出のみ → 人手修正

AI レビューのコスト目安
モデル用途コスト感
Haikuバルク処理(全Part一括)$0.10 以下 / 1作品
Sonnet重要パート / 翻訳監修数倍
Opus翻訳の最終チェック10倍前後

→ 大量処理は Haiku、品質重視は Sonnet 以上を使い分け。


辞書更新ルール

処理のたびに辞書を育てる。

更新タイミング
  • 新しい固有名詞・表記揺れ・誤変換パターンを発見したら即追加
更新方法
  • 新固有名詞: プロジェクトの DICTIONARY.md の適切なカテゴリに行追加
  • 新誤変換パターン: 誤変換テーブルに行追加
  • ファイル末尾の 最終更新: 日付を更新
自動追記の指示(Claude へ)

SRT 生成中に以下を発見したら、処理完了後に 自動で `DICTIONARY.md` を更新 する:

  • 辞書にない人名・作品名・組織名・専門用語
  • ASR が繰り返し誤変換しているパターン
  • 初登場のエピソード固有用語

バージョン管理
状態命名
最新版<name>_claude.srt
退避<name>_<YYYYMMDD>_claude.srt → _archive/
マスター MD変更時末尾に 最終更新: YYYY-MM-DD — 変更サマリ を追記
L1-07 ElevenLabs 書き起こし汎用ワークフロー
ElevenLabs Scribe API を使った音声 → SRT / JSON 書き起こしの汎用フロー。 全プロジェクト共通の入り口。プロジェクト固有のパス・パラメータは各プロジェクトの PROJECT_RESOURCES.md を参照。

前提
API キー
  • 環境変数: ELEVENLABS_API_KEY(User scope)
  • 参照方法:
  • PowerShell: $env:ELEVENLABS_API_KEY
  • Python: os.environ["ELEVENLABS_API_KEY"]
  • Node.js: process.env.ELEVENLABS_API_KEY
  • 再設定(漏洩時 or 新キー発行時):

```powershell

[System.Environment]::SetEnvironmentVariable("ELEVENLABS_API_KEY", "<new-key>", "User")

```

→ ターミナルから直接実行。チャットに値を貼らない。


モデル選択
モデル用途特徴
scribe_v1標準日本語含む 99 言語対応 / 単語タイムスタンプ
scribe_v1_experimental検証用最新精度実験版(API 変更リスクあり)

→ 通常は scribe_v1 を使う。


書き起こしフロー
音声ファイル (mp3 / wav / m4a / mp4 / webm)
        ↓
   ① ElevenLabs Scribe API 呼び出し
   - model: scribe_v1
   - diarize: true(話者分離)
   - timestamps_granularity: word(or character)
   - tag_audio_events: false(無音/笑い等のタグ)
        ↓
   ② レスポンス取得
   - text: 全テキスト
   - words[]: 単語ごとのタイムスタンプ + speaker_id
   - language_code, language_probability
        ↓
   ③ JSON 保存(後段パイプラインの正規ソース)
   ↓ <name>_書き起こし_<date>.json
        ↓
   ④ SRT 生成(必要なら)
   - 単純: 一定区切りで改行
   - 推奨: semantic_srt パイプライン経由
       (詳細: 05_transcription_pipeline.md)
        ↓
   ⑤ 多面修正パス
   → 08_multipass_correction.md

推奨パラメータ
パラメータ推奨値説明
model_idscribe_v1標準モデル
language_codeja / en 等自動判定でも可、明示すると精度↑
diarizetrue話者分離(複数話者番組では必須)
num_speakers2〜4わかっていれば指定すると精度↑
timestamps_granularitycharactercharacter-level がセマンティック分割に必要
tag_audio_eventsfalseテロップ用なら true

Python 実装テンプレ
import os
import requests
import json
from pathlib import Path

API_KEY = os.environ["ELEVENLABS_API_KEY"]
ENDPOINT = "https://api.elevenlabs.io/v1/speech-to-text"

def transcribe(audio_path: Path, language: str = "ja", num_speakers: int = 3) -> dict:
    with open(audio_path, "rb") as f:
        files = {"file": (audio_path.name, f)}
        data = {
            "model_id": "scribe_v1",
            "language_code": language,
            "diarize": "true",
            "num_speakers": str(num_speakers),
            "timestamps_granularity": "character",
        }
        headers = {"xi-api-key": API_KEY}
        r = requests.post(ENDPOINT, headers=headers, files=files, data=data, timeout=600)
        r.raise_for_status()
        return r.json()

if __name__ == "__main__":
    import sys
    audio = Path(sys.argv[1])
    result = transcribe(audio)
    out = audio.with_suffix(".json")
    out.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"Wrote: {out}")

Node.js 実装テンプレ
import fs from "node:fs";
import path from "node:path";

const API_KEY = process.env.ELEVENLABS_API_KEY;
const ENDPOINT = "https://api.elevenlabs.io/v1/speech-to-text";

export async function transcribe(audioPath, { language = "ja", numSpeakers = 3 } = {}) {
  const form = new FormData();
  form.append("file", new Blob([fs.readFileSync(audioPath)]), path.basename(audioPath));
  form.append("model_id", "scribe_v1");
  form.append("language_code", language);
  form.append("diarize", "true");
  form.append("num_speakers", String(numSpeakers));
  form.append("timestamps_granularity", "character");

  const res = await fetch(ENDPOINT, {
    method: "POST",
    headers: { "xi-api-key": API_KEY },
    body: form,
  });
  if (!res.ok) throw new Error(`ElevenLabs API ${res.status}: ${await res.text()}`);
  return res.json();
}

JSON レスポンス構造
{
  "language_code": "ja",
  "language_probability": 0.99,
  "text": "全テキスト...",
  "words": [
    {
      "text": "こんにちは",
      "start": 0.12,
      "end": 0.48,
      "type": "word",
      "speaker_id": "speaker_0",
      "characters": [
        { "text": "こ", "start": 0.12, "end": 0.18 },
        { "text": "ん", "start": 0.18, "end": 0.24 },
        ...
      ]
    },
    ...
  ]
}
後段パイプラインで使うフィールド
フィールド用途
words[].start / endブロックタイムスタンプ
words[].characterscharacter-level gap でセマンティック分割([05](05_transcription_pipeline.md))
words[].speaker_id話者識別
words[].text書き起こしテキスト

エラーハンドリング
ステータス原因対処
401API キー無効 / 失効$env:ELEVENLABS_API_KEY を確認、再発行
422パラメータ不正model_id / language_code を確認
429レート制限リトライ(exponential backoff)
5xxサーバーエラーリトライ
timeout大きいファイルチャンク分割(10 分単位推奨)

コスト目安
  • Scribe: 約 $0.40 / 時間(2026 時点)
  • 60 分番組 1 本 ≈ $0.40
  • 月 20 本書き起こし ≈ $8

チャンク分割(長尺対応)

10 分超の音声は API timeout のリスクがあるため、推奨はチャンク分割:

# ffmpeg で 10 分ごとに分割
ffmpeg -i input.mp3 -f segment -segment_time 600 -c copy chunk_%03d.mp3

分割後、各チャンクの words[].start にオフセット(チャンク番号 × 600 秒)を足してマージ。


ASR 結果の素直な誤り傾向

→ プロジェクトの DICTIONARY.md § ASR 誤変換パターン に追記して育てる。

代表的な誤り(日本語):

実音ELScribe 出力説明
せ [se]て [te]歯茎: 摩擦 → 破裂
ひらがな名カタカナ名あさお → アサオ
長母音子音挿入まーけ → まがき

→ 多面修正パスで一括除去([08_multipass_correction.md](08_multipass_correction.md))。


話者ラベリング(グローバルルール。浅尾確定 2026-08-10 / マイク主体へ改定 2026-08-12)

ASR の `speaker_N` を入力にしない。話者はマイクから決める。

ダイアライゼーションは同じ人を複数ラベルに割り、無音や被りを別人として立て、

被りの少ない人をいちばん取りこぼす。

実測 2026-08-12(ディレクターズ葛藤、全 12 Part / 語 20,559 件):

ASR の話者分離マイクから決めた結果
Part1 の Fujii2.0%(29 秒)6.9%(94 秒)
Part6 の Fujii0%(speaker_0 が 77.6% を丸呑み)43.1%(738 秒)
Part7 の Fujii0%28.2%(472 秒)
ラベルの健全性36 ラベル中 14 が誤検出 / 6 が未確定—

Fujii のラベリアは他マイクとの相関が 0.17〜0.29(Asao↔Nabe は 0.63〜0.81)。

席が離れて被りが無い。被りで人を分ける方式は、被りの無い人を落とす。

ラベリアが鳴っていれば本人なので、マイクから決めればこの失敗は原理的に消える。

手順(3 層を分ける。同じ閾値を 2 つの目的に使わない)
  • 識別 — どのファイルが誰のマイクか。置き場が人名なら known(Audio/Asao/…)。

機材名の置き場(Audio/Wireless1/…)は unknown のまま残す。人名へ寄せない

  • 同期 — 本編の時刻 → 生マイクの時刻。推定し直さず、区間写像を読む

(Final/Audio/_partmix/<Part>_mix_source.json の editMap.placements[].intervals[])。

原点を 0 と決め打たない。 書き出しの開始 TC(Part9〜12 は 01:00:00:00)と、

本編音・字幕の版ずれが乗る。軸のずれは測る。検算は 3 点以上(グローバル正本 L4)

  • 割り当て — 語ごとに全マイクの SNR(自分の暗騒音からの上げ幅)を比べ、首位を採る。

マイクごとにゲインが違うので、生のレベルで比べない

「誤検出」と「未確定」を区別する
  • 確定 — 一致率 70% 以上かつ票 5 以上
  • 誤検出 — 測れているのに首位が立たない / 票が複数のマイクへ割れる。人名へ寄せない
  • 未確定 — 測れた語が少なすぎる。誤検出とは別物として残す
  • 同じマイクへ確定した speaker_N が複数あれば統合する
確信度は必ず一緒に持つ

差が小さいものを黙って 1 位へ落とさない。

最尤は入れてよいが、1 位と 2 位の差(dB)と絶対レベルを添えて印を付ける。

下流(ショートのアングル確定など)が自分で判断できるようにする。

「既定のアングルに落とさない」は「印を付けずに落とすな」の意味。

出す数値(書かずに表だけ置かない)

Part ごとに: 軸のずれと検算した点数 / 同期の検算 / 語の確定率 / 一致率(ASR との突き合わせ) /

precision と recall を別に / 拾えた秒(引き算ではなく区間の集合差) /

確定できなかったものを理由別に。

画面に映らない声(ディレクターなど)は話者として立てつつ、

表示は (カッコ)付きにする。話者数を音声だけで決めない(本編の静止画で人数を目で確かめる)。

この表はプロジェクトと Part の二層で持つ。ゲストが変わる案件があるので、

手動登録の口を必ず用意する。

置き場が機材名のときに人名をどこから取るか(追記 2026-08-12)

音は名前を出せない。 マイクから保証できるのは「同じ人が同じマイク」までで、

Audio/Wireless1/ や Audio/TX_MIC001_20250217_025120/ のような機材名の置き場には

外から名前を入れるしかない。 探す順(強い順)と、実測で分かったこと。

順証拠実測 2026-08-12(ディレクターズ葛藤 260411 セッション)
1出荷された映像に焼かれた話者名テロップあった。 Final/Part9.mp4 50s / Part10.mp4 44s / Part11.mp4 45s の冒頭に、引き出し線つきで ふじい(左)あさお(中央)なべ(右)
2映像で「誰が喋っているか」マイクが首位の長い区間の 1 コマを見る。他の 2 人がそちらを向く。席とマイクを結ぶのはこれ
3字幕の自己紹介「◯◯です」全 12 Part の冒頭にある。呼びかけ(「〜さん」)は直接の同定に使えない
4制作資料Claude/PROJECT_REFERENCE.md §4-1 にホスト 3 人の統一表記。本名は載っていない

`.drt` / `.drp` に話者名テロップは無い。 ディレクターズ葛藤.drp(306 エントリ)に

StyledText は 0 件で、Part9〜12 の Master の映像段にも話者名の段が無かった。

lib/nle/layout/service.ts の 'V8 - 話者名' はこれから組む本番タイムラインの設計であって、

人手の完成品には存在しない。焼かれた映像から読む。

送信機番号(`TX01` / `TX02` / `TX03`)をセッションをまたいで運ばない。

260215 は Audio/Asao=TX01 / Audio/Fujii=TX02 / Audio/Nabe=TX03 だが、

260411 でこの対応は成立しない(TX02 のカードが Asao、TX01 が Nabe)。

機材の通し番号を人の同一性として使うと逆の答えが出る。

自己紹介を票にする条件(答え合わせで決めた)

置き場が人名の Part1〜8 は正解が分かるので、同じ手順を当てて precision を測れる。

条件測れた件数正解precision
条件なし121083.3%
キュー 1.5 秒以内 + 首位差 6dB 以上 + 割り当ての閾値を通過77100%

外れた 2 件はどちらも「あさおです」というテキストで、実測は Nabe だった。

**自己紹介は 3 人が 1 秒ずつ続けて言うので、キューの区切りが崩れると 1 つのキューが

2 人ぶん・3 人ぶんを抱える**(Part4 #24 は 3.88 秒あり、同じ窓に Nabe / Fujii / Asao が入っていた)。

キューの長さを見ずにレベルだけ測ると、いちばん長く鳴っていた人が出てくる。

同じ人が 2 本のマイクに票を持つ、同じマイクに 2 人の票が入る場合は

多数決で潰さず、両方を残して確定にしない(decideFromIntroVotes が 1 対 1 を課す)。

軸のずれは 1 本の定数で足りないことがある(未修正の実測。Part11)

Final/Audio/Part11.mp3(マスター字幕と 1 対 1。字幕の末尾 2503.62s / mp3 2503.97s)を

Final/Part11.mp4 へ相関で当てると、

字幕の時刻映像とのずれscore
60 / 300 / 500 / 700 / 900s+44.20s0.86〜0.87
1200 / 2100 / 2300s−44.20s0.92〜0.94

で、字幕 1000〜1100s のどこかに 88.4 秒の段差がある(mp3 にあって映像に無い塊)。

台帳 speaker-confirmation-20260812.json は Part11 に単一の −44.3s を当てているので、

前半(字幕 0〜約 1020s)のマイク割り当ては 88.5 秒ずれている。

Part11 の ASR 一致率が全 Part 最低(0.39〜0.67)なのはこれで説明がつく。

`--verify` の 3 点検算は、3 点が同じ側に寄っていると段差を見逃す。

scripts/infer-speaker-names.mts は --astats のときだけこの区間に測り直した軸を当てる。

台帳側は直していない。

実装
何どこ
区間写像の読み取り・本編↔生マイクの変換lib/speakers/interval-map.ts
軸のずれ / 同期の検算 / レベルの測定lib/speakers/measure.ts
割り当て・一致率・誤検出/未確定の判定・統合lib/speakers/decide.ts
マイクだけで話者区間を作るlib/speakers/segments.ts
被覆の比較(集合差)lib/speakers/coverage.ts
人名の解決・手動登録lib/speakers/people.ts
自己紹介の別名表・票の条件・証拠のまとめlib/speakers/names.ts
実行と台帳scripts/confirm-speakers.mts
機材名の置き場へ人名を入れるscripts/infer-speaker-names.mts
npx tsx scripts/confirm-speakers.mts --all          # 台帳と話者インデックスを作る
npx tsx scripts/confirm-speakers.mts --verify       # 別経路(ffmpeg astats)で標本を測り直す
npx tsx scripts/infer-speaker-names.mts             # 自己紹介から人名を引いて報告(書かない)
npx tsx scripts/infer-speaker-names.mts --astats    # 別経路(ffmpeg astats)で intro を測り直す
npx tsx scripts/infer-speaker-names.mts --write     # dev/knowledge/speaker-people.json を書く

出力は dev/speakers/<Part>.mic.json(framing_track() が読む形の上位互換)と、

台帳 dev/knowledge/speaker-confirmation-20260812.json、

人名の表 dev/knowledge/speaker-people.json。

マスター字幕は書き換えない。話者は別ファイルで持つ。

speaker-people.json は confirm-speakers.mts が --people で読む手動登録ファイルそのもの。

第一階層は note / project / parts だけにする(parseOverrides の ignoredKeys を空に保つ)。

各エントリは person / displayName / onScreen / note に加えて

`confidence`(`confirmed` = 2 系統以上が一致 / `estimated` = 1 系統)と `sources` と `evidence` を持つ。

sources に manual が入っているエントリは再生成で上書きしない(人が入れた印)。


関連
  • [05_transcription_pipeline.md](05_transcription_pipeline.md) — JSON → SRT 変換
  • [08_multipass_correction.md](08_multipass_correction.md) — 多面一括修正
  • [../L2_sns/15_shorts_composition_workflow.md](../L2_sns/15_shorts_composition_workflow.md) §6 — 話者とアングルの割り当て
  • 各プロジェクトの PROJECT_RESOURCES.md — 音声ソース・出力先・話者数
L1-08 書き起こし多面一括修正フロー
ElevenLabs 書き起こし(JSON / SRT)に対して、1 パスで 6 種類の修正を一気に適用する統合フロー。 従来「ルール検出 → 人手修正 → AI 検出 → 人手修正」のループを 1 ステップに圧縮。

段 0: 時刻の潰れ(他の何より先に直す)

ASR は時刻を潰すことがある。 何十文字もが同じ時刻に貼り付き、時間情報がまったく無い

範囲が出る。しかもその本文は数秒後に正しい時刻で再出力される。これが「二重出力」の実体。

見つけ方(2 つの形で現れる)
データ見え方
生出力幅ゼロの文字(start === end)が同じ時刻に並ぶ
補正済みそれらが 1 語へ連結され、話速が人の限界を超える

日本語の話速は 8 字以上の語で 中央 8.5 / p90 13.2 / p99 20.0 字/s。

潰れた語は 21〜833 字/s になる。20 字/s を超える語があれば潰れている。

音イベントのタグ((笑い) (拍子木の音))は発話ではないので長さから除く。

直し方

生データの段で直す。 補正済みでは潰れた文字が 1 語へ連結され、重複した部分と

固有の部分が同じ塊に混ざって境界が取れない。生なら幅ゼロの並びがそのまま境界になる。

範囲まるごとではなく文字ごとに、近傍 ±90 秒に再出力があるかを 12 文字の並びで見る。

  • 大半(50% 以上)が覆われている範囲 → 端の削り残しごと落とす
  • 覆われていない固有の内容 → 落とさず、前後の語のあいだへ時刻を等分で割り直す

端を残すと「それティッ一発で挟む。ヒ俺らもその感じ分かってま」のような読めない断片になる。

照合の前に約物を落とす。 句読点そのものが幅ゼロで出力されるので、時刻を持つ語だけを

集めた側には句読点が 1 つも残らない。素のまま突き合わせると、同じ本文でも一致率が

26% に見える。約物を外すと 90% になる。

やってはいけないこと
  • 時刻の巻き戻りを探す。 潰れは時計が止まるだけで戻らない(実測 0 箇所)
  • 範囲をまるごと消す。 固有の内容が混ざる。落とす前に必ず再出力の有無を確かめる
  • 機械的な言い直し除去。 畳語を壊す(そうそう→そう、いろいろ→いろ)

段 0.5: 辞書をゼロから作る(案件ごとに毎回やる)

辞書は成果物ではなく、最初に作る道具。 新しい案件の辞書は空から始まる。

「辞書を当てたら誤変換が 100% 消えた」は同語反復で、品質を何も示さない

(その辞書は同じ書き起こしから作ったのだから消えて当然)。測るのは辞書の作り方の再現性。

手順(この順に固定する)
#やること出力人に聞くか
1頻度と表記ゆれで候補を機械的に集める候補リスト聞かない
2別系統の書き起こしと突き合わせる系統が割れた語=要確認聞かない
3制作資料を当たる確定分聞かない
4音で詰める(別系統 ASR / 素音)確定分聞かない
5残りを一覧にして人へ出す質問リスト聞く

1 件ずつ人に尋ねる前に、必ず 2〜4 を全部やる。 実測では残り 160 件のうち

123 件(77%)が制作資料だけで解けた。先に聞くのは相手の時間を捨てている。

候補の集め方(段 1)
  • 同じ読みで表記が複数ある語(回をまたいで揺れているもの)
  • 文脈上は人名・作品名・会社名のはずなのに、一般語として書かれている語
  • 頻出するのに一般的でないカタカナ語
  • ASR が音写に失敗した痕跡(不自然な漢字の並び、意味を成さない語)
資料の優先順(段 3)

同じ音源の書き起こしが複数あることを前提にする。 そして日本語の正式表記は、日本語の書類にしか無い。

順資料何が決まるか
1クレジット画像・キャスト表・スタッフ表実在の人名・役職。役職の帯ごとに全段を見る(キャスト欄だけ見て「無い」と判断しない)
2請求書・契約書・NDA・発注書漢字表記。 ローマ字クレジットでは 斉藤/齋藤、渡辺/渡邊 が決まらない。請求書には取引先と個人の氏名が日本語の正式表記で書かれている
3日本語の脚本劇中人物名の日本語表記
4別系統の ASR2 系統が一致すれば実発話、割れていれば要確認
5校正済みの字幕突合には使えるが独立な証拠ではない(同じ生出力の下流物。実測で生より劣化した箇所が 3 件あった)
6企画書・あらすじ・ロケハン資料地名・団体名
7公開済みの概要欄・チャプタータイムコード付きの一次資料

2 と 3 を見ずに「資料に無い」と結論しない。 クレジットは英語で作ることが多いので、漢字は別の書類にしか無い。

同一区間の表記揺れは機械で拾える

生出力が同じ数分の中で同じ語を違う表記で書いているなら、それは辞書に入れるべき語。

実測例: 同一 3 分窓に「ウエスタン」と「ウェスタン」が混在していた。

候補収集(段 1)でこれを自動で洗い出す。 人の目に頼らない。

突合材料は全尺そろわない前提で組む

実測(13 回ぶん): 別系統 ASR が 5 本、校正済み字幕が 8 本。Part9 以降は材料ゼロ。

材料が無い区間で confident を出す条件を先に決める。音と文脈だけで確定してよいのは、既知パターン表に載っている形と、資料で確定した語の別表記だけ。 新しい固有名詞を材料なしで confident にしない。

候補を種類で分けてから数える

「直した件数」を混ぜて数えると水膨れする。実測の内訳は 資料で解ける固有名詞 23 / 発話どおり保持 5 / 表記統一の判断 9 / 数字 5 / 現場ネタ 5。

種類ごとに解決手段が違う。 固有名詞は資料、表記統一は案件の方針決定、数字は音、現場ネタは実物か人。混ぜると「音で粘る」を全部に当ててしまう。

合格条件

1 回で作ろうとしない。 実測(同じ 36 分・同じ手順・独立 2 系統):

対象AB両方一致率
辞書51583751.4%
うち confident37502847.5%
補正ルール2515721.2%

1 回では半分しか拾えない。 ただし同じ語に違う答えを当てた例は 0 件だった。

割れているのは「拾ったか」であって「何と読んだか」ではない。だから和集合を取ってよい。

指標合格測り方
独立に回す回数2 回以上。和集合を採る1 回では半分
矛盾0 件同じ ASR 形に違う答えが付いていないか。ここが 0 でなければ和集合を採ってはいけない
人へ回した件数候補の 20% 前後実測 A 26/77・B 20/93
誤登録0 件実在の人名・作品名で資料の裏付けが無いものが registered になっていないか
揺れるのは「見つける」段で、「読む」段ではない

一致率が低いのは候補収集(段 1)の差。候補さえ挙がれば確定は再現する(矛盾 0 件がその証拠)。

ただし候補収集は機械化できない。 実測(scripts/term-candidates.mts):

条件候補数recall当たり率
signal 2 つ以上436.5%16%
カタカナ 3 字以上・1 回以上1,10247.7%4.6%
全字種 2 字以上・1 回以上4,94482.2%1.8%

使えるスイートスポットが無い。 recall 82% を得るには 4,944 件を出すことになり、

98% がノイズ。それを読むのは書き起こしを全部読むのと同じ。

理由は 2 つ。固有名詞の大半は 1〜2 回しか出ない。 そして

メルカリ『フォード』インパラ は普通のカタカナ語と統計的に区別できず、

清水『東映』高円寺 は敬称の付かない漢字 2 字で一般語と同じ形をしている。

だから LLM のパスは置き換えられない。 検出器の役どころは「LLM の代わり」ではなく

「LLM が見落としたものを後から拾う網」。精度が出るのは次の 2 つだけ。

  • 同一区間のカタカナ表記ゆれ(ウエスタン と ウェスタン が同じ 3 分に同居)
  • 敬称つきの漢字列(さん / くん / 氏 が続く)

漢字列の頻度と 1 文字違いは使わない。貪欲に切ると動詞語幹が混ざり(映画作る → 映画作)、

漢字で 1 文字違いは別語(映画作『映画館』映画好 は変種ではない)。

再現性は N 回回して和集合を取ることで上げる。 機械化ではなく回数で稼ぐ。

音でも頻度でも拾えない語がある

ソウ(映画 Saw)は ひらがな「そう」に化けていた。一般語と同形になるので、頻度でも表記ゆれでも系統間比較でも引っかからない。

見つかったのは企画書の競合表と Part1 の画面の数字が一致したから($1M → $102M ↔「1ミリオン…150億」)。

企画書・競合表・資料に出る数字と、本文の数字を突き合わせる段を入れる。 音では絶対に取れない語がここで取れる。


判断が割れる箇所(実測された類型)

ここが品質のボラティリティの本体。 以下はすべて 2026-08-03 の実作業で実際に起きたもので、

仮定ではない。同じ入力・同じ正本でも、判断者が変われば結果が変わる箇所。

#類型実際に起きたこと潰すルール
1辞書の向きが逆大嶋 → 大島 というルールが登録されていたが、正は大嶋だった人名の登録は資料の画像・表を根拠に記録する。 根拠を書けない人名ルールは作らない
2注記の無視大金 → なべ に note: 文脈確認のこと と書いてあるのに無条件で当て、「プラスで大金取られる」を人名に化けさせた注記付きルールは機械適用から除外する。 実装で弾く(文字列一致で 文脈確認 を検出)
3もっともらしい捏造発展市場編 を「発展途上編」と直した。正解は 発展in千葉編 だった副題・見出し・固有の言い回しは、公開物(概要欄・サムネ)で裏を取るまで直さない。 意味が通るだけでは根拠にならない
4数字の計算による補完音が欠けた数字を前後から計算して 60万 と埋めた。音源には 100万 と入っていた数字は素材に無い値で埋めない。 音で取れなければ unresolved にする
5資料に本当に無い固有名詞美術部が作った小道具のネタ(車のナンバープレートの文字列など)は脚本にもクレジットにも一切無い。音の形まで絞っても確定できない現場で作られた文字列は資料に載らない。 音で粘らず、実物の写真か関係者への 1 問で回収する
6発話と正式表記の衝突ギークピクチャーズ と発話。正式社名は ギークピクチュアズ字幕は発話どおり。 正式表記へ寄せるかは案件の判断。迷ったら keep として記録し、勝手に寄せない
7機械的な言い直し除去畳語を壊した(そうそう→そう、いろいろ→いろ)。Part7 だけで 52 箇所重複の機械除去をしない。 言い直しは文脈判断に任せる
8原文が正しいのに直す提案 160 件のうち 5 件(3%)が「原文が正しく、提案が誤り」だったprecision を抜き取りで測る。 20〜30 件を独立な根拠で検証してから全量に当てる
9資料を見る前に人へ聞くアリサ『三輪』ジャン・K・ポール を人に尋ねたが、キャスト表とクレジットに全部書いてあった。カワムカイ もクレジットに `"Jan-Ken Rock" Written by Taisei Kawamukai` と載っていたのに「資料に一切無い」と結論して人に聞いた段 2〜4 を全部やってから聞く。 実測 77% は資料で解ける。クレジットは役職の帯ごとに全段を見る(キャスト欄だけ見て「無い」と言わない)
10役職名を照合に使っていないクレジットに Watanabe が 3 名いて、名字だけでは決まらない役職 × 本文の文脈で照合する。 「鼻の対応してた」→ Key Makeup Artist、のように担当が噛み合うものを採る
11ローマ字クレジットで漢字を確定したSaito からは 斉藤/齋藤/斎藤 が決まらない。Watanabe も 渡辺/渡邊ローマ字は reading の根拠にしかならない。 漢字は日本語表記の資料が無ければ unsure のまま人へ
12下流の成果物を独立な証拠として使った校正済み字幕(*_claude.srt)を独立系統として扱ったが、あれは同じ生出力から作った下流物。しかも生出力より劣化した箇所が 3 件あった(出洲港→デズ湊 ほか)下流の成果物は独立な証拠ではない。 突合には使えるが、割れたとき下流を正と決め打ちしない
13既知パターン表を引く前に音で粘った間引き予算 は L1-05 §4 の「まーけ→まがき」で即座に マーケ予算 に落ちた段 4(音)より前に既知の誤変換パターン表を引く。 表にあるものは考えずに解ける
迷ったときの既定

確定しない。 unresolved に落として人へ回す。

もっともらしい候補を confident に上げるのが、最も損害が大きい失敗

(後段の全工程がその誤りを前提に進む)。


適用する 6 つの修正パス
  • 固有名詞補正 — DICTIONARY.md の置換ルール適用
  • 噛み・繰り返し除去 — 「8月8月25日」→「8月25日」
  • 数字・日付の補完 — 「4月1から」→「4月1日から」
  • 単語分断の修正 — 改行/エントリ跨ぎの活用形を結合
  • 不自然なスペース除去 — 語句内の不要スペース
  • STT アーティファクト除去 + 冗長表現の凝縮
  • 意味不明な英語断片 → 削除 or 日本語化
  • 「させていただいております」→「しております」
  • 「っていうところ」→「って」

統合実行のコンセプト
従来(段階実行)の問題
パス1: 固有名詞補正 (ルール) → 出力
パス2: 噛み除去 (AI) → 出力
パス3: 改行修正 (ルール) → 出力
パス4: 凝縮 (AI) → 出力
...

→ パスごとに差分レビュー / API コスト / 待ち時間が発生。コンテキストも分断される。

多面一括の方針
入力 SRT / JSON
    +
プロジェクト辞書 (DICTIONARY.md)
    +
ルール (L1-06 §Step3 の 6 項目)
    ↓
[Claude AI] 1 リクエストで 6 種類の修正を同時適用
    ↓
出力 SRT + review.md(修正ログ)

修正種別を タグ付き で記録するため、後から問題箇所を見分けられる。


プロンプト雛形(Claude 用)
あなたは映像字幕の校正専門家です。
以下の SRT ファイルに対して、6 種類の修正を **1 パスで** 同時適用してください。

## 修正種別

| タグ | 内容 |
|---|---|
| [DICT] | 固有名詞補正(辞書置換) |
| [REPEAT] | 噛み・繰り返し除去 |
| [DATE] | 数字・日付の補完 |
| [SPLIT] | 単語分断の修正(改行 / エントリ跨ぎ) |
| [SPACE] | 不自然なスペースの削除 |
| [TRIM] | STT アーティファクト除去 + 冗長表現の凝縮 |

## プロジェクト辞書

<DICTIONARY.md の内容を貼る>

## 制約

- 1 行: <プロジェクト規定値> 文字以下
- 行数: 最大 2 行
- 句読点: 不使用(プロジェクト規定に従う)
- CPS 上限: <値>
- 話者ラベル: <出力ポリシー>

## 入力 SRT

<対象 SRT の本文>

## 出力フォーマット

修正後の SRT を出力した後、修正ログを以下のフォーマットで出力:

| エントリ番号 | タグ | 修正前 | 修正後 | 理由 |
|---|---|---|---|---|
| 12 | [DICT] | 大金です | なべです | 自己紹介の誤変換 |
| 15 | [REPEAT] | 8月8月25日 | 8月25日 | 噛み |
| 23 | [SPLIT] | 撮りた/いよ | 撮りたいよ/みたいな | 活用が跨ぐ |

モデル別のコスト / 精度
モデル推奨用途精度コスト感(60分1本)
Haiku 4.5バルク処理(全 Part 一括)良$0.05 〜 $0.15
Sonnet 4.6重要パート / 翻訳監修優$0.30 〜 $0.80
Opus 4.7翻訳の最終チェック / 難解な凝縮最高$1.50 〜 $3.00

推奨: 第 1 パスは Haiku で全体を一気通貫 → 残った問題箇所だけ Sonnet で再パス。


チャンク戦略

1 リクエストに入れる SRT エントリ数の目安:

モデル推奨チャンクサイズ
Haiku100 〜 200 エントリ
Sonnet50 〜 100 エントリ
Opus30 〜 50 エントリ

→ Context window を圧迫しないこと。辞書 + 制約 + 入力 + 出力 を全部入れて 50% 以下に収める。


並列実行

複数 Part / Shorts を持つプロジェクトでは並列実行で時間短縮:

import asyncio
from anthropic import AsyncAnthropic

client = AsyncAnthropic()

async def correct_one(srt_path: Path, dict_text: str) -> str:
    # ...
    return corrected

async def main():
    parts = list(Path("Subtitles").glob("Part*_claude.srt"))
    dict_text = Path("Claude/DICTIONARY.md").read_text(encoding="utf-8")
    tasks = [correct_one(p, dict_text) for p in parts]
    results = await asyncio.gather(*tasks)
    # write back

出力 review.md フォーマット
# Part1 多面修正レビュー(2026-05-11)

## サマリ

| タグ | 件数 |
|---|---|
| [DICT] | 18 |
| [REPEAT] | 7 |
| [DATE] | 3 |
| [SPLIT] | 12 |
| [SPACE] | 5 |
| [TRIM] | 24 |
| **合計** | 69 |

## 詳細

| エントリ | タグ | 修正前 | 修正後 | 理由 |
|---|---|---|---|---|
| 12 | [DICT] | 大金です | なべです | 自己紹介の誤変換 |
| 15 | [REPEAT] | 8月8月25日 | 8月25日 | 噛み |
...

## 要人手確認

以下は AI が修正を保留した箇所(自動修正できなかった):

- エントリ 47: 文脈不明な英語断片「porque」(削除? 残す?)
- エントリ 83: 5.8 秒の長尺ブロック(分割点が見つからない)

関連
  • [05_transcription_pipeline.md](05_transcription_pipeline.md) — パイプライン全体
  • [06_quality_review_workflow.md](06_quality_review_workflow.md) — Step1-4 検証フロー
  • [07_elevenlabs_workflow.md](07_elevenlabs_workflow.md) — 上流の書き起こし
  • プロジェクト側 DICTIONARY.md — 辞書ソース
L1-09 切り方の優先順位とアルゴリズム(グローバル)

どこでキューを割り、どこで改行するか。その順番を決める唯一の正本。

01_subtitle_format_jp.md §4 が「切ってよい位置」と「切ってはいけない位置」を並べているのに対し、

この文書は それらが衝突したときにどちらを取るか を定める。実装(lib/subtitle/)は

この順番のとおりに動く。ここを変えたらコードも変える。逆はしない。

浅尾確定(2026-08-07)。

**位置のレベル(L0 強調フレーズ / L1 話者交代 / L2 意味のまとまり / L3 改行ポイント / L4 音節)はグローバル正本

docs/SHORTS_CUTTER_SUBTITLES.md が持つ。**

この文書の 段0〜段4 は工程(どの順に判定するか)、L0〜L4 は位置の種類であり、別物である。


1. 5 段のふるい

上の段が下の段を必ず上回る。下の段が上の段を逆転することはない。

                     本文 + 実測時刻(words[])
                              │
   ┌──────────────────────────▼──────────────────────────┐
   │ 段0 禁則        ここで切ってはいけない位置を落とす      │
   │                 語の内部・行頭行末の禁則・活用語尾      │
   │                 → 罰点ではなく「不可」。候補にしない    │
   └──────────────────────────┬──────────────────────────┘
                              │  切ってよい位置の集合
   ┌──────────────────────────▼──────────────────────────┐
   │ 段1 時間        読めない字幕は字幕ではない              │
   │                 最小 0.5s/最大 7s/ギャップ 2 フレーム │
   │                 余韻 +0.5s(上限 1.0s)/重なり 0      │
   │                 → ここでキューに割る                   │
   └──────────────────────────┬──────────────────────────┘
                              │  キュー列
   ┌──────────────────────────▼──────────────────────────┐
   │ 段2 意味        切れ目の質で選ぶ                       │
   │                 句点>空白>読点>接続詞>連用形>      │
   │                 格助詞>改行ポイント>複合語>字種>モーラ │
   └──────────────────────────┬──────────────────────────┘
                              │
   ┌──────────────────────────▼──────────────────────────┐
   │ 段3 容量        1 行の幅と行数に収める                 │
   │                 収まらなければ ─ 段1 へ戻ってキューを割る│
   └──────────────────────────┬──────────────────────────┘
                              │  行つきキュー
   ┌──────────────────────────▼──────────────────────────┐
   │ 段4 形          ここまで同点のときだけ効く              │
   │                 12+2 より 7+7。短すぎる行を避ける      │
   └──────────────────────────┬──────────────────────────┘
                              ▼
                            確定
「切ってよい位置の集合」は誰が作るか

候補は LLM が出す。 L0 強調フレーズ / L1 話者交代 / L2 意味のまとまり / L3 改行ポイント / L4 音節 のレベルを付けて申告し、

機械はそれを段0 の禁則に当てて検算し、通らないものを落とす。段1 以降が扱うのはその残りである。

幅と行数は LLM に見せない(見せると特定の文字数に最適化された付与になる)。

実装は lib/subtitle/break-levels.ts(依頼書と応答を hash で束縛する)。

段4 が最後にある理由

正本 §4 は 「意味的完全性 > 視覚的バランス」。行長を均等にしようとしてはいけない。

一方で 12+2 より 7+7 のほうが読みやすいのも事実である。この 2 つは矛盾しない。

切れ目の質が同じときだけ形で決めるなら、意味を犠牲にせずに形が整う。

実装はこの一文を数値で表している。切れ目の罰点は 0 / 2 / 8 / 12 / 16 / 18 / 20 / 24 / 30 / 35 / 90 で、

いちばん小さい差が 2。形の罰点の最大値をこれより小さくすれば、形が意味を逆転できない。

(候補に無い位置は既定コスト 60 として扱う。60 は罰点ではない。)

2026-08-07 まで形の重みは 40(最大 22.5 点)で、切れ目の質が 11 段階悪くても形で逆転できた。 較正されていない目的関数を最小化すると、忠実に間違った答えを出す。

2. 段0 禁則(不可。候補にしない)
禁則例
活用語尾の途中`撮りた \い 関係な \い 言っ \た して \ない で \すけど`
送り仮名の途中`食 \べましょう`
固有名詞の内部辞書に載っている語の中
形式名詞の途中ところ こと もの という っていう
カタカナ複合語の途中ただし継ぎ目が分かっている語は例外(§5)
行頭に置けない字小書き仮名・長音・約物・閉じ括弧・格助詞
行末に置けない字開き括弧
数字と単位のあいだ`3 \年 40 \分`
複合動詞の内部`送り \込みます 立ち \上げる 問い \合わせ`
縮約した助動詞・補助動詞`持って \く(ていく)衝突して \まう(てしまう)用意しと \いたら(ておく)決まって \てで`(ていて)
て形 + 助動詞の活用形`出せて \なかった して \ますね して \いたんですね。**で も て形**(死んで \るのよ`)
敬称(2026-08-14。浅尾承認)`齋藤 \君 デューク \氏 ジャイアントGPS \くん`。名前の一部である
連語(複合助詞)(同上)`映画制作に \おいて プロダクションと \して シーンに \よって`
敬称は左に「名前らしさ」を要求する。 左の末尾が漢字・カタカナ・英数字のときだけ。 ひらがなの直後は名前ではない(実測: ちゃん 38 件は全件が副詞の `ちゃんと`、 様 3 件は全件が `様々な`)。君臨 氏名 氏族 のように語の一部になる形も外す。 入れたのは実測で出た 君 11 / くん 1 / 氏 1 の 13 件で、全件読んで過検出 0。 連語は `として` だけ文脈を見る。 意志形(作ろうと \| して)と擬態語 (ピリッと \| して)は と までが前の語で、続くのは動詞 する である。 除外を入れる前は 58 件中 20 件を読んで 3 件が過検出、入れたあとは 50 件中 20 件で過検出 0。 `かどうか` は実測 0 件なので入れない(出たものだけ入れる fail-closed の運用)。
罰点 16「連用形・て形の直後」には 「節が閉じる」 という条件が付いている(§4)。 送り込む の連用形は節を閉じないので、そこは切れ目ではなく語の内部である。 同じ理由で、て に続くのが単独では語にならないもの(ない た る ます て)なら切れない。 実装は lib/subtitle/conjugation.ts の wordSplit。生成側と検証側が同じ関数を見る。 語彙(複合動詞の後項・全部ひらがなの 1 語・助数詞・敬称・連語)は lib/subtitle/compound-lexicon.ts。追加の経緯と実測は dev/knowledge/recall-rounds-20260813.json。
補助動詞の直後は「切ってよい」ではない(2026-08-14 改訂)

**ここには「て に続くのが独立した語(くれる もらう いただく ある くる)なら

切ってよい」と書いてあった。浅尾の判断で取り消す。**

補助動詞は基本切りたくないな。完全にどうしても切れない場合だよ。 (浅尾確定 2026-08-14)

独立した語なので禁則ではない(候補には立つ)。しかし良い切れ目でもない。

扱いは音節改行と同じ「最後の手段」で、§4 の罰点 90 に置く。

他に合法な候補があるかぎり選ばれない。

縮約形        て+た / てる / てん / てへん / てく / てまう / とく
              1 語。**候補にしない。切ったら違反**(段0 禁則)
完全な補助動詞 くれる くださる くる もらう いく ある いただく おく(いる ※)
              **最後の手段の候補**(罰点 90)
みる          `〜てみる`(補助動詞)と `〜て、見る`(本動詞)を字面で区別できない。
              **対象外。**罰点 16 のまま(正本「推測で決めない」)

※ いる(〜ている とその活用形)は 段0 禁則のまま落としている。「最後の手段」より厳しい。

緩めると補助動詞の切れ目が増える方向に動くので、浅尾の判断に反する。

違反の定義。 補助動詞の直後で切れていて、かつ他に合法な候補があったなら違反。

候補が無くて仕方なく切ったなら違反ではない。判定は validator.ts の hasAlternativeBreak

(同じ 2 行を、その位置以外の合法な候補で組み直せるか)。

代償として、この判定は「候補集合の中でいちばん良い位置を選べたか」の遵守率になる(§5)。

罰点の値は測って決めた。 本番経路(全 13 Part / L 境界 15,960 箇所)で

「補助動詞の直後が選ばれた件数」:

罰点選ばれた件数うち他に候補ありchar_limit
16(変更前)1791081
40 / 60 / 75 / 90 / 120 / 2007001

35 を超えたところで頭打ちになる(普通の切れ目の罰点の最大が 35 だから)。

最小に届く値のうち、浅尾指示「音節改行と同じ扱いにしろ」に従って 90 を採る。

残る 70 件は他に候補が無い箇所で、どんな値でも減らない。

段の 1 つにしてはいけない。 findBreakPoints は当たった段のうちいちばん上を採るので、

連用の段の中で罰点を上げると改行ポイント(20)に負ける。実測 2026-08-14

(※ 当時は文節モデル BudouX。2026-08-16 に廃止し、この段は LLM の L2 付与が受け持つ):

そこを理解して|もらえるって(モデルが文節と言う位置)に罰点が一度も付かなかった。

lastResortBreakAt を段の並びの外に置いて最後に上書きする。


3. 段1 時間
項目値出所
最小表示時間0.5 秒Netflix 日本語
最大表示時間7 秒同
キュー間ギャップ最小 2 フレーム同
入口(先出し)発話の 1〜2 フレーム前同
余韻(出口)言い終わり +0.5 秒同
余韻の上限1.0 秒。最小表示に届かないときだけここまで同
CPS 理想 / 上限プロジェクト設定(§6)実測から決める

次のキューが来るなら、そちらが優先。 次の入口 − 最小ギャップを超えて伸ばさない。

時刻は実測から貼る。按分しない。 文字数の比で尺を割ると、開始が平均 −0.55 秒・最大 1.4 秒ずれる

(Part10 実測)。


4. 段2 意味(切れ目の質)

上ほど良い切れ目。数字は実装の罰点。

罰点位置判定
0句点の直後。!? の後
2半角スペースの直後§4 で最優先
8読点の直後、 の後
12接続詞の直前でも・だから・つまり・なので
16連用形・て形の直後〜して 〜くて で節が閉じる
18格助詞の直後1 文字のものは直前が体言のときだけ
20L3 改行ポイント(LLM 付与)全部ひらがなの区間はここしか手がかりが無い
24複合語の継ぎ目`パラノーマル \アクティビティ`(§5)
30/35文字種が変わるところ漢字→ひらがなは送り仮名なので除く
90語の内側(モーラ境界)1 語が 1 行に載らないときだけ立てる。字種は問わない。最後の手段
90て形 + 補助動詞の直後やって|おく 変えて|くれる。§2 の改訂(浅尾確定 2026-08-14)
902 文字以上の付属語が行頭する|しかないで クランクイン|ですよ。終助詞・副助詞・コピュラ
罰点 90 の 3 つは段の並びの外で最後に上書きする(lastResortBreakAt)。 段の 1 つにすると、文節(20)など上の段が先に当たったときに効かない。
罰点 55「カタカナ語のモーラ境界(N 字以上続く語)」は 2026-08-11 に撤去した。 候補を出す findBreakPoints は行幅を知らないので、幅を知らないまま「N 字以上なら割ってよい」と 言うとその行に載る語まで割れる(実測: katakana_break 405 件のほぼ全部が タイムラ|イン キャラク|ター のように不要な分割だった)。 幅を持つ syllableBreakPoints(罰点 90)へ寄せ、字種の縛りも外した。
罰点 20 は文節モデルではなく LLM が出す(2026-08-16)

文節モデル(BudouX 等)は使わない。 実測(2026-08-16)で

ぜひよろしくお願いしますはいじゃあ に対し はい を は|い と割る位置を優先候補として返し、

本当の切れ目 します|はい は候補にすら立たなかった。全部ひらがなの区間は字面に手がかりが無いので、

そこを埋めるのは LLM の L3 改行ポイント付与である。

助詞の禁則は既定で ON のまま、LLM が語頭を申告した位置だけ外す。

ぜひ|よろしく(語頭の よ)と Zoom|の雰囲気(助詞の の)は機械には字面で区別できない。

申告が効いた回数を必ず数えて出す。 禁則本体(ん が行頭、活用語尾の分断、固有名詞の内部)は

申告があっても外さない。

「音節」ではなく「モーラ」と呼ぶ

モーラ結合則で決まるのは「切ってはいけない位置」だけである。

次の文字が  拗音 ゃゅょ / 小書き母音 ぁぃぅぇぉ / 促音 っ /
            長音 ー   / 撥音 ん      / ゎ ヵ ヶ
なら、その位置では切らない

これは文字クラスだけで決まる。辞書も文脈も要らない。

ただし「切ってよい位置」は決まらない。 母音字による長音・二重母音は

エ \| イリアン が規則上ゆるされてしまう(ー による長音と表記で区別できないため)。

だから「音節境界で割れる」とは言えない。


5. 語彙は共有し、規則は独立に持つ

規則は生成側と検証側がそれぞれ独立に持ってよい。日本語の事実は共有する。

分けて持っていたために実害が出た(2026-08-07 実測)。

語彙生成側が持っていた量検証側が持っていた量直したときの効果
カタカナ複合語の継ぎ目0(辞書で保護するだけ)11 組katakana_break 294 → 274
行頭助詞の例外7 個60 個以上particle_start の候補が増えた
活用語尾の表3 語6 パターン`verb_break` 345 → 11

3 つとも「検証側だけが知っていて、生成側は知らない」状態だった。

パラノーマルアクティビティ は辞書にあって範囲ごと保護されるため内部に候補が立たず、

10 字に収まらないので機械折り返しへ落ちて語中で割れていた。検証側はその継ぎ目を知っていた。

(機械折り返しの経路そのものは 2026-08-11 に削除した → §6。継ぎ目辞書は今も要る。

未解決: この辞書に 1 語足すと上限超過が 32 → 9 になるが、辞書語の分割禁止テストが 3 件落ちる。)

置き場所:

lib/subtitle/compounds.ts     カタカナ複合語の継ぎ目
lib/subtitle/particles.ts     行頭助詞と、その例外の語形
lib/subtitle/conjugation.ts   活用語尾の表
句読点も共有する(2026-08-12 追加)

語彙より先に、句読点そのものが検証側へ渡っていなかった。

表示では 、。 を落とす(01_subtitle_format_jp.md §4)。落としたあとの行だけを

検証すると、**§4 の罰点表がいちばん良い切れ目(0 点=句点の直後)と定めている位置が、

語の分断に見える**。

組んだ本文   まあしゃあないよな。いや。
出力の行     まあしゃあないよな | いや
検証が見たもの  …な + い…  →  「ない」の分断  ← 誤り

句読点をまたぐ語は無い。 活用語尾も、複合語も、格助詞も。したがって

その切れ目が句読点の位置なら、cue_break / verb_break /
katakana_break / particle_start は判定しない

cuesFromSource は前から句読点つき本文を bodies で返している。

validateSubtitles の bodies に渡すだけで、過検出 174 件のうち 141 件が消えた

(実測 2026-08-12・全 13 Part。内訳は 01_subtitle_format_jp.md §4 の表)。

渡さない呼び出しは従来どおり検出する(安全側)。

同語反復は 3 回以上も同じ規則

repetitionSeams(旧 repetitionSeam)は 2 回の繰り返ししか見ていなかった。

キュッキュッ|キュッ ベロベロ|ベロ に継ぎ目が立たず、

生成側は語の途中で折り、検証側は正しい出力を違反と数えていた(実測 23 件)。

検証側の katakana_break も lineCurr === lineNext(行まるごと一致)しか見ていなかったので、

結構キツ|キツやったりとか のように反復の片側に他の語がくっついている形を

全部違反にしていた。いまは生成側と同じ compoundBreakPositions で見る。

統合してはいけないもの

「行頭に置くのを避けたい助詞」と「正本が禁止している格助詞」は別物である。

生成側 LINE_START_PARTICLES  18 個  … 終助詞・副助詞も含む。「避けたい」=罰点相当
検証側 CASE_PARTICLES         9 個  … が は を に で の と も へ。正本 §4 の禁止事項

一度これを統合して particle_start が 87 → 674 に増えた。

2 文字以上の付属語は第 3 の表(2026-08-14 追加。浅尾承認の提案 1・2)

上の 2 つはどちらも 1 文字しか見ていない。前の語にぶら下がるという点は同じなのに、

インプットする|しかないで(終助詞・副助詞)と クランクイン|ですよこれ(コピュラ)が

素通りしていた。実測 177 箇所。

BOUND_PARTICLE_FORMS  ぜ ぞ ねん しか だけ ほど ばかり ばっかり
                      くらい ぐらい こそ さえ など まで のみ     … 45 件読んで過検出 0
COPULA_FORMS          です でしょ でした じゃな じゃね だった である
KANSAI_COPULA_FORMS   やった やねん やわ                        … 動詞 `やる` と同字面

1 文字の表と統合しない。 あちらは字面で 1 字を当てるので例外表が要る。

こちらは 2 文字以上あるので語形がほぼ決まり、例外表を通さずに判定できる。

関西のコピュラは左を見る。 動詞 やる は格助詞を取るので、左が

で を に と て が は も へ さ で終わるものを外すと分かれる(こういう体制で|やった

それを|やった)。取りこぼす側に倒してある。`やってん` は入れない

(お前ら全部|やってんの? のように動詞であることが多い)。

`です` は `ですご` `ですぐ` を除く(格助詞 で + すごく すぐ)。

当たった全件(重複を除いて 28 件)を読んで過検出 0。

禁止ではなく罰点 90(§4)。 禁止にして本番経路で測ったら、候補が消えた分だけ

組めない本文が出た(実測 2026-08-14: char_limit 1 → 11、katakana_break 38 → 48)。

1 文字の格助詞の禁則には文節モデルの免除という逃げ道があるが、こちらは語形で決め打つので

逃げ道が無い。候補は残して、他に候補があるかぎり選ばれないようにする。

連語の頭の `に` は格助詞ではない。 において に関して に対して によって

について にとって、それに副助詞の のみ を LINE_START_EXCEPTIONS へ入れた。

入れないと語の直後という唯一の良い切れ目が候補に立たず、エキストラに|関してはね

(連語の内部 → char_limit)や オペレー|ションに関して(カタカナ語の内部 →

katakana_break)へ落ちる。同時に particle_start の過検出 7 件(〜|に対して)も消えた。

共有した代償

表に載っている語について、その項目は遵守率になり、正しさの指標ではなくなる。

表に無い語の側は依然として独立に検出できるので、そこに信号が残る。

報告するときは名前を分ける。


6. プロジェクトごとに変わる値

グローバルはここまで。以下は案件ごとに決める。

値どこで決まるディレクターズ葛藤の値
1 行の文字数プロジェクト設定本編 13 字 / ショート 10 字
1 枚の合計文字数同上26 字 / 20 字
はみ出しの許容(検証側だけ)同上2 字
CPS 理想 / 上限同上8 / 12
固有名詞の辞書プロジェクトの辞書lib/katto/dictionary.ts
割ってよい複合語同上lib/subtitle/compounds.ts
幅を狭くすると何が起きるか

合法な組み方が存在しないキューの割合(実測)。

幅 20 字 ─────────────────────────────────  0.00 %
幅 16 字 ─────────────────────────────────  0.02 %
幅 13 字 ─────────────────────────────────  0.28 %
幅 10 字 ████──────────────────────────────  4.13 %
幅  8 字 ████████████████████████████──────  33.69 %

10 字は 13 字の約 15 倍。 焼き込みでも罰点でも消えない、幅そのものの制約である。

そこはキューを割るしか手がない。プロジェクト設定で狭い幅を選ぶときは、この率を警告に出す。

はみ出しの許容は無い(2026-08-11 改訂)

以前はここに「合法な位置で 1〜2 字はみ出す」を生成側の逃げ道 ① として書いていた。

浅尾確定 2026-08-10 で取り消した。幅は案件ごとのパラメータなので、

2 字という固定値には根拠が無い。収まらないならカットを入れる。

生成側(layoutLinesDetailed)の順は 改行 → カット → 音節改行 の 3 つで、

どれも切れ目は合法。上限に収まらない本文は outcome: 'cut' で返り、

呼び出し側がカットするか、出すのをやめる。

2026-08-10 の時点では、overflowTolerance を検証側にだけ残して error と warning を

分ける階層にしていた(一律 error にしていた頃は全 error の 17% = 886 件がこれだった)。

2026-08-11 にその階層ごと削除した。 理由は、階層が前提にしていた

「生成側が幅で折り返してはみ出す」経路が生成側から消えたため

(breakPoints.ts の 改行 → カット → 音節改行、alignToSource.ts の等分割 splitByLength 削除)。

いま上限を超えた行が出るのは「はみ出しを許した」のではなく「組めなかったのに出した」ときなので、

1 字でも `char_limit`(error)にする。

char_limit_overflow の残り方(2026-08-13 訂正)

ここには長らく「型も警告コードも無い」と書いてあったが、2 箇所で不正確だった。

実装はこうなっている。

場所状態理由
lib/subtitle/thresholds.ts の SubtitleRuleSet.overflowTolerance無い2026-08-11 に型ごと削除
lib/subtitle/validator.ts出さない(makeViolation の呼び出しに無い)1 字でも char_limit(error)
lib/types.ts の TimingViolationType無い(2026-08-13 に削除)この型は「いま出せる違反」の一覧。出せない値を残さない
lib/production/spec/schema.ts の SubtitleViolationCodeSchema残す保存済み spec の warningCodes を読む列挙。値を消すと 2026-08-07〜08-11 に作った spec が .strict() で落ちる

つまり「保存されうる値」⊃「いま出せる値」で、この差は意図したものである。

enum は増やす方向が後方互換、減らす方向が破壊なので、永続化側からは消さない。

2026-08-12 に 0 件。 継ぎ目辞書(§5 / lib/subtitle/compounds.ts)へ

じゃんけん+デスゲーム ほか 3 語を足し、行頭助詞の例外に やっ して を足して

32 → 4 まで下げ、残り 4 件は縮約と再分割で消えた。

本番経路 scripts/build-master-srt.mts / 全 13 Part / 33,556 キューで char_limit 0 件。

2026-08-13 の見逃し監査(9 周目)で 1 件に戻った。放置している。

2ヶ月ぐらいかかってんねん(12.5 字 / ショート上限 10)に切れ目候補が 1 つも立たない。

候補になりそうな位置立たない理由
2ヶ|月数字+助数詞の禁則(9 周目に追加)
月|ぐらい漢字→ひらがな。findBreakPoints の字種の段も syllableBreakPoints も送り仮名として除外
ぐらい|かかってbreakPoints.ts の助詞表 PARTICLES に ぐらい くらい が無い。当時の文節モデル(BudouX。2026-08-16 に廃止)も 1 塊と見た
かかっ|てんねん促音便(っ+て)の禁則
かかって|んねん行頭禁則(撥音 ん)

それまでは `月ぐら|い` と語を割っていた。 §4 の

「幅を超えるほうが、語を割るよりよい」に従えばいまの 12.5 字の行のほうが正しいので、

char_limit はここでは「組めなかったことの報告」として正しく出ている。

直すなら助詞表に `ぐらい` `くらい` を足す(breakPoints.ts。2026-08-13 時点で別作業中)。


7. 出荷条件(教師データに依存しない)

規則違反ゼロは「遵守率」であって「正しさ」ではない。 生成と判定が同じ根拠を共有した時点で、

測れるのは「規則に従ったか」だけになる。正しさは次の 4 つで見る。

⓪ その前に、違反件数そのものを疑う。(2026-08-12 追加)

規則違反の件数は、検出器が正しいときにしか意味を持たない。 実測では

error 1,374 件のうち 174 件(12.7%)が過検出、警告 15,202 件は 全件が過検出だった。

  precision  違反と言ったもののうち本当に違反だった割合
             → 種別ごとに全件(多い種別は等間隔 30〜40 件)を読む
  recall     残っている違反のうち見つけた割合
             → 全長から等間隔に 300 箇所抜いて目で読み、拾えていない数を数える

recall を測っていないなら「規則違反 0」と言わない。 実測 2026-08-12 の recall は

約 13%(改行系。検出 55 件に対し推定総数 416 件)。precision を上げる作業だけを

続けると、この数字は下がる一方になる。

  ① 規則違反 0
       validator の error が 0
       ただし表を共有した項目は compliance と呼び、正しさとは呼ばない

  ② 短すぎるカットが無い
       1 行 2 字未満が 0
       短い 1 枚は「前後に間があるもの」だけ

  ③ 無音を抱えた字幕が無い
       言い終わりから 0.5 秒(上限 1.0 秒)を超えて残っているものが 0

  ④ 全長から等間隔に 20 件を目視
       遵守率 100% でも規則の取り違えはここでしか見つからない
② の判定

機械的に畳んではいけない。「喝!」のような一言は意図して 1 枚にしてある。

実測すると 2 つの母集団に分かれる(ショートの 4 字以下 1,300 件)。

前後に間がある前後が詰まっている
件数281,272
表示秒数 p501.21 秒0.78 秒
CPS p502.64.1
最小表示未満0 件230 件
残す側   「大金です」7.00s   「あえて」3.45s   「懐かしい」3.21s
畳む側   「で」0.10s    「その」0.12s    「るわけ」0.12s

一言は溜めて言うので前後に無音があり CPS が低い。断片は詰まっていて CPS が高い。

したがって畳む条件は「短い」ではなく 「短い かつ 前後が詰まっている」。


8. 抜けている工程 ── 縮約

字幕は逐語ではない。 ところが三層(00_subtitle_layers.md)には縮約の段が無い。

層1 オリジナル   逐語で書き起こす
層2 校正         誤変換・固有名詞を直す   ← 字数は変わらない(正本が明記)
層3 マスター     編集で落とした区間を反映
     ?          ここに「縮約」が無い
組版             切って・並べる

そのため CPS が話速そのものになる。字幕が発話と同じ長さで出るなら、CPS は話速である。

違反キューの発声区間あたり p50 は 11.96。

規模(2026-08-07 実測)
CPS 超過ショート 690 件 / 本編 465 件
伸ばすだけで届く80 件(11.5%)。後ろに無音があるものだけ
縮めるしかない611 件(88.4%)
落とす文字数p50 2 字 / p90 4 字 / max 9 字 / 合計 1,215 字

ショート全体は約 21 万字なので、0.6% 削れば足りる。「〜っていうふうに」を「〜と」にするだけで 5 字。

04_translation_principles.md の冗長表現リストがそのまま使える。

縮約は Part 単位で 1 回(2026-08-10 訂正)

以前ここには「幅ごと。Part 単位で 1 回ではなく」と書いてあった。取り消す。

00_subtitle_layers.md §1 と 10_condensation.md §53 の「Part 単位で 1 回。

target ごとにやらない」が正しい。両方に浅尾確定の日付があり正面から矛盾していたので、

実測で決めた。

  • 本編とショートの本文は 210,986 字中の差が 2 文字(99.99905% 一致)。13 Part 中 12 Part が完全同一
  • 本編 cue をショート規則(10 字)に当てた違反 7,185 件は 全件 2 枚で解ける(3 枚以上は 0 件)
  • 幅が変えるのは改行位置と 1 枚に入る量であって、字数の密度ではない

幅ごとに縮約すると本文が 2 つに分かれ、00_subtitle_layers.md §2

「ショートは本文のコピーを持たない」が壊れる。校正が入るたび両方直すことになる。

本文を削ると時刻の対応が変わるので、縮約したら再アラインする(Part 単位で 1 回)。


9. 常に 100% にする仕組み

未知のものが出たら止める。訊いた答えは辞書に入る。だから同じ問いは二度と来ない。

term-queue(08_multipass_correction.md)が固有名詞でやっていることを、改行にも当てる。

   ① 組む
        │
   ② 割れた語・未知の語を集める          scripts/collect-unknown-compounds.mts
        │
   ③ 未知が 0 か?
        ├─ はい ──────────────────────→ 出荷
        └─ いいえ → **止める(fail-closed)**
                      │
   ④ 判定する
        ├─ 機械で決まるもの
        │    中黒がある      ジェイソン・ステイサム → ジェイソン・|ステイサム
        │    同語反復        ワンカットワンカット   → ワンカット|ワンカット
        │    辞書にある      継ぎ目を足すだけ
        ├─ 言語知識で決まるもの(Claude が読む)
        │    1 語で割れない  コミュニケーション / ドキュメンタリー
        │    2 語の複合      ショート+ムービー / アクション+シーン
        └─ 資料が要るもの(人へ)
             固有名詞        エルマリアヤッツ
                      │
   ⑤ 辞書へ登録 ────────┘ → ①へ戻る

2 周目以降は必ず 0 になる。 新しい回が来ても、増えるのは新語の分だけ。

件数ではなく回数で見る

同じ語が 13 回に出れば、決めるのは 1 回でよい。

違反 166 件  →  語の種類 121  →  人に訊くのは数語

**「違反が 266 件ある」と言うと絶望的に見えるが、問うべき回数は 121 で、

そのうち人にしか決められないのは数語しかない。** 報告するときは回数で言う。

過検出も同じ扱いにする

止まったもののうち、正しい出力なのに違反と判定されているものがある。

テレワイド|ミドル          正しく切れている(2 語)
ワンカット|ワンカット      正しい(同語反復の境界)
なんかもう怖い|もん見たら   「もん」は形式名詞。格助詞ではない
その時間が|もったいない     「もったいない」は形容詞。「も」ではない

これは判定側の語彙が足りないということなので、同じループで例外表へ足す。

「規則が間違っている」場合と「語彙が足りない」場合を混同しない。


10. 手で直されたものを学習する

人が Resolve 上で字幕を直したら、その改行を取り込む。逆算の経路は実在する

(2026-08-07 に 495 キューで実証済み)。

   人が Resolve 上で字幕を直す
        │
   ① DRT を書き出す                    timeline.Export(path, EXPORT_DRT)
        │
   ② EffectFiltersBA から本文を復元      zstd / UTF-16LE。改行込み
        │   ※ <Name> は import 時の古い ASR ラベルで、表示に使われていない
        │   ※ Resolve の API は改行を半角スペースへ潰すので、そちらからは取れない
        │
   ③ 句読点を落とした本文のハッシュで元キューと突合
        │
   ④ 学習する
        ├─ **pin**(この本文・この幅ではここで切る)        安全
        ├─ **辞書へ昇格**(パラノーマル|アクティビティ)   安全。一般化できる
        └─ **罰点の重み**                                  **危険。やらない**
罰点の重みを学習してはいけない

2026-08-03 の事故がその理由。data/fix の 142 件は 73% が ceil(n/2) ちょうど、

本編 2,624 件は 1 行目が常に 13 字だった。どちらも機械の折り返しで、人の改行ではなかった。

それを教師にして重みを合わせにいくと、機械の癖が全幅・全 Part へ波及する。

pin は特定の本文にしか効かない。辞書は語の事実なので一般化してよい。

重みだけが全体に効くので、そこは人が決める。

教師データを鵜呑みにしない

人の改行にも規則違反が混ざる。実測(2026-08-07 / recall 72.7%)で外した 33 件のうち、

追ってはいけないものがあった。

ていうのがあったも|のの        形式名詞「ものの」を割っている
40分ランチしようっ|てできるけど 「って」を割っている
ここが大事にして|なかったら     「してなかったら」を割っている

recall 100% を目指してはいけない。 合わせにいくと規則が壊れる。

教師を使う前に、それが人の手によるものかを署名で確かめる

(1 行目が ceil(n/2) ちょうどの割合。機械なら 70% を超える)。


11. 実装との対応
段実装
候補(L1/L2/L3 の付与)lib/subtitle/break-levels.ts(LLM への依頼書と応答を hash で束縛)
段0 禁則lib/subtitle/breakPoints.ts の canStartLine / splitsConjugation / protectedRanges
段1 時間lib/subtitle/cueTiming.ts の finalizeCueTiming
段2 意味lib/subtitle/breakPoints.ts の findBreakPoints(罰点付き候補)
段3 容量lib/subtitle/breakPoints.ts の layoutLinesDetailed(動的計画法)
段4 形同上の offTarget(shortnessWeight)と runt(minLineWidth)
数値lib/subtitle/thresholds.ts → lib/subtitle/presets.ts で識別子から解決
判定lib/subtitle/validator.ts
出荷条件scripts/analyze-line-balance.mts analyze-short-cues.mts analyze-cps-strategies.mts

キューを割る位置と改行の位置は、同じ候補集合から選ぶ。 別々の規則を持たない。


関連
  • 01_subtitle_format_jp.md — 切ってよい位置と禁止の一覧(この文書の入力)
  • 00_subtitle_layers.md — 三層。§8 の縮約はここに足す必要がある
  • 04_translation_principles.md — 冗長表現の削り方。縮約の実務
  • 05_transcription_pipeline.md — 語単位タイムの作り方
L1-10 縮約(層4)── 書き起こしを字幕原稿にする

字幕は逐語ではない。 話した言葉から、意味を変えずに字数を減らす工程。

テレビ・映画の字幕制作では標準の技法で、これが無いと読み切れない字幕が出る。

浅尾確定(2026-08-07)。


1. なぜ要るか

人は聞くより読むほうが遅い。

話速(実測)        12.0 字/秒
読める速さ           8〜12 字/秒

逐語をそのまま出すと、字幕が発話と同じ長さになる。そのとき CPS は話速そのものなので、

上限を守れない。実測では全 error の 75% がこれだった(1,155 件)。

伸ばして直せるのは 11.5% だけ。88.4% は縮めるしかない。


2. 層のどこに入るか
層1 オリジナル   逐語で書き起こす
      │
      ▼ 誤変換・固有名詞を直す(字数は変わらない)
層2 校正
      │
      ▼ 編集で落とした区間を反映(時間が縮む)
層3 マスター
      │
      ▼ 字数を減らす(時間は変わらない)      ★ この文書
層4 縮約
      │
      ├──────────────┬──────────────
      ▼              ▼
   本編 16:9      ショート 9:16
   (切って並べる) (切って並べる)
カットと縮約は別物
何が減る時間どこ
カット(層3)区間ごと落とす縮む00_subtitle_layers.md §7
縮約(層4)文字だけ減らす変わらないこの文書
縮約は Part 単位で 1 回。target ごとにやらない

本編用とショート用で別々に縮約してはいけない。 本文が 2 つに分かれた瞬間、

00_subtitle_layers.md §2「ショートは本文のコピーを持たない」が壊れる。

校正が入るたびに両方直すことになり、ずれる。

縮約は幅に依存しない。幅が変えるのは改行位置で、字数の密度ではない。

縮約したマスターを 1 本作り、そこから本編もショートも切って並べる。

この層は既定で通す。切れるようにしてはいけない。

scripts/build-master-srt.mts は 2026-08-12 まで --condense を付けたときだけ縮約していた。

アプリ経路(lib/shorts/cutFrames.ts の planCutsFromWords)は前から無条件に縮約しているので、

同じマスターから本文が 2 種類出ていた。いまは既定で通し、--no-condense は

層3 と層4 を測り比べるためだけに残す。

ショート選定と縮約の順序

順序を逆にしてはならない。

  • 校正済み master.json の全文・話者・時間を正本にする。
  • フック、情報密度、テンション、汎用性、共感と前後文脈を評価し、ショート候補と cutFrames を確定する。
  • 承認済みの cutFrames と話者判定を固定する。候補段階は candidate と明記する。
  • master.words[] を Part 単位で一度だけ縮約し、derivedFrom とhashを記録する。
  • 同じ縮約済みmasterから本編SRT、各ショートSRT、集約SRTを生成する。
  • SRTを一括投入し、DRT読戻しで部分cue、冒頭断片(「したりとか」等)、フィラー、重複、話者列との不整合を止める。

ショートごとに文章を再要約したり、縮約後の字幕から再び構成を選んだりしない。時間を縮めるのは cutFrames、文字を減らすのは縮約層であり、別の責務である。

残る CPS 違反はキュー分割の粒度から来るので、それは組版(L1-09 の L1)で扱う。

3. 削ってよいもの(優先順に)

上から順に当てる。上で足りたら下へ行かない。 削りすぎないための順番。

  ① 非発話          (笑い) (拍手) (拍子木の音) [BGM]
                    ── 字幕に出す要素ではない。縮約以前
  ② フィラー        あの / その(単独) / えー / あー / ま / まあ / なんか(単独)
  ③ 重複・言い直し  「なるなるんだなる」→「なる」
                    「しびれしびれしてた」→「しびれてた」
  ④ 冗長な言い回し  〜っていうふうに → 〜と
                    〜ということ     → 〜こと
                    〜っていうのは   → 〜とは
                    〜のような感じ   → 〜のよう
                    〜という形で     → 〜で
                    〜っていう       → 〜という   ← 上の長い形が当たらなかった残り
  ⑤ 敬語の冗長      させていただいております → しております
                    〜と思っております       → 〜と思う
                    〜させていただく         → 〜する
  ⑥ 相槌            はい / ええ / うん
                    ── ただし話者交代の合図なら残す(§4)
  ─────────────────────────────────────────
  ここから下は意味が変わる。**機械で削らない。人が判断する**
っていう 系の語尾は「落とす」ではなく「寄せる」

浅尾指示は「縮約で落とせ」だが、丸ごと落とすと 4 件中 3 件が日本語にならない。

2026-08-12 に本番経路へ残っていた 1 行の上限超過 4 件を 1 件ずつ読んだ実測。

本文落とすと寄せると
映画の概要をシノプシスっていうんやけど映画の概要をシノプシス(述語が消える)〜シノプシスというんやけど
なんで12個やったかっていうとなんで12個やったか(後続へ繋がらない)〜やったかというと
このプロダクションっていうのを制作ねこのプロダクションのを制作ね(体言化が消える)〜プロダクションというのを制作ね
うまいこといかんかったっていうかうまいこといかんかった(成立する)〜いかんかったというか

落として成立するのは 4 件目だけで、それは意味の取捨(この節の「ここから下」)なので

機械では触らない。代わりに口語の っていう を書き言葉の という へ寄せる。

1 字しか減らないが、意味は 1 文字も動かない。

実測(本番経路 / 全 13 Part / 229,366 字): っていう は 1,093 回。

うち 153 回は上の長い形(っていうのは 97 / っていうこと 46 / っていうふうに 10)が先に食い、

残り 940 回がこの規則に当たる。1 字 × 940 で 940 字。これで上限超過 4 件が 0 件になる。

直前 1 文字を全件見て、V-って+いう と読める危険形(動詞の促音便)が無いことを確かめた。

漢字が直前に来る 96 件はすべて名詞(仕事っていう 助監督っていう 小物っていう)で、

っていう が引用の って+いう でない例は 0 件。

語の切れ目は語の境目ではない

ASR の words[] は音の切れ目で分かれるので、語がまたぐ。実測(Part4):

「このプロダクションっ」「ていう」「のを制作ね、」

語ごとに縮約すると、この っていう はどちらの語からも見えない。

実測で置換規則が継ぎ目にまたがったのは 13 Part で 52 箇所。

condenseWords は語ごとの縮約のあとに継ぎ目だけを見る 1 パスを通す。

置換後の文字は元の一致がどちら側から何字来ていたかに合わせて分け直すので、

buildCharTimeline の等間隔割りによる時刻のずれは 1 字ぶんに収まる。


4. 削ってはいけないもの
種類理由例
固有名詞情報そのもの人名・作品名・会社名・地名
数字情報そのもの「2000万円」「3人」「1ヶ月」
否定意味が反転する〜ない / 〜へん / 〜ん
程度・態度の語話者の温度が消えるめっちゃ / 全然 / 絶対 / 一番
方言の語尾文体が変わる〜やん / 〜やねん / 〜へん
話者交代の合図誰が話しているか分からなくなる交代直後の「はい」「うん」

方言は文体である。 L2-01 が「ゆるくするための方言ではない。断定の強さは標準語版と同じに保つ」と

定めている。関西弁を標準語へ寄せる縮約はしない。

「なんか」は文脈で変わる。 単独のフィラー(なんか、その の なんか)は削ってよいが、

なんか変やな(= 何か)は意味を持つ。直後が読点・フィラー・言い直しのときだけ削る。


5. 保存量の検算(必須)

縮約は意図して内容を減らす変換なので、字数の一致では検算できない。代わりにこうする。

  落とした文字を全件リストにする
        │
        ▼
  そのすべてが §3 の規則に載っているか
        │
        ├─ はい → 通す
        └─ いいえ → **止める**。規則外の削除は 1 件も許さない

これが唯一の安全弁である。 「合計 N 字減った」は検算にならない。増減が打ち消し合うため。

リストに穴が開いていないことを、文字の突き合わせで確かめる。

入力と出力の文字を種類ごとに数え、その差が removals の

「落とした文字 − 置き換えた文字」と完全に一致すること。1 文字でも余れば、

それは記録されていない削除である。

2026-08-12 に実測してこれを見つけた。縮約で隣り合った約物を畳む処理が removals に残っておらず、13 Part で 、 188 字 。 52 字が説明できない削除になっていた。 字幕からは約物が落ちるので画には出ないが、説明できない削除がある状態そのものが 安全弁を壊す。いまは punctuation として記録し、未説明の差は 0 字。

さらに、削ってはいけないもの(§4)が消えていないかを別に見る。

  縮約の前後で、固有名詞・数字・否定形の出現回数が変わっていないこと

6. 機械にできること / できないこと
段機械理由
① 非発話できる括弧で囲まれた定型
② フィラーできない(下)直後の文脈では決まらなかった
③ 重複・言い直しできる同じ語の連続。ただし畳語(そうそう・いろいろ)を壊さない
④ 冗長な言い回しできる置換表。04_translation_principles.md が持つ
⑤ 敬語の冗長できる同上
⑥ 相槌半分話者交代の判定が要る
これ以上できない意味の取捨。人が読む

機械で足りない分だけ人(または Claude)が読む。 先に機械を当てて、残りの量を測ってから人へ回す。

② フィラーは「直後の文脈で決まる」が誤りだった(実測 2026-08-17)

上の表は 2026-08-07 まで「② フィラーはできる。語彙リストと直後の文脈で決まる」と

書いていた。測ったら除去率 12% だった。

実測(本番の condenseWords / Part1 / 原文 10,454 字):

縮約後10,240 字(2.0% 減)
フィラー 落とした / 残した20 / 149(除去率 12%)
なんか70 → 63(10%)
その57 → 53(7%)
まあ36 → 28(22%)
えー2 → 2(0%)

実装は「単独」を直後が読点・句点・別のフィラー・文末と定義している

(condense.ts の AFTER_FILLER)。ASR はフィラーの直後に読点を打たないので、

ほぼ全部が内容語に続き、149 件が「単独ではないので意味を持つ」として残る。

残った なんか を無作為に 10 件読んだら全件がフィラーだった:

別に|なんか|何かしたい        そう|なんか|もうふじい映画     やばくて|なんか|もう怖いの
見たら|なんか|ホラーとか       みんな|なんか|もう無理        その|すごい|なんか|ネットニュース
長くね|なんか|こうなんで       一緒に|なんか|おもろいこと     とか|なんか|映像を撮ったり
だけで|なんか|一緒に

なんか変やな(何か の意味)と なんかもう怖い(フィラー)はどちらも直後が内容語で、

字面では区別が付かない。これは意味の判断で、機械には決められない。

切る位置のレベル(L2 の継ぎ目か L3 の内側か)と同じ型である。

実装(2026-08-17): フィラーと繰り返しの判定を LLM が持つ

形は切る位置のレベルと同じ。機械が依頼書 → LLM が応答 → 機械が検算して格納。一方向。

実装は lib/subtitle/condense-decisions.ts と scripts/build-condense-decisions.mts。

持ち物
機械(候補の網)語彙と正規表現で落とす候補を全部挙げる。recall 側
LLM1 件ずつ「落とす/残す」を決める。判断はここだけ。検算もここ
機械(帳簿)hash 束縛・全件 answered・本文が動いたら当てない。中身は見ない

機械は判断を検算しない(浅尾指示 2026-08-17:「機械検算はいらない。

LLM のロジックがしっかりしていたらいい」)。落としてはいけないもの

(数字と助数詞・カタカナ語・漢字 2 字以上・否定の語尾・固有名詞)は

規則文に書いて LLM が守る。機械が二重に持つと規則が 2 箇所に分かれて食い違う。

判断の理由: 同じ日に機械の検算が 2 回とも間違ったほうだった。

① 切る位置のレベルで、検算が固有名詞の内部で L2 を落として ブレアウィッチ|プロジェクト を

封じていた。② 縮約の保存量で、電流なんか半分半 からフィラーを落として漢字の並びが

繋がった瞬間に「電流 が消えた」と誤検出し正しい出力でビルドが止まった。

そのうえ Part1 の 1 巡(候補 173 件)で保護対象を巻き込む削除の却下は 0 件だった。

機械が持ち続けるのは判断ではないものだけ。

hash 束縛(応答がどの依頼書に対するものか)、全件 answered(回し忘れた帯が

「フィラーの少ない Part」に化けるのを防ぐ)、本文の照合(上流が動いたら当てない

=別の語を落とさない)。網から漏れたものは extras として LLM が申告し、

その数がそのまま網の recall になる。

npx tsx scripts/build-condense-decisions.mts request --part Part1 --band-size 120
npx tsx scripts/build-condense-decisions.mts materialize --part Part1 --band b1
npx tsx scripts/build-condense-decisions.mts merge --part Part1
npx tsx scripts/build-master-srt.mts --out dev/master-srt --part Part1   --master-dir build/katto-release/Part1 --context build/katto-context/Part1/context-bundle.json   --condense-decisions "build/katto-release/{part}/{part}.condense-decisions.current.json"

実測(Part1・本番経路):

判定なしLLM 判定
縮約10,454 → 10,240 字(2.0%)10,454 → 9,978 字(4.6%)
フィラーの除去率12%71%
フィラーで落ちた字数54310
本編 CPS 警告9273
ショート CPS 警告145127
ショート error1817
本編 error610

候補 173 件(フィラー 169 / 繰り返し 4)を 2 帯で判定。落とす 120 / 残す 53、検算での却下 0、

文脈の食い違い 0。残した 53 件はほぼ全部が指示詞の `その`(その辺 その時 そのグー)と、

ZOOMかなんか(=何か)、そうそうそう(強調の反復)。

本編の error が 6 → 10 に増えたのは `reading_speed`(4 → 7)。 尺 0.45〜1.0 秒の

短いキューに集中している。総表示時間は 1,718 → 1,716 秒でほぼ不変、総文字は 261 減って

いるので全体の CPS は下がっている(それが CPS 警告 92 → 73)。

フィラーだけの語が丸ごと落ちると、その語の時間が字幕から外れるのが原因。

隣のキューがその時間を吸えるようにする必要がある——未対応。

残ったフィラーは組版にも効く

実測: そのすごいなんかネットニュースに は本編 13 字で 2 行に割れていたが、

その と なんか が落ちれば すごいネットニュースに(11 字)で 1 行に収まる。

保存量の検算は部分文字列で数える(2026-08-17 修正)

一致どうしを突き合わせていたので、間の字が落ちて 2 つの並びが繋がると誤検出した。

電流なんか半分半しびれ からフィラーを落とすと漢字の並びが 電流半分半 という

1 つの一致になり、電流 と 半分半 が「消えた」として本番のビルドが止まった。

文字は 1 つも消えていない。部分文字列で数えれば繋がっても通る。

判定条件(1 回も出てこなくなったら止める)は変えていない。

2026-08-03 の事故を繰り返さない。機械的な言い直し除去は 「そうそう→そう」「いろいろ→いろ」と畳語を 52 箇所壊した。 ③ は畳語の辞書を持ってから当てる。

7. 成果物
.transcripts/PartN.condensed.json

  layer         condensed
  derivedFrom   PartN.master.json のパスと sha256
  rulesVersion  縮約規則の版
  removed[]     落とした文字と、その根拠(§3 のどれか)
  kept[]        規則に載っているが**文脈で残した**もの

00_subtitle_layers.md §5 の作法にそろえる。何を落としたかが後から分かる形にする。

分からなくなったら、それは層を壊している。


8. 実測(ディレクターズ葛藤・2026-08-07)
CPS 超過ショート 690 件 / 本編 465 件
うち尺 0.5 秒以上(=真に縮約が要る)566 件
落とす文字数p50 2 字 / p90 4 字 / max 9 字
合計1,107 字
本文全体約 21 万字
削る割合0.5%

実例:

「脚本を作るっていうのはそういうことで」 18字 → 「脚本を作るとはそういうこと」 13字  −5
「こういう機能がありますっていうのも」   17字 → 「こういう機能があるのも」     11字  −6
「何かどういうことをしてきたかみたいな」 18字 → 「どういうことをしてきたか」   12字  −6
「喋っちゃいけないっていうか」           13字 → 「喋っちゃいけない」            8字  −5
ただし 1,107 字は 4 種類が混ざっている
種類実例本当の対処
冗長表現脚本を作るっていうのは縮約(この文書)
音イベントのタグ(笑い)アップルウォッチついてた(笑い)表示から除外。書き起こしの段
噛み・言い直しむしろなるなるんだなるじゃあなる校正(08_multipass_correction)の取りこぼし
キュー分割の欠陥まあジェイソン・ステイサムは全部ジェイソ語の途中で切れている。組版の欠陥

縮約に回す前に、他の 3 つを先に落とす。 そうしないと縮約の量を過大に見積もる。


関連
  • 00_subtitle_layers.md — 三層。この文書が層4 を足す
  • 04_translation_principles.md — 冗長表現の削り方(§3 ④⑤ の実務)
  • 08_multipass_correction.md — 校正の多段処理(§8 の「噛み・言い直し」はここ)
  • 09_break_priority.md — 縮約のあとの組版
L1-11 テンポのためのカット(語尾カット)

全 Project・全 Part に適用するグローバルルール。 ショートの編集で使う。

浅尾確定 2026-08-10。


1. なぜ切ってよいか

字幕を出しているので、語尾を最後まで聞かせなくても意味は落ちない。

話し言葉の語尾は長い。「〜っていうふうに思っててんな」の後半は情報を持たないのに、

そのまま残すと 1 枚あたり 1〜2 秒を食う。ショートは 15〜60 秒なので、

語尾だけで 1 割が消える。

これは[縮約](10_condensation.md)とは別の操作である。

何を減らす時間本文
縮約(層4)文字変わらない変わる
カット(層3)区間縮む変わらない
テンポカット(本書)区間縮む変わらない(字幕は最後まで出す)

テンポカットは映像だけを切る。字幕の本文は削らない。

字幕は言い切った形で出し、映像と音声を先へ進める。


2. 切ってよい語尾

次のどれかで終わる発話は、そこから後ろを切ってよい。

型例切る位置
引用・伝聞の尾〜っていうふうに / 〜みたいな感じで / 〜っていう引用句の直前
説明の尾〜っていうことなんですけど / 〜ということですね主節の直後
ためらいの尾〜かなと思ってて / 〜かもしれないですけど述語の直後
言い足し〜なんですよね / 〜やねんな / 〜やんか述語の直後
相手待ち〜でしょ? / 〜やんな?次の話者が被るところ

切ってはいけないもの。

  • 否定・逆接がその後ろに来るもの(「〜と思ってたけど違った」)
  • 数字・固有名詞がその後ろに来るもの
  • 語尾そのものがパワーワードになっているもの(「喝!」)
  • 次の話者の被せが意味を作っているもの(掛け合いの補強は残す)

3. 判定の順序
  • 意味が閉じているか。 述語まで出ているか。出ていなければ切らない
  • 後ろに新情報があるか。 あれば切らない
  • 切ったあとの尺が最小表示時間を満たすか。 満たさないなら切らない
  • 次の発話との間隔が最小ギャップを下回らないか

3 と 4 の値は [01_subtitle_format_jp.md](01_subtitle_format_jp.md) §3。

プロジェクトで上書きしてよいのはその表の値だけで、上の判定順は動かさない。


4. 字幕との関係
  • 字幕の本文は切らない。言い切った形のまま出す
  • 字幕の表示尺は切った映像に合わせて詰める。CPS の上限は守る
  • 上限を超えるなら、それはテンポカットではなく縮約の仕事(L1-10)

5. 実装状況

実装済み。既定は OFF(lib/subtitle/tempoCut.ts / planCutsFromWords({ tempoCut: true }))。

既定を ON にしない理由は、cutFrames が黙って変わると実データで突き合わせ済みの生成物と

食い違うため。切るかどうかは呼び出し側が明示する。

判定の入力は語単位 ASR(master.words[])と、品詞ではなく語尾の字面。

形態素解析は入れていない(lib/subtitle/breakPoints.ts と同じ方針)。

「間」は語の実測から測る(2026-08-12 に直した)

っていう という みたいな は連体形なので、**間を置かずに次が鳴り出すなら

それは語尾ではない。** その間をどこから取るかで結果が変わる。

2026-08-11 まで字幕キューの TC の差を見ていた。呼び出し側が渡すキューは

finalizeCueTiming を通ったあとで、キュー間は最小ギャップへ平坦化されている。

つまり測っていたのは息継ぎではなく規則が作った隙間だった

(実測: キューの間 p50 0.083s = minGapSec そのもの)。

いまは words[].start/end から測る。

測れるのは ASR が切った塊と塊のあいだの無音であって、息継ぎそのものではない。

PAUSE_AFTER_SEC = 0.35 は息継ぎの目安で、画で検証していない。

実測(2026-08-12 / 本番経路 planCutsFromWords / 10 Part・11,534 キュー)
TC を見ていたとき語の実測を見るいま
切った14 件 (0.12%) / 12.4 秒44 件 (0.38%) / 36.5 秒
総尺-0.06%-0.14%
connective で止まった347260

規則違反は 1 件も増えていない。

char_limit cue_break katakana_break particle_start verb_break

reading_speed min_duration max_duration は同数、min_gap は -7。

唯一増えるのは cps_ideal(warning)で 2,808 → 2,843(+35)。

本文を削らずに尺だけ詰めるので CPS が上がるのはこの操作の定義である。

守るのは §4 のとおり上限(cpsMax)で、そこは cps_over ガードが止めている

(39 件が上限に当たって切られなかった)。

「語尾だけで 1 割が消える」(§1)には届いていない。 実測は総尺 -0.14%。

効いていないのは間の判定ではなく語尾の表で、11,534 キューのうち

95.26%(10,987 件)が `no_tail`=§2 のどの字面でも終わっていない。

§1 の「1 割」はショート(15〜60 秒)の体感で書かれた数字で、Part 全長では測っていない。

伸ばすなら表を増やすことになるが、**増やすと precision が落ちるので、

増やした語ごとに切った実物を読む。**

precision(切った 44 件を全件、目で読んだ)
件数
妥当39
誤り(連体形で次へ掛かっている)3
判断が割れる2

誤りの 3 件は、次のキューが「その語尾が掛かる名詞句」になっている。

  • スシ道っていう → 次 寿司めっちゃめっちゃ作ってバトルみたいな(同格の言い換え)
  • 五年かけて資金を集めましたっていう → 次 その一応前提があっての映画は多分(前提 に掛かる)
  • 結構好きっていう → 次 個人的な好み(好み に掛かる)

どれも音には 0.35 秒以上の間がある(息継ぎを挟んで連体修飾が続く)。

間だけでは分からない型で、字面でも解けない。

その で始まる次キューを一律に止めると、妥当な切り(買い付けに来るみたいな →

その一番初めの企画から…)まで落ちる。形態素解析なしでは残る。

判断が割れる 2 件。

  • スシ堂だけやったら言ってたかもしれない — 落とす語尾の中に否定(ない)がある。

音だけ聞くと「言ってた」に反転する。字幕は最後まで出るので §1 の前提では成立するが、

§2「否定がその後ろに来るもの」の趣旨には触れる

  • キャスティングコールっていう — 次が これもさっきの仲間集めのところ で新しい文。

同格とも読める

間が短すぎて止めた型(`connective` 260 件)は、字面の 1 つ

(次の頭がまた引用標識)だけ別に数えている(chained 3 件)。

いやなんでこうなった?みたいな → っていうところを紐解いていく のような入れ子で、

音には間があるのに語尾ではない。

recall を測った(2026-08-13)

`no_tail` から等間隔に 150 件抜いて目で読んだ(scripts/audit-recall.mts --tempo。

入口は本番と同じ planCutsFromWords)。数えたのは「§2 の 5 つの型に当たるのに

表へ載っていない字面」。

件数
読んだ no_tail150
§2 の型に当たるのに表に無かった10(6.7%)

`no_tail` の大半は表の不足ではない。 残る 93% はキューが文の途中で終わっていて、

そもそも語尾が出ていない(何をやりたいか決まってない状態で 多分本当はもっと)。

カットの機会は「表を増やす」ではなく「キューの切り方」の側にある。

見つかった 10 件から同じ 5 つの型の字面を 11 足した(新しい型は作っていない)。

型足した字面
引用・伝聞というか(っていうか の非縮約)/ というふうに
ためらいのかな / かなと思う / と思ってさ
言い足しもんな / もんね / やん
相手待ちってこと? / ってことね

実測(全 13 Part / 11,482 キュー / scripts/measure-tempo-cut.mts):

足す前足した後
no_tail10,978(95.61%)10,747(93.60%)
切った43 件 / 33.7 秒79 件 / 57.5 秒
cutFrames 本数755 → 789755 → 814
増えた error00
増えた warning(cps_ideal)+29+52

規則違反(error)は 1 件も増えていない。 切った実物を抜き取って読み、

外人から見たらおもろいやん 動き方が決まるもんね できひんもんな のように

述語まで出たあとの終助詞だけが落ちていることを確かめてある。

まだ測っていないもの
  • 画。 切った 44 件を実際に繋いで見ていない。PAUSE_AFTER_SEC の 0.35 秒も同じ
L1-12 カットポイントの一連の流れ

**縮約された本文から、切ってよい位置を全部出し、そこから改行とキューの分割の両方を出す。

cutFrames はそのあとに来る別物。** この 1 本の流れが読める場所が無かったので置く。

この文書は実装がどうなっているかを書く。規範(何をすべきか)は下の 4 本が持つ。

上ほど上位で、下と食い違ったら上が正。

順何正本
1切る位置のレベル(L0–L4)と機械/LLM の棲み分けグローバル正本 `docs/SHORTS_CUTTER_SUBTITLES.md`
2切ってよい位置・切ってはいけない位置の一覧[01_subtitle_format_jp.md](01_subtitle_format_jp.md) §4
3衝突したときどちらを取るか(5 段のふるい)[09_break_priority.md](09_break_priority.md)
4縮約で何を落としてよいか[10_condensation.md](10_condensation.md)

値そのものは上の 4 本と重複させない。ここに書くのは順序と受け渡しである。

最終確認 2026-08-13(§3 の判定順・§8-1・§8-5・§9 を本番経路の実測で更新)。

2026-08-16 にレベル付与の段(§1 の①-b・§10)を追記し、文節モデル(BudouX)の記述を

廃止の決定に合わせて書き直した。


1. 一連の流れ
   語単位の書き起こし(words[] = 実測時刻)
        │
        ▼ ① 縮約                    lib/subtitle/condense.ts    condenseWords
   本文(縮約済み・句読点つき) + 語の実測時刻
        │
        ▼ ①-b レベル付与            lib/subtitle/break-levels.ts
        │                           運用は scripts/build-break-levels.mts
        │  機械  依頼書を作る        createBreakLevelRequest(規則文と固有名詞を封入し hash で束縛)
        │  LLM   位置とレベルを返す  Master の全 unit に 1 回。幅と行数は見せない
        │  機械  検算して格納        materializeBreakLevels(rejectionReason が禁則・固有名詞・
        │                           不可分語に当てて落とす。落ちた位置は理由つきで残る)
        │  機械  帯を統合            mergeBreakLevels(未付与の unit があれば止まる)
   L0 強調フレーズ / L1 話者交代 / L2 意味のまとまり / L3 改行ポイント / L4 音節   + rejected(理由つき)
        │  読み出しは wrapPositions(L1+L2)と cuePositions(L1 のみ)、
        │  L3 の解禁は isSyllableUnlocked(語長 > 1 行の文字数)
        │
        │  ここが要点 ── この段はまだ下流へ繋がっていない(§10)。
        │  いま本番が通るのは下の②③で、そこは文節モデルのままである。
        │
        ▼ ② 1 次候補                lib/subtitle/breakPoints.ts findBreakPoints
   切ってよい位置 + 罰点 0〜35     禁則は罰点ではなく「候補にしない」
        │
        ▼ ③ 2 次候補(音節)        同 syllableBreakPoints(layoutBreakPoints が混ぜる)
   候補と候補のあいだが 1 行に載らない区間だけ、語の内側にモーラ境界を足す(罰点 90)
        │
        ├────────────────┬────────────────
        ▼                ▼
   ④ 改行              ⑤ キューの分割
   solveLayout         splitToFit / splitText
   1 次 + 2 次        1 次を使い切ってから 2 次へ降りる
        │                │
        └────────┬───────┘
                 ▼ ⑥ 時刻の確定      lib/subtitle/cueTiming.ts   finalizeCueTiming
   字幕 TC(縮約済み・改行済み・尺とギャップが規則どおり)
                 │
                 ▼ ⑦ cutFrames       lib/shorts/cutFrames.ts     deriveCutFrames
   [startFrame, endFrame) の列 ── **切る位置ではなく、残す区間**

②③④は 1 つの関数(layoutLinesDetailed)の中で起きる。その中の降り方が

01_subtitle_format_jp.md §4「1 枚に収まらないとき」の 3 段

(改行 → カット → 音節改行)そのもので、詳細は §5 に書く。

本番の入口は 2 つあり、どちらも同じ順で通る。

入口実装
本編・ショートのマスター SRTscripts/build-master-srt.mts → condenseWords → cuesFromSource
アプリ経路(構成案 → cutFrames)lib/shorts/cutFrames.ts planCutsFromWords → condenseWords → cuesFromSource → deriveCutFrames

2. 起点は縮約済みの本文

候補は本文から作られるので、本文を変える処理は候補より前に置く。

縮約(層4)は本文を変える最後の処理なので、ここが起点になる。

condenseWords(words) は語の列に当てる。平の text に当てると words[] との対応が切れ、

時刻が実測から按分へ落ちる(condense.ts 冒頭。実測 2026-08-07 で cuesFromSource の

補間が Part7 ショートで 0 → 962 に跳ねた)。語ごとに縮約して空になった語だけ落とせば、

残った語の実測時刻はそのまま使える。

既定で通る。切る手段は測り比べ用にしか残っていない。

経路縮約
scripts/build-master-srt.mts既定 ON。--no-condense で切れる(層3 と層4 を測り比べるためだけ)
planCutsFromWords無条件。切る口が無い
句読点は落とさずに持ち回る

縮約は句読点を落とさない。落とすのは表示の直前。

先に落とすと罰点 0(句点)と 8(読点)というもっとも強い切れ目が消える

(01_subtitle_format_jp.md §4。合法率が 54% で止まった)。

alignToSource.ts の toLines は、句読点つきの本文で組んでから

stripDisplayPunctuation で落とす。行幅を測る measure だけが句読点を除いて数える。

縮約が落とすもの

規則に載っているものだけ。落とした文字は removals に全件残り、

呼び出し側が「規則外の削除が 0 件」を検算する。中身と優先順は

[10_condensation.md](10_condensation.md) §3 が正本。

⑥ 相槌は `condense.ts` では扱わない。 話者交代の判定が要るため(condense.ts の

CondenseKind に相槌が無い)。「ここから下は意味が変わる」の側も機械では触らない。


3. 1 次候補 ── 語と文節の境界

findBreakPoints(text, opts) が、位置と罰点だけを返す。行幅は知らない。

位置は「その直前で切る」意味で、text.slice(0, i) と text.slice(i) に分かれる。

位置は 1 以上 本文の長さ − 1 以下。先頭と末尾には立たない。

禁則(罰点ではなく「候補にしない」)

規則の一覧は 09_break_priority.md §2 が正本。ここはどこが持っているかだけ。

禁則実装規模
行頭に置けない字(約物・小書き仮名・長音・撥音・閉じ括弧)breakPoints.ts NO_LINE_START48 字
行末に置けない字(開き括弧)同 NO_LINE_END11 字
活用語尾の途中conjugation.ts splitsConjugationAt10 型(conjugationSplit が返す型の数)
行頭の 1 文字助詞particles.ts LINE_START_PARTICLES18 語(例外 LINE_START_EXCEPTIONS 121 語形)
敬称(名前の一部)compound-lexicon.ts honorificSplit君 氏 くん。左が漢字・カタカナ・英数字のときだけ
連語(複合助詞)の内部同 compoundParticleSplit8 語(として は意志形・擬態語を除く)
固有名詞・形式名詞の内部breakPoints.ts protectedRangesプロジェクト辞書 + NEVER_SPLIT 19 語
割れない 1 語のカタカナ語の内部compound-lexicon.ts ATOMIC_KATAKANA34 語
文節モデルが 1 語と見た短い塊の内側breakPoints.ts phraseSegments の inner6 字以下の塊(VETO_MAX_CHUNK)
英数字の途中・数字と助数詞のあいだfindBreakPoints の字クラス判定—

保護を上書きできるのは複合語の継ぎ目だけ(compound-lexicon.ts COMPOUND_SEAMS 61 組

+ 中黒 + 同語反復)。上書きが効くのは、継ぎ目の語と保護範囲が同じ語のときに限る

(seamCovers)。入れ子の語で外側の範囲が内側の継ぎ目を通してしまうのを防ぐため。

protectedRanges は 1 字の語を無視する。辞書に 1 字の語を入れても保護されない。

罰点と、判定の順序

09_break_priority.md §4 の表は罰点の順(良い切れ目の順)に並んでいる。

実装が見る順はそれとは別で、当たった段のうちいちばん上のものが確定する。

この並びが、規則どうしがぶつかったときの正本である(§8-1。罰点順で選び直して測り、悪化したので戻した)。

罰点は solveLayout が使うコストであって、段どうしの優先順位ではない。

判定の順条件罰点reason
—上の禁則のどれかに当たる—候補にしない
1直前が 。 ! ?0句点
2複合語の継ぎ目24複合語
3直前が空白・直後が空白でない2空白
4直後が空白—候補にしない
5直前が 、8読点
6文節モデルの短い塊の内側—候補にしない
7語が始まる位置(現行は文節モデルの区切り/保護語の前後)20文節 / 語頭
8直後が接続詞(13 語)12接続詞:…
9直前が て / で(その前が っ でない)16連用:…
10直前が助詞(29 語。1 字のものは直前が体言のときだけ)18助詞:…
11字種が変わる(漢字→ひらがな と カタカナ→ひらがな を除く)30 / 35字種:X→Y
最後最後の手段(particles.ts lastResortBreakAt)。上のどれで立った候補でも上書きする90補助動詞:X / 付属語:Y

7 が 8・9・10 を食う。 文節モデルが語頭と言う位置は、そこが接続詞の直前でも

連用形の直後でも格助詞の直後でも 20 になる。

7 の供給元は 2026-08-16 に廃止が決まった。 文節モデル(BudouX)は使わない (グローバル正本「切る位置のレベル(L0–L4)」/根拠は下の「品詞では切っていない」)。 移行先は LLM が付けた L3 改行ポイント(§1 の①-b)。 以下 7 についての記述は、移行が済むまで現行が何をしているかの記録である。

「最後の手段」だけは段の外に置く。 2026-08-14 追加。て形+補助動詞の直後

(浅尾確定「補助動詞は基本切りたくないな。完全にどうしても切れない場合だよ」)と、

2 文字以上の付属語が行頭に来る位置。段の 1 つにすると 7 に食われて効かない。

実測: 連用の段(9)の中で罰点を上げた版では そこを理解して|もらえるって

(文節モデルが文節と言う位置)に罰点が一度も付かなかった。

句読点の直後は対象外(そこは正本の最良の切れ目で、。じゃあ は接続詞)。

usePhraseModel: false を渡すと 7 が消えるので、下の段が何を出すはずだったかが見える。

実行して確かめた(3 文 / 2026-08-12)。

それが面白かった。でも時間が無かった
  model on   3:文節:20        | 9:句点:0 | 11:文節:20     | 14:文節:20
  model off  3:字種:H→K:30    | 9:句点:0 | 11:助詞:でも:18 | 14:助詞:が:18

予算が足りひんかったから諦めた
  model on   3:文節:20    | 5:文節:20 | 12:文節:20
  model off  3:助詞:が:18 |           | 12:助詞:から:18

そこでちょっと空気変えてくれるよつって
  model on   3:文節:20      | 7:文節:20 | 12:連用:えて:16 | 15:文節:20
  model off  3:連用:こで:16 | 7:字種:H→K:30 | 12:連用:えて:16
2026-08-14 以降、この例の 12:連用:えて:16 は 12:補助動詞:くれる:90 になる。 そこでちょっと|空気変えてくれるよつって(7/12)が両方とも上限 13 に収まるので、 補助動詞の直後は選ばれない。

格助詞(18)・連用形(16)が立つはずの位置は、文節モデルが入ると全部 20 になる。

逆に字種(30)だった位置は 20 へ下がるので、向きは一方向ではない。

同じ理由で 2 が 3・5 を食う(複合語の継ぎ目が空白や読点の直後にあれば 24 になる)。

罰点の絶対値を読むときは、この順序を前提にする。

品詞では切っていない

形態素解析は入れていない(breakPoints.ts の注記。kuromoji は辞書 5MB を積むうえ

2022 年から更新が止まっている)。「助動詞・動詞・名詞で切る」に相当するものは、次の 4 つの近似である。

見たいもの実装の代わり移行先
文節・語の頭現行: 文節モデル BudouX(Apache-2.0)の区切り。2026-08-16 に廃止決定LLM が付けた L3 改行ポイント(break-levels.ts。§1 の①-b・§10)
動詞・助動詞の活用conjugation.ts の 10 型(語彙。品詞タグではない)据え置き(検算側として残る)
助詞字面の一致(29 語)+「1 字は直前が体言のときだけ」据え置き。禁則は既定 ON のまま、LLM が `wordStart` を申告した位置だけ外す(rejectionReason)
名詞固有名詞辞書と ATOMIC_KATAKANA の保護、COMPOUND_SEAMS の継ぎ目。名詞の検出はしていない据え置き(protectedSpans が同じ辞書を見る)

文節モデルを入れてあったのは、全部ひらがなの区間に候補が 1 つも立たない穴を埋めるため。

字種の変わり目も助詞の直後も無いので、罰点モデルだけでは語の途中で折れる。

穴は埋まらなかった。廃止(2026-08-16、グローバル正本)。

実測: ぜひよろしくお願いしますはいじゃあ に対し BudouX は はい を は|い と割る位置を

優先候補として返し、本当の切れ目 します|はい は候補にすら立たなかった

(break-levels.ts 冒頭)。字面に手がかりが無い区間を埋めるのは LLM の仕事で、

機械は検算と幅の適用だけを持つ。


4. 2 次候補 ── 長い語を音節で割る

syllableBreakPoints が足す。1 語が 1 行に載らないときだけ立つ最後の手段。

立つ条件はこれだけ。

anchors = [0, …1 次候補…, 本文末]
隣り合う 2 点のあいだが maxWidth を超えている区間  ← ここだけ内側を開ける

つまり幅で決まる。字種の条件は無い。 長いカタカナ語も長い漢語も同じ経路で割れる

(浅尾指示 2026-08-11「6文字以上の長い単語やカタカナ語」)。

「カタカナが N 字以上なら割ってよい」という罰点 55 の段は 2026-08-11 に撤去された。

候補を出す側は幅を知らないので、その条件を置くと行に載る語まで割れる

(01_subtitle_format_jp.md §4 / 09_break_priority.md §4 に実測あり)。

区間の内側でも、次は塞ぐ。

  • canStartLine が false(行頭禁則・活用語尾・行頭の 1 文字助詞)

ここでは語頭の免除を渡さないので、助詞の禁止がそのまま効く

  • 保護語のうちその語自身が 1 行に載るもの(insideKeptWord)。

という は 3 字なので行に載る。ここを緩めると とい|うか が出る

  • 送り仮名の途中(漢字→ひらがな)
  • 空白の隣、数字と助数詞のあいだ、1 行に載る英数字の連なりの内側

罰点は SYLLABLE_PENALTY = 90、reason は 音節。

isSyllableBreak はこの reason で 1 次と 2 次を見分ける。

allowSyllableFallback: false を渡すと 2 次候補は立たない(既定は立つ)。

モーラ結合則が決めるのは「切ってはいけない位置」だけである

(09_break_priority.md §4「『音節』ではなく『モーラ』と呼ぶ」)。

実装も禁則の裏返しとして出しているだけで、音節境界を積極的に求めてはいない。


5. 同じ候補集合から、改行とキューの分割が出る

ここがこの文書の要点。 別々の規則を持たない

(09_break_priority.md §11 の最終行と同じ約束)。

改行(layoutLinesDetailed)

下の①②③は 01_subtitle_format_jp.md §4 の 3 段(改行 → カット → 音節改行)。

§1 の①〜⑦とは別の番号なので混ぜない。

本文が maxWidth に収まる ─────────────────────→ そのまま 1 行 outcome:'fit'
        │
① 1 次候補だけで solveLayout が解ける ─────────→ outcome:'fit'
        │ 解けない
② canCut かつ「割れば解ける」 ─────────────────→ fits:false / outcome:'cut'
        │                                        (割るのは呼び出し側の仕事)
③ 2 次候補を混ぜて solveLayout が解ける ───────→ outcome:'syllable'
        │ 解けない
④ 合法な切れ目だけで、いちばんはみ出しの小さい形 → fits:false / outcome:'cut'

canCut の既定は false。②を実行できるのは呼び出し側だけなので、外から受け取る。

true を渡しているのは alignToSource.laysOut と cueTiming.splitText の 2 か所で、

どちらも .fits しか読まない。

solveLayout は動的計画法で、コストは 3 つ。切れ目の罰点がいちばん重い。

コスト実装既定
切れ目の罰点penaltyAt上の表
狙いの幅からの外れoffTarget(shortnessWeight)重み 3 / 狙い幅は maxWidth * 0.75
逆ピラミッドの崩れpyramid(pyramidWeight)0(掛けない。案件の判断)
短すぎる行runt(minLineWidth)下限 2 字。割ると罰点 200

行数は収まる最小を採る(2 行から順に試す)。

キューの分割

割る位置も同じ候補から選ぶ。ただし音節候補は最後まで使わない。

語の途中でカットすると次のキューが語の途中から始まる(検証側の cue_break)。

降り方は、候補が字面から来ているかレベルから来ているかで別。

実装どこ降り方
splitToFit(字面)alignToSource.ts(本番の入口)1 次 → 1 次+2 次 → はみ出し最小(1 次のみ)→ はみ出し最小(2 次込み)
splitToFit(レベル)同上・cutPoints を渡したときL1 → はみ出し最小(L1)。以上。
splitTextcueTiming.ts(splitOverlongCue 側)1 次 → 1 次+2 次(levelDriven なら割らない)
cutHelpsbreakPoints.ts(②の判定)音節候補を数に入れない

どれも「前半が 1 枚に収まる候補のうち、いちばん後ろ」を採る。前を詰めきってから

次へ送るほうが枚数が増えない。幅で割る経路は持たない(splitByLength は

2026-08-11 に削除)。合法に割れる位置が 1 つも無ければ、残りを 1 枚のまま出す。

レベルの経路は音節(L3)へ降りない(2026-08-17)。L1 が「キューを割ってよい唯一の位置」で、

L3 は行の折返しに使う位置だからである(正本 docs/SHORTS_CUTTER_SUBTITLES.md §1)。

枚を割ってよいのは、他に位置が無いときだけ(最後の手段。浅尾確定 2026-08-21)。

降りる段が残っていたのは、候補を字面から作っていた頃の 3 段をそのまま持っていたため。

実測(Part1・13字×2行)でその段は 2 回効いており、2 回とも固有名詞の内部でキューを割っていた

(思ったのパラノーマル+アクティビティって…、ブレアウィッチ+プロジェクトって…)。

外すと char_limit が 2 → 3 に増えるが、幅を超えるほうが語を割るよりよい。

「はみ出し最小」は残す。 1 枚に収まる L1 が無いときに割るのをやめると、

位置の合法性は 1 つも変わらないまま 1 枚が長大になる(実測 2026-08-17:

キュー 572 → 566、total_char_limit 0 → 1、最長のキューが 125.5 字)。

キューの切れ目は候補だけでは決まらない

cuesFromSource は、候補へ降りる前に 3 つの切れ目を先に入れる。

1. 句点 。?!?!  で切る
2. 話者が変わる位置で切る(1 のあとに追加で割る)
3. 1 枚の上限を超えるものだけ、読点 、, で切る
4. それでも収まらないものを splitToFit(=候補集合)で割る

1〜3 は候補集合とは別の規則である。候補集合が効くのは 4 だけ。


6. cutFrames は 3 つ目の別物

混同しやすいので分ける。

何を決めるか単位入力実装
改行1 枚の中でどこに行を割るか本文の文字位置1 次 + 2 次候補layoutLinesDetailed / solveLayout
キューの分割1 枚を 2 枚以上に割る位置本文の文字位置同じ候補(2 次は最後)splitToFit / splitText
cutFramesどの区間を残すかフレームの半開区間 `[startFrame, endFrame)`確定した字幕 TCderiveCutFrames

cutFrames の境界は候補集合と何の関係も無い。 本文の文字位置ではなく、

タイムライン上の時間の範囲である。長さは endFrame - startFrame frames。

出し方は 4 段(cutFrames.ts)。どこにも「切ってよい位置」は出てこない。

  • 開始時刻で安定ソート(cueIndexes は並べ替え前の添字を持つ)
  • 生の秒で間隔が joinGapSec 以下なら同じカットへまとめる
  • padInSec / padOutSec を足し、IN は floor / OUT は ceil でフレームへ丸める
  • フレーム上で重なる/接するカットをまとめる
値既定根拠
padInFrames2字幕の IN 点(音声開始の 1〜2 フレーム前)と同じ
padOutSec0.2浅尾判断 2026-08-11。 字幕側に余韻 +0.5 秒が既に入っているので二重に足さない
joinGapSec0.6突き合わせで使った値。根拠は未測定

構成案が持っている区間(extracts の tc)は「どの発話を採るか」という編集の選択であって、

カットの境界ではない。境界は選ばれたキューの TC から出す(selectCueIndexes)。

語尾カット([11_tempo_cut.md](11_tempo_cut.md))は⑥と⑦のあいだに入る。既定は OFF。

ON にすると区間だけを詰め、同じ値を字幕側の end にも書き戻す(片方だけ縮めない)。


7. どこまでが機械で、どこから人か
機械が決める
段実装決め方
縮約condense.ts規則に載っている 6 種の置換・削除だけ。落とした文字は全件返る
候補と罰点findBreakPoints上の表。入力が同じなら出力も同じ
2 次候補syllableBreakPoints幅を超えている区間だけ
行の選択solveLayout動的計画法。コスト最小
キューの分割位置splitToFit / splitText候補のうち前半が 1 枚に収まるいちばん後ろ
時刻finalizeCueTiming余韻・最短・最大・ギャップ。実測から貼り、按分しない
cutFramesderiveCutFrames字幕 TC + 余白 + fps の四則演算

この 7 段に人の判断は入らない。 文字数の上限を変えれば、改行位置もキューの分割も

cutFrames も機械的に付け直る(planCutsFromWords の maxCharsPerLine)。

人が決める
何どこ備考
割ってよい複合語 / 割れない 1 語compound-lexicon.ts未知の語が出たら止まる(fail-closed)。09_break_priority.md §9
行頭助詞の例外語形particles.ts過検出を実物で読んで足す
活用語尾の型conjugation.ts見逃しを数えて足す
固有名詞lib/<project>/dictionary.ts資料の裏付けが要る
1 行の文字数 / 行数 / CPSthresholds.ts編集 UI は無い(コード定数)
上下のバランスpyramidWeight既定 0。ディレクターズ葛藤は均等寄り(浅尾確定)
意味の取捨(縮約の「ここから下」)—機械では触らない
相槌を落とすか—話者交代の判定が要る。condense.ts に無い
padOutSecDEFAULT_CUT_MARGINS浅尾判断

未測定として残っているもの: joinGapSec = 0.6 の根拠。

1 次候補の reason 別の出現分布(本番経路からの実測は未実施)。


8. 実装との食い違い(2026-08-13 時点)

1・4・5 は決着した(1 は測って実装が正、4 と 5 は直した)。2・3・6・7 は記録だけで直していない。

検証へ渡す句読点つき本文(bodies)の食い違いは §9 に分けて書く。

  • 罰点の順位と判定の順序が違う。 → 実装が正。測って決めた(2026-08-13)。

01_subtitle_format_jp.md §4 と 09_break_priority.md §4 の表は

「上ほど良い切れ目」として 12(接続詞)→ 16(連用)→ 18(格助詞)→ 20(文節)と並ぶが、

findBreakPoints は 20 を先に判定するので、文節境界と重なる位置では 12 / 16 / 18 が立たない。

§3 の実行例のとおり。

「罰点の低い順に選ぶ」(=表どおり)へ直して本番経路で測り、戻した。

候補の位置はどちらでも同じ(どれか 1 段でも当たれば候補、は変わらない)ので、

キューの分割・尺・ギャップは 1 件も動かない。動くのは行の割り方だけ。

実測(scripts/build-master-srt.mts 全 13 Part / 33,556 キュー):

判定の順(現行)罰点の順
error 合計1,2061,207
particle_start1617
katakana_break / verb_break / cue_break / char_limit39 / 0 / 0 / 039 / 0 / 0 / 0
尺・ギャップ・CPS の各違反同数同数
改行が変わったキュー—1,688 件(5.0%)
1 行目が 3 字以下589831(+41%)

罰点の順にすると オペレーションに|関してのみで が

オペレーションに関して|のみで(行頭 の)へ動く型で行頭助詞が増える。

文節モデルが語頭と言う位置は canStartLine の助詞ガードを免除されるので、

12 / 16 / 18 の段を先に通すほど、検証側が助詞と見る字が行頭へ出やすい。

**結論: 正本の表は「規則の格付け」であって、複数の規則が同じ位置で当たったときに

どれを採るかは決めていない。** その解決は §3 の「判定の順」の表が正本である。

findBreakPoints はこの並びを continue の連鎖ではなく

「当たった段を集めて先頭を採る」形で持つようになった(出力は 33,556 キュー全件同一)。

  • `01_subtitle_format_jp.md` §4 の罰点表に 24(複合語)と 30(ひらがな→他の字種)が無い。

実装は両方を出す。09_break_priority.md §4 の表には両方ある。

  • `09_break_priority.md` §1 の「候補に無い位置は既定コスト 60」は到達しない。

solveLayout の cuts は候補の位置からしか作られないので、

penaltyAt.get(c) ?? 60 の既定側が使われることがない。

  • `breakPoints.ts` のコメントに撤去済みの罰点 55 が残っていた。 → 直した(2026-08-13。コメントのみ)。

ファイル冒頭の一覧(罰点 55「長い語の音節境界」)、

LayoutOptions.shortnessWeight の較正メモ(「0/2/8/12/16/18/20/24/30/35/55/60」)、

SYLLABLE_PENALTY の説明(「普通の切れ目(最大 55)より重く」)、

isSyllableBreak の説明(「罰点 55(カタカナ音節 / 漢字音節)」)の 4 か所。

55 の段は 2026-08-11 に撤去済みで、いま出るのは 0 / 2 / 8 / 12 / 16 / 18 / 20 / 24 / 30 / 35 / 90。

カタカナ音節 漢字音節 という reason はもう生成されない。

冒頭の一覧には 24(複合語)と 30(ひらがな→他の字種)が抜けていたので併せて足した。

  • `cueTiming.splitText` の 1 段目のフィルタが無効だった。 → 直した(2026-08-13)。

points.filter((p) => !isSyllableBreak(p)) の points は findBreakPoints の戻り値で、

そこに 音節 で終わる reason は入らない。1 段目と 2 段目は同じ集合を渡していた。

段を実際の 2 つ(1 次だけ → 1 次+2 次)に直した。出力は変わらない

(本番経路 33,556 キュー全件同一)。

2 次候補が「候補と候補のあいだが maxWidth を超えた区間だけ」に立つことは

§4 のとおりで、syllableBreakPoints の anchors 判定を読んで確認した。正本の側は正しい。

  • `layoutLinesDetailed` の②が返す `lines` は 2 次候補で割れていることがある。

②の分岐は leastOverflow(text, withSyllables, opts) を呼ぶので、

音節の切れ目を使った行が入りうる。いまの呼び出し側は②のとき .fits しか読まないので

実害は無いが、fits:false を無視して lines をそのまま出す呼び出しを足すと

③が②より先に効いたのと同じになる。

  • 浅尾のイメージ「助動詞・動詞・名詞などでカットポイントを全部切る」と実装の距離。

実装に品詞解析は無い(§3 末尾)。近似が当たらない位置では候補が立たない、

または語の内側に立つ。[docs/PLAN.md](../../docs/PLAN.md) にも

「BudouX が 6 字を超える塊にまとめた区間では内側の拒否が効かない」として残っている。

※ 2026-08-16 に BudouX は廃止(決定)。上の 1 文は当時の実測記録として残す。

この距離を詰めるのが §10 のレベル契約で、位置の判断そのものを LLM が持つようになる。


9. 検証へ渡す句読点つき本文(bodies)

**表示では 、。, を落とすので、出力の行だけを見ると「句点をまたいで語が割れている」ように

見える。** validateSubtitles に bodies(句読点つきの本文。cue と同じ順序・同じ長さ)を

渡すと、その位置を verb_break / katakana_break / particle_start / cue_break から外す。

4 つとも severity は error。 渡せない面は、その分だけ error を過剰に数える。

突き合わせに 1 文字でも失敗すれば punctuatedLineBreaks は全部 false を返す

(=渡さないのと同じ。安全側)。渡し忘れと渡し損ねは、どちらも黙って過検出になる。

呼び出しbodies理由(2026-08-13 実測)
scripts/build-master-srt.mts渡すcuesFromSource が返す bodies をそのまま渡す。本番経路
lib/production/spec/builder.ts渡す(2026-08-13)plan 経路の refineShortCues が本文を持ち回るようにした(下記)
lib/render/ffmpeg/contracts.ts渡さない入力は Timeline IR の TimelineSubtitleSchema。.strict() で text しか無く、その text は焼き込む文字列。IR は manifestSha256 で封をした契約物なのでフィールドを足せない
scripts/assemble-cues.ts渡さない本文は wordsToPhrases 由来。同関数が `。、` を区切りシグナルとして消費して消す。 実測 Part7: phrases 1,442 件中 句読点つき 0 件 / Claude の返した行 1,775 本中 0 件。phrases から bodies を組んで渡してみたが Part7/8/9(3,575 キュー)で内訳が 1 件も変わらなかった
scripts/build-cues.ts渡さない同上。実測 Part7(語 1,584 中 1,225 が句読点つき)→ phrases 747 件中 句読点つき 0 件、出力 SRT に 、 0 個 / 。 0 個
scripts/audit-built-timeline.mts渡さない読むのはディスク上の完成 SRT。句読点は表示直前に落ちていて復元できない。ここで作り直すと「読み返す」ではなく「組み直す」になる

過検出の大きさ(実測 2026-08-13)。 本番経路で組んだ同じ 33,556 キューを、

bodies つき(生成側)と bodies なし(SRT を読み返す側)で測ると:

cue_breakverb_breakparticle_startkatakana_break
bodies つき001639
bodies なし97113644
builder.ts へ本文を通した(2026-08-13)

shortCueRefine / asrNormalize が句読点つき本文を持ち回るようにした。

  • normalizeAsrCues は cuesFromSource の bodies を捨てず、畳み(4 か所)では

表示行と同じ順で連結する。表示行の組み方は変えていない。

  • refineShortCues は 2 回目の正規化に 1 回目の本文を持ち込み、

出力行へ貼り直す(alignBodyToCues)。

  • 貼り直しは「行の可視文字+、。,」の形を作るので、返った本文は必ず

punctuatedLineBreaks に通る。通らなかったものは空文字にして件数を返す

(RefineShortCuesResult.bodiesUnaligned)。builder はその件数を

SUBTITLE_VALIDATION_FAILED のメッセージに載せる。黙って fallback に落とさない。

出力は変わっていない。 変更前後の refineShortCues を同じ実データ

(.transcripts 全 13 Part → 45 秒窓 829 本 / 17,206 キュー)へ通して比較:

枚数差 0 / 本文差 0 / 時刻差 0 / 統計差 0。

消えた過検出(実データ・15,929 キュー、`katto-shorts`)。

bodies あり / なしの差。42 件すべて 。 か 、 の直後で、全件を目視した。

cue_breakkatakana_breakparticle_start他
bodies なし3004610差 0
bodies あり261449差 0

貼り直せなかったのは 15,929 中 3 件(0.02%)。原因は漢数字→算用数字の変換が

1 回目と 2 回目で違う境界に当たること(六本木→6本木 など)。

その 3 件は従来どおり過検出のまま(安全側)で、件数として出る。


10. レベル契約(L0–L4)── 付与はできるが、下流へは未接続

規範はグローバル正本(docs/SHORTS_CUTTER_SUBTITLES.md)が持つ。

ここに書くのは実装の現在地である。

できていること
段誰が実装
依頼書を作る(規則文+固有名詞を封入し hash で束縛)機械break-levels.ts createBreakLevelRequest
プロンプト本文と帯分割(既定 --band-size 120)機械scripts/build-break-levels.mts
位置とレベルを付けるLLM応答 JSON(requestSha256 で依頼書に束縛。別の依頼書の応答は通らない)
検算して落とす機械同 rejectionReason → materializeBreakLevels
帯を Part へ統合機械同 mergeBreakLevels(未付与の unit があれば例外で止まる)
幅の適用(L3 の解禁判定)機械同 isSyllableUnlocked(wordLength, maxCharsPerLine)
読み出し機械同 wrapPositions(L1+L2)/ cuePositions(L1 のみ)

rejectionReason が返す理由は

index-not-integer / index-out-of-range / unknown-level / inside-proper-noun /

inside-atomic-word / cannot-end-line / particle-at-line-start / cannot-start-line、

materializeBreakLevels 側が duplicate-index。

build-break-levels.mts の reportLevels が理由別の件数を必ず出す。

`wordStart` は申告であって助言ではない。 機械には お願い|します と Zoom|の雰囲気 を

字面で区別できないので、行頭助詞の禁則は既定 ON のまま、申告があった位置だけ外す。

禁則本体(行頭禁則・活用語尾・固有名詞の内部)は申告があっても外さない

(rejectionReason の最後の 2 行)。申告が実際に効いた回数は totals.wordStartExemptions

に出る。全部に付けて回る応答はここで見える。

L3 だけは保護範囲の内側に立ってよい。 音節は語の内側であることが定義なので、

使うかどうかは幅を知る側(isSyllableUnlocked)が決める。ただし**固有名詞の内部は

どの幅でも通さない**(rejectionReason は L3 の early return より前に inside-proper-noun を見る)。

未接続(実装済みとして扱わない)
  • `lib/subtitle/break-candidates.ts` の `chooseBreaks` はレベル契約に繋がっていない。

buildBreakCandidates は findBreakPoints(=文節モデルつきの現行経路)から

1 次候補を作り、2 次は syllableCandidates が幅の下限(ALWAYS_FITS)で立てる。

L1 / L2 / L3 も wordStart も読んでいない。

  • **break-levels.ts を import しているのは scripts/build-break-levels.mts と

tests/subtitle/break-levels.test.ts の 2 つだけ。** 本番経路(build-master-srt.mts →

cuesFromSource、planCutsFromWords、lib/subtitle/canonical-transcript.ts)からは

一度も呼ばれていない。この段の数値を本番の実測として報告しない。

  • package.json に build-break-levels.mts の npm script は無い。tsx で直接叩く。
  • 帯の付与を実データで通した件数・rejected の内訳は未測定。

繋ぐときに決まっていないこと: cuePositions(L1)を

cuesFromSource の 4 段目(splitToFit)へどう入れるか、

wrapPositions(L1+L2)を solveLayout の罰点へどう写すか(レベルは罰点ではない)。

どちらも未着手。


関連
  • グローバル正本 docs/SHORTS_CUTTER_SUBTITLES.md — この流れの最上位の規範
  • [01_subtitle_format_jp.md](01_subtitle_format_jp.md) §4 — 切ってよい位置と禁止の一覧(この流れの入力)
  • [09_break_priority.md](09_break_priority.md) — 5 段のふるい。衝突したときどちらを取るか
  • [10_condensation.md](10_condensation.md) — 層4 縮約。何を落としてよいか
  • [11_tempo_cut.md](11_tempo_cut.md) — 語尾カット。字幕 TC と cutFrames のあいだ
  • [05_transcription_pipeline.md](05_transcription_pipeline.md) — 語単位タイムの作り方(この流れの手前)
  • [../L2_sns/15_shorts_composition_workflow.md](../L2_sns/15_shorts_composition_workflow.md) §4 — 字幕 TC → cutFrames の向き

レベルごとの実測(自動生成)

<!-- generated:level-report -->

この表は成果物の実測から生成される。手で書き換えない(yarn levels:report)。

unit 内 は unit の内側に付いた印、継ぎ目 は unit の境界に付いた印。

話者は語の途中で変わらないので、L1 は継ぎ目にしか出ない。

片方だけを見て「そのレベルは 0 件」と読まないために、ここで合算まで出す。

Part1
レベル名前unit 内継ぎ目合計
L0強調フレーズ000
L1話者交代0304304
L2意味のまとまり395296691
L3改行ポイント155501555
L4音節15015
L5冗長000

検算で落とした位置 0 / 語頭申告が効いた位置 24

Part2
レベル名前unit 内継ぎ目合計
L0強調フレーズ23023
L1話者交代0249249
L2意味のまとまり414461875
L3改行ポイント235902359
L4音節000
L5冗長000

検算で落とした位置 104 / 語頭申告が効いた位置 0

<!-- /generated:level-report -->

出荷済み字幕の実測基準(成功要因)

Status: canonical / 実測 / 2026-08-11(同日タイミング節を訂正)

これは規則ではなく、実際に出荷された 18,060 キューから取り出した実測値である。

新しく生成したものが「良いか」を判定するとき、規則の遵守率ではなくこの分布と比べる。

対象は dev/master-srt-j/*.shorts.srt の13本(Part1〜Part12 と Special2)。

IMPORTANT: 使える軸と使えない軸がある。 本文・改行・字数・行数は教師に使える。尺・ギャップ・余韻・CPS は使えない (タイミングが引き算で作られているため。次節)。 混ぜて「出荷実測に合わせた」と言わない。
IMPORTANT: 教師かどうかは「軸ごと」に決まる(2026-08-11 訂正)

同じファイルが、ある軸では人の手で、別の軸では機械であることがある。

このドキュメントは当初、改行の署名だけを見て「13本すべてで機械の署名が出ない」と

結論し、それを尺・ギャップまで一般化していた。それが誤りだった。

軸判定根拠
本文と改行人の手(教師に使える)下の署名表。ceil(n/2) が 23〜27% に留まる
タイミング(IN / OUT / ギャップ)機械(教師に使えない)すぐ下。OUT が引き算で作られている
本文と改行 — 機械の署名は出ない
署名実測機械出力の例
1行目が ceil(n/2) ちょうど23〜27%73%
1行目が常に上限値0.1%常に13字
2行目が1文字0.4%—

ceil(n/2) が 4分の1程度に留まるのは、**改行が意味の切れ目に置かれていて、それが

中点と一致するのが4回に1回しかない**ということ。ここが 50% や 73% に寄ったら、

それは機械が割っている。

タイミング — 機械の署名が出る

ショート13本 18,047 ペアを ms 単位で数えた(scripts/inspect-shipped-timing-provenance.mjs)。

ギャップ件数割合
84ms7,43841.2%
83ms6,96538.6%
次点(117ms)240.1%

1000/24 × 2 = 83.333ms を整数 ms へ丸めると 83 か 84 になる。その2値だけで 79.8%。

次に多い値が 0.1% しか無いのだから、これは「2フレーム以上空ける」を守った結果ではなく、

`OUT = 次の IN − 83.333ms` という引き算の結果である。

裏付けがもう1つある。時刻はフレーム境界に乗っていない(IN 3.9% / OUT 3.7%)。

IN の下1桁 ms は 0〜9 に散っている。境界に乗っていない時刻どうしの差が定数になるのは、

片方をもう片方から計算したときだけ。NLE で人が置いたならフレーム境界に乗る。

だから、このファイルの OUT 点には発話の情報が入っていない。 余韻(言い終わり +0.5 秒)が

守られているかを、ここからは測れない。

**当初この表には「尺が 24fps のフレーム境界へ量子化 5%」という行があり、低い値を

『機械ではない』根拠として読んでいた。逆だった。** 量子化されていないのに差が定数、

というのが署名そのものだった。タイミングの署名は「量子化」ではなく「定数への張り付き」で見る

(グローバル CLAUDE.md「タイミングなら、値が定数の倍数に揃っている」)。

~~成功要因1: キュー間ギャップは「常にちょうど2フレーム」~~ → 取り下げ(2026-08-11)

これは成功要因ではなく、機械の署名だった。 上の「タイミング」節のとおり、

83〜84ms が 79.8% を占めるのは運用の判断ではなく 次の IN − 83.333ms の引き算である。

当初ここには「中央値も下位10%も張り付いている」=**限界まで詰めて次を出すのがテンポの

土台、と書いていた。張り付きこそが機械の証拠**だったので、そのまま真似ると

引き算を再現するだけになる。

尺・ギャップ・余韻の基準は、この13本からは取れない。 取るなら、人が NLE 上で

置いた(=フレーム境界に乗った)タイムラインから測り直す。それまではグローバル正本

(CLAUDE.md の字幕タイミング表: 余韻 +0.5 秒 / 上限 1.0 秒 / 最小 500ms / 最小ギャップ 2 フレーム)

を根拠にする。

成功要因2: 読む速さ(CPS)の分布
全体Part ごとの幅
CPS 中央6.66.2〜7.1
CPS p9010.39.7〜11.0
CPS p9914.7—

中央 6.6 文字/秒。 上限を1つ決めるより、この分布に収まっているかで見る。

p90 が 10 を大きく超えるなら詰まりすぎ、中央が 5 を切るなら間延びしている。

ただし、この CPS が何を測っているかに注意する(2026-08-11 追記)。

CPS = 字数 ÷ 尺だが、上のとおり尺は編集者が選んだ表示時間ではない。

OUT = 次の IN − 83.333ms なので、尺は実質「次の発話が始まるまで」=

発話の onset 間隔である。IN は ASR 由来の実測なので、この数字は

話速に近いものであって、読みやすさの判断ではない。

だから「p90 が 10 を超えたら詰まりすぎ」という読み方は、字数側が多すぎるときにだけ

正しい。尺側が短いときは、編集の判断ではなく発話が速いだけのことがある。

縮約が効いているかの指標としては使えるが、表示時間の基準としては使えない。

成功要因3: 1キューの字数と行数
全体Part ごとの幅
1キューの字数 中央1211〜13
同 p901817〜18
同 最大26—
2行率59%54〜63%
3行以上0全13本で0

1行の上限13字とは別に、1キュー全体では中央12字・p90 18字。 2行のときは

だいたい 9+9 前後になる。

3行は13本 18,060 キューで1件も無い。 ここは例外なし。

成功要因4: 規則違反はほぼゼロ、ただし1点だけ例外がある
項目13本の実測
14字以上の行0〜3件(1200キュー中)
ギャップ違反(83ms未満)0〜1件
尺 6.5 秒超0〜3件
3行以上0件
尺 0.5 秒未満18〜61件

最後の1行だけが系統的に外れている。 全13本に 18〜61 件あり、基準の Part7 にも25件。

393ms  なるんだ      436ms  とかね        416ms  なんか
463ms  砂漠とか      450ms  んねんから    424ms  だって別に
これは規則違反である(浅尾確定 2026-08-11)。実測で理由も割れた

全 33,494 キューのうち 500ms 未満は 522 件(1.6%)。**そのうち 522 件、つまり 100% が

「次のキューが早く来たから切られた」側で、発話そのものが短いものは 0 件**だった

(scripts/inspect-shipped-timing-provenance.mjs ④)。

つまりこの 522 件は「短い発話をどう扱うか」という問題ではなく、上の引き算の帰結にすぎない。

OUT = 次の IN − 83.333ms で作れば、キューが密なところでは尺が 500ms を割るのは当たり前で、

そこに編集上の意図は入っていない。

だから規則を実測へ寄せてはいけない。 寄せると機械の算術を正典化することになる。

最小表示時間 500ms は規則として維持し、割ったものは違反として扱う。

直す向きは上流。 尺が足りないなら、次のキューへ押し込むのではなく

前のキューへ畳むか、切り方を変える。「500ms に届かないから伸ばす」ではなく

「500ms に届かない切り方をしない」。

当初ここには「両立しない要求の帰結なので未決」と書いていた。**両立しないと見えたのは、

取り下げた成功要因1(常に2フレームで詰める)を前提に置いていたからで、その前提が誤りだった。**

判定に使うときの注意

遵守率と良さを混ぜない。 ここに並べた数字のうち、生成側が同じ閾値を見て

作っているもの(字数・ギャップ・尺)は遵守率であって正しさではない。

独立に測れるのは、生成側が見ていない観点だけである。

  • 改行が意味の切れ目に落ちているか(ceil(n/2) 率が 50% へ寄っていないか)
  • CPS の分布が上の帯に入っているか
  • 3行が出ていないか

数値が通ったら、全長から等間隔に20件ほど抜いて実物を読む。 遵守率100%の中に、

活用語尾の分断のような欠陥が隠れる。

測り直す
node scripts/measure-shipped-subtitles.mjs
話者の判定(誰が喋っているか)

Status: canonical / 2026-08-11

Angle は話者ごとの寄りなので、列を決める前に「誰が喋っているか」が要る。

ここは推測で埋めない。使える系統を強い順に並べ、いま何が塞がっているかを書く。

同期は通った(2026-08-11 実測)。原因は「基準が無音」だった

Part11 の ASR は3話者を出し、speaker_2 は全体の 0.4%(3語 / 1 turn)。

これを音声で裏付ける前提が同期で、長らく maxCorrelation がほぼゼロだった

(TX01 -0.0057 / TX02 0.035 / Wireless1 0.0031)。

原因は相関計算ではなく、基準に選んでいた映像の音声が無音だったこと。

20260411_katto/2026-04-11 15-15-34.mov は AAC 48kHz 2ch のトラックを持つが、

43 分まるごとデジタル無音(0s / 60s / 300s / 600s / 1200s / 1800s / 2400s / 2570s の

8 点すべてで RMS も peak も厳密に 0)。無音とは相関しない。計算は正しかった。

そして Clips_Raw/260411/ には `Cam1/` と `Cam2/` があり、解析は一度も見ていなかった。

Cam1/20260411_C0355.MP4 は pcm_s16be 48kHz 2ch で中身があり(RMS 0.07〜0.10)、

尺 2603.1s と無音カメラの 2608.5s はほぼ同じ=同じテイクの別アングルである。

これを基準に測り直した結果(tools/audio/sync_to_reference.py、3 点検算):

系統オフセット相関(先頭 / 中間 / 末尾)判定
Wireless1+25.900s0.652 / 0.633 / 0.754synced
TX01+31.550s0.836 / 0.849 / 0.879synced
TX02+34.000s0.976 / 0.970 / 0.988synced

3 点すべてでばらつき 0.000s。 ドリフトも録画欠落も出ていない。

`TX02` が独立した裏付けになるとは限らない。 相関 0.97〜0.99 は「同じ音」に近く、

`Cam1` の音声が TX02 受信機の出力そのものである可能性が高い。話者の判定に使うときは、

TX02 と Cam1 を 2 系統として数えない。

訂正: Wireless2 の除外は欠陥ではなかった

このドキュメントは以前「Wireless2(5ファイル)が丸ごと除外されている」を欠陥として

挙げていた。誤りだった。 実尺から区間を出すと、Wireless2 は午前のセッションで

Part11 の映像と一切重ならない。

系統収録区間映像(15:15:34–15:59:02)との重なり
Wireless1 0003715:14:02–15:58:082554s = 97.9%
Wireless2(最終 00022)〜13:35:260.0s = 0%

除外は正しい動作だった。「使われていない素材がある」を、確かめずに欠陥として書かない。

実行結果(Part11 全編、2026-08-11)

同期が通ったので scribe_v2 の multichannel を全編に通した。3ch / 2600 秒 /

課金 7800 秒。`use_multi_channel=true` と `tag_audio_events=true` は両方効く。

実測
各チャンネルの語数17,406 / 17,049 / 17,036
話者が決まった語11,737 / 16,656 = 70.5%(エネルギー差 3dB 以上)
内訳TX01 51.8% / Wireless1 24.7% / TX02 23.5%
話者の切り替わり1,352 回
差(dB)中央 7.1 / p25 2.2 / p90 16.0
音声イベント72 件 → 畳んで 49 件([笑い] 40 / [咳払い] 6 / [マイク擦れ] 2 / [背景の雑音] 1)
マイクと席の対応(Part11。目視で確定 2026-08-12)
マイク席見た目発話量人物名
TX01中央黒Tシャツ51.8%未登録
Wireless1右キャップ・眼鏡・白T24.7%未登録
TX02左スーツ・眼鏡23.5%未登録

決め方: 各マイクが連続して勝ち続け、かつ差が大きい区間を出し、その時刻の Cam1 から

静止画を取って「口が開いていて、他の 2 人がその人を見ている」人を読んだ

(TX01 = 2554s / Wireless1 = 636s / TX02 = 1729s)。3 枚とも一致した。

人物名は推測で埋めない。 実在の人名なので、浅尾の登録を待つ

(正本 CLAUDE.md「人物ラベルはプロジェクトと Part の二層で持つ。手動登録を必ず用意する」)。

`TX02` は部屋マイクではなく個別のラベリアだった。 語ごとのレベル行列で対角が立つ。

勝者Wireless1TX01TX02
Wireless1-40.6-46.7-48.9
TX01-49.4-35.4-47.0
TX02-56.4-54.3-37.2

自分の語と他人の語の差は Wireless1 +12.3dB / TX01 +15.1dB / TX02 +10.7dB。

Cam1 と一致するのは受信機の出力が `Cam1` へ入っているからで、マイク自体は有効。

それでも `Cam1` と `TX02` を 2 系統として数えない。

`Cam2` はこの収録ではない。 ファイルの mtime が 4/10 の夕方〜夜で、

Part11(4/11 15:15)と重ならない。このセッションのアングルは `Cam1` の 1 本だけ。

IMPORTANT: 旧 diarization の第3話者 0.4% は誤りだった

単体 diarization は speaker_2 を 0.4%(3語 / 1 turn) としていた。

マルチチャンネル + エネルギーで測ると 23.5%(2,758 語)。桁が違う。

speaker_2 をほぼ発話しない人として扱った設計(Angle の V4 が 5 フレームしか

無かったのはこれが原因)は作り直す。

系統の強さ
① マルチチャンネル STT(diarization を使わない)

ElevenLabs Scribe v2 は multichannel mode でチャンネルごとに transcript を返し、

channel_index と speaker_id が付く。

IMPORTANT: それでもチャンネル = 話者にはならない。 Part11 実測では

3ch とも同じ 60 秒を 430 語前後で書き起こし、テキスト一致率 0.824〜0.941。

マイクは音響的に分離しておらず、全チャンネルが部屋全体を拾っている。

かつてここに「1人1マイクを1チャンネルへ入れれば話者の推測が一切要らなくなる」と

書いていた。誤りだった。 かぶりは「限界」ではなく既定の状態で、②が必須になる。

② マイクごとのエネルギー比較(必須。検算ではない)

各語の区間で最大エネルギーのチャンネル=話者とする。差が 3dB 未満の語は

speaker: null のまま残す(tools/audio/assign_speakers_by_energy.py)。

残った 29.5% を平均や多数決で埋めない。 埋めると、決まっていないことが

決まったように見える。①だけなら推測 100%、②を重ねて **推測は 29.5% まで下がり、

どれが怪しいかが明示される。** そこが成果であって、「推測が消える」ではない。

マイクが個別かどうかは先に測る(tools/audio/measure_mic_isolation.py)。

Part11 実測: 勝者マイクは Wireless1 30.1% / TX01 34.9% / TX02 35.0% と入れ替わり、

勝者と2位の差は中央 7.9dB(個別ラベリアの目安 15〜25dB より小さい=かぶりが有意)。

系統間の相関は Wireless1×TX01 -0.027 / Wireless1×TX02 +0.010 /

TX01×TX02 +0.586。

笑い声(tag_audio_events)

既存の書き起こしには笑い声が一件も入っていなかった(唯一の「笑」は「笑顔」という

単語の中)。取れば純粋な新規情報になる。正本 CLAUDE.md の「SRT に笑い声が取れていれば、

全体のカットかその人物の抜きカットを入れる」がこれで実行できる。

IMPORTANT: 音声イベントを区間として扱わない。点である

最初この工程は「長さ 0 のイベントに最小長 0.30 秒を当てる」設計だった。

0.30 が短すぎるという話ではなく、当てる先が測定値ではなかった。 構成から誤りだった。

同期済みの同じ音を複数チャンネルが報告した 16 組で実測すると:

実測
長さの食い違い中央 0.364s / 最大 1.120s。16 組中、片方が 0 秒か 2 倍以上違うものが 13 組(0 秒を除いて 2 倍以上だけ数えると 9 組)
開始位置のずれ中央 0.330s / 最大 1.170s
長さ 0.05 秒未満72 件中 24 件(33%)、うちちょうど 0.00 が 9 件

具体例。68.84s の [笑い] は TX01 0.50s / TX02 0.52s / Wireless1 0.01s、

76.04s の [咳払い] は TX01 0.60s / TX02 0.00s / Wireless1 0.01s。

時間が合っている同じ音でこれだけ散るなら、それは長さの測定値ではない。

音そのものも見た。報告位置の前後 4 秒はずっと発話で埋まっており、

「笑いの塊とその前後の静寂」という形になっていない。トークンは文の途中に挿さる

(…切ってる人を ▸[笑い]◂ 、Tips にありつけ…、…この図の通り ▸[笑い]◂ 、なんか左で…)。

つまり [笑い] はその発話に笑いが混じっていたという注記であって、

切り出して使える 1 個の音のイベントではない。**長さを補うと、測っていない値を

測ったように見せることになる。**

だから成果物は次の形で持つ(tools/audio/build_speaker_attribution.py)。

  • atSeconds — 点。複数 ch が報告したときは開始の中央値
  • startSpreadSeconds — その点のばらつき。±0.33 秒程度は乗っている前提で使う
  • confidence — corroborated(複数 ch。49 件中 16 件)/ single-channel(33 件)
  • reportedNotMeasured — 各 ch の生の報告。証跡としては残すが導出に使わない

カットの長さは編集側のパラメータから取る。ASR から導かない。

どれだけ見せるかは演出の判断であって、笑い声の物理的な長さではない。

モデル間でも検出はずれる(v1 は 29.06 を拾い v2 は拾わない、v2 は 127.29 を拾い v1 は拾わない)。

取りこぼしを減らすなら v1 と v2 の和集合を取る。

冒頭の `[咳払い]` はスレートである。 2.08s / 9.16s / 14.05s は

カウントイン(よーい、そ、四、三)の中にあり、本編の咳ではない。

時刻だけで拾うと、頭のスレートがリアクションカットになる。

③ 声紋(speaker embedding)

きれいな区間から各人の埋め込みを作り、以後の区間を最近傍で当てる。

個人マイクが無い素材でも使えるのが利点。①②が使えるならそちらが上。

④ 単体の diarization

いま使っている経路。人数も境界も推定なので、単独では確定に使わない。

同期のときに外す3つ

①②はどちらも時間が合っていることが前提になる。Part11 で実際に外したのは次の3つ。

  • 基準に無音を選ぶ。 いちばん高くつく。相関が 0 になるので原因が計算に見えるが、

計算は正しい。着手時に基準の音を数点測る(sync_to_reference.py は自動で止める)

  • 素材の置き場を全部見ない。 Cam1/ Cam2/ が並んでいるのに

20260411_katto/ だけを見ていた。同じ日の撮影フォルダを列挙してから選ぶ

  • 分割ファイルを 1 本だけ渡す。 TX は 30 分で切れるので、末尾の検算点が

ファイルの外へ出て「同期失敗」に見える。失敗しているのは同期ではなく測る範囲の取り方

補足:

  • TX01 / TX02 はファイル名にタイムスタンプを持つ(TX02_MIC005_20260411_101232)。

30分ごとに分割されているので、同一系統としてまとめてから使う

  • ワイヤレス12本はタイムスタンプを持たない。 ファイルの mtime から引く候補値は

あくまで候補で、時刻の正本にしない。ただし mtime − 実尺 で出した区間は、

「そもそも重なるか」の足切りには十分使える(Wireless2 はこれで 0% と分かった)

  • 同期は ①埋め込みTC ②音声波形 ③手動イン点/スレート の順。

使った方法と信頼度を必ず記録し、先頭・中間・末尾の3点以上で検算する。

3点が一致しなければ「同期失敗」として残す。平均で埋めない

  • 相関は窓ごとに正規化する。 全体の平均と分散で割ると値が 1 を超えることがあり

(実測 1.033)、そうなった時点でそれは相関ではない。ピーク位置は正しくても

大きさを相関として引用できない

進め方
  • ~~Wireless2 を解析対象へ入れる~~ → 不要。映像と 0% しか重ならない(上の訂正)
  • ~~TX の分割ファイルをまとめる~~ → 済。`--source NAME=a.wav,b.wav`
  • ~~波形相関で合わせ、3点で検算する~~ → 済。3系統とも synced、ばらつき 0.000s
  • ~~3系統をマルチチャンネルへまとめて ① を通す~~ → 済。全編 70.5% が確定
  • ~~② を独立に走らせる~~ → ②は独立の検算ではなく必須の工程だった(かぶりのため)。

実行済み。差 3dB 未満の 29.5% は speaker: null のまま残してある

  • 次はここ。人数を最後にマルチカムの静止画で目視確認する(正本 CLAUDE.md)。

マイクを着けていない話者(画面外のディレクター等)はチャンネルを持たないので、

この方法では絶対に拾えない。 目視は省けない

6 が通るまで、話者ラベルを `known` に昇格しない。 70.5% が決まったことは

「誰が喋ったか」ではなく「どのマイクがいちばん大きいか」であって、

そのマイクを誰が着けていたかはまだ紐づいていない。

IMPORTANT: 生カメラの時間軸と編集後の時間軸は、定数では繋がらない

今回の話者データは `Cam1` の生カメラ時間軸にある。いっぽう DRT のカット範囲と

part11-condensed-*.json は 編集後の Part11 時間軸にある

(timebase.drpOffsetFrames = 1145 が DRT の source_body_origin 87545 − timeline_origin 86400

と一致することで確認)。

この 2 つを引き算で繋がない。 本文一致で 6 点測ると、ずれは階段状に飛ぶ。

位置15%25%40%50%85%90%
ずれ+13.71s+13.42s+39.42s+39.23s+92.93s+92.59s

一次あてはめ(新 = 1.046 × 旧 − 10.2s)の残差は最大 8.09s。レートのずれなら

残差は小さくなるはずで、そうならない。編集で抜かれた尺が段ごとに積み上がっている。

13.5 → 39.3 で約 25.8 秒、39.3 → 92.8 で約 53.5 秒が抜けている。

必要なのはオフセットではなくカットリスト(生のどの範囲が編集後のどこへ来たか)。

Part11 の DRT が生素材への In 点を持っているので、そこから引ける。

それが取れるまで、話者データを DRT へ適用しない。

密度相関では出せない(旧は縮約済みで 1,572 語、新は 16,656 語と 10 倍違う)。

本文一致で 3 点以上測り、一致しなければ「繋がっていない」として残す。

再現手順
# ① 同期(基準が無音なら止まる)
python tools/audio/sync_to_reference.py --reference ".../Cam1/20260411_C0355.MP4" \
  --source "Wireless1=.../00037_Wireless_PRO.WAV" \
  --source "TX01=.../MIC043.wav,.../MIC044.wav" \
  --source "TX02=.../MIC013.wav,.../MIC014.wav" --out dev/knowledge/part11-sync-20260811.json

# ② マイクが個別かを先に測る
python tools/audio/measure_mic_isolation.py

# ③ オフセットを当てて 3ch にまとめ、scribe_v2 の multichannel + tag_audio_events へ

# ④ 語ごとにエネルギーで話者を決める(差 3dB 未満は null のまま)
python tools/audio/assign_speakers_by_energy.py <3ch.wav> <response.json>
L2-01 プラットフォーム別ルール
YouTube / Shorts / TikTok / Instagram / X / Podcast 各プラットフォームのタイトル・キャプション・配信ルール。

文体(全プラットフォーム共通)

SNS 投稿文は、YouTube フル尺の概要欄以外すべて関西弁で書く。

対象文体
YouTube(フル尺)のタイトル・概要欄標準語(ですます / 断定調)
YT Shorts / TikTok / Instagram / X関西弁
英語版(postsEn)対象外(英語のまま。方言化しない)
ハッシュタグ・固有名詞・数字対象外(そのまま)
なぜ

YouTube フル尺は検索流入とアーカイブが主で、読み手が広くフォーマルな説明を要する。

それ以外は話者本人(関西)の語り口に寄せた方が、タイムライン上で自然に届く。

書き方
  • 素材は本編の発言そのもの。話者が実際に喋っている関西弁をそのまま活かすのが基本で、標準語に直してから方言化し直さない。
  • 断定は関西弁の断定形で行う(「〜や」「〜やねん」「〜やった」「〜へん」「〜アカン」)。

ゆるくするための方言ではない。 断定の強さは標準語版と同じに保つ(→ [L2-05](./05_x_post_style.md))。

  • エセ関西弁にしない。迷ったら本編字幕(Subtitle_Short/*.srt)の言い回しを引く。
実装例: data/fix/katto/Part9.json(9-1〜9-7)。YT Shorts / TikTok / Instagram / X の 4 つが関西弁、postsEn は英語のまま。

YouTube(フル尺)
Title
制限値
Hard limit100 文字
Display limit≈ 70 文字(検索・モバイル)
Sweet spot50〜65 文字
Winning Patterns
パターン例効くポイント
[Bracket Hook] + Keyword[Zero Budget] How We Made a Feature Film with No Experience視覚的に止まる + キーワードを前出し
QuestionCan 3 Amateurs Actually Make a Real Movie? (We Tried)Curiosity gap
Number + Transformation3 Beginners, 0 Film Experience, 1 Actual Cinema Release数字で信用
Pain-pointNobody Warned Us About This Part of Making a Movie共感
ChallengeWe Made an Indie Film With No Money — Here's What Happenedドキュメンタリー感
Rules
  • 最強キーワードを 先頭 50 文字以内 に
  • 高 CTR + 低 retention のクリックベイトは YouTube が penalty
  • 英語は Title Case(主要語を大文字化)
  • 数字 > スペルアウト(3 > Three)
  • 末尾の (Full Story), (Ep. 1) は SEO 影響なしで文脈追加
  • ALL CAPS は禁止(叫び扱い → CTR 低下)

Description
構造(実証テンプレ)
[Hook Line — タイトルを膨らませる 1〜2 文]

[Episode summary — 3〜5 文]

[Key timestamps]
00:00 Intro
xx:xx [Chapter 1]
xx:xx [Chapter 2]

[Series context — チャンネル / ドキュメンタリーの位置づけ 1〜2 文]

[Links: next episode, playlist, social]

[Hashtags — 3〜5 個、末尾]
Best Practices
  • 先頭 2〜3 行が "Show more" 上に出る。ここで決める
  • プライマリーキーワードを最初の文に自然に
  • 人間優先・アルゴリズムは二次(YouTube の NLP は semantic intent を読む)
  • Episode 番号で位置付け(Ep. 1 | <Series>)
  • CTA を入れる(subscribe / next episode)
  • Hashtags は末尾に 3〜5 個。先頭 3 個がメタデータに入る
Length
  • 最適: 150〜300 ワード
  • 100 ワード未満 = SEO 損
  • 500 ワード超 = 深くインデックスされない(diminishing returns)

YouTube Shorts
Title
制限値
Limit100 文字
Display (mobile)≈ 50 文字

ワンクリアフック: 質問・断定・POV フォーマット。

例:

  • POV: You're making a movie with no money
  • The hardest part of writing a screenplay (nobody talks about this)
  • We built a film crew from zero. Here's how.
Caption(Shorts / TikTok / Instagram 共通)
  • 先頭 1 行がフック("more" 前に見える)
  • 可視部分は 150 文字以内
  • 改行で読みやすく
  • 末尾は質問 or CTA でコメントを誘導
  • Hashtags 4〜6 個(broad + niche + 作品名)
高パフォ Hashtags(英)
#filmmaking #indiefilm #lowbudgetfilm #moviemaking #behindthescenes
#filmmakingtips #indiefilmmaker #screenwriting #directorscut #nobudgetfilm

TikTok
YouTube との違い
  • フックは 1〜2 秒で着地(視覚 / テキスト両方)
  • 雑な・生っぽいトーン > 磨き上げ
  • "Story time" / "POV" フォーマットが完了率を伸ばす
  • トレンド音源で大幅にリーチ増(Shorts 再投稿時も検討)
Caption Rules
  • Visible 150 文字 / Max 2,200 文字
  • 出落ち先頭: "We had no actors. No crew. No money. We made a movie anyway."
  • Hashtags 3〜5 個まで(多すぎはスパム扱い)

Instagram
Caption 構造
行内容
1強いフック(フィードプレビューに出る)
2〜4短いストーリー / 鍵となる気付き
5CTA(Full episode link in bio)
(空行)
末尾Hashtags 5〜10、ボリューム混在
Tone
  • 個人的・脆さを見せる(filmmaker community に刺さる)
  • 舞台裏 (BTS) フレーミング
  • 一般向け投稿では業界ジャーゴンを減らす

X (Twitter)
スタイル: 本編引用 × 議論喚起

X ポストは 「動画の宣伝」ではなく、本編の発言を引用しながら 業界 / 制作 / 社会の議論テーマを提示する 形で書く。

視聴者が「これ本当にそう」「いや違う」と反応したくなる構成にする。

構造(必須)
{パワーワード見出し — 太字的な一行}
───
{本編の主張を噛み砕いて 3〜5 文で展開。断定調。
具体的な数字や事例を入れる。
最後に視聴者が考えたくなる問いかけ or 皮肉で締める。}
ルール
  • 280 文字制限は無視してよい(Premium 長文前提)。ただし 400 文字以内 目安
  • Hashtag は使わない(アルゴリズム上、Hashtag は X ではリーチを下げる)
  • 1 行目がパワーワード。ここでスクロールを止める
  • ─── (罫線) で区切る。視覚的にタイトルと本文を分離
  • 本編の発言をそのまま引用せず、一般化・抽象化して議論テーマに昇華 させる
  • 動画の宣伝感を出さない。業界全体への問題提起として書く
  • ショート動画のリンクは別途添付前提。本文中 URL は入れない
トーン
  • 断定調。文体は関西弁(→ [文体](#文体全プラットフォーム共通))。断定形は「〜や」「〜やねん」「〜でしかないわ」
  • 業界の常識に切り込む(「誰も言わないけど」「本音を言うと」)
  • 数字で裏付ける
  • 皮肉・逆説
  • 賛否が分かれる主張ほどリーチが伸びる

→ 詳細は [05_x_post_style.md](05_x_post_style.md) 参照。


Podcast (Spotify / Apple)
Title
  • エピソード番号 + 主題(Ep. 1: <Topic>)
  • 検索フレーズを含める
  • 50〜80 文字
Description
  • 本編の概要 3〜5 文
  • Show notes(リンク・人物・参考文献)
  • タイムスタンプ(YouTube より細かく可)

差別化(A 案 / B 案 / C 案)
案方向特徴
A設定値に忠実標準案
Bターゲットを一般視聴者寄り専門用語を減らす
Cコンテンツ軸をエモーショナル感情・物語重視

ハッシュタグ運用まとめ
プラットフォーム数配置
YouTube (本編)3〜5description 末尾
YouTube Shorts4〜6caption 内
TikTok3〜5caption 内
Instagram5〜10末尾、空行で分離
X0使わない
Podcastプラットフォームによるdescription
L2-02 ショート構成の共通ルール

作る順序は [`15_shorts_composition_workflow.md`](15_shorts_composition_workflow.md) が正本

(タイトル起点の 4 段。浅尾確定 2026-08-10)。本書はその中で使う制作条件を並べたもので、

順序について本書と 15 が食い違う場合は 15 を採る。

詳細な評価・縮約字幕・話者・リアクション・Resolve連携は [13_short_editorial_scoring.md](13_short_editorial_scoring.md) を正本とする。

強調テロップは [16_telop_emphasis.md](16_telop_emphasis.md)。

制作条件
  • 全Project・全Partに適用する。
  • Master SRTの時間を正本にする。構成案はMasterの区間から作り、本文だけを縮約する。
  • 尺は15〜60秒。挨拶、出演者名の読み上げ、回数確認、意味を進めない相槌、接続語だけの導入は落とす。
  • 本編1分につき最低1候補。候補はScore 70以上を優先し、弱い候補で本数を水増ししない。

本数はタイトル観点からの逆算で決める(1分ごとに1本という意味ではない)。

同じクリップを別のタイトルで複数回使ってよい。

本数の床は未実装(requiredShortCount はあるが本番から呼ばれていない)。

Score の床のほうは 2026-08-11 に本番の門になった(→ L2-15 §8)。

  • この「70」は LLM rubric 8 軸の等重み平均(RUBRIC_SCORE_THRESHOLD /

shortsRubric.ts の editorialPolicy.rubricScoreThreshold)。

落とすのはこの尺度だけ(RUBRIC_SCORE_BELOW_MINIMUM)。

  • shortsScoring.ts にも 70 があるが(PRODUCTION_SCORE_THRESHOLD、hook 0.24 の重み付き)、

そちらは門ではなく Studio の並び順と `adopt` / `review` の表示に効く。

  • 同じ数字が別の尺度に当たっていた問題は決着済み(浅尾確定 2026-08-12: 実装が正)。

2 つの尺度の比較と、hook の重視が門から消える件は → L2-15 §8-1。

  • 内輪ネタは除外ではなく補完する(浅尾確定 2026-08-10)。分からない前提はテロップか字幕で1行補い、

補完しても伝わらないものだけ除外する。

lib/katto/shortsRubric.ts はこの転換を反映済み(supplements 4規則 + detectSupplements()、

exclusions から内輪ネタは削除済み)。

2026-08-11 に生成側へ繋がった。 lib/analysis/candidates/editorial-gate.ts が

detectSupplements() を本番の門の中から呼ぶ。当たっても落とさず、印を付けて残す

(SUPPLEMENT_REQUIRED / SUPPLEMENT_PROPER_NOUN_LIMIT)。初出の固有名詞は 1 本 2 語まで。

同じ関数を検証側の lib/shorts/ruleCheck.ts(旧 lib/shorts/audit.ts)も使うが、

そちらは生成側と根拠を共有しているので測れるのは遵守率であり、まだ本番から呼ばれていない。

  • 連続する同一話者はまとめる。話者切替、聞き手の反応、頷き、笑い、全体カットは別の編集単位として残す。
構成データ

各候補は次を持つ。

Master SRT
  → 導入・フィラー監査
  → source start/end と cutFrames
  → 縮約字幕の表示尺
  → speaker / reaction / angle
  → BG / Angle / Slide / Overlay
  → Dialogue / BGM / SE
  → Resolve / Premiere / CapCut handoff

字幕がない区間は空白、笑い・反応は (笑) などの状態として保持する。元のMaster、構成案、FIXは上書きしない。採用・要確認・除外と除外理由を候補に紐付ける。

評価軸

フック、明快さ、テンポ、面白さ、反応カット、話者切替、完結性、尺適合、導入ペナルティを機械的に記録する。Scoreはバズを保証する値ではなく、候補を同じ基準で並べるための値である。公開後の視聴、完走、共有、返信を日次で結合し、重みの変更は実績を確認してから行う。

ここに書いた重み(フック 24 等)は `shortsScoring.ts` の重み付きスコアのもので、本番の門の重みではない。 門は同じ 8 軸を等重みで平均する。重みを変えるなら ShortYieldPolicy.rubricWeights 側を変える(→ L2-15 §8-1)。

禁止事項
  • 本編にない発言、時刻、話者、NLE構造を補わない。
  • 未承認の候補を完成動画や投稿文の正本にしない。
L2-03 サムネタイトル方針
YouTube サムネイル上に大きく表示されるテキストの設計。最大限に扇情的 が原則。

ルール
項目基準
記号! !? を積極的に使う(感嘆・驚き)
数字推奨。必須ではない(浅尾確定 2026-08-19)。入れるなら「2000万」「3万円」「887カット」等の具体性を持たせる
文字数15 文字以内が理想 / 最大 20 文字(サムネ上で読める範囲)
手法煽り・挑発・疑問形で感情を揺さぶる
案分けマスター / B 案 / C 案で切り口を変える

案分けの例
案スタイル例
マスター数字 × 衝撃個人口座3万円!? 予算2000万のヤバすぎる正体
B 案連続否定補助金ゼロ! スポンサーゼロ! 全額自腹の映画制作
C 案感情 × 覚悟全財産3万円! それでも映画を撮った男の覚悟

構成パターン

どの例も落差の核を持っている(§煽りは「落差」で作る)。

核が無い形——3万円から始まった映画 のように数字を置いただけのもの——は、

このパターン表には載せない。規則で落ちるものを手本にしない。

パターン例核
数字 + !?887カット撮影!? 編集地獄の真実異常
連続否定スポンサー無し!実績無し!それでも公開!矛盾
疑問形映画好きが誰もいない3人で長編?極端
反転誰も教えない 映画制作の現実タブー
数字 × 結果3万円から始まった映画が2000万に化けた話落差(数の差 666 倍)

煽りは「落差」で作る(浅尾確定 2026-08-20)

数字と `!` を並べただけでは扇情にならない。 1500万で撮り切る唯一の条件とは? は

要素の数だけ見れば足りているが、平板で押しが無い。

題には「落差の核」を必ず 1 つ入れる。

核何を見せるか例
落差・失敗失った・届かない補助金ゼロ! スポンサーゼロ!
矛盾・反転前提と結果が食い違う`映画好きが一人もいないのに長編を作った!?`
タブー・裏言わないはずのこと誰も教えない映画制作の現実
極端常識の外全額自腹 一人で
異常状況そのものが変編集地獄

数字・!・? は核を強める補助であって、それだけでは核にならない。

いちばん強いのは矛盾。「Xが無いのにYした」「Xのはずが Y だった」の形。

浅尾の例: `映画好きなやつひとりもおらんで、長編映画つくった!?`

固有名詞は「有名なら使う」(浅尾確定 2026-08-20)

知らない名前は引きにならない。知っている人にしか効かず、

知らない人には意味の無い文字列になる。

  • 一般に通じるものは使ってよい。むしろ引きが強くなる
  • 通じない人名・作品名・社名は題から外す。中身では使ってよい
  • 出すなら説明と一緒に(ロドリゲス ではなく 100万で撮った監督)
  • 内輪ネタは題に出さない。本編では補完する(正本 L2-15)

有名かどうかは人が決める。機械は題に出ている固有名詞を並べるだけで、落とさない。

案件ごとに「使ってよい」と認めた語は

[../L3_projects/directors_katto.md](../L3_projects/directors_katto.md) が持つ。

認めた固有名詞を含む題は 22 字まで許す(浅尾確定 2026-08-20)。

名前は字数を食うが認知を買う。スパイダーマン だけで 7 字あり、

20 字では落差を書く余地が残らない。認めた語が入っていないなら 20 字のまま。

書き方の型(実例つき)

「Xなのに Y」を骨にして、有名な名前と数字を差す。

型例効いている要素
有名名詞 × 異常`スパイダーマン2がグロくて無理!? 夢遊病に`有名・異常・感嘆・疑問
前提の否定映画好きが1人もいないのに長編を作った矛盾・数字
数の落差1千万が100億! それでも二度と無理落差・矛盾・数字・感嘆
常識の外プロは1人だけ! 残り全員が素人で撮った極端・数字・感嘆
言わないことハードルは撮影じゃない!? 誰も言わないタブー・極端

避ける形(実測 2026-08-20 でこれを 17 本作ってしまった):

1500万で撮り切る唯一の条件とは? 数字と ? だけ。落差が無く平板

ビッグプロジェクトがやりたい!? 業界語で引きが無い

ロドリゲスが学生時代に撮ったやつ!? 知らない名前

機械が落とすもの / 助言に留めるもの

落とす(数えられる事実): 20 字超 / 抽象的な煽りだけ(すごい やばい のみ)/

落差の核が 1 つも無い。

助言(落とさない): 15 字超 / 数字が無い / ! !? ? が無い / 型が字面から読めない /

固有名詞が入っている(有名かどうかは人が判断する)。

型(扇情 / Tips / 目を引く)の判定は人と LLMが持つ。字面の目印はその権威を持てない

(lib/shorts/titleFirst.ts の judgeTitle の注記)。字面を hard gate にすると

表に当たる書き方へ寄せるだけの最適化になる。

実装は checkThumbnailTitle、鎖の段は title-slate。

NG パターン
  • 文字数オーバー(読めない)
  • 抽象的(「すごい」「やばい」だけ)
  • 煽りなし(淡々とした事実列挙)。ただし Tips 型は煽った形にできるなら可

(浅尾確定 2026-08-10。型の定義は [15_shorts_composition_workflow.md](15_shorts_composition_workflow.md) §1)

  • ネタバレ過多(クリック後の retention 低下 = YouTube penalty)

ABテスト

YouTube Studio の thumbnail テスト機能で 3 案を回し、CTR × retention で評価。

高 CTR + 低 retention は penalty 対象なので注意。


関連
  • [01_platform_rules.md](01_platform_rules.md) §YouTube Title — タイトル本体(描画されるテキスト)の規則
  • [02_shorts_planning_framework.md](02_shorts_planning_framework.md) — ショート企画
L2-04 YouTube チャプター生成
ロング本編の YouTube ディスクリプションに含めるチャプター(タイムスタンプ目次)を SRT から生成する手順。

入力ソース
種別パス例
フル尺 SRTプロジェクトの Subtitles/<Part>_claude.srt
書き起こし TXTプロジェクトの Contexts/<Part>_書き起こし_*.txt

→ プロジェクト固有のパスは各プロジェクトの LOCAL_PATHS.md 参照。


生成手順
1. SRT 全文を読む

オフセットを分割して最後まで読み切る。

2. トピック遷移を特定

以下のシグナルを探す:

  • 話題の明確な切り替わり(「次は〜」「ここから〜」)
  • 司会者・サブの質問による話題転換
  • 長い沈黙(SRT タイムスタンプギャップ)後の新トピック
3. SRT タイムコード → YouTube 形式
SRT 形式: 00:17:49,296
   ↓
YouTube 形式: 17:49
  • 秒単位 の精度(ミリ秒は切り捨て)
  • 1 時間超は HH:MM:SS
4. チャプタータイトル
  • 25 文字以内 の日本語(英語は ~40 字)
  • 内容を一目で伝える
  • 釣りすぎず、正直に

出力フォーマット
00:00 オープニング
00:48 今回のテーマ:予算策定
02:27 英語脚本とローカライズの壁
05:33 キャスティングの裏側
...
33:11 まとめと次回予告

YouTube チャプターの制約
制約値
最初のチャプター必ず 00:00 から
最低数3 チャプター以上
各チャプター間隔10 秒以上
推奨数8〜15 個(多すぎると視聴者が使わない)

ベストプラクティス
  • 序盤は粒度を細かめに(視聴者が「中身」を判断する材料)
  • 中盤は内容のまとまりで割る
  • 締めは「まとめ」「次回予告」等で必ず作る
  • 検索キーワードをタイトルに含めると見つかりやすい

NG パターン
  • 00:00 Intro のみで終わる
  • すべて 30 秒刻みで機械的
  • チャプタータイトルが長すぎてスマホで切れる
  • ネタバレ過多
L2-05 X (Twitter) ポストスタイル
ショート動画とセットで投稿する X ポストの専用スタイル。動画の宣伝ではなく、議論喚起のスタンドアロン投稿 として書く。

スタイル: 本編引用 × 議論喚起

X ポストは「ショート動画の宣伝」ではなく、本編の発言を引用しながら業界 / 制作 / 社会の議論テーマを提示する スタイルで書く。

視聴者が「これ本当にそう」「いや違う」と反応したくなる構成にする。


構造(必須パターン)
{パワーワード見出し — 太字的な一行}
───
{本編の主張を噛み砕いて 3〜5 文で展開。断定調。
具体的な数字や事例を入れる。
最後に視聴者が考えたくなる問いかけ or 皮肉で締める。}

参考例
例 1: 業界批判系
アイドル出演させりゃ売れる
───
「最近の邦画はアイドル映画ばっかりでつまらん」て文句言う人が多いけど、無名監督のオリジナル脚本なんて誰も1円も出さへん。でも「〇〇が出ます」と言うた瞬間、中身スカスカでも数千万集まんねん。パトロンが欲しいのは作品の質ちゃう。ファン数の確約や。
例 2: 戦略系
海外直談判
───
日本の映画配給に持ち込んでも「実績がない」で門前払いや。日本の狭いムラ社会で顔色うかがって映画作るくらいなら、最初から世界狙た方がええ。国際映画祭の裏のマーケットで、海外配給会社に直接売り込んだ。結果は全滅やったけど、閉鎖的な日本で消耗するより全然マシやと思えた。

ルール
項目ルール
文字数400 文字以内 目安(Premium で 280 制限は無視可。長すぎると読まれない)
Hashtag使わない(X のアルゴリズム上、リーチを下げる)
1 行目パワーワード。ここでスクロールを止める
区切り───(罫線)で見出しと本文を分離
引用そのまま引用ではなく、一般化・抽象化して議論テーマに昇華
宣伝感出さない。業界全体への問題提起として書く
URL本文中には入れない(動画リンクは別途添付前提)

トーンガイド
推奨
  • 断定調。文体は関西弁(→ [L2-01 文体](./01_platform_rules.md#文体全プラットフォーム共通))。

断定形は「〜や」「〜やねん」「〜でしかないわ」「〜しとる」を使う。

方言にしても断定の強さは落とさない(「〜かもしれん」「〜と思うわ」等に逃げない)

  • 業界の常識に切り込む(「誰も言わないけど」「本音を言うと」)
  • 数字で裏付ける(「2000万」「80人」「30秒」)
  • 皮肉・逆説を効かせる(「無名監督のオリジナル脚本なんて誰も1円も出さへん」)
  • 共感と反発の両方 を狙う(賛否が分かれる主張ほどリーチが伸びる)
NG
  • ふんわりした感想(「面白かったです」「考えさせられました」)
  • 動画の宣伝口調(「ぜひ見てください」「リンクはこちら」)
  • ハッシュタグ(#映画 #インディーズ 等)
  • URL 直貼り(本文中に)

罫線文字
───

U+2500 (BOX DRAWINGS LIGHT HORIZONTAL) を 3 個。

代替: ___ (アンダースコア 3)、--- (ハイフン 3) でも可。X 上で見やすい区切りを選ぶ。


チェックリスト
  • [ ] 1 行目だけでスクロールが止まる
  • [ ] 罫線で見出しと本文が分離されている
  • [ ] 数字 or 具体例が入っている
  • [ ] 断定調(関西弁の断定形。強さを落としていない)
  • [ ] 業界 / 社会への抽象化ができている
  • [ ] ハッシュタグ・URL なし
  • [ ] 400 文字以内
  • [ ] 動画の宣伝ではなく、独立した主張として読める
L2-06 ショート × SNS 投稿テーブル
1 ショート企画に対して X / Instagram / YouTube Shorts / TikTok の各投稿(タイトル + ディスクリプション)を 1 つのテーブルで スマートに表示するためのフォーマット仕様。

1. メインテーブル(必須出力)

ショート企画を出した後、必ずこのテーブルを出力する。

| P | ショート企画 | プラットフォーム | タイトル / 1行目 | ディスクリプション / 本文 | 文字数 | ハッシュタグ |
|---|---|---|---|---|---|---|
| P1 | {ショートタイトル} | YouTube Shorts | {タイトル本文} | {キャプション本文} | {len} | {tags} |
| P1 | 〃 | TikTok | {タイトル本文} | {キャプション本文} | {len} | {tags} |
| P1 | 〃 | Instagram | — | {キャプション本文} | {len} | {tags} |
| P1 | 〃 | X | {パワーワード見出し} | {罫線+本文} | {len} | — |
| P2 | {ショートタイトル} | YouTube Shorts | ... | ... | ... | ... |
| ... |
列の意味
列内容
Pショート企画番号(P1〜P12)
ショート企画企画タイトル(15 文字以内)
プラットフォーム4 種類: YouTube Shorts / TikTok / Instagram / X
タイトル / 1行目YT/TT はタイトル、X はパワーワード見出し、IG は通常空欄
ディスクリプション / 本文キャプション本文(IG は全部ここ、X は罫線+本文)
文字数ディスクリプションの文字数(プラットフォーム制約確認用)
ハッシュタグプラットフォーム指定数 (X は —)

2. プラットフォーム別の制約(埋める前に確認)
プラットフォームタイトルキャプション可視キャプション最大ハッシュタグ
YouTube Shorts100 文字(表示 50)1〜2 行5,0004〜6 個
TikTok—150 文字2,2003〜5 個
Instagram—1 行(フィード)2,2005〜10 個(末尾)
X—重み 280(日本語は1文字=2)140 文字(実測。Premium のみ長文可)0 個

→ 詳細ルールは [01_platform_rules.md](01_platform_rules.md) と [05_x_post_style.md](05_x_post_style.md) 参照。


3. テーブル出力例(Katto Part1 の P1 を例に)
| P | ショート企画 | プラットフォーム | タイトル / 1行目 | ディスクリプション / 本文 | 文字数 | ハッシュタグ |
|---|---|---|---|---|---|---|
| P1 | 邦画はアイドル映画 | YouTube Shorts | アイドル出ない映画は売れない…無名監督の現実 | 「最近の邦画つまらない」とは言うけど、無名監督のオリジナル脚本に1円も出資されないのが現実。/ Full episode → | 87 | #映画 #邦画 #インディーズ映画 #映画制作 |
| P1 | 〃 | TikTok | アイドル出ない映画は売れない問題 | 邦画が「アイドルばっか」って言われる本当の理由。出資の構造を業界の中の人が話します。 | 64 | #映画 #邦画 #インディーズ |
| P1 | 〃 | Instagram | — | 邦画がアイドル映画ばかりなのは、無名監督に1円も出資されないから。/ 出資する側は作品の質じゃなく「ファン数の確約」を買っている。/ Full episode link in bio | 92 | #映画 #邦画 #インディーズ映画 #filmmaking #映画好きと繋がりたい |
| P1 | 〃 | X | アイドル出演させりゃ売れる | ───<br/>「最近の邦画はアイドル映画ばかりでつまらない」って文句言う人多いけど、無名監督のオリジナル脚本なんて誰も1円も出資しない。でも「〇〇が出ます」と言った瞬間、中身スカスカでも数千万集まる。パトロンが欲しいのは作品の質じゃなくて、ファン数の確約。 | 187 | — |

4. スプレッドシート / Notion へのエクスポート

このテーブルは:

  • スプレッドシート に貼り付け → タブ区切り (TSV) に変換
  • Notion にコピー → そのまま Notion テーブルになる
TSV 変換(スプレッドシート貼付用)
# Markdown table → TSV
lines = md_table.strip().split("\n")
header = [c.strip() for c in lines[0].strip("|").split("|")]
rows = [[c.strip() for c in r.strip("|").split("|")] for r in lines[2:]]
tsv = "\n".join(["\t".join(header)] + ["\t".join(r) for r in rows])
スプレッドシート列構成(推奨)
Col内容
AP 番号
Bショート企画
Cプラットフォーム
Dタイトル / 1行目
Eディスクリプション本文
Flen (D の文字数)
Glen (E の文字数)
Hハッシュタグ
Iステータス(未生成 / 完成 / 投稿済)
J投稿日時
KURL

→ 文字数列を 2 つに分ける(タイトル / 本文)と、各プラットフォーム制約のチェックが楽。

Notion DB へのエクスポート

Notion テーブルは Markdown コピーで直接貼れる。

プロパティとして:

  • Title: P 番号 + ショート企画名(例: P1 邦画はアイドル映画)
  • Platform: Select (YouTube Shorts / TikTok / Instagram / X)
  • Status: Select (Draft / Final / Published)
  • Caption: Text
  • Hashtags: Multi-select
  • Posted at: Date
  • URL: URL

→ 各プロジェクトの Notion DB URL は PROJECT_RESOURCES.md に記載。


5. 文字数ヘルパー(埋める前にチェック)
制約チェック式
YT Shorts タイトルlen(title) <= 100
TikTok 可視キャプションlen(caption) <= 150
IG 可視 1 行最初の改行までが <= 80 文字推奨
X 本文len(body) <= 140(実測。192字が 403 で落ち、130字は通り145字は落ちる)
JavaScript で文字数列を自動計算(スプレッドシート)
// セル D2 に "タイトル"、F2 にその文字数を入れたい場合
=LEN(D2)

6. 出力チェックリスト(生成後)
  • [ ] P1〜P{N} 全部に 4 プラットフォーム × 1 行 = 合計 4×N 行ある
  • [ ] X だけハッシュタグ無し(—)
  • [ ] 各プラットフォームの文字数制約を超えていない
  • [ ] X は罫線(───)で見出しと本文を分離している
  • [ ] Instagram のタイトル列は —、本文に集約している
  • [ ] TSV 化したときに改行・タブが本文に混入していない(セル分割が壊れる)

7. セル内改行の扱い

Instagram キャプションや X 本文は 複数行 で書きたいことが多い。

スプレッドシート(Google Sheets)

セル内改行を含むテキストは ダブルクォートで囲む:

def q(s): return '"' + s.replace('"', '""') + '"'
tsv_cell = q("1行目\n2行目\n3行目")

ダブルクォート内のダブルクォートは "" でエスケープ。

Markdown テーブル

セル内改行は <br/> を使う:

| ... | ... | ───<br/>本文1行目<br/>本文2行目 | ... |
Notion テーブル

Notion はセル内 \n を自動で改行扱い。


関連
  • [01_platform_rules.md](01_platform_rules.md) — 各プラットフォームの詳細ルール
  • [02_shorts_planning_framework.md](02_shorts_planning_framework.md) — ショート企画フレームワーク
  • [05_x_post_style.md](05_x_post_style.md) — X ポスト書式
  • プロジェクト側 PROJECT_RESOURCES.md — スプレッドシート / Notion DB URL
L2-07 YouTube アルゴリズムと成長設計(2026)
2026 年時点の YouTube 推薦システムの構造と、そこから逆算した打ち手の優先順位。 推測でなく実測から始めるのが本ドキュメントの原則。チャンネル固有の実測値は §4 に置く。

1. 2026 年の構造変化(4 点)
変化内容実務への含意
満足度 > 生の視聴時間1〜5 星のアンケート結果が直接ランキングに入る。「4 分で満足」が「8 分で我慢」に勝つ尺を伸ばす最適化は無効。密度を上げる
セッション寄与が長尺の主signal自分の動画の後に 2 本以上見たか、それともアプリを閉じたかを評価「次に見るもの」を用意した動画が伸びる。回遊設計が本体
Shorts と長尺のアルゴリズムが分離悪いショートが長尺の露出を削らない。逆にバズったショートが自動で長尺へ客を送ることもない送客は自動では起きない。明示的に導線を作る必要がある
Shorts→長尺のブリッジが計測対象ショートを見た人が長尺をクリックした経路にアトリビューションが付く。この橋を設計したチャンネルが最速で伸びる導線設置そのものが独立したランキング資産になる

補足:Browse(ホーム)の personalization は「広いトピック分類」から「視聴履歴クラスタ」へ移行。2026 年 5 月から AI 生成コンテンツの開示が強制力を持ち、未開示の写実的 AI 映像は推薦が抑制される。


2. サーフェス別の評価軸

同じ動画でも、どの面で戦うかで効く指標が変わる。

サーフェス主要signal効かないもの
Shorts フィード冒頭数秒のスワイプ率 vs 視聴率、リプレイ、シェアメタデータ(タイトル/タグ)の寄与は小さい
関連動画(Suggested)セッション寄与、視聴維持、同一視聴クラスタとの一致—
ホーム(Browse)視聴履歴クラスタとの一致、CTR、満足度—
検索クエリ一致、視聴維持規模が小さいチャンネルでは費用対効果が低い場合がある

結論:タグは「検索」でしか効かず、その検索が全体の何%かを実測してから投資判断する。


3. 打ち手の優先順位(原則)
  • 導線(Shorts→長尺):分離されたアルゴリズムを人力で橋渡しする。最も costly かつ最も効く
  • 回遊の器:プレイリスト・終了画面・カード。セッション寄与を作る装置
  • 満足度:冒頭で約束を回収する編集。維持率より満足度
  • メタデータ(タイトル・概要欄・タグ):上記が終わってから

この順序を守る。逆順(タグから)は、実測上ほぼ効果が出ない。


4. ディレクターズ葛藤 実測(2026-01-01〜07-22 / 全 54,086 再生)
流入源
流入源再生視聴時間(分)平均視聴率
Shorts フィード44,150 (82%)7,47566.1%
関連動画5,58394,470 (85%)49.1%
登録者1,6062,6104.8%
チャンネルページ9462,31818.6%
検索699 (1.3%)394 (0.4%)14.5%
プレイリスト22239—
終了画面363—
ショート→本編リンク3822—
検索クエリの中身

「古着」6/「大谷翔平」5/「高田延彦」5/「ikea」3/「エアコン2027年問題」3/「日向坂で会いましょう」3/「鹿児島 観光」3。

指名検索「ディレクターズ葛藤」でも 7。映画関連は「自主制作映画」3、「ゾンビ映画」2 程度。

→ 検索流入はほぼノイズ。ショートが無関係クエリに偶発的に当たっているだけ。

読み取れる事実
  • Shorts が流入の 82%、関連動画が視聴時間の 85%。この 2 つが事業の本体
  • ショート 48,976 再生に対し、本編への送客は 38 再生。橋が架かっていない
  • プレイリスト 22・終了画面 3=回遊の器が未設置。関連動画が視聴時間の 85% を生んでいるのに、それを増幅する装置がない
  • 登録者の平均視聴率 4.8% = ショートで登録した層が本編に定着していない
  • 全 51 本でタグが空。ただし検索が 1.3% のため、タグ投入の期待値は小さい

5. KPI(この順で見る)
優先指標現在値見るべき理由
1ショート→本編リンク経由の再生38橋の太さ。ここが伸びれば全体が伸びる
2プレイリスト経由の再生22回遊の器が機能しているか
3関連動画の視聴時間94,470収益と満足度の本体
4登録者の平均視聴率4.8%ショート層が本編に定着したか
5検索流入比率1.3%ここが 10% を超えたら SEO に投資する

検索流入が全体の 10% を超えるまで、タグ・検索SEO は最優先にしない。


6. 出典
  • [YouTube 公式ブログ: How to convert YouTube Shorts views into long-form channel growth](https://blog.youtube/creator-and-artist-stories/youtube-related-videos-traffic-guide/)
  • [OutlierKit: YouTube Algorithm Updates 2026](https://outlierkit.com/resources/youtube-algorithm-updates/)
  • [vidIQ: YouTube Algorithm 2026](https://vidiq.com/blog/post/understanding-youtube-algorithm/)
  • [Sprout Social: The YouTube algorithm](https://sproutsocial.com/insights/youtube-algorithm/)
  • 実測値は YouTube Analytics API(yt_analytics)で取得。再取得は tools/youtube-mcp/dump-channel.mjs
L2-08 ショート→本編の導線設計(最優先オペ)
2026 年の YouTube は Shorts と長尺のアルゴリズムが分離している。 バズったショートが自動で本編へ客を送ることはない。橋は人力で架ける。 YouTube 公式が「あなたの第一目標は、60 秒の動画から深いカタログへの移行を完全に摩擦ゼロにすること」と明言している領域。

1. 3 つの導線と、API でできるかどうか
導線効果API誰がやるか
関連動画リンク(Shorts の専用リンク枠)最大。公式が推す本命× 非対応人間が Studio で設定
概要欄のリンク中○ 対応Claude Code が一括投入
固定コメント中△ 投稿は可・ピン留めは不可人間が Studio でピン留め
終了画面中(長尺のみ)× 非対応人間が Studio で設定
プレイリスト中○ 対応Claude Code が一括作成

API で完結しないものは、必ずチェックリスト化して人間に渡す(studio の /youtube 面)。


2. 関連動画リンクの設定手順(Studio・手動)

YouTube Studio で 1 本ずつ設定する。ショート 1 本あたり 15 秒程度。

  • Studio 左メニュー → コンテンツ
  • 対象のショートを選択(鉛筆アイコンで詳細へ)
  • 右側メニューの 「関連動画」 をクリック
  • チャンネル内から送客先の本編を選択
  • 保存
送客先は「長尺」「ライブ配信」「別のショート」から選べる。本シリーズでは該当回の本編を選ぶ。

3. 送客先の選び方(意図一致)

公式が挙げる成功要因の第一が Intent Matching。

ショートの中身送客先
手早い小技・Tips を教えているその Tips を網羅的に扱った本編(該当回)
事件・エピソードの断片その事件が丸ごと入っている回
シリーズの世界観・自己紹介的な内容第 1 回(入口)

やってはいけない:全ショートを一律で最新回や第 1 回に送る。意図が一致しないと離脱し、ブリッジの評価がむしろ下がる。


4. ショート側の CTA(ラスト 5 秒)

公式ガイダンスの要点。

  • 言語 + 視覚の両方で誘導する。「明示的に、何をすべきか言う」
  • 下を指差す、または送客先サムネイルを一瞬オーバーレイする
  • 尺の最後 5 秒に置く。冒頭に置くとフック力が死ぬ

本シリーズのトンマナに合わせた CTA 例(関西弁・断定調):

  • 「この話、本編で全部しゃべってるから見てや」
  • 「続きは本編。リンク下や」

5. 本編側の受け(冒頭 5〜10 秒)

橋を架けても、着地が悪ければ離脱する。公式は「長尺の最初の 5〜10 秒で、ショートが約束した話題・疑問・フックに直接答えろ」と指示している。

  • やってはいけない:チャンネルロゴのアニメーション、無関係な挨拶から始める
  • やるべき:ショートで引っ張った問いに冒頭で触れる。「さっきの話の続きやけど」で入る

既存 9 本の本編はこの観点で冒頭を作っていない。再編集が難しい場合は、チャプター先頭に該当箇所を置き、概要欄とコメントで時間指定リンクを出すことで代替する。


6. 固定コメント(Studio・手動)

API ではピン留めできない。人間が実行する。

  • 対象ショートを開く
  • コメント欄に送客文を投稿
  • コメント右の「︙」→ 固定

文例(関西弁・断定調、本編URLを添える):

この話の全部は本編でしゃべってる → https://youtu.be/XXXXXXXXXXX

7. 概要欄テンプレ(API で一括投入)

ショートの概要欄末尾に、以下のブロックを追加する。既存本文は保持する。

━━━━━━━━━━━━━━
▶ この話の本編(フル尺)
{EPISODE_TITLE}
{EPISODE_URL}

▶ シリーズ再生リスト
{PLAYLIST_URL}
━━━━━━━━━━━━━━

本編側の概要欄には、前後の回・再生リスト・関連ショートを入れてセッションを繋ぐ(L2-07 §3 の「回遊の器」)。


8. 実行順(ROI 順)
  • 再生数上位のショートから関連動画リンクを設定する。42 本を一度にやらず、上位 10 本で効果を測る
  • 概要欄は API で全 42 本に一括投入(コストがゼロに近いため全部やる)
  • 固定コメントは上位 10 本のみ(手作業コストが高い)
  • 本編 9 本に終了画面 + 概要欄内部リンク

測定:yt_analytics の insightTrafficSourceType == SHORTS_CONTENT_LINKS を週次で見る。現在 38。ここが動かなければ送客先の意図一致を疑う。

L2-09 TikTok / Instagram / X / note の成長土台(2026)
各プラットフォームの 2026 年時点のランキング signal と、そこから逆算した実務ルール。 文体ルール(YouTube 本編概要欄以外は関西弁)は L2-01 が正本。本ドキュメントは配信設計を扱う。

1. TikTok
評価軸(重い順)
signal内容
インプレッションあたり視聴時間総再生数より重い。尺 × 完走率で評価される
コメントの深さ単発でなく会話が続くか
シェア(特に DM)送信率
リプレイループして再視聴されたか
文字起こしとの一致度音声内容とトピックの整合
実務ルール
  • 冒頭数秒が最強の signal。フックを 1 秒目に置く
  • 尺は15〜60秒の範囲で、内容が完結する最短尺にする。長さを足すための引き延ばしはしない
  • ニッチ適合 > 広いバズ。2026 の TikTok はコミュニティ整合を優先する。映像制作・自主映画クラスタに刺さる語彙を使う
  • 指標は視聴時間・完走率・シェア・保存の 4 つで見る。いいね数は見ない
このプロジェクトでの適用

YouTube Shortsと同じ15〜60秒で組み、冒頭は断定調パンチライン(L2-02 §3)を置く。媒体差は尺ではなく本文・公開先・指標の見方で吸収する。


2. Instagram(Reels)
評価軸(Mosseri 公表)
signal内容
視聴時間 / 完走率最重要。最初の 3 秒を超えて見続けたかを強く見る
sends per reach(DM 送信率)2 番目。フォロワー外リーチ(発見タブ)の主動力
likes per reach既存フォロワー向けの指標

Instagram は 2025 年に単一の「アルゴリズム」表現をやめ、Feed / Stories / Reels / Explore で別々のランキングシステムを持つ。

実務ルール
  • 最初の 3 秒で完結する強い画を置く。テロップだけの導入は捨てられる
  • 「友達に送りたくなるか」で企画を選ぶ。sends がフォロワー外リーチを決めるため、共感・驚き・あるあるの純度を上げる
  • 既存フォロワー向け(likes)と新規向け(sends)で企画を分けて考える
このプロジェクトでの適用

「TEMU はゴミ。」「衣装を店で買うやつは、最初からアホ。」型の断定 + あるあるは sends が伸びる型。この系統を Instagram に優先配分する。


3. X
評価軸

2026-01-20 に X がフィードアルゴリズムを GitHub(xai-org/x-algorithm)で全面公開。重み付けは以下。

signal重み
リプライ非常に高い(会話は品質のシグナル)
リポスト高
ブックマーク高(衝動でなく持続価値)
プロフィールクリック高
いいね中
滞在時間中
リンククリック低(X は外部流出を抑制する)

リプライ 1 件 ≒ いいね 27 件。会話を生む投稿が構造的に強い。

実務ルール
  • 投稿本体にリンクを貼らない。X は外部リンクを優先度低に扱う。リンクはリプライ(スレッドの 2 投稿目)に置く
  • 断定 → 問いかけで終える。会話を起こす設計にする(ただし断定の強さは落とさない。L2-05 が正本)
  • 初速が全て。投稿直後の反応密度が「おすすめ」配信を決める。投稿後 30 分は反応に返信する
  • ブックマークを狙うなら「保存して後で使える形」(手順・数字・リスト)にする
このプロジェクトでの適用

制作費・スケジュール・失敗談はブックマーク狙いの数字リストに組み替える。「2000 万円の内訳」「80 人のキャスティング手順」など。


4. note
構造的な強み

note.com はルートドメインの権威性が高く、公開初日から検索上位を狙える。2025 年の note × Google の資本業務提携以降、Gemini を用いた機能開発も進む。ZFILMS が自前ドメインで同じ順位を取るより速い。

実務ルール
  • H2 に主対策キーワードと共起語、H3 にサジェストキーワードを配置する
  • 300 文字ごとに画像か箇条書きを挟む。スマホのスクロール疲れが直帰を生む
  • 一次体験の独自性が最優先。2026 の Google は AI 生成の一般論を淘汰している。実額・実日数・実名・失敗を書く記事だけが残る
  • 能動運用(返報性):他者の記事にスキ・コメントを残すと自記事への流入が増える
このプロジェクトでの適用

「ディレクターズ葛藤」の note 記事は一次体験の塊(実際の製作費・実際の失敗)なので、note の評価軸と完全に噛み合う。

本編動画への導線と、記事同士の内部リンク(前後の回)を必ず入れる。記事フォーマットは studio の「note記事」モードが正本。


5. 共通原則
原則理由
冒頭 1〜3 秒に全部賭ける4 プラットフォーム全てで冒頭が最重要 signal
完走率 > 再生数TikTok・Instagram・Shorts すべてが視聴完了を見る
シェア/送信を設計するInstagram の sends、TikTok の DM 共有、X のリポストは全てリーチの主動力
外部リンクは本文に置かない(X)X のみリンククリックを低評価。他は問題ない
一次体験の固有名詞と数字note・Google 検索・AI 要約すべてで独自性が評価される

6. 出典
  • [Hootsuite: How the TikTok algorithm works in 2026](https://blog.hootsuite.com/tiktok-algorithm/)
  • [Sprout Social: How the TikTok Algorithm Works in 2026](https://sproutsocial.com/insights/tiktok-algorithm/)
  • [Later: Instagram algorithm in 2026](https://later.com/blog/how-instagram-algorithm-works/)
  • [SocialPilot: X (Twitter) Algorithm ranking factors](https://www.socialpilot.co/blog/twitter-algorithm)
  • [posteverywhere: How the Twitter/X Algorithm Works in 2026 (Source Code)](https://posteverywhere.ai/blog/how-the-x-twitter-algorithm-works)
  • [SEO Works: note SEO 対策の完全ガイド](https://seo-works.jp/note-seo%E5%AF%BE%E7%AD%96%E3%81%AE%E5%AE%8C%E5%85%A8%E3%82%AC%E3%82%A4%E3%83%89%EF%BC%9A%E5%88%9D%E5%BF%83%E8%80%85%E3%81%8B%E3%82%89%E4%B8%8A%E4%BD%8D%E8%A1%A8%E7%A4%BA%E3%82%92%E7%8B%99%E3%81%86/)
L2-10 Threads の成長設計(2026年7月時点)
Threads は他4プラットフォーム(L2-09)と評価軸が根本的に違う。YouTube の投稿文を転載すると必ず外す。 文体ルール(YouTube 本編概要欄以外は関西弁)は L2-01 が正本。本ドキュメントはThreads固有の配信設計と書き方を扱う。 調査日: 2026-07-27。Threads は変化が速いので、四半期ごとに §9 の数値を実測で更新する。

1. 現状診断(2026-07-27 実測)

@directorskatto は フォロワー35人 / 投稿あたりのいいね1。投稿はYouTube Shortsの概要欄をそのまま流している。

実物(2日前の投稿):

「何も決まってない現場」は、映画のプロから見たら終わりやねん🎬
普通の現場は、誰がどの車で何時に入るかまで全部決まってんねん。
俺らの現場は、決まってへんかった。
(中略・全7行)
裏側はYouTube本編で(プロフィールのリンクから✅)
#映画制作 #低予算映画 #グーチョキデッド #インディーズ映画 #ディレクターズ葛藤

Threadsの要件を5点すべて外している。

#外している点Threadsの実際
17行と長い1〜2文の短い投稿が強い
2告知で締めている伸びるのは質問・告白・小さな観察。告知の顔をした投稿は沈む
3返信の入口が無い返信が最強のシグナル。答えようがない文は配信されない
4ハッシュタグ5個Threadsはトピックタグ1投稿1個。# を並べる文化ではない
5投稿後の設計が無い最初の30分の返信速度で配信量が決まる

内容そのものは強い。書き方だけが間違っている。


2. 評価軸(重い順)
signal内容
返信の速度投稿後 15〜30分にどれだけ返信が付いたか。30分で20返信は、24時間で50返信に勝つ
返信の深さ単発でなく会話が続くか。投稿主が会話に戻って返しているか
返信の総量(アカウント単位)Mosseri いわく「返信の総量は投稿の総量と同じくらい価値がある」
リポスト・引用会話を外へ広げる行為として重い
いいね上記より軽い。単独では配信が伸びない
フォロワー数効きが弱い。小さいアカウントでも初速が出れば配信される

核心はここ。 Threads は「良い投稿」を配信するのではなく、「会話が始まった投稿」を配信する。だから設計対象は文章ではなく、返信が湧く構造になる。


3. 2026年に起きた変化
時期変化運用への影響
2026-02Dear Algo — 見たいトピックを公開投稿で申告ユーザー側が能動的にトピックを選ぶ時代に入った
2026-06Your Algo — 同じことを非公開で、1/3/7日の期限付きで指定(現在は米英加豪NZのみ)ニッチの一貫性が効く。テーマがブレるアカウントは選ばれない
2026-06月間5億ユーザー突破母数が増え、フォロワー外リーチの余地が広がった
2026年フォロー中の比率を引き上げ、露骨なエンゲージメント釣りを抑制「いいねしてね」「保存推奨」型は逆効果
2026年トピックタグ(# なしのフレーズ形式)を導入ハッシュタグの並べ書きは意味を持たない
2026-07コメントの並べ替え(Following / Top / Recent)、引用ボタンを composer に追加引用されやすい断定文の価値が上がった

最重要は Your Algo。 視聴者が「映画制作の話が見たい」と自分で申告できるようになった以上、テーマを絞り切ったアカウントほど配信先を得る。映画制作から外れた雑談を混ぜると、その申告に引っかからなくなる。


4. 投稿の型
4-1. 基本構造
[1行目:断定 or 告白]     ← ここだけで意味が通る
[2行目:具体の一撃]       ← 数字・固有名詞・金額
[3行目:返信の入口]       ← 質問、または判断を委ねる一文

3行以内。 4行を超えたら削る。Threadsは折りたたまれた瞬間に負ける。

4-2. 1行目の作り方

効くのは4種類。

型例
告白「撮影で使った血で、体にカビ生えた。」
断定「映画の撮影順は、血が飛ぶ方向で決まる。」
小さな観察「現場に住み込んだら、自分の家の空気が重かった。」
数字の提示「布団12セット借りて、誰も泊まらんかった。」

避けるもの:「〜な話です」「〜について話しました」「〜を公開しました」。告知の顔になった時点で沈む。

4-3. 3行目(返信の入口)の作り方

ここが配信量を決める。答えるのに知識が要らない質問にする。

良い悪い
「そっちの現場は、何を最初に削る?」「どう思いますか?」(漠然としていて答えられない)
「これ、やりすぎやと思う?」「ぜひコメントください!」(エンゲージメント釣りとして抑制される)
「1時間かけて撮り直す? 一発で決める?」「詳しくは本編で!」(会話が終わる)

二択で聞くのが最も返信率が高い。 考える負荷が低く、立場を表明したくなる。

4-4. リンクの扱い

2026年、リンクのペナルティは解消済み。ただし文脈のない裸のリンクは伸びない。

  • 本文にリンクを置いてよい。ただしリンクだけの投稿にしない
  • 推奨は 投稿本文にはリンクを置かず、会話が立ち上がってから返信でリンクを出す。初速を返信で稼いだあとに送客する順序
  • プロフィールのリンクへ誘導する定型文(「プロフィールのリンクから✅」)は毎回付けない。告知臭が強く、返信を殺す
4-5. トピックタグ
  • 1投稿につき1個だけ。# は不要(フレーズで置ける)
  • タグは配信の倍率ではなく同点時の判定材料。付けても内容が弱ければ伸びない
  • Threadsは本文中の語すべてが検索対象。だから「低予算映画」「自主映画」といった語は、タグにせず本文に自然に埋める
  • このアカウントの標準タグ:映画制作 を1個。回によって 自主映画 撮影現場 に振る
4-6. メディア

テキスト単体より、画像・動画を付けたほうが強い。 完成尺のショートをそのまま添付するのではなく、その回の1カットを静止画で切り出すほうが会話が起きやすい(動画は視聴の負荷があり、返信までの距離が遠い)。


4.5 話法(2026-07 調査)

型は §4 で足りるが、1行目の作り方だけは別で詰める価値がある。Threads は

スクロールを止めるまでに約1.5秒しかない。ここで止められなければ、内容が何であれ届かない。

4.5-1. 効くフックの3型
型仕組み例
逆説(Counterintuitive Truth)常識と反対のことを先に置く。脳が矛盾を解消しようとして止まる「インディー映画でジブクレーン、贅沢に見えるやろ? 逆やねん」
失敗の警告(Mistake Warning)相手が今やっているかもしれない失敗を名指しする「メルカリで小道具を探すとき、"新品"で絞ったらアカン」
数字の提示具体的な数字は無条件で止まる「布団を12セット借りて、誰も泊まらんかった」

逆説が最も強い。 「贅沢に見えるやろ?→逆やねん」「馴れ合いちゃうかった」のように、

読み手が持っている前提を先に言語化してから裏切ると、返信が「え、なんで?」になる。

4.5-2. 1行目の禁則
  • 抽象語で始めない。 「大事なのは」「実は」は読み飛ばされる型として学習されている
  • フック単体で意味が通ること。 2行目を読まないと分からない1行目は、1行目として機能していない
  • 10〜14語で決める。 長い前置きは1.5秒に収まらない
4.5-3. 締めは「問い」より「隙」

§4-3 で「二択で聞くのが最も返信率が高い」と書いたが、日本語圏ではもう一段ある。

ツッコミを入れられる隙を残すほうが、質問より返信が湧く。

締め方返信の起き方
二択の質問「Aかな」と答えが返る。1往復で終わりやすい
断定+わずかな余白「いやそれは違う」「うちはこうやった」と相手が話し出す

例:「段取り完璧でも人の気持ちは別、って話やと思ってる。」

→ 質問していないのに、経験のある人ほど自分の話をしたくなる。

言い切ったうえで、少しだけ隙のある言い方にする。 完璧に閉じた文には返信が付かない。

4.5-4. 字数と改行
字数100〜150字が中心。上限500字だが、長いほど不利
改行3〜5行ごとに空行。スマホで塊に見えたら読まれない
行数3〜4行(§4-1のとおり)

4.6 いまのアカウントの俯瞰(2026-07-27 実測)
InstagramThreads
フォロワー25635
投稿37実質0
フォロー中—2

Threads はまだ運用を始めていない。 プロフィールに数件見えるものは、本文が Instagram の

キャプションと完全一致しており、Instagram からのクロス投稿と判断した。Threads 用に書かれた

投稿は存在しない。

これは不利ではなく、有利
悪い癖が付いていない告知アカウントとして学習されていない。最初から正しい型で始められる
35人は「勝手に付いた」数字何もしていないのに集まった=Instagram のプロフィール導線から流れてきた人。もともと関心がある層が既にいる
素材が54本ある書き下ろし済み。始めた瞬間から2か月ぶん途切れない
ただし、先に直す2つ

1. クロス投稿を止める

Instagram のキャプションがそのまま流れると、Threads 側では

  • 長い(Instagram は125字で折り返す前提の長文が普通)
  • ハッシュタグが並ぶ(Threads ではほぼ無効)
  • 告知で締まる(「プロフィールのリンクから」)

の三重で外す。しかもアルゴリズムに「告知アカウント」として学習される。

Your Algo でユーザーがトピックを申告する時代に、これは効く。自動連携は切る。

2. フォローを増やす

フォロー中が2人。§6 の返信戦略は「ターゲット50〜100アカウントの直近30分の投稿に返信する」

という設計だが、そもそも返信しにいく相手がいない。Threads は返信の総量が投稿の総量と

同等に評価される以上、この状態では配信が伸びない。

  • 映像制作・自主映画・撮影機材・脚本・俳優のクラスタで 50〜100アカウントをフォロー
  • そのうち投稿頻度が高く、返信が付いているアカウントを30ほどに絞る
  • 毎日その30を見て、直近30分の投稿に5〜8件返信する

投稿を1本出すより、これを2週間続けるほうが効く。

このチャンネルの持ち味は Threads と相性がいい

ディレクターズ葛藤は2人の会話で、関西弁で、失敗を自分から言う。数字が具体的に出る。

Threads が評価するのは「会話が始まった投稿」。もともと会話の形をしたコンテンツなので、

1人語りのアカウントより有利な素材を持っている。この持ち味を消さないこと。

  • 「〜です」に直さない。関西弁のまま出す
  • 失敗を先に言う。 成功談は返信が付かない
  • 数字を必ず1つ入れる。 750万、12セット、80人、1493分。全部返信の起点になる
  • 2人の会話から生まれた言葉(「読もうとしてへんかった」等)は、そのまま強い

5. 初速30分の運用(ここが本体)

投稿は「出して終わり」ではない。出した後の30分が作業時間。

手順
時刻やること
投稿直前返信できる30分を確保してから投稿する。確保できない時間帯には投稿しない
0〜5分自分の投稿に自分で1つ目の返信を付ける(補足・数字・裏話)。会話の起点を作る
5〜30分付いた返信に全部返す。1行で終わらせず、質問を返して往復させる
30〜60分他アカウントへの返信に切り替える(§6)

投稿後に離席するくらいなら、投稿を翌日に回したほうがいい。 初速を落とした投稿は、後から伸ばせない。


6. 返信戦略(投稿より重い)

Mosseri は「伸ばしたいなら、投稿より返信を増やせ」と明言している。伸びているアカウントは、自分の投稿より他人への返信に時間を使っている。

実務手順
  • ターゲットリストを作る(50〜100アカウント)。映像制作・自主映画・撮影機材・脚本・俳優のクラスタ
  • 直近30分以内の投稿で、初速が出ているもの(投稿5分で10いいね以上)を10〜15件見つける
  • そのうち 5〜8件に返信する
返信の書き方
  • 同意だけの返信は書かない。「わかります」「いいですね」は 2026年に抑制対象
  • 自分の現場の具体を1つ足す。「うちは血の掃除に1時間かかるんで、撮影順を血の位置から決めてます」
  • 宣伝を入れない。返信で伸びるのは人格であって、リンクではない

1日の配分の目安:自分の投稿1〜2本、他人への返信10〜15本。


7. 投稿頻度と時間
推奨
頻度一般調査は 1日1〜2本。5本以上は1本あたりのエンゲージメントを薄める
時間帯平日の午前 8〜11時。最良は木曜9時(Buffer・250万投稿の集計)
曜日水・木・火が強い
2026-08-03、運用は1日3〜5本(9/12/15/18/21 JST)へ引き上げた。 在庫を撒き切るための 浅尾の判断で、上の一般調査に反している。実際に動いている数字は lib/social/playbook.ts が真実源で、このドキュメントは調査結果のほう。 30分後の返信数(§9)が落ちるようなら本数を戻す。

時間帯の最適化より、返信できるかどうかが先。 最適時刻に投稿して離席するより、返信できる時間に投稿するほうが伸びる。

プラットフォームで最適時刻が違う(2026-07-27 確認)

Instagram の20時を Threads に持ち込まないこと。

一般調査自前の実測採用
Instagram夕方〜夜 18〜23時、特に19〜21時20時が19件で平均リーチ7,685(17時は9件で平均361)20時。両者が一致している
Threads平日午前 8〜11時、最良は木曜9時無し(未投稿)9時。自前データが溜まったら測り直す

Instagram は一般調査と実測が一致したので確度が高い。Threads は自前データがまだ無いので、

一般調査から始めて 投稿後30分の返信数(§9)で測り直す。実装上の既定値は

app/api/ops/queue/seed/route.ts の DEFAULT_HOUR_JST にある。

なお Instagram の実測では、17時台の7本が同じ「17:02」に固まっていた(予約ツールの

一括投入の痕跡)。伸びた20時台は19本すべてが「20:00」ちょうど。**半端な分のほうが手動らしく

見える、ということはなかった。** 効いていたのは時刻と書き方で、分の刻みではない。


8. Part11 の8本を Threads 型に書き直す

現行の投稿文(data/fix/katto/Part11.json の posts)は YouTube Shorts 向けで、Threads にはそのまま使えない。以下が Threads 用の正本。

11-1 撮影順
映画の撮影順って、血が飛ぶ方向で決まるん知ってた?

一回散らばった血は元に戻せへんから、物語の順番やなくて
「現場が戻せるかどうか」でスケジュール組んでる。

そっちの現場で「戻されへんもの」って何?
返信の一手:「うちはじゃんけん大会の会場で、80分の映画のうち50分がここに集中してる」
11-2 一発撮り
やり直しに1時間かかるシーンは、絶対に一発で撮る。

アクターを風呂まで送って戻すのに1時間。
やから桶で血をまっすぐ飛ばす練習を30分した。

1時間かけて撮り直す派? 30分練習してから一発で決める派?
返信の一手:「結果、一発で決まった。あの30分がいちばん安い投資やった」
11-3 カビ
撮影で使った血が原因で、体にカビ生えた。

東大病院行っても原因不明。冗談やなくて診断されてる。

作品に映らんとこ、どこまで手ぇ抜いてる?
返信の一手:「アドレナリン出てるから現場では気づかへん。請求書は後から来る」
11-4 事前撮影
映画の事前撮影、最低5名でやった。

複数人が映るシーン以外を先に全部撮り切ると、
あとは穴埋めするだけになる。

人数削るのと、日数削るの、どっちが効くと思う?
返信の一手:「副産物で全員の練習にもなった。本番の初日が2回目みたいになる」
11-5 住み込み
小道具の指輪を当日忘れる。それが一番こわい。

やから現場に住み込んだ。忘れ物は根性では減らへんけど、
現場におる時間を増やすと構造的に減る。

注意力に頼らん仕組み、なんか作ってる?
返信の一手:「住み込んだあとに自分の家帰ったら、空気が重すぎて驚いた」
11-6 テニスコート泊
布団を12セット借りて、誰も泊まらんかった。

最終日、テニスコートに全員で泊まる予定やってん。
後輩は全員アパホテルに消えた。

段取り完璧でも人の気持ちは別、って話やと思ってる。
返信の一手:「天井が高くて、あんなとこで寝る機会もう無いんやけどな」
11-7 ジブクレーン
インディー映画でジブクレーン、贅沢に見えるやろ?

逆やねん。持ち替える時間が消えるから、結局いちばん速い。
固定カットまで全部これで撮った。

現場のボトルネック、機材ちゃうくて「持ち替え」ちゃう?
返信の一手:「俯瞰も寄りも1本でいける。低予算ほど速さで買うべきやと思う」
11-8 血の配置
どっちを殺すかは、血が飛ぶ方向で決めてた。

演出の都合やなくて、現状復帰にかかる時間で配置が決まる。
結果、けっこう見破られてたけど。

制約から逆算した結果って、そのまま画になると思わへん?
返信の一手:「死ぬ側が事前にバレるのが唯一の欠点。そこは工夫のしどころやった」
書き直しで変えたこと
現行(YouTube向け)Threads版
行数5〜7行3〜4行
締め「裏側はYouTube本編で」二択 or 問い
タグハッシュタグ5個トピックタグ1個(本文外)
リンク本文に導線本文に置かない。返信で出す
投稿後なし返信の一手を用意しておく(0〜5分で自分から置く)

9. 測る指標

週次で見る。数字が出たらこのドキュメントの推奨値を更新する。

指標着手前(2026-07-27)見る理由
フォロワー35遅行指標。これ単体では判断しない
投稿あたりの返信数ほぼ0最重要。配信量の直接の入力
投稿後30分の返信数未計測初速。ここが2以上になると配信が変わるはず
いいね/投稿1参考。返信より軽い
プロフィールのリンククリック未計測YouTubeへの送客が起きているか

フォロワー数を目標にしない。 Threadsはフォロワーの効きが弱く、初速のほうが配信を決める。追うのは「投稿後30分の返信数」。


10. やらないこと
  • YouTube / Instagram の投稿文を転載しない。 型が根本的に違う
  • ハッシュタグを並べない。 トピックタグ1個で足りる
  • 「いいね・保存・フォローお願いします」を書かない。 2026年に抑制対象
  • 同意だけの返信をしない。 「わかります」「いいですね」は評価されない
  • 返信できない時間に投稿しない。 初速を落とした投稿は後から戻せない
  • 映画制作から外れた話題を混ぜない。 Your Algo の指定に引っかからなくなる

参照
  • [Threads Algorithm 2026 — MomentumHive](https://momentumhive.app/blog/threads-algorithm-guide-2026)
  • [How the Threads Algorithm Works in 2026 — PostEverywhere](https://posteverywhere.ai/blog/how-the-threads-algorithm-works)
  • [What's Actually Working on Threads in 2026 — Postory](https://postory.io/blog/what-works-on-threads-2026)
  • [Threads伸ばし方の完全ガイド — tatap](https://tatap.jp/knowledge/how-to-extend-threads/)
  • [Threadsアルゴリズム完全攻略 — アドネスラボ](https://addness.co.jp/media/threads-algorithms/)
  • [Control Your Threads Feed With Dear Algo — Meta 公式](https://about.fb.com/news/2026/02/threads-dear-algo/)
  • [New Features to Celebrate 500 Million Monthly Users — Meta 公式](https://about.fb.com/news/2026/06/meta-launching-new-features-500-million-monthly-threads-users/)
  • [Topic Tags on Threads — EmbedSocial](https://embedsocial.com/blog/threads-topic-tags/)
  • [Best Time to Post on Threads in 2026(2.5M件の集計)— Buffer](https://buffer.com/resources/the-best-time-to-post-on-threads/)
  • [(July 8) 2026 Threads news and features — SocialBee](https://socialbee.com/blog/threads-news/)
L2-11 SNS送出と予約
現行ルール
  • 投稿文・動画・公開先・予約時刻は、Projectの設定と制作画面で同じジョブに紐付ける。
  • X / Threads は各媒体で1日最大5本。予約時刻にschedulerが送出し、媒体別上限・最小間隔・接続アカウント・重複・本文・動画を送出直前に再検査する。
  • YouTube Shortsは完成動画を非公開で登録し、publishAtで最大5本/日を予約する。
  • Instagram、TikTok、noteは媒体のAPI制約に従い、未対応の操作を勝手に自動化しない。
  • 緊急停止は OPS_SCHEDULER_DISABLED=1。停止中は予約を削除せず、状態を保持する。
正本経路
Master字幕 / FIX
  → 媒体別投稿文・動画・連投を生成
  → Project設定でアカウントと予約を確認
  → PostgresのPublishJobへ保存
  → schedulerが送出直前に再検査
  → 外部ID・URL・時刻・失敗理由をPublishJobへ記録

投稿文の内容は Studio、アカウントとルールは /projects/{slug}/settings、送出結果と予約の実行状態は /projects/{slug}/production を見る。旧 /ops/* は互換転送だけを残し、旧管理画面を正本にしない。

重複防止

同じProject・媒体・素材IDはPostgresの一意制約で二重登録しない。Threadsは外部タイムラインも送出直前に照合し、同じ本文・同じ素材の再送を止める。外部に残った過去の重複投稿は、送出キューの削除とは別の監査・手動整理として扱う。

完了条件
  • 投稿文、動画、連投、タグ、公開先アカウントが同じジョブにある。
  • 予約時刻が媒体別の1日上限と最小間隔を満たす。
  • 送出結果に外部IDまたは失敗理由が記録される。
  • 動画付き投稿は外部媒体側の埋め込み表示と再生を確認する。

実装の正本は lib/social/、予約処理は app/api/ops/scheduled/route.ts、状態の正本はPostgresである。過去の手動公開専用ルールや旧Blobキューの説明は現行ルールではない。

L2-12 プラットフォーム別の最適解(2026年7月)
数字の真実源は `lib/social/playbook.ts`。 このドキュメントは読み物で、実際に予約時刻や 確認画面の検査を動かしているのはコード側。両方に数字を書くと必ずズレるので、 変更するときは playbook.ts を直し、ここは説明だけ合わせる。 調査日: 2026-07-27

1. 一覧
投稿時刻(JST)強い曜日本数本文の狙い折返しタグ上限追う指標
Instagram20:00水・木1日1本80〜400字125字5リーチ・シェア率
Threads9/12/15/18/21火・水・木1日3〜5本100〜150字—1投稿後30分の返信数
X10/13/16/19/22火・水・木1日3〜5本60〜140字—2インプレッション
TikTok20:00 / 8:00火〜金1日1〜2本150〜300字100字5完走率
YouTube——————関連動画経由の再生
本数は 2026-08-03 に引き上げた(浅尾の判断)。 Threads・X ともに 1〜2 → 3〜5本。 在庫(各49本)を撒き切ることを優先したもので、一般調査(§4)はこれに反する。 30分後の返信数が落ちるようなら戻す。数字を動かすのは lib/social/playbook.ts。 X は Threads から1時間ずらしてある。 同じ本文が同じ分に両方へ出ると転載botに見えるため。 これは実測ではなく見え方の判断。

2. なぜこの時刻か
Instagram = 20:00(確度が高い)

一般調査と自前の実測が一致している。 これが他より確度が高い理由。

  • 一般調査(Buffer 960万投稿ほか): 夕方〜夜 18〜23時、特に19〜21時が最良
  • 自前の実測(32投稿): 20時が19件で平均リーチ7,685、17時は9件で平均361

しかも上位20本のうち19本が 20:00 ちょうど。人が習慣として同じ時刻に押している形。

Threads = 9:00(暫定)

自前の実測が無い。 まだ1本も投稿していないので、一般調査から始める。

  • Buffer 250万投稿: 平日午前 8〜11時、最良は木曜9時

Instagram の20時を持ち込む根拠は無い。 別プラットフォームで使われ方が違う。

自前の数字が溜まったら測り直す。追うのは投稿後30分の返信数。

TikTok = 20:00

火〜金の 7〜9時と 19〜23時が強い。ただしTikTok Analytics の「フォロワーのアクティビティ」で

自分の視聴者が online の時間を見るのが最も確実。接続したら実測に切り替える。


3. 分の刻みについて(検証済み)

「半端な分のほうが手動っぽく見えて有利では」という仮説は、実データで否定された。

  • 伸びた20時台: 19本すべて `20:00` ちょうど
  • 沈んだ17時台: 7本が `17:02` に固まっている ← 一括投入の痕跡

きっちりした時刻のほうが人の習慣に見える。効いていたのは時刻と書き方で、分の刻みではない。

予約は各プラットフォームの最適時刻ちょうどに入れる。


4. 本文の型
Instagram

1行目で決まる。 リールは特にキャプションが畳まれるので、125字までで意味が通ること。

実測(32投稿)で最も差が出たのはここだった。

冒頭の型件数平均リーチ中央値最大
【】で始まる128663662,584
話し言葉の断定207,0862,17254,334
  • `【】` を使わない
  • 1行目に絵文字を置かない
  • 話し言葉のまま言い切る。「〜だった話。」「〜は、最初からアホ。」
  • ハッシュタグは末尾にまとめ、5個まで(2025-12-18 から1投稿5個の制限)

動画側では冒頭3秒の保持率が効く。60%超は40%未満の5〜10倍のリーチ。

2026の最大の予測子はシェア率(reshare rate)。

Threads

L2-10 が正本。3〜4行、100〜150字、二択か「隙」で締める、トピックタグ1個。

TikTok

先頭約100字で「…もっと見る」に切れる。 フックがそこに無ければ読まれない。

150〜300字が最もリーチする(長いものより平均18〜27%上)。動画は30〜60秒。

完走率が最重要なので、短く刻むより持たせられるなら長いほうが配信が広がる。

X

本文にURLを置かない。伸びない上に課金が $0.015 → $0.20 に跳ねる。

リンクは本文の直後の返信へ。


5. コードとの対応
値使われている場所
postHourJst / secondHourJstapp/api/ops/queue/seed の予約時刻
bodyIdeal / visibleChars確認画面の「狙いの長さ」検査
tagMax確認画面の「タグの数」検査
keyMetric確認画面の「公開後に見る数字」

これらは警告であって、公開は止めない。 止めるのは block(公開先アカウント不明・

本文が空・上限超過・メディア欠落・Xの本文URL・公開済み)だけ。


参照
  • Threads の投稿設計と話法: manual/L2_sns/10_threads_growth.md
  • SNS API 連携の構築手順: manual/L2_sns/11_sns_api_integration.md
  • 他4プラットフォームの成長設計: manual/L2_sns/09_platform_growth.md
L2-13 ショート構成の編集前評価と縮約字幕
目的

全Project・全Partで、Master SRTを正本にして「何を残し、何を落とし、どの順で作るか」を機械的に比較する。

これは再生数を保証する予言器ではなく、編集前の候補を同じ物差しで並べるための説明可能な評価である。

共通ルール
  • 本編1分につき最低1候補を作る。候補の採用はスコア70以上を基本とし、弱い候補を水増しして採用しない。

本番で落とすのは LLM rubric 8 軸の等重み平均が 70 未満のときだけ(RUBRIC_SCORE_THRESHOLD)。

本書が下の「評価パラメータ」で並べる重み付きスコアは門ではない(→ [15_shorts_composition_workflow.md](15_shorts_composition_workflow.md) §8-1)。

  • 尺は15〜60秒。尺を埋めるために挨拶・自己紹介・回数確認・意味を進めない相槌を残さない。
  • 時刻はMaster SRTから決める。縮約字幕はSRTの発話境界・カット単位に合わせ、字幕がない区間は(空白)、笑い・反応は(笑)として記録する。
  • 連続する同一話者は共通化する。ただし話者が変わった箇所、聞いている側の反応、笑い・頷きは別の編集単位として残す。
  • 除外した区間は削除理由と元の時刻をsidecarに残す。元のMaster SRT・plan・FIXは上書きしない。
  • Scoreは「採用候補/要確認/除外」を分ける。最終的な映像・音声・字幕の正しさはResolve/Premiereで確認したFIXが正本である。
自動で落とす導入
  • 挨拶、出演者名の読み上げ、収録開始の掛け声
  • 第2回じゃあのような回数確認
  • 回数確認の直後にあるじゃあまあなどの接続
  • えーと、まあ、そうそうなど、意味を進めない言い淀み
  • 本題へ入る前の短い説明だけの区間

内容を進める語を含む場合は無条件に文字を消さず、前後の発話と合わせて区間単位で判断する。Part2 P1の冒頭はこのルールで、名前読み・第2回確認・直後の接続を除外し、Master SRT上の最初の本題から再計算する。

評価パラメータ

この表は重み付き production score(`lib/katto/shortsScoring.ts`)のもので、本番の門の重みではない。

門は同じ 8 軸を等重みで平均する(ShortYieldPolicy.rubricWeights が Katto では全軸 1)。

下の重みが効くのは Studio の並び順と 評価 / 制作候補 タグの表示。決着の経緯は

[15_shorts_composition_workflow.md](15_shorts_composition_workflow.md) §8-1(浅尾確定 2026-08-12)。

軸重み見るもの
フック24冒頭の断定、逆説、数字、疑問、強い言葉
明快さ18単体で主張と回収が分かるか
テンポ15字幕キューの長さ、情報密度、不要な停滞
面白さ10笑い、ツッコミ、意外性、言葉の強さ
反応カット10聞き手の顔、頷き、笑い、話者以外の反応を置けるか
話者切替8会話の往復とカメラ切替の根拠があるか
完結性10冒頭から結論・オチまで単体で成立するか
尺適合515〜60秒に収まるか
導入ペナルティ最大25前後除外した導入の割合。長いほど減点

Scoreは候補ごとに内訳、除外秒数、除外区間、話者、反応候補、順位を保存する。UIでは評価 / 制作候補 / 導入除外 / 順位のタグで表示する。

DaVinci / Premiereへの渡し方

構成案は次の編集単位を生成する。

Master SRT
  → filler / transition audit
  → retained cut units(source start/end)
  → 縮約字幕 + speaker label + reaction flag
  → video: BG / Angle / Slide / Overlay
  → audio: Dialogue / BGM / SE
  → Resolve / Premiere用handoff

音声は本編から確定したDialogueを一本の正本ラインとして使い、映像は話者・Angle・反応の単位で切り替える。cutUnitsはsource timeを持つため、NLE側で配置を再計算できる。字幕を画面へ焼き込んだ画像で代替しない。

調査ソース(日本語20件以上)

公式・一次情報を優先し、実務記事・研究資料は編集仮説の補助として扱う。サイトの主張をそのまま採用せず、公開後のviewed / swiped away、平均視聴時間、完走、リプレイ、共有、返信と照合して重みを更新する。

  • [YouTube ヘルプ:YouTube ショートの作成](https://support.google.com/youtube/answer/10059070?hl=ja-US)
  • [YouTube ヘルプ:Shortsのアナリティクス](https://support.google.com/youtube/answer/12942217?co=YOUTUBE._ytvideotype%3Dshorts&hl=ja)
  • [YouTube ヘルプ:視聴者維持率の重要な瞬間](https://support.google.com/youtube/answer/9314415?hl=ja)
  • [YouTube 公式:Shortsから長尺への導線](https://blog.youtube/creator-and-artist-stories/youtube-related-videos-traffic-guide/)
  • [TikTok Creative Centerについて](https://ads.tiktok.com/help/article/creative-center?lang=ja)
  • [TikTok:クリエイティブなベストプラクティス](https://ads.tiktok.com/business/ja/blog/creative-best-practices-top-performing-ads)
  • [TikTok:クリエイティブパターン](https://ads.tiktok.com/business/creativecenter/creative-pattern/pc/ja)
  • [TikTok:動画インサイトのベストプラクティス](https://ads.tiktok.com/help/article/best-practices-for-video-insights?lang=ja)
  • [TikTok:クリエイター商用コンテンツ品質基準](https://ads.tiktok.com/help/article/about-tiktoks-content-quality-standard-for-creator-commercial-content?lang=ja)
  • [TikTok:クリエイティブのヒントファインダー](https://ads.tiktok.com/business/creativecenter/tiktok-creative-tips-finder/pc/ja)
  • [課題解決プラットフォーム:ショート動画の企画フレーム](https://0120.co.jp/blog/video-103/)
  • [TATAP:ショート動画制作ガイド](https://tatap.jp/knowledge/short-video-production/)
  • [1onepiece:ショート動画の尺と構成](https://1onepiece.jp/column/shortmovie001/)
  • [Wakku:冒頭フックと視聴維持率](https://wakku.co.jp/2026/03/11/series2-day2/)
  • [発信ラボ:ショート動画の5ステップ](https://hasshin-lab.comtri.jp/short-douga-tsukurikata/)
  • [Edimakor:切り抜き動画の編集手順](https://edimakor.hitpaw.jp/ai-video-tools/how-to-make-vtuber-clipping-videos.html)
  • [ScriptAI:切り抜き前提の構成](https://scriptai.jp/use-cases/gaming)
  • [Baycosme:ショート動画3秒フック資料](https://baycosme.com/wp/wp-content/uploads/2025/05/%E3%82%B7%E3%83%A7%E3%83%BC%E3%83%88%E5%8B%95%E7%94%BB3%E7%A7%92%E3%81%AE%E6%8E%A7%E3%81%BF.pdf)
  • [PR TIMES:YouTube動画構成テンプレート資料](https://prtimes.jp/a/?c=49325&f=d49325-94-f012aae62e0caf42a0e4a32837f72b14.pdf&r=94)
  • [IPSJ九州:文字起こしに基づく動画の切り抜き箇所推定](https://www.ipsj-kyushu.jp/page/ronbun/hinokuni/1013/Papers/A3-1.pdf)
  • [arXiv:文字ベースのTalking-head編集](https://arxiv.org/abs/1906.01524)
  • [arXiv:話者追従型字幕](https://arxiv.org/abs/1407.5145)
  • [東海ファジィ研究会:リアクションを含む切り抜き研究](https://soft-cr.org/tokai/files/proceeding/himaken2023.pdf)
  • [Smarvee:ショート動画の構成・字幕比較](https://smarvee.com/wp-content/uploads/2024/10/Recsma_white_paper_202410.pdf)
実装
  • ルールと既定値: lib/katto/shortsRubric.ts
  • 評価・除外・順位: lib/katto/shortsScoring.ts
  • Master SRTからのカット再計算: lib/studio/cut-level-plan.ts
  • Studioの表示タグ: app/studio/page.tsx
L2-14 SNS接続の診断と復旧

配信が止まったとき、原因を推測で埋めずに切り分けるための手順。2026-08-10 に X / Threads が2日間止まった実例から起こした。

この文書の要点は「どこが壊れているかを、独立した経路で1つずつ測る」こと。 接続まわりは「繋がっているように見えて出ていない」が起きやすく、内部の記録だけを見ていると原因を取り違える。


1. 何がどこにあるか

役割ごとに置き場が違う。1箇所を直しても、他が古いままだと動かない。

対象置き場読み出せるか
予約ジョブ(本文・動画・連投・時刻)Postgres(Neon)の PublishJobいつでも読める。これが正
接続トークン(X / Threads)R2 private/operations/*.json(AES-256-GCM)OPS_DATA_ENCRYPTION_KEY があれば読める
動画・原稿R2 media/ data/R2トークンで読める
資格情報(client id / secret / 鍵)Vercel 環境変数Sensitive 指定は二度と読めない

接続情報はローカルと本番で同じR2を共有する。だから OPS_DATA_ENCRYPTION_KEY を片方だけ変えると、もう片方が復号できなくなる。鍵を変えるときは必ず両方同時に。


2. 症状から原因への切り分け

上から順に測る。先に進む前に、その段が通っていることを確認する。

2-1. そもそもschedulerは動いているか

Activity テーブルを見る。SchedulerRun が空でも、Activity に「公開処理を確保した」「ready へ戻した」が15分おきに並んでいれば cron は生きている。

SELECT date_trunc('hour', at) h, COUNT(*) FROM "Activity"
WHERE at > NOW() - interval '36 hours' GROUP BY 1 ORDER BY 1 DESC;

「確保 → ready へ戻す」が延々と並ぶのは、接続で弾かれている形。 cronは正常で、送出直前の検査に落ちている。この往復は失敗理由を残さないので、画面からは「何も起きていない」ように見える。

2-2. 資格情報が本番に入っているか

vercel env ls で名前は見えるが、値は Sensitive だと `vercel env pull` で空文字になる。

npx vercel env pull prod.env --environment=production --scope <team>

空 = 未設定、ではない。 名前が一覧にあれば設定されている。読めないだけ。逆に、一覧に名前が無ければ本当に未設定。2026-08-10 の X は X_CLIENT_ID / X_CLIENT_SECRET が一覧に無かったので未設定と確定できた。

pullしたファイルはリポジトリ外へ置き、確認後すぐ消す。

2-3. client id / secret が正しいか

推測せず、Xに聞く。 トークンエンドポイントへダミーの認可コードを投げる。

POST https://api.x.com/2/oauth2/token
Authorization: Basic base64(client_id:client_secret)
grant_type=authorization_code&code=dummy&redirect_uri=<登録済みURL>&code_verifier=<43文字>
応答判定
401 / invalid_client資格情報が違う
400 Value passed for the authorization code was invalid.資格情報は正しい。コードだけが不正

Basic認証が受理された時点で、アプリが Confidential client として登録されていることも同時に分かる。Public client なら Basic は拒否される。

grant_type=client_credentials は必須パラメータの検証で先に落ちて資格情報まで到達しないので、判定に使わない。

2-4. 認可画面が拒否される

X の「問題が発生しました / アプリにアクセスを許可できません」は原因を区別しない。

サーバー側からは切り分けられない。 認可URLを curl で叩いても、ログインセッションが無いため、正しいURLも未登録のURLも同じエラー画面が返る。実際に2026-08-10、スコープとリダイレクト先を4通り変えて叩いたが全部同じ結果で、この方法では特定できなかった。

人の手で切り分ける。疑うのは2つ。

  • Callback URI が未登録 — 開発者コンソールの登録文字列とアプリの redirect_uri を一字一句照合する。プロトコル・ポート・末尾スラッシュまで完全一致が要る。ローカルと本番の両方を登録する
  • 同一ブラウザに複数アカウントがログインしている — シークレットウィンドウで対象アカウントだけログインして試すと切り分けられる

3. 暗号化鍵を作り直す

OPS_DATA_ENCRYPTION_KEY を失った場合。作り直す前に、失うものを数える。

3-1. 何が暗号化されているかを数える
grep -rn "from '@/lib/ops/encryptedStore'" --include="*.ts" lib app

2026-08-10 時点では3つだけだった。

データ失う影響
x-connection.v1.jsonトークン失効済みで、どのみち再接続が必要。影響なし
meta-connections.v1.json存在しなかった。影響なし
social-publish-queue.v1.jsonPostgresへ移行済み。正はNeonの109件。影響なし

全部が「作り直せる or 既に別の場所が正」なら、ローテーションのコストはゼロ。 1つでも唯一の正本があるなら、鍵を作り直してはいけない。

3-2. 旧い封筒は必ず消す

新しい鍵では復号できず、`getEncryptedJSON` は `null` を返さず例外を投げる。

Unsupported state or unable to authenticate data

read() を呼ぶ画面とAPIが全部落ちる。残しておくと「接続が無い」ではなく「画面が壊れる」になる。 消すのは掃除ではなく必須手順。

消す前にローカルへコピーを取る。現行コードが読むのはR2なので、旧 Vercel Blob 側にも同名ファイルが残っていることがある。両方見る。

3-3. 順番を守る
  • 新しい鍵を生成(base64の32バイト。crypto.randomBytes(32).toString('base64'))
  • 先に本番Vercelへ登録し、再デプロイする(envはビルド時に焼き込まれる)
  • 同じ値をローカルへ入れる
  • 再接続する

ローカルだけ新しい鍵にして接続すると、本番が復号できない接続情報でR2を上書きする。 順番を逆にしない。

逆にすると本番のジョブが `failed` に落ちる。 復号例外は retryable ではないので、送出直前の検査ではなく送出そのものが例外になり、markFailed へ行く。failed は人が戻すまで復活しない。2026-08-10、この順序を守らなかったため cron 1回で X 5件が failed になった。戻すのは PATCH /api/ops/jobs/<id> に {"status":"ready"}。

3-4. デプロイ前に .vercelignore を確認する

env を入れても再デプロイしないと反映されない。そのデプロイが通らないと復旧できない。

vercel --prod はローカルのディレクトリを丸ごと上げるので、dev/ のような大きい作業ディレクトリがあると Function のサイズ上限(250MB)を超えて落ちる。2026-08-10 は api/studio-matrix が 1.93GB になった。

除外パスには先頭スラッシュを付ける。 dev/ と書くと gitignore と同じ規則で階層を問わず一致し、lib/dev/ まで除外してビルドが落ちる。/dev/ と書く。


4. 接続できたことの確認

「保存された」と「使える」は別。3段階で測る。

4-1. 保存内容を復号して読む

R2から x-connection.v1.json を取り、復号して中身を見る。

username     : directorskatto            ← 出し先が合っているか
scopes       : offline.access tweet.write media.write users.read tweet.read
refreshToken : あり                       ← 無いと2時間で止まる

`scopes` は要求した値ではなく、実際に許可された値。 2026-08-08 は media.write が落ちた状態で保存され、動画投稿が403で全部弾かれた。スコープは接続時のトークンに焼き付く。コード側の X_SCOPES に足しただけでは効かず、繋ぎ直すまで古いまま。

4-2. アプリのコードを通さず外部APIへ直接聞く

自作コードの戻り値は「自分がそう思っている」だけ。媒体側に答えさせる。

GET https://api.x.com/2/users/me
Authorization: Bearer <復号したトークン>
→ {"data":{"id":"...","username":"directorskatto"}}

これが通れば、トークンが有効で、出し先が正しいことをX側が保証している。

4-3. 投稿せずにアップロードだけ試す

POST /api/ops/x/media-test に R2 の pathname を渡す。紐付けなかった media_id は24時間で消えるので、タイムラインには何も出ない。

返ってきた media_id が本当に媒体の発番かは、snowflakeをデコードすれば分かる。

const ms = Number((BigInt(mediaId) >> 22n) + 1288834974657n)
new Date(ms)   // 実行時刻と一致すれば X が採番したID

R2やCDNは X の epoch を持つIDを作れない。「通信先はストレージだけではないか」という疑いは、これで潰せる。


5. 落とし穴

タイムスタンプ。 Postgresの timestamp 列はUTC naiveで、@neondatabase/serverless はこれを実行マシンのローカル時刻として解釈する。JSTで見るときはSQL側で `AT TIME ZONE` 変換してから取り出す。 9時間ずれた表を作って一度誤読した。

本番APIとDBで時刻の基準が違うことがある。 突き合わせる前にどちらの基準か確かめる。

「投稿された」の正はアカウント側。 内部の記録だけを見ていると、公開済みが ready に巻き戻っていても気づけない。公開ページを実際に見る。連投がツリーになっているかも、投稿詳細ページを開けば分かる(自己返信が本文の下に並ぶ)。

連投が途中で切れても本文は出ている。 そこで全体を失敗にすると、再試行で本文が二重に出る。replyError として記録だけ残し、published のままにする。

env を入れただけでは反映されない。 Vercelはビルド時に焼き込むので、再デプロイが要る。

設定先を取り違えない。 本番とstagingで Vercel のチーム・プロジェクトが別なら、片方に入れても本番は変わらない。


完了条件
  • cronが動いていることを Activity で確認した
  • 資格情報が本番の環境変数一覧に名前として存在する
  • 復号した接続情報の username が出し先と一致し、必要なスコープが実際に許可されている
  • アプリのコードを通さない直接呼び出しで、媒体が200を返す
  • 投稿せずにメディアアップロードが通り、media_id が媒体の発番だと確認できた
  • ローカルと本番で OPS_DATA_ENCRYPTION_KEY が一致している

1つでも未測定なら「復旧した」と言わない。 測っていない項目は「X は通った、Y は未測定」と書く。

L2-15 ショート構成のワークフロー(タイトル起点)

全 Project・全 Part に適用するグローバルルール。 案件ごとに変わる値は §7 の表だけで、

それ以外をプロジェクト側に書かない。プロジェクト固有の値は L3_projects/<project>.md に置く。

浅尾確定 2026-08-10。従来の「Master SRT を順に読んで良さそうな区間を拾う」を捨て、

タイトルから逆算して構成を作る順序へ変える。

0-A. Context-bound subtitle route (release required, 2026-08-16)

The active release route is fixed to Project Context Bundle -> Canonical Transcript -> Context Master -> Part Release Input -> Short structure -> DRT/Resolve. A Short must never apply a second dictionary or correction pass.

  • Compile and approve the Project Context Bundle. Its revision, dictionary hash and correction hash are the subtitle authority.
  • Use scripts/build-canonical-transcript.mts to materialize the corrected Canonical Transcript from raw ASR or an approved proofread source.
  • Use scripts/materialize-context-master.mts with a verified frame/timebase to materialize the Context Master.
  • Export the approved Project Settings snapshot, then run yarn shorts:release-input -- --part Part1 --project-settings <snapshot.json> --context <bundle.json> --canonical <canonical.json> --master <context-master.json> --sync-master <verified-part-sync-master.manifest.json> --audio-manifest <partmix.json> --out <Part1.release-input.json>. This validates the whole Part input before any short is rendered.
  • Use scripts/build-master-srt.mts --context <bundle> --master-dir <context-master-dir> and scripts/build-shorts-from-master.mts --release-input <Part1.release-input.json> --sync-master <verified-part-sync-master.manifest.json> --tempo-level <Project Settings editorial.tempoLevel> only from that Context Master. The compiler maps every selected half-open source range through the verified drpOffsetFrames into SyncMaster record frames, and requires both picture and Dialogue Master coverage. A mismatched semantic-selection level or input-bundle hash is candidate-only and cannot be promoted.

Any legacy .transcripts/*.master.json, dev/master-srt-j/*, or fixed KATTO_DICTIONARY/KATTO_CORRECTIONS output is historical evidence only. It is not a release input unless re-materialized through the route above. Missing or mismatched Context/SOT hashes are stale and must stop promotion.

0. テンポの正本(全 Project 共通)

テンポは感覚だけで決めず、short-tempo-v1 の5段階プロファイルで機械監査する。Level 1〜5は同じ判定順を使い、Levelが上がるほど重複・言い換え・フィラー・余計な修飾語・長い連続runを厳しく落とす。LLMはMaster字幕の意味単位とタイトルを選ぶだけで、時間軸とカットを決めない。

Levelカット密度 / 10秒連続run上限隣接重複上限フィラー率上限
11.5–88秒0.920.40
22–77秒0.860.32
32.5–6.56秒0.800.25
43–65秒0.740.18
53–65秒0.680.12

全Level共通で短尺は15秒以上、1カットは1秒以上(23.976fpsでは24フレーム以上)とする。katto はProject設定で Level 5 を使用する。設定値は [lib/projects/master-settings.ts](../../lib/projects/master-settings.ts) の editorial.tempoLevel、プロファイル本体は [lib/shorts/tempoProfile.ts](../../lib/shorts/tempoProfile.ts) に集約する。

機械監査の判定順
  • Master source range を半開区間 [start,end) として正規化する。
  • 同一・ほぼ同一の隣接字幕、言い換え、フィラー、余計な修飾語、未完文を除外候補にする。
  • 1秒未満のカット/Angle変更/Reactionは結合またはFAILとする。
  • 10秒窓のカット密度、連続run、隣接テキスト重複、フィラー率をLevelの閾値で判定する。
  • 機械が字幕TC・Resolve frame・映像Angle・Dialogue Masterを同じsource rangeから生成し、source-range auditを通す。

人手は実再生・聴感・人物/画・チャンネル確認だけに限定する。未監査の構成を公開候補へ昇格させない。


1. なぜタイトルが先か

ショートは尺が短く、最初の 1 秒で見るか離れるかが決まる。

1 本のショートは「ひとことで言えること」を 1 つだけ持つ。 それがタイトルであり、

タイトルが立たない区間は、どれだけ内容が良くても 1 本にならない。

区間を先に選ぶと「この区間で何が言えるか」を後から探すことになり、

タイトルが事実の要約(=目を引かない)に落ちる。順序を逆にする。

タイトルが満たすべき条件は次のどれか。どれにも当てはまらないなら、その候補は作らない。

型中身
扇情数字・落差・タブー・失敗。「1日延びたらプラス300万」
Tips見た人が真似できる一手。「管理表がないと欠落して終わる」
目を引く状況そのものが異常。「体にカビが生えたのは冗談やない」

事実の列挙は不可。 煽った形にできるなら Tips 型でも可(L2-03 の「事実列挙=NG」をこの条件で緩和。浅尾確定 2026-08-10)。


2. 4 段のワークフロー
1. タイトル設計      構成の観点を決める。ここで本数が決まる
2. 構成(尺と区間)  マスター字幕 JSON からタイトルを成立させる区間を集める
3. 強調の重み付け    字幕のどこを立てるかを決め、タイムラインを最後まで作る
4. 監査             別観点のエージェントが評価軸で走査する

各段の出口は次の段の入口になる。段を飛ばして先に映像を組まない。

段 1. タイトル設計
  • 出力は「タイトル文 + 型(扇情 / Tips / 目を引く) + 根拠になる発話の時刻」
  • 本数はタイトル観点からの逆算で決める。下限は 本編 1 分につき 1 本以上

(40 分なら 40 本以上)。これは「1 分ごとに 1 本作れ」という意味ではない。

同じクリップを複数のタイトルで使ってよい。 同じ素材でも切り口が違えば別の 1 本になる

  • タイトルが被ったら統合する。似た型ばかりに寄ったら、他の型で作り直す
段 2. 構成(尺と区間)
  • 時間の正本は Master SRT / マスター字幕 JSON。構成案は Master の区間から作る
  • 尺は 15〜60 秒
  • 落とすもの: 冒頭の挨拶、出演者名の読み上げ、回数確認、意味を進めない相槌、

接続語だけの導入、内容の繰り返し

  • 残すもの: 複数人の掛け合いによる補強。同じ内容でも、

相手が言い直す・被せる・笑うことで強くなる部分は落とさない

  • 内輪ネタは除外ではなく補完する(浅尾確定 2026-08-10。従来の「除外」から転換)。

見た人が分からない固有名詞・前提は、テロップか字幕で 1 行補う。

補完しても伝わらないものだけを除外する

段 3. 強調の重み付け
  • タイトルの型に対応する語を字幕から拾い、重み付けする(→ [16_telop_emphasis.md](16_telop_emphasis.md))
  • ここまで決めてからタイムライン(カット・アングル・テロップ)を最後まで一気に作る
段 4. 監査
  • 生成したのとは別の観点で走らせる。 生成と同じ評価軸を同じ順で当てても、

測れるのは遵守率であって良さではない

  • 見るもの: テンポ(間延び・詰まりすぎ)、タイトルと中身の一致、

補完なしで通じるか、同じ話の繰り返し、落とし忘れた挨拶・相槌


3. カットのルール(テンポ)

目標は圧倒的なテンポ感。 判断の順は次のとおり。

  • 内容の繰り返しを削ぎ落とす
  • 掛け合いによる補強は残す(複数人が絡む部分はテンポを上げる要素であって、冗長ではない)
  • 語尾が長い発話は途中でカットしてよい → [../L1_subtitle/11_tempo_cut.md](../L1_subtitle/11_tempo_cut.md)

字幕を出しているので、語尾を最後まで聞かせなくても意味は落ちない。

どこまで切ってよいかの規則は L1-11 に置いた(グローバルルール)。

テンポが出ているかは、規則の遵守率ではなく出荷済みの分布と比べて判定する。

実測値は [../L1_subtitle/12_shipped_baseline.md](../L1_subtitle/12_shipped_baseline.md)

(18,060 キュー)。

IMPORTANT: 使える軸と使えない軸がある(2026-08-12 訂正)

かつてここに「キュー間ギャップは常にちょうど 2 フレーム。限界まで詰めて次を出すのが

テンポの土台」と書いていた。取り下げた。 それは成功要因ではなく機械の署名だった。

出荷済みの OUT 点は 次の IN − 83.333ms という引き算で作られている

(83ms と 84ms の 2 値だけで 79.8%、次点は 0.1%。時刻はフレーム境界に乗っていない)。

真似ると引き算を再現するだけになる。

軸使えるか
本文・改行・字数・行数使える。 人の手による
尺・ギャップ・余韻使えない。 引き算で作られている
CPS表示時間の基準には使えない。 尺が引き算なので、CPS は「編集者が選んだ表示時間あたりの字数」ではなく発話の onset 間隔あたりの字数=話速に近いもの。縮約が効いているかの指標としてだけ使う

いま使える要点:

  • 1 キューの字数 中央 12 / p90 18。2 行率 59%
  • 3 行は 18,060 キューで 0 件。 例外なし
  • CPS 中央 6.6 / p90 10.3 — ただし上の限定つき(縮約の指標としてのみ)

詳細と、なぜ取り下げたかは

[../L1_subtitle/12_shipped_baseline.md](../L1_subtitle/12_shipped_baseline.md) の

「教師かどうかは軸ごとに決まる」節。

測り直しは node scripts/measure-shipped-subtitles.mjs(分布)と

node scripts/inspect-shipped-timing-provenance.mjs(タイミングが機械かどうか)。


4. 字幕とカットの上流関係

字幕のタイムコードが上流。 cutFrames は字幕 TC から出す(浅尾確定 2026-08-10。

docs/PLAN.md の未決「字幕TC と cutFrames のどちらが上流か」を (A) 完全反転で決着)。

マスター字幕 JSON(縮約済み・Part 単位で 1 回)
  → ショートの字幕 TC(プロジェクトの文字数上限で縮約・改行・カット)
  → cutFrames(タイムライン上のカット)
  → Resolve / Premiere

この向きなので、JSON の時間を厳密に見て、テンポに違和感の出ない範囲で早めに作る。

副作用として「縮約を直すとカットが動く」。それは仕様であり、

縮約は Part 単位で 1 回しか行わないので、動くのは 1 度だけ。

文字数上限を変えたら、改行位置とカット位置が機械的に付け直せること。

上限は全経路パラメータで、lib/subtitle/breakPoints.ts の layoutLinesDetailed が

唯一の改行器。焼き込みタイトルも同じ実装を通る(2026-08-10 実装済み)。

この向きは 2026-08-11 に実装へ入った。lib/shorts/cutFrames.ts の planCutsFromWords が

上の 4 段を 1 本で通し、入口は語単位の書き起こしと文字数上限だけ(cutFrames は出力)。

scripts/build-shorts-from-master.mts / tools/resolve-adapter/plan_short_cuts.py /

lib/studio/cut-level-plan.ts の 3 経路とも既定は subtitle-tc で、

外から cutFrames を受け取る口は互換のために残るが既定ではない。

どちらを通ったかは成果物の cutFramesSource に残す。

導出は 1 成果物につき 1 回。 生成器が既に字幕 TC から出しているものを下流で出し直すと、

生成器が字幕キューの TC を見て下流が縮約語の TC を見るため値が食い違う(実測: 総尺 −5.6〜−8.9%)。

カットの余白(DEFAULT_CUT_MARGINS)
値既定根拠
前の余白 padInFrames2 frames字幕の IN 点(音声開始の 1〜2 フレーム前)と同じ。頭を欠かないための値なので二重にはならない
後ろの余白 padOutSec0.2 秒(浅尾判断 2026-08-11)字幕の余韻を流用しない。 キューの終わりには finalizeCueTiming が既に言い終わり +0.5 秒を入れており、その上にカットの余白を足すと二重になる。実測: 0.5 → 0 で総尺が 551,398f → 519,067f(−5.9% / −22.4 分)
連結の間隔 joinGapSec0.6 秒根拠は未測定。 2026-08-10 の突き合わせで使った値がそのまま既定になっている。本数はこの値だけで動く(join=0 で +94%、join=0.6 で −21%)ので、決まったと書かない

5. 縮約は本編とショートで同じものを使う

浅尾確定 2026-08-10: 本編と同じ縮約を使う。 別々にしない。

実測(13 Part):

本文の差210,986 字中 2 文字(99.99905% 一致)。13 Part 中 12 Part が完全同一
語の削除・言い換え・別内容0 件
本編 cue をショート規則(10字)に当てた違反7,185 件。全件 2 枚で解ける(3 枚以上は 0 件)
境界を動かさず 20 字超だけ割る split-only99.987% 成立

別々に縮約すると本文が 2 つに分かれ、校正のたびに両方直すことになる。

処理も重い。幅が変えるのは改行位置と 1 枚に入る量であって、字数の密度ではない。

詳細は [../L1_subtitle/10_condensation.md](../L1_subtitle/10_condensation.md)。


6. アングルの割り当て

基本は話者に合わせる。 ただし同じ話者が長く続くときは、

相槌・かすかな喋り・頷き・笑いを拾ってアングルを満遍なく分ける。

  • 1 秒未満の相槌は証跡として保持してよいが、単独のAngle/リアクションカットにはしない。隣接するAngle runへ結合する(24フレーム未満はFAIL)。
  • SRT に笑い声が取れていれば、全体のカットかその人物の抜きカットを入れる
  • 話者の判定は書き起こしのラベルだけで決めない → [../L1_subtitle/07_elevenlabs_workflow.md](../L1_subtitle/07_elevenlabs_workflow.md)

7. グローバル / プロジェクトの棲み分け

この表がグローバルとプロジェクトの境界。 迷ったらここを見る。

決めることどこ例
タイトルの型(扇情 / Tips / 目を引く)グローバル(本書 §1)—
4 段のワークフローと段の出口グローバル(本書 §2)—
落とすもの・残すもの・内輪ネタの扱いグローバル(本書 §2)—
語尾カットの判定規則グローバル(L1-11)—
改行・音節改行・禁則グローバル(L1-01 §4)—
縮約の規則と単位グローバル(L1-10)—
字幕 TC → cutFrames の向きグローバル(本書 §4)—
テロップ強調の段と実装先の選び方グローバル(L2-16)—
門が見る Score の尺度(8 軸を等重み平均する、という決め)グローバル(本書 §8-1)—
LLM の段を N 回回して合議する、という決めと取り方(型は和集合/強調語は K 回以上/順位は平均順位)グローバル(本書 §8-2)—
合議の回数 N と閾値 K、および合議を回すかどうかプロジェクトShortYieldPolicy.consensus(runs / emphasis.minRuns。実測で N=5 / K=2。§8-2、§8-3)
門の Score の床と軸の重みプロジェクトShortYieldPolicy.minRubricScore / rubricWeights(Katto は 70 / 全軸 1)
1 行の文字数・1 枚の文字数プロジェクト本編 13 / ショート 10
強調テロップの色・字数・縁・フォントプロジェクト黄 + 黒縁 / 最大 5 字
強調語の辞書プロジェクトlib/katto/generationSettings.ts
話者の名前とラベル対応プロジェクトL3_projects/directors_katto.md
アングルの列数と幾何プロジェクト(さらに Part 単位)—
除外の具体例プロジェクトlib/katto/shortsRubric.ts

8. 実装状況(2026-08-11 更新)

書いてあることと動いていることを分ける。 未実装を完了扱いにしない。

本番経路はここ。yarn worker → scripts/production-worker.ts →

lib/production/worker/transport-planning-plugin.ts →

lib/production/planning/{worker-bridge,commit-service,planner}.ts →

lib/analysis/candidates/pipeline.ts。「実装済み」と書いてよいのはこの線に乗っているものだけ。

項目状態
文字数上限のパラメータ化(改行器の一本化)実装済み。layoutLinesDetailed が唯一
音節改行(語が 1 行に載らないときだけ)実装済み。幅で判断するので字種は問わない(カタカナ 6 字以上の段は 2026-08-11 に撤去)
字幕 TC → cutFrames(§4 の向き)実装済み。lib/shorts/cutFrames.ts の planCutsFromWords が 縮約 → 改行 → 字幕 TC → cutFrames を 1 本で通す。3 経路とも既定は subtitle-tc
縮約を本編と共通化未実装。 実測で「統一してよい」まで確認済み。本編側の scripts/build-master-srt.mts は --condense の任意フラグ止まりで、アプリ経路が縮約済みマスターを常に読む形になっていない
段 1 タイトルの型の必須化実装済み。lib/analysis/candidates/editorial-gate.ts の judgeTitleGate。LLM の申告と judgeTitle の両方が「型なし」のときだけ落とし、割れたものは needs_review で残す
段 1 型の判定を AI にする(浅尾確定 2026-08-12)実装済み。judgeTitleGate が段 3 へ渡す型を judged(字面だけ)から kinds(申告 + 字面)に変えた。既存データへの付け直しは scripts/judge-title-kinds.mts(判定は lib/analysis/candidates/title-kind-judge.ts)。実測 610 本で型が付いたのは 255(41.8%)→ 553(90.7%)、強調が付いたのは 30.3% → 36.4%。内訳と precision / recall は L2-16 §5-4
段 1 LLM を通る段の合議(同じ入力を N 回、答えを 1 つに畳む)実装済み(本番未接続)。 合議は lib/analysis/consensus/**(純関数)、測る器は scripts/measure-llm-volatility.mts。生成経路(`pipeline.ts` / `claude-transport.ts`)からはまだ呼ばれていないので、出荷される候補はいまも 1 回のまま。実測と取り方の根拠は §8-2
段 1 本数の床(本編 1 分に 1 本)未実装。 lib/shorts/titleFirst.ts の requiredShortCount / reviewTitleSlate はあるが、呼ぶのはテストだけ
段 2 挨拶・名前読み・回数確認・相槌の除外実装済み。cleanEditorialCues を門の中で通す(NODE_ENV の分岐なし)。落とした残りが最小尺に届かないものだけ捨て、それ以外は理由コードごと残す
段 2 Score の床実装済み。ShortYieldPolicy.minRubricScore 未満は selectShortCandidates が RUBRIC_SCORE_BELOW_MINIMUM で落とし、件数を auditCodeSummary に残す。閾値はプロジェクト設定から引く(既定は Project rubric の rubricScoreThreshold = 70)。ただし Project Settings に編集フォームはまだ無い
段 2 Score 70 の尺度決着済み(浅尾確定 2026-08-12)。門は等重み平均のまま。マニュアル側を実装に合わせた。 詳細は下の §8-1
内輪ネタの補完実装済み。detectSupplements() を門の中から呼ぶ。落とさずに印(`SUPPLEMENT_REQUIRED` / `SUPPLEMENT_PROPER_NOUN_LIMIT`)を付けて残す。 初出の固有名詞は 1 本 2 語まで
タイトル起点のワークフロー段 1〜3 は実装済み、順序は未転換。 生成は区間先行のまま(candidates/windows.ts が尺と finding で区間を切る)で、タイトルの型は後段の門が見る。段 3(強調の重み付け)は evaluateEditorialGate の中で走る(L2-16 §5)。「タイトルから逆算して区間を集める」という §1 の順序そのものはまだ入っていない
語尾カットの判定L1-11 が仕様。接続状況は L1-11 を見る
強調テロップ(Text+ の演出)未実装(L2-16 が仕様)。焼き込みは ASS 1 系統で Subtitle / Hook の 2 スタイルが各 1 色。行内の色替えは escape で潰しており、Text+ の出力は無い
話者ラベリングの統合未実装(L1-07 が仕様)
段 4 の別観点監査未実装。 lib/shorts/ruleCheck.ts(旧 lib/shorts/audit.ts)が 8 項目を complianceFindings(生成側と辞書・閾値を共有=遵守率)と independentFindings(dead_air / title_mismatch / repetition)に分けたが、本番経路から呼ばれていない(呼ぶのはテストだけ)。段 4 を満たすには判定の根拠を生成と分ける必要がある

8-1. 「Score 70」は 2 つある。門はどちらか(浅尾確定 2026-08-12)

同じ 70 が別の尺度に当たっていた。実装が正とし、マニュアルを直した。

2026-08-12 より前の本書・L2-02・L2-13 は、門が重み付きスコアを見ていると読めた。それは誤り。

本番の門順位付けだけ
呼び名RUBRIC_SCORE_THRESHOLDPRODUCTION_SCORE_THRESHOLD
置き場lib/katto/shortsRubric.ts の editorialPolicy.rubricScoreThresholdlib/katto/shortsScoring.ts の SHORTS_EDITORIAL_POLICY.productionScoreThreshold
値7070
尺度LLM rubric 8 軸の等重み平均。 Katto は rubricWeights が 8 軸すべて 1重み付き。 hook 0.24 / clarity 0.18 / tempo 0.15 / humor 0.10 / reaction 0.10 / speakerChange 0.08 / completeness 0.10 / durationFit 0.05、から導入除外のペナルティを減算
どこで効くかShortYieldPolicy.minRubricScore → selectShortCandidates。未満は RUBRIC_SCORE_BELOW_MINIMUM で落ちる。この経路の順位(comparePrepared → P1..P6)も同じ等重み平均で決まるrankShortCandidates → lib/studio/cut-level-plan.ts → /api/studio-shorts・/api/studio-matrix。落とさない。Studio の並び順と `adopt` / `review` の表示を決めるだけ
誰が付ける値かLLM が候補ごとに申告した 8 軸Master SRT のキューから機械的に算出

この 2 つは別の経路で、途中で合流しない。 門(Production の計画経路)は

selectShortCandidates の中で完結し、重み付きスコアを一度も読まない。

重み付きスコアが動くのは Studio が計画成果物(dev/short-plan/*.json 等)を読み直すときだけ。

この決定の犠牲は、hook の重視が門から消えること。

門は 8 軸を等しく見るので、「フックだけ強い候補」は通らない。

実測(selectShortCandidates に KATTO_SHORT_YIELD_POLICY をそのまま渡して 1 回走らせた):

hook 100 / 他 7 軸 64 の候補は metricBasisPoints 6850(= 68.50)で

thresholdBasisPoints 7000 に届かず、`RUBRIC_SCORE_BELOW_MINIMUM` で落ちた。

同じ 8 軸を上の重み付きの係数で合成すると 72.64 になり、70 を超える

(この 72.64 は係数を当てた算術で、scoreFromBreakdown を走らせた値ではない。

あの関数は LLM の申告値ではなく Master SRT のキューから出した軸しか受け取らない)。

通す/落とすが実際に入れ替わる差であって、表記だけの違いではない。

hook の重視が残っているのは Studio 側だけ。 Studio が計画成果物を読み直して並べるとき

(rankShortCandidates)は今も hook 0.24 が効き、フックの強い候補が上に来る。

門と、門の中の順位(P1..P6)からは消えた。 selectShortCandidates は落とすときも

並べるときも等重み平均しか見ない。

hook を門でも重く見たくなったら、`SHORTS_EDITORIAL_POLICY` の重みを持ち込まない。

ShortYieldPolicy.rubricWeights はプロジェクト設定なので、そこで hook の weight を上げる。

2 つの尺度を混ぜると、また同じ事故になる。


8-1-1. バズ用の内容評価は別契約にする(katto-virality-v2-10point・2026-08-15)

既存の8軸Scoreは、候補を処理できるか/Studioでどう並べるかの尺度である。

「パワーフレーズを冒頭に置く」「内容の重複を切る」「タイトルで約束した結論を回収する」

までを表すバズ評価ではない。したがって、既存の70点をバズの合格点とは呼ばない。

バズ用の内容評価は [lib/katto/viralityRubric.ts](../../lib/katto/viralityRubric.ts)

に固定する。LLM/機械が根拠付きで採点する6軸は次のとおりで、総合100点へ変換する入力は各軸0〜10点である。

軸重み判定すること判定主体
パワーフレーズ25断定・数字・落差・反転・賛否を生む一文があるかLLM
ためになる20視聴後に持ち帰れる判断・手順・知識があるかLLM
共感性15痛点・失敗・迷い・あるあるを自分事化できるかLLM
テンポ20意味単位の密度、不要な間・重複、冒頭の立ち上がり機械
結論・オチ10冒頭の約束を最後まで回収できているかLLM
タイトルの約束10本文に根拠のある強い主張になっているかLLM

各軸は0〜10点で、0〜1/2〜3/4〜5/6/7/8/9〜10を不可/弱い/要改善/成立/強み/優秀/決定的へ非線形変換する。priority は総合84点以上だけでは足りず、全軸7以上、パワーフレーズ8以上、タイトルの約束8以上、結論・オチ7以上を同時に満たす。70〜83.9点は review、70点未満は再構成する。

このScoreは配信結果を予言しない。公開後の維持率・完走・共有・再生率で再校正する。

機械関門は内容Scoreとは別に必ず通す。

  • 短尺は 00:00:00(frame 0)から始め、最初の内容は24フレーム以内に置く。
  • 23.976fpsの整数frame、半開区間 [startFrame,endFrame)、Master字幕由来のsource rangeを使う。
  • 編集カットは字幕行の数ではなく意味単位で数え、カット密度と連続runの関門は選択した short-tempo-v1 Level のプロファイル値を適用する。Katto(Level 5)は 3〜6カット / 10秒、連続run 5秒以内 とし、プロファイル範囲外は要確認またはFAILとする。
  • 1秒未満のAngle変更/リアクションは使わず、24フレーム未満は結合またはFAILとする。これはテンポLevelにかかわらず共通の下限である。
  • 同じ主張の反復、未完文、意味を進めない語尾、「みたいな」等を機械監査で検出し、LLMは削る範囲だけをMaster上で指定する。
  • 後半のパワーフレーズを冒頭へ移す場合も、映像・字幕・Dialogue Masterを同じsource rangeの短尺配置へ束ねる。P1の「再現性がない」は冒頭候補、「4億5億上がってる / みたいな / というのがあって」は意味単位の分割候補である。

タイトルは、扇情(数字・断定・落差)、Tips(視聴者が使える一手)、共感(痛点・失敗)のいずれかを1主張で持つ。

本文にない主語・数字・結論をタイトルやサムネイルへ足さない。タイトル、冒頭字幕、最後の回収文は同じ主張を指す。


8-1-2. 厳しい採否・出典・人物Angleレーンの正本規則(追記・2026-08-15)

§8-1-1の6軸を制作判断へ使うときの運用を、次のとおり固定する。既存の8軸Score、過去の実測値、履歴上の候補数は削除せず、別の尺度として保持する。

key重み採点の意味主体
Power25冒頭へ置ける強い断定・数字・落差・反転・賛否の根拠LLM(Master根拠付き)
Useful20視聴後に使える判断・手順・知識が本文に残るLLM(Master根拠付き)
Relatable15痛点・失敗・迷い・あるあるが自分事として伝わるLLM(Master根拠付き)
Tempo20意味単位の密度、不要な間・重複、冒頭の立ち上がり機械
Payoff10冒頭の約束を最後に回収し、短尺単体で閉じるLLM(Master根拠付き)
TitlePromise10タイトルが本文の強い事実を正確に約束するLLM(本文照合)
  • 重みは Power25 / Useful20 / Relatable15 / Tempo20 / Payoff10 / TitlePromise10、合計100点から変更しない。候補間の差を明確に出すため、各軸の 0点を許可する。根拠がない/軸を満たさない場合を中央値や最低点で埋めず、0点のまま記録する。
  • reviewFloor=70 未満は reject として制作候補から落とす。70〜79.9点の review は保留であり、再評価を通るまで制作候補に含めない。priority は合計80点以上、かつPower 70点以上・TitlePromise 70点以上・Payoff 60点以上を同時に満たすものだけとする。低得点を制作候補へ残すための手動救済は行わない。
  • タイトルと本文の出典を分離する。 タイトル側の出典はタイトル文・型・パッケージング根拠として扱い、本文側の出典は Master字幕とMasterのword clockだけ とする。タイトル、サムネイル、派生SRT、旧SRT、候補JSON、LLMの要約・推測、映像や音声だけの印象は本文源ではない。TitlePromiseはタイトル側とMaster本文の照合であり、タイトルの文言で本文の欠落を補わない。
  • 制御の優先順位は 機械制御 > LLM制御 > 手動。機械がMaster word clock、source range、字幕TC、cutFrames、Score集計、閾値、重複・未完文・Angle run、レーン数と検算を決める。LLMはMaster上の意味単位の選定、タイトル案、根拠付きの軸評価だけを行う。手動は最後の実再生・聴感・人物/画・チャンネル確認と公開承認だけを行い、機械FAILやrejectを上書きしない。
  • 人物AngleレーンはPart設定を唯一の人物リストとする。speakers.profiles と speakers.partOverrides[PartN].profiles から、対象Partに存在する人物を 最大10人物分 だけ機械的に展開する。BG/overview等の非人物列は10レーンに数えない。11人目、未登録人物、LLM推測の人物を新規Angleレーンにせず、unassigned/reviewとして止める。手動でレーンを増やすのも不可とする。
  • 実行順は Master字幕/word clockからLLMがsource rangeを選ぶ → 機械が字幕TC・Resolve frame・人物Angleレーンを導出する → 手動が最終QCする。本文を派生字幕や画面表示から再構成しない。

8-2. LLM を通る段の揺れ(実測 2026-08-12)

揺れるのは LLM を通る段だけ。 縮約 → 改行 → カット → 字幕 TC → cutFrames は純関数で、

同じ入力なら同じ出力になる。同じ入力で答えが変わるのは次の 2 つだけで、どちらも

title-kind-judge.ts / claude-transport.ts が 1 回の呼び出しで取る。

段揺れるもの
段 1タイトルの型(titleKind)
段 3強調語とその順位(emphasisCandidates)
測り方

buildTitleKindRequest が組んだ本番と同じ system prompt(プロファイル由来の 5 / 14 が

入ったもの)を書き出し、別プロセスの Claude(Sonnet)5 回に、独立に判定させた。

返りは parseTitleKindDeclarations(本番と同じ schema)と judgeTitleGate(本番と同じ合流)を通す。

標本は 610 本から等間隔で 40 本(乱数を使わないので、同じ母集団なら誰が走らせても同じ 40 本)。

証跡と回そのものは dev/knowledge/llm-volatility-20260812.json(rawRuns)。

`ANTHROPIC_API_KEY` を使った往復は、この環境ではまだ 1 度も走っていない。

回が無く鍵も無いときは、measure-llm-volatility.mts は数値を出さずに落ちる。

揺れの実測(合議する前の 5 回。N=5 / 40 本)
測ったもの値
型が 5 回とも同じ26 / 40(65.0%)
型が割れた(2 種以上の型)11 / 40(27.5%)
型あり / none で割れた4 / 40(10.0%)
型の一致率(最多票 / 5)の平均0.89
強調語の一致(回のペアごとの Jaccard の平均)0.674
強調語が 5 回とも完全一致14 / 40(35.0%)
順位の入れ替わり(両方の回に出た語の組)0.05(比べられたのは 10 本)

型は 27.5% で割れ、強調語は 3 分の 1 しか完全一致しない。 順位は、同じ語が

両方の回に出たときはほぼ動かない(入れ替わるのは 5%)。**揺れているのは順位ではなく、

どの語を挙げるか。**

抑え方(グローバル。取り方は段ごとに違う)
対象取り方なぜ
型和集合(票が最多のものを 1 値の口へ、残りは記録)judgeTitleGate が LLM と字面で割れたときに既にそうしている。同じ段に 2 つの取り方を置かない
強調語K 回以上に出た語(K はプロジェクト設定。実測で K=2)下の表で測って決めた
順位出た回の平均順位入れ替わりが 5% と小さく、平均で潰して失うものが小さい
K を測って決める(同じ 5 回・40 本を K だけ変えて通した)
K強調が付いた本数候補数字面に無い語目安 5 字超範囲が重なる組1 回抜いたときの Jaccard
1(和集合)40(100%)730660.968
2(採用)40(100%)570420.943
338(95.0%)460300.834
429(72.5%)320200.821
5(全会一致)23(57.5%)250100.946

「1 回抜いたとき」は 5 回の合議と 4 回の合議を語の集合で比べたもの(40 本 × 5 通り = 200 組)。

K は絶対値のまま落とす(1 回落ちた本番でそう振る舞うから)ので、

K=5 の行だけは「5 回中 5 回」と「4 回中 4 回」の比較になっている。

「字面に無い語」が全 K で 0 なのは、この 40 本でそうだったというだけで、幻覚が無いという意味ではない。

K=2 を採る。 和集合(K=1)と同じ 100% の被覆を保ったまま、回どうしの矛盾が 6 組 → 2 組

に減る。グローバル正本の「矛盾が 0 なら和集合は安全」は、ここでは前提が成り立っていない

(おじさん と 知らないおじさん、プチっと と プチっ のように、回ごとに語の切れ目が違う)。

重なりは下流の resolveOverlaps が長い方を採って解くので、放っておくと

L2-16 §2 の「いちばん短い範囲を抜く」と逆に働く。

K=2 の犠牲は 2 つ。 1 回抜いたときの安定が 0.968 → 0.943 に下がること

(和集合の方がわずかに動かない)。それと、呼び出しが 5 倍になること

(40 本 1 バッチが 5 バッチ。トークンも 5 倍)。矛盾が 0 になる K=3 以上は

被覆が 95% → 57.5% と落ちるので採らない。

型(和集合)の側も 1 回抜いて測った。**主の型が変わるのは 3.0%(6 / 200)、

和集合が縮むのは 7.0%(14 / 200)。**

決定的にできる部分(recall と当たり率を両方出す)

合議を「正」として測った一致率であって、人の正解との一致ではない。

決定的な経路recall当たり率判断
型を目印(judgeTitle)だけで決める30.0%(12 / 40)85.7%(12 / 14)置き換えられない。 当たったときは合うが、7 割の型を見落とす。いまどおり「後から拾う網」として残す
強調語を数字 + 単位の正規表現で決める5.3%(3 / 57)100%(3 / 3)移す価値がない。 当たり率は満点だが、当たる語(19歳 45倍 1本)はもともと 5 回中 4〜5 回で一致していて揺れていない。決定的にしても消える揺れがほぼ無い
強調語をプロジェクト辞書の固定ピンで決める0%(0 / 57)0%(0 / 2)別の仕事。 40 本中 2 本しか当たらず、当たった 2 本でも AI は別の語を選んだ(低予算 に対し 英語 成功)。L2-16 の「AI はその文で目立つ語、辞書はこの案件で毎回赤くしたい語」を数で裏づけた
測っていないもの
  • 人の正解との一致。 教師データが無い。ここに出るのは回どうしの一致と、合議への一致だけ
  • 候補の選定そのもの(段 2 の区間と rubric)の揺れ。 測ったのは型と強調語だけ
  • `temperature` を変えたときの揺れ。 lib/ai/claude.ts の既定 0.2 のまま
  • 実 API の往復。 上は別プロセスの Claude で取った 5 回
  • 40 本より広い標本。610 本のうち 6.6%
走らせ方
# 別プロセスに判定させる(API キーが無い環境)
npx tsx scripts/measure-llm-volatility.mts --sample 40 --emit-prompt <dir>
#   <dir>/prompt.txt を N 回、独立に判定させて <dir>/runs/run-*.json に置く
npx tsx scripts/measure-llm-volatility.mts --items <dir>/items.json --runs <dir>/runs \
  --policy <policy.json> --out dev/knowledge/llm-volatility-<date>.json

# 実 API(ANTHROPIC_API_KEY が要る)
npx tsx scripts/measure-llm-volatility.mts --live --policy <policy.json> --sample 40

N と K は `--policy` かプロファイルの `analysis.consensus` から引く。

lib/analysis/** に既定値は無く、設定が無ければ動かずに落ちる。

プロジェクト設定の置き場は `ShortYieldPolicy.consensus` に決めた(2026-08-14。§8-3)。

測るときは引き続き --policy で渡してよく、使った値は成果物の policy に記録される。


8-3. 合議を本番へ繋いだ(浅尾承認 2026-08-14)

§8-2 で測った合議は、2026-08-13 まで本番から 1 度も呼ばれていなかった。

呼び出し元は scripts/measure-llm-volatility.mts だけで、出荷経路

(lib/analysis/candidates/pipeline.ts)には入口が無かった。

繋いだ場所と、生成を N 回にしなかった理由

繋いだのは「判定」の段で、「生成」の段ではない。

  • 生成を N 回まわしても合議できない。 回ごとに選ぶ窓もタイトルの文言も変わるので、

回どうしを突き合わせる id が無い。合議は「同じ入力を N 回通して分散を測る」もの

  • 揺れを実測したのも判定の段。 §8-2 の 65.0% / 35.0% は

title-kind-judge.ts の prompt と schema で測った値。測った段に合議を当てる

経路はこうなった。生成は 1 回のまま。窓もタイトルも動かない。

生成(1 回)→ 接地の検算 → [合議:判定を N 回まわして型と強調語を引き直す] → 編集の門 → 選定
                              ↑ lib/analysis/candidates/title-kind-consensus.ts

置き換えるのは titleKind と emphasisCandidates の 2 つだけ。

窓・引用・タイトルの文言・rubric には触らない。

フラグと既定
回すかどうかの決め手プロジェクト設定 `ShortYieldPolicy.consensus`。 省略すれば回らない
既定off(KATTO_SHORT_YIELD_POLICY に consensus を置いていない)
既定を off にした理由コスト。判定の呼び出しが N 倍(実測 5 倍)になる。 生成は 1 回のままなので、増えるのは判定ぶんだけ
設定はあるのに継ぎ目が無い場合落ちる(candidate_consensus_transport_missing)。「設定したのに黙って回っていなかった」を作らない
回が 2 回に満たない場合落ちる。 1 回の答えを合議と呼ばない
off のときの manifest鍵ごと無い。 既存の manifestHash / policyHash は 1 ビットも変わらない(テストで確認)
分散の前後(10 回。2026-08-12 の 5 回 + 2026-08-14 の 5 回)

同じ量の 2 つの独立な推定がどれだけ一致するかで比べた。形をそろえてある。

  • 合議なし … 10 回から 2 回を選ぶ 45 組
  • 合議あり … 10 回を 5 回ずつの重ならない 2 群に割る 126 通り

合流も門も本番の関数をそのまま呼んでいる(parseTitleKindDeclarations /

buildConsensus / judgeTitleGate)。

型が同じ強調語の集合が同じ強調語の Jaccard門の可否が同じ
合議なし(45 組 / 1,800 本)81.1%59.4%0.67995.8%
合議あり(126 通り / 5,040 本)94.9%68.9%0.81699.6%

下がったのは分散だけ。 正しさは測っていない(教師データが無い)。

5 回とも同じ誤りを言えば、合議は誤りを 5 回ぶんの自信つきで通す。

落とす方向に働いていないこと(確かめた)
1 回あたり合議(N=5 / K=2)
門が落とす本数平均 1.30(最悪 3)0
型が付いた本数平均 38.20 / 40(最少 36)40
強調が付いた本数平均 37.20 / 40(最少 35)40

和集合は none に負けず(1 回でも型を言えば型が立つ)、K=2 は §8-2 の表どおり

被覆 100% を保つ。回どうしを比べるかぎり、どちらも落とす方向には働かない。

本番経路でも同じことを確かめてある(tests/lib/analysis-title-kind-consensus.test.ts:

候補数が減らない/dropped が増えない)。

**ただし 1 つだけ落とす経路が残っている。生成が自分で申告した型を、判定 N 回が

揃って否定した場合。** 生成(claude-transport.ts)と判定(title-kind-judge.ts)は

prompt が違うので、置き換えれば型が消えることは原理的にありうる。

「起きないはず」で済ませずに数える。 manifest の

titleKindConsensus.titleKindLostFromProposal がその件数で、

0 でなければ合議は落とす方向にも働いている。

(judgeTitle の目印が立てば TITLE_KIND_SPLIT で残るので、実際に落ちるのは

判定 N 回と字面の両方が「型なし」と言ったときだけ。)

コスト(呼び出し回数を数えた)
生成の呼び出し判定の呼び出し
合議なしrequest.calls.length0(生成が同じ 1 回で型と強調語も返す)
合議あり同じruns × ceil(本数 / batchSize)。40 本・batch 40・N=5 で 5 回

manifest の titleKindConsensus.extraProviderCalls に実測が必ず入る。

まだ検証していないこと
  • 実 API の往復は未検証。 ANTHROPIC_API_KEY がこの環境に無い。

10 回とも別プロセスの Claudeに、buildTitleKindRequest が組んだ同じ prompt を渡して取った

  • 本番の生成が返すタイトル(dev/short-plan の 610 本ではなく、

claude-transport.ts が実際に生成したもの)での揺れ

  • 合議を on にした状態での end-to-end(Studio → 生成 → 選定)の実行
9. Resolve生成へ渡す時間軸と素材の不変条件(2026-08-14)

ショートの編集時間軸は字幕TCを上流にする。字幕のキューから採用区間を決め、cutFrames は半開区間 [startFrame, endFrame) として計算する。区間端をかすっただけのキューを丸ごと採用しないため、既定の採用条件はキュー長の50%以上が区間内にあることとし、TypeScriptとPythonで同じ閾値を使う。

レコーダーが30分ごとにファイルを分けても、それはeditorial cutではない。同一連続素材として扱い、物理ファイル境界を字幕TCやcutFramesへ持ち込まない。分離するのは、実測された真のgapまたはoverlapなど、連続性が壊れた場合だけである。

AngleはV1だけを正本とせず、V1〜V3を重ねたhole-free source itemsから生成する。元MP4を起点にResolveのsource timelineを作り、そこから縦のAngle・BG・字幕・音声を組む。カメラ素材の候補、実際の採用角度、字幕と音声のreadbackがそろうまで、画面上の配置だけで完成とは判定しない。


10. 詰まる 4 型と、詰まらせない書き方(2026-08-20 実測)

Part2 を素材から通したとき、格納の直前で落ちたのは延べ 118 件だった。

中身は違うのに、原因は 4 つしかない。しかも 4 つとも

「依頼書に書いた文と、機械が実際に回している述語が別物」に還元できる。

型実測何が起きたか
A. 表が小さい29 + 51依頼書は接続語 10 語と書き、検証は UNCUTTABLE_FILLER を含む判定で見ていた。題も、依頼書は「煽りの要素を 2 つ」、ゲートは「核(落差・矛盾・タブー・極端・異常)が 1 つ以上」
B. 式が違う12 + 8尺を「先頭から末尾までの幅」で測ったが、契約は発話の合計で、しかも L5 を引いたあとに床を当てる
C. 条項が無い14題の裏取り(titleConceptsOf)は機械だけが持っていて、依頼書に 1 行も無かった
D. Part1 の実測値が焼いてある430 / 29 / 59 が literal で 2 箇所。構造は同じなのに数が違うだけで止まる

どれも LLM の判断が悪かったのではない。問いの書き方が悪かった。

直し方は 1 つ。依頼書へ「述語そのもの」を封入する

散文で言い換えない。機械が持っている関数・正規表現・定数を、そのまま依頼書へ入れる。

言い換えた瞬間に A と C が生まれる。実装例: scripts/split-failing-titles.mts は

VIRAL_CORE / VIRAL_SUPPORT をそのまま帯へ書き出している。

格納の前に「全部の違反を一度に出す」監査を必ず置く

格納する関数は 1 件目で例外を投げる。**直しては走らせるを 1 件ずつ繰り返すのが、

いちばん時間を溶かす。**出荷される関数を 1 本ずつ呼んで理由を集める監査を先に通す。

npx tsx scripts/audit-editorial-titles.mts --part PartN # 題の裏取り

npx tsx scripts/audit-editorial-selection.mts --part PartN # 冒頭・尺・点・根拠

npx tsx scripts/audit-title-slate.mts --part PartN # 落差の核と字数

別の式で測り直さない。監査は必ず出荷される関数を呼ぶ(型 B はこれで消える)。

直しは「機械の権威で足りるもの」と「人の判断が要るもの」に分ける
機械でよい人が要る
根拠の引き直し(その語が載っている unit を探して足す)題の書き直し(本文にその語が無い)
冒頭を、通る unit まで送る落差の核を立てる(本文の何が落差かを決める)
床に届くまで供給の続きを足す却下へ回すかどうかの最終判断

実装: scripts/repair-editorial-titles.mts / scripts/repair-editorial-openings.mts。

人が要るものは表に書いて残す(RETITLE)。黙って直さない。

Part 固有の実測値を検算に書かない。構造で書く

40 / 39 / 79 も 30 / 29 / 59 も尺が決める。確かめるのは

「網羅は 1 分に 1 本」「橋は継ぎ目ぶん(網羅 − 1)」「合計はその和」。

順番
  • 監査 3 本を通す(全部の違反を一度に見る)
  • 機械で直せるものを直す
  • 残りだけを帯に切って並列で書き直す(split-failing-titles.mts の形)
  • 統合 → 監査 → 格納

11. 編集の立場(style)をパラメータにする(2026-08-20)
なぜ

Part2 の題の門は 落差・矛盾・タブー・極端・異常 のどれか 1 つを常に要求していた。

採用 87 本のうち 51 本が落ち、書き直しで いきなり 全部 死ぬ を入れる方向へ全体が寄った。

書き直した側は毎回「本文が言っていない 地獄 失敗 は足していない」とことわっている——

規則が煽りへ引っぱっているのに、書く側が押し返していた。

規則が間違っていたのではない。立場が 1 つしか無かった。

バズを狙う回・信頼を積む回・記録を残す回では、「良い題」も「良い構成」も別物になる。

どの立場でも変わらないもの(style が上書きできない)
  • 本文が言っていないことを題に書かない
  • 題の具体物(カタカナ 3 字以上・ラテン・数字)は選んだ本文、とくに最初のキューにあること
  • 冒頭はフィラー・接続語で始めない
  • 語の途中で切らない
  • 数量の数字は半角
立場ごとに変わるもの

宣言は [lib/shorts/editorial-style.ts](../../lib/shorts/editorial-style.ts)。値はそこが持つ。

バズ viralブランディング brand記録 record
題に要る核1 個以上不要不要
使ってよい核全部落差・矛盾・極端(タブー / 異常は使わない)なし
煽りの要素2 個以上1 個以上0
字数20(理想 15)22(理想 18)24(理想 20)
重い軸powerPhrase 25 / usefulness 20 / tempo 20usefulness 30 / payoff 20 / titlePromise 20usefulness 40 / payoff 25
出荷の床共感・実用に床なしusefulness 7 / titlePromise 7usefulness 6 のみ
テンポ532
画のカット目安4.53/10s3.0/10s2.0/10s
L5fulllightlight
余白が効いていることを数字で出す

通った題で測っても意味が無い(最も厳しい立場に合わせて書いてあるので全部通る)。

落ちた題を別の立場へ当てて初めて幅が出る。

npx tsx scripts/measure-style-latitude.mts --titles <題を 1 行 1 本>

実測 2026-08-20(viral が落とした 51 本をそのまま当てる):

バズ viral 通る 0/51

ブランディング brand 通る 51/51

記録 record 通る 51/51

0 → 51。同じ入力で採否が全部ひっくり返る。これが余白の幅である。

npx tsx scripts/compare-editorial-styles.mts --part PartN # 採用分を 3 立場で測る

選び方

どの立場を使うかはプロジェクト設定が持つ。機械が既定を決めない(§7 の棲み分け)。

checkThumbnailTitle に style を渡さなければ今日までと同じ `viral` で動く——

既定を黙って変えない。

立場を変えたら、その Part の判定は全部 stale になる。それが狙い(正本 §6)。

別の立場で付けた判断は別物である。

いま繋がっているところ / いないところ
状態
題の門(核の数・種類・字数)`style` で動く
軸の重み・出荷の床宣言はある。ゲートはまだ読んでいない
テンポ・画のカット目安・L5 の強さ宣言はある。構成側はまだ読んでいない
依頼書の規則文を表から生成titleRulesForRequest は在る。依頼書はまだ使っていない(使うと既存の判定が stale になるので、次の Part から)

規則値(自動生成)

この節の表は `lib/rules/registry.ts` から生成される。手で書き換えると `yarn rules:docs --check` が落ちる。

値を変えるときは実定数を変え、yarn rules:docs で吐き直す。

全体の既定から外す場合は、このマニュアルに根拠を書いてから authorizedBy を埋める(正本 §7)。

<!-- generated:rules:subtitle-shorts-ja -->

字幕(ショート・日本語 / `SHORTS_JA_DEFAULT`) — この表は lib/rules/registry.ts から生成される。手で書き換えない(yarn rules:docs)。

値いま全体の既定認可根拠
maxCharsPerLine10 字6〜16 の範囲manual/L1_subtitle/01_subtitle_format_jp.md §1浅尾 2026-08-20 — 13 へ広げる案を測って却下。Part1 で幅超えは 10 字 31.4% / 13 字 5.7% だが、幅超えは全部「縮約字幕 1 件が単独で 2 行に組めない」もので、繋ぎすぎではない(縮約が本編の 13 字幅で書かれているため)。9:16 の可読性を優先し 10 字を維持する。溢れは規則どおりの出力で、欠陥ではない。画面はその場に超過を出す
maxLines2 行1〜3 の範囲manual/L1_subtitle/01_subtitle_format_jp.md §1—
maxTotalChars20 字maxCharsPerLine × maxLines—行幅か行数を動かすと連動する
cpsMax18 字/秒12 字/秒manual/L1_subtitle/01_subtitle_format_jp.md §CPS 上限の上書き浅尾 2026-08-18 — Part1 本文 10,422 字。上限超過 28 件の分布 12.1〜17.3 / 中央 14.0。閾値を 12/14/15/16/18 の 5 通りで振っても SRT の長さは 31,122 / 37,541 で同一
minDuration0.4 秒0.5 秒manual/L1_subtitle/01_subtitle_format_jp.md §最小表示時間の上書き浅尾 2026-08-18 — Part1 実測: 本編 5 件・ショート 16 件が 0.5 秒未満、最小 0.196 秒。0.5 では次の発話に押されて守れない箇所が残る
maxDuration7 秒7 秒—ドキュメンタリー側。ドラマは 6.5
softMaxDuration4.5 秒——根拠が無い。 正本にも manual にも docs にも記載が無いのに lib/subtitle/shortCueRefine.ts:58 が出荷経路で使っている
minGapSec0.083417 秒2 フレーム—23.976fps 基準。24fps へ丸めない
flickerGapSec0.2 秒最小ギャップ—正本 §9 は「0 か最小ギャップ以上」。0.0834〜0.2 が正本では合法・実装では違法になる帯
leadInSec0.083417 秒2 フレーム——
leadOutSec0.5 秒0.5 秒——
maxLeadOutSec1 秒1 秒——
fps23.976024 fps23.976024 fps——
  • maxCharsPerLine: 正本は範囲を持つ。10 は葛藤ショートの既定値
  • cpsMax: 業界の目安(8/10)は劇映画・ロング向け。この案件は実測で 18 を採る
  • minDuration: 守れない規則を掲げるより、守れる値へ下げて違反を実数で数える

<!-- /generated:rules:subtitle-shorts-ja -->

<!-- generated:rules:tempo-level-5 -->

テンポ(Level 5 / `getTempoProfile(5)`) — この表は lib/rules/registry.ts から生成される。手で書き換えない(yarn rules:docs)。

値いま全体の既定認可根拠
minCutsPer10Sec3 /10s——唯一の持ち主は `lib/shorts/tempoProfile.ts`(2026-08-19 に viralityRubric の定数を廃止して一本化)
maxCutsPer10Sec6 /10s———
maxContinuousRunSec5 秒———
minCutDurationSec1 秒——23.976fps で 24 フレーム
minShortDurationSec15 秒———

<!-- /generated:rules:tempo-level-5 -->

<!-- generated:rules:virality -->

バズ 6 軸の床(`VIRALITY_THRESHOLDS`) — この表は lib/rules/registry.ts から生成される。手で書き換えない(yarn rules:docs)。

値いま全体の既定認可根拠
reviewFloor70———
priorityFloor84———
priorityPowerPhraseFloor8——軸は 0〜10。総合点の 0〜100 と混ぜない
priorityTitlePromiseFloor8———
priorityPayoffFloor7———
rejectAxisFloor3———

<!-- /generated:rules:virality -->

1 Part を 1 本のタイムラインへ並べる

short ごとにタイムラインを作らない。master-layout.json の authority.policy が

逐語で禁じている("never create one production timeline per short")。

それでも実測 2026-08-22 の実機には short ごとの composite が 30 本並び、集約は 0 本だった——

policy を読んでいるコードが 1 つも無かったからである。

判定の持ち主は [lib/shorts/reel-contract.ts](../../lib/shorts/reel-contract.ts)。

本数の床を満たしているかは yarn shorts:coverage --part PartN が見る。

床は切り上げる。「29 分あるから 29 本」で丸めると、

本編の最後の 1 分がショートを 1 本も持たないまま通る。

間隔は最後の 1 本の後ろに付けない。付けると末尾に黒が入る——

実測 2026-08-22 に、頭の空き 87 フレームで同じことが 30 本すべてに起きていた。

<!-- generated:rules:shorts-reel -->

1 Part を 1 本へ並べる(`lib/shorts/reel-contract.ts`) — この表は lib/rules/registry.ts から生成される。手で書き換えない(yarn rules:docs)。

値いま全体の既定認可根拠
shortsPerMinuteFloor1——akihiro.asao 2026-08-22 — CLAUDE.md「本編1分につき最低1候補」を宣言へ移した。それまで散文にしか無く、番人も無かった——実測 2026-08-22: Part1 は 29.9 分(床 30)に対し採用 22 本のまま誰にも止められていなかった
minShortDurationSec15 秒——akihiro.asao 2026-08-22 — 「ほんとうにおもしろかったら15秒でもいい」。15 は合法な尺<br>上書き: 一度 20 へ上げたが、意味探索の identity が変わって選定が組めなくなった(Semantic discovery identity is stale)。実務の床は adoptionMinShortDurationSec が持つ
adoptionMinShortDurationSec25 秒——akihiro.asao 2026-08-22 — 「25-35 だな目標というかスイートスポットは」。段が違えば規則も別に持つ——硬い下限(15)と同じ値にすると意味探索の identity が変わる<br>上書き: 同日の 20 秒(「下限は20秒とかかなぁ」)。その日のうちに 25 へ動いた
sweetMaxShortDurationSec35 秒——akihiro.asao 2026-08-22 — 「20-35(35秒ぐらいまでが一旦の上限値)以外はちゃんとド必要な要素を圧倒的にスマートにシンプルにした結果の内容として入れ込むべきだ」<br>上書き: 硬い上限 60 秒だけを見ていた。60 は「何があっても出さない壁」として残す
targetMeanMinSec25 秒——akihiro.asao 2026-08-22 — 「25-30 が平均値いめーじだ」
targetMeanMaxSec30 秒——akihiro.asao 2026-08-22 — 「25-30 が平均値いめーじだ」
maxShortDurationSec60 秒——akihiro.asao 2026-08-22 — CLAUDE.md「Shortsは15〜60秒」を宣言へ移した。下限だけが宣言されていて上限は散文にしか無かった——実測 2026-08-22: 4 本が超えたまま通っていた(P16 77.5s / P21 73.7s / P15 70.5s / P20 67.7s)
reelGapSec15 秒——akihiro.asao 2026-08-22 — 浅尾の指定「ビデオ間はデフォルト15秒だぞ」。測って決めた値ではない
  • shortsPerMinuteFloor: 本編 1 分につき最低何本採るか。床は切り上げ(29.9 分 → 30 本)
  • minShortDurationSec: 硬い下限。意味探索の最小尺でもあるので、変えると上流が全部古くなる
  • adoptionMinShortDurationSec: スイートスポットの下端。候補は 15 秒から在ってよいが、採るのは 25 秒から。割るなら理由を directorNote へ
  • sweetMaxShortDurationSec: 実務の上限。ここを超えるのは「削った結果それでも要る」ときだけ
  • targetMeanMinSec: 目標平均の下端
  • targetMeanMaxSec: 目標平均の上端。1 本ずつではなく全体の平均で見る
  • maxShortDurationSec: これを超えるショートは出さない。下限と隣に置く
  • reelGapSec: 集約タイムラインでショートとショートの間に空ける秒数。最後の 1 本の後ろには付けない

<!-- /generated:rules:shorts-reel -->

リアクションカットの入れ方

入れすぎるとうっとうしい。入れるかどうかだけを判定していた頃は、

マイクの有音区間で他人の turn に重なるものを全部リアクションにしていた

(実測 2026-08-20 / Part1: 候補 281 件・1 本あたり平均 4.0 件)。

間隔も密度も人物の散らばりも見ていなかった。

優先順位は 5 段([lib/shorts/reaction-placement.ts](../../lib/shorts/reaction-placement.ts))。

  • 実測の相槌が在るところを、無いところ(全体のカット)より先に採る
  • 競合したら声が長いほうを採る——実際に反応が出ている証拠が強い
  • 直前に採ったものから近すぎるなら落とす(近くにしすぎない)
  • 直前と同じ人物が近すぎるなら落とす(人物をばらけさせる)
  • 密度の上限を超えたら落とす(入れすぎない)

落としたものは理由ごとに数えて返す。黙って絞ると「全部載った」と誤読される。

下の 3 つの値は測っていない。浅尾の指示を仮の数へ置いたもので、

人手の完成ショート 66 本の DRT からリアクション区間の密度と間隔を測るまで正本ではない。

<!-- generated:rules:reaction-placement -->

リアクションカットの入れ方(`PROVISIONAL_REACTION_RULES`) — この表は lib/rules/registry.ts から生成される。手で書き換えない(yarn rules:docs)。

値いま全体の既定認可根拠
minGapSec3.5 秒——machine 2026-08-21 — 未測定。浅尾の指示「近くにはしすぎないほうがいい」を仮の数へ置いたもの。人手の完成ショート 66 本の DRT からリアクション区間の間隔を測るまで正本ではない
samePersonGapSec8 秒——machine 2026-08-21 — 未測定。浅尾の指示「人物はちゃんとばらけさせたほうがいい」を仮の数へ置いたもの
maxPer10Sec1.2 /10s——machine 2026-08-21 — 未測定。浅尾の指示「入れすぎるとうっとうしいので、いれすぎない」を仮の数へ置いたもの。適用の実測 2026-08-21 / Part1: 候補 281 → 採用 131(0.73/10s・最小間隔 3.5s・中央 11.1s、なべ 53 / ふじい 40 / あさお 38)
  • minGapSec: 仮の既定。実測していない。隣のリアクションと近すぎない距離
  • samePersonGapSec: 仮の既定。実測していない。同じ顔が続けて出ない距離
  • maxPer10Sec: 仮の既定。実測していない。20 秒に 2〜3 件の目安

<!-- /generated:rules:reaction-placement -->


モジュールの接続状況(自動生成)

手で書かない。 import グラフの実測から yarn modules:status が吐く。

文書が「本番から呼ばれていない」と手で書いていた主張は、2026-08-19 の監査で

19 件が事実と違った(docs/SOT_CONTRADICTIONS_20260819.md §3)。だから機械に測らせる。

<!-- generated:module-status -->

この表は import グラフの実測から生成される。手で書き換えない(yarn modules:status)。

本番 = app/** のルートか package.json の scripts から到達する。

テストのみ はまだ本番へ繋がっていない。「未実装」ではなく「未接続」。

モジュール状態
lib/shorts/ruleCheck.ts本番
lib/subtitle/break-levels.ts本番
lib/subtitle/telop.ts本番
lib/shorts/telopSettings.ts本番
lib/shorts/digest.ts本番
lib/shorts/emphasis.ts本番
lib/shorts/titleFirst.ts本番
lib/shorts/cutFrames.ts本番
lib/subtitle/condense.ts本番
lib/audio/recorder-timecode.ts本番
lib/analysis/candidates/editorial-gate.ts本番
lib/shorts/export-plan.tsテストのみ
lib/shorts/numbering.tsテストのみ
lib/shorts/part1-release-acceptance.tsテストのみ
lib/shorts/visual-tempo-plan.ts本番
lib/rules/registry.ts本番
lib/shorts/editorial-triage.ts本番

<!-- /generated:module-status -->

L2-16 テロップの強調設計

全 Project・全 Part に適用するグローバルルール。 色・字数・フォントは案件ごとの値で、

§4 のテンプレートに入れる。浅尾確定 2026-08-10。


1. 強調は 3 段ある

混ぜると、どこを見ればいいか分からなくなる。1 枚に 2 段以上を同時に出さない。

段何を立てるか実装先1 本あたりの目安
T1 本文通常の字幕字幕トラック全編
T2 タイトルの強調ランタイトルの中の扇情 / Tips / 面白い部分Text+(上下 2 段)1 枚につき 0〜2 ラン
T3 強調フレーズその回でいちばん効く一続きText+ 単独(本文とは別に置く)1 本につき 0〜1 回

IMPORTANT: T2 も T3 も「語」ではなく「フレーズ」(浅尾指摘 2026-08-17)。

文書のあちこちに残る「パワーワード」は名前の名残で、単位は一語ではない。

実装は前からフレーズ単位(selectPowerPhrases / PowerPhraseCandidate、字数上限 12)。

一語で足りることもあるが、それは結果であって単位ではない。

T3 を毎回出すと効かなくなる。出さない回があってよい。


2. どこを強調するか

タイトルの型から引く。 タイトルと無関係な語を大きくしない。

型は AI が入れる(浅尾確定 2026-08-12)。 字面の正規表現(judgeTitle)は検算に回す。

合流は lib/analysis/candidates/editorial-gate.ts の judgeTitleGate 1 箇所で、

LLM の申告と字面の両方が「型なし」のときだけ型なしにする(§5-4)。

タイトルの型T2 / T3 に選ぶ語
扇情数字・落差・タブー・失敗そのもの(300万 不可能 カビ)
Tips真似できる一手の核(管理表 逆算 先に決める)
目を引く異常さの中心(東大病院 12時間)

語は AI が選ぶ(浅尾確定 2026-08-12)。 タイトルを作るのと同じ呼び出しで、

順位つきのパワーワードを一緒に返させる(§5-5)。辞書は「必ず強調する語」の固定ピンへ

役割を変えた。以前の規則 1「辞書に当たった語だけ」は幻覚の防止が目的だったので、

その保証は §5-5 の 3 つの門が引き継ぐ。

強調は「囲う」のではなく「抜く」。 節や文を赤くするのは、抜いたのではなく囲っただけで、

1 枚に 2 段以上を出さない(§1)という設計とも合わない。効く部分を、いちばん短い範囲で抜く。

選び方の規則:

  • ~~辞書に当たった語だけを使う~~ → 辞書ではない(浅尾指摘 2026-08-12)。下記。

代わりに タイトルの本文に字面で存在する語だけを使う(発明しない)。

出どころは 3 つで、優先はこの順。①AI の候補(順位つき)②プロジェクトの辞書(固定ピン)

③型の目印。どこから来たかを source(ai:<順位> / lexicon / 扇情:number)に必ず残す

  • ~~感情のピークと一致する語を優先する~~ → 取り下げ(浅尾確定 2026-08-12)。下記
  • 同じ語を 1 本の中で 2 回強調しない
  • 字数上限を超える語は強調しない。 縮めて意味が変わるくらいなら強調をやめる。

切り詰めない(語の途中で切れる)。目安(T3 の字数)を超えたものは落とさず順位を下げる

  • 上限(1 枚 0〜2 ラン)に収まらないときは順位の低いものから落とす。

目安に収まるもの → 固定ピン → AI の順位 → 目印 の順で残る

型から引くもの、引かないもの(2026-08-12 に分けた)

「タイトルの型から引く」は AI の候補と目印にだけ掛かる。固定ピンには掛からない。

出どころ型が立たないとき
AI の候補出さない。 型が none なら AI 自身が候補を返さない
型の目印出さない。 型が無ければ目印を走査しない
プロジェクトの固定ピン出す。 根拠は型ではなく「案件として必ず強調すると決めた語」

以前はピンも型に依存していたが、AI 抽出へ切り替えた時点で前提が変わっている。

実測(2026-08-12): 低予算やとこの撮り方しかできひん は辞書の 低予算 を持つのに、

AI が型を none と申告したせいで強調 0 件になっていた。**固定ピンなのに型が立たないと

出ないのは設計の誤り**なので、こちら(§2)を直した。

ピンから出たランは型を持たないので kind は null になる(推測で型を埋めない)。

IMPORTANT: 辞書ではない。その回の中身から拾う(浅尾指摘 2026-08-12)

かつて規則 1 は「辞書に当たった語だけを使う」だった。枠が違った。

辞書は「あらかじめ許した語の一覧」だが、実際に要るのは

その回の中身からいちばん強い一言を拾うことである。

単位語だけでなく一言のパワーフレーズ
本数本編 1 本(約 40 分)で 20 本前後
密度2 分に 1 つくらい(density で変えられる)
間隔固めて出さない。近すぎる候補は落とす

強さの判断はコードが持たない。 何が「効く一言」かは中身の判断で、機械の閾値では

出ない。selectPowerPhrases が持つのは点数を受け取ったあとの機構

(逐語の裏取り・密度・間隔・重複・字数)だけで、点数と理由は外から与える。

辞書が守っていたものは残す。 画面へ焼く文字は、**書き起こしに実在する連続した

区間そのもの**でなければならない(findVerbatim)。ここを外すと、ASR の誤変換や

言い間違いをそのまま大きくしたり、実際には言っていない言い回しを作ったりする。

点数がどれだけ高くても、裏が取れない文字列は採らない。

ショート側(T3)は本編の拾いに縛らない(浅尾確定 2026-08-17)。

以前ここに「本編で拾った一言のうちその範囲に入るものから選ぶ」と書いていたが取り下げ。

ショートはショートとして完結する 1 本なので、その範囲でいちばん効く一続きを選べばよい。

本編の拾いと一致することはあるが、一致を条件にしない。


2-2. T2 以上は冒頭ダイジェストの構成そのもの(浅尾確定 2026-08-17)

本編から拾った T2 / T3 のフレーズを、まとめて冒頭ダイジェストへ持ってくる。

ダイジェストを別に企画しない——拾った強調フレーズの並びが、そのままダイジェストの構成になる。

そうなる理由は、両方が同じ問いに答えているからである。

強調フレーズは「その回でいちばん効く一続き」で、ダイジェストは「その回で先に見せるべきもの」。

別々に選ぶと、冒頭で見せた山と本編で強調する山がずれる。

素材本編の T2 / T3 フレーズ(逐語の裏取り済み=findVerbatim を通っている)
並び本編の時間順。組み替えない(因果が壊れる)
本数密度の設定がそのまま効く。実測 Part1(29.9 分)は既定(2 分に 1 つ)で 15 本
長さ1 本 12 字まで。フレーズ単位

切る位置のレベルの L0 と同じものを指している。 L0「強調フレーズ」は

「内側はどのレベルでも切らない一続き」で、それがそのまま T2 / T3 の素材であり、

ダイジェストの構成要素でもある。3 つの名前が同じ実体を指すので、

拾うのは 1 回でよい(docs/SHORTS_CUTTER_SUBTITLES.md §1)。

未実装: 拾ったフレーズをダイジェストの構成として出す経路。

selectPowerPhrases の出力は T2 / T3 のテロップへは流れているが、

ダイジェストの組み立てへは繋がっていない。

IMPORTANT: 選定は二段。感情は選定を動かさない(浅尾確定 2026-08-12)

かつて規則 2 は「感情のピークと一致する語を優先する」だった。取り下げる。

まず強調する語をフラットに選び、そのあとで感情の旗を立てる。

段見るもの結果
① 選定辞書・字数・重複・本数だけどの語を出すか
② 感情本文(既定)/音声イベント/音量どの見た目で出すか

逆にすると、感情の判定が付いた回だけ違う語が選ばれる。 強調だけで出す回が

ほとんどなので、選定が感情に依存してはいけない。感情が付かなくても選定は完結する。

実装は lib/subtitle/emphasis.ts の selectEmphasis(①)と attachEmotion(②)。

pickPowerWords は両方をこの順で回すだけで、順序は入れ替えられない。


3. 字幕トラックか Text+ か

判断は「動かすか」で決まる。

字幕トラックText+
位置・大きさを 1 枚ごとに変えるできないできる
縁取り・影・グラデーション実装依存で不安定できる
一括の書き出し・差し替え強い(SRT 1 本)弱い(1 枚 1 comp)
枚数が多いときの重さ軽い重い

したがって:

  • T1 は字幕トラック。 例外なし
  • T2 / T3 は Text+。 「セリフは字幕トラックだけ」という旧規則は 2026-08-10 に変更した
  • Text+ は .comp を書いて ImportFusionComp で流し込む。

InsertFusionTitleIntoTimeline はリップル挿入になるので使わない(実測 2026-08-08)


4. テンプレート(プロジェクト設定に置く)

デザインはプロジェクト設定で変えられなければならない。 他の案件でも使えるように、

値をテンプレートとして持ち、実装はテンプレートを読むだけにする。

置き場は案件のプロファイル(例 templates/shorts/katto/_base.json の text)。

スキーマは docs/schemas/shorts-profile.schema.json の definitions.telop(実装済み)。

読むのは lib/shorts/telopSettings.ts の resolveTelopSettings:

"telop": {
  "preset": "katto-yellow-outline",
  "presets": [
    { "id": "katto-yellow-outline", "label": "黄・黒縁(ディレクターズ葛藤)",
      "tiers": {
        "title":     { "font": "Noto Sans JP", "style": "Black", "size": 240,
                       "color": "#040000", "emphasisColor": "#aa0000",
                       "maxCharsPerLine": 14, "maxLines": 2 },
        "powerWord": { "font": "Noto Sans JP", "style": "Black", "size": 420,
                       "color": "#ffe000", "outline": { "color": "#000000", "width": 12 },
                       "maxChars": 5, "maxPerShort": 1, "centerY": 0.5 }
      } }
  ],
  "tiers": {}   // 1 値だけ変えたいときの浅い上書き。**同じ値を presets[] と二重に持たない**
}
  • `maxChars` は演出の一部。 5 字を超えると画面で潰れる
  • presets は複数持ち、案件ごとに選ぶ。テンプレを増やすのはコードではなく設定側。

プロファイルの telop.presets[] に足したものは組み込み(lib/subtitle/telop.ts の

TELOP_PRESETS)と同じ扱いになり、同じ id なら設定が勝つ

  • telop.tiers は段ごとの浅い上書き。色だけ変えたいときに全部書かせない
  • 組み込みに案件の実値を置かない(2026-08-12 に移設済み)。 以前は組み込みの

yellow-outline が葛藤の実値(#040000 / #aa0000 / #ffe000 / 縁 12px / 5 字)を

持っていた。いまの組み込みは plain-white と news-lower-third だけで、葛藤の型は

templates/shorts/katto/_base.json の telop.presets[](katto-yellow-outline)にある。

移設で出力は変わっていない(実測: 610 本のタイトルを layout-lines.mts ->

plan-telop.mts へ通した結果と、解決後の telop / powerWordOutline が前後で完全一致)。

組み込みへ案件の色が戻っていないことは tests/lib/subtitle-telop.test.ts が見張る

実装(2026-08-12)

上の「提案・未実装」は EditingLayoutPreset.telop として入った

(lib/nle/layout/schema.ts、画面はプロジェクト設定の editing-layout)。

行幅と行数は字幕と同じ可変域(1 行 6〜16 字 / 最大 3 行、JA_LAYOUT_BOUNDS)に

そろえてある。字幕とテロップで別の規則を持たない。

IMPORTANT: 動き(アニメーション)は装飾ではなく、Text+ を使う理由そのもの

§3 は「判断は動かすかで決まる」と書いている。**動かさないなら字幕トラックで足り、

Text+ の重さ(1 枚 1 comp)を払う意味が無い。** 逆に言えば、Text+ に載せた段が

静止しているなら、それは設計の誤りである。

だから telop.tiers.*.animation を持ち、全段が `none` の設定は保存できない。

値意味
in / outnone / fade / pop / slide / typewriter と長さ(フレーム。秒で持たない)
slide の fromleft / right / top / bottom。slide 以外は向きを取らない
minHoldFrames動きが終わってから最低これだけ静止させる

`minHoldFrames` が要る理由。 出て消えるだけで止まらないと、いちばん目立たせたい語が

いちばん読みにくくなる。読めない強調は強調ではない。

`typewriter` は T3 パワーワード単独のときだけ。 T2 は上下 2 段の中の

強調ランなので、1 文字ずつ出すと本文の組みが崩れる。

これらの既定値は採寸していない。 現物のテロップから測ったら差し替える。

hold の inFrames: 2 / outFrames: 6 も同様で、出典は無い。

組み立ての向きは変わらない。**本文は全部 T1(字幕トラック)で、Text+ は

「特に目立たせたいところ」にだけ載る。** Text+ を本文の代わりに使わない。

パワーワードを感情で出し分ける(2026-08-12)

テレビのテロップはここで差が付く。同じ「一語を大きく出す」でも、笑いの直後と

張った声とで色も大きさも変える。telop.powerWordVariants に変種を持つ。

IMPORTANT: 測れない感情を作らない。 変種は必ず evidence(どの信号で選ぶか)を

持ち、その信号が無い区間では選ばれない。喜び 怒り のような名前だけの感情を

並べると、当てる根拠が無いまま人の勘で塗ることになり、再現できなくなる。

evidence.kind何で当てるか要るもの
`text`(既定)字幕の本文。語そのもの(scope: word)か前後の文(scope: context)字幕だけ
audio-eventScribe の tag_audio_events([笑い] など)からの距離音声の工程
loudness語ごとのマイクレベル。話者ごとの基準からの差(絶対 dB で書かない)音声の工程
manual人が指定する—

既定は `text`。 音声や同期に依存しないので、字幕さえあれば動く。

audio-event と loudness は音声側の工程が通ってから足す。

パターンはプロジェクトが持つ。 コードが「怒りとはこういう語」と決めると、

案件が変わるたびに嘘になる。壊れた正規表現では当てない(設定側の誤りが

静かに効くほうが危ない)。

audio-event は点であって区間ではない(開始位置のばらつき中央 0.33 秒。

manual/L1_subtitle/13_speaker_attribution.md)。だから withinSeconds で幅を持たせ、

corroboratedOnly で「複数チャンネルで裏が取れたものだけ」を選べるようにしてある。

取れないものは `manual` にする。 人が指定したという事実を記録に残すほうが、

測ったふりをするより安い。

優先順(priority)は同点を作れない。同点だとどちらが出るか決まらない。

実データで 1 回通した(Part11、2026-08-12)

候補はこのセッションの Claude が書き起こしを読んで出した。API 経由ではない。

実測
素材Part11 / 1,572 語 / 41.7 分
提案72 件(うち捏造を 2 件わざと混ぜた)
逐語の裏取り70 / 72 = 97.2%
落選仕込んだ捏造 2 件だけ(体にカビが生えました / 全員が死を覚悟した。どちらも score 99)
採用20 本。分あたり 0.48(= 2.08 分に 1 つ)
分布00:26 〜 40:37。固まりなし

点数が最高でも、書き起こしに無い文字列は通らない。 守りが実データで効いた。

採用された 20 本(抜粋):

00:26 30日間の戦い    14:22 背水の陣        21:41 カビ生えたしな
04:24 住み込み        14:47 300万とか       22:05 東大病院まで
10:55 超危ないやり方  16:19 破滅する        29:47 撮り直し不可能カット

この 1 回で欠陥が 2 つ出た(語境界の照合・句読点の混入)。どちらも直して

lib/subtitle/verbatim.ts へ集約した。合成データでは出なかった。

実装(lib/subtitle/emphasis.ts)

§2 の 4 規則をそのまま実装した。pickPowerWords は選んだ語と、**選ばなかった理由の

内訳**を返す。0 件だったときに「なぜ 0 か」が分かる形にしてある。

  • 辞書に当たらなければ 0 件を返す。0 件は失敗ではない。 正本が「出さない回が

あってよい」と書いているとおりで、埋め草を作らない

  • 測っていない語に `loudness` の変種を当てない。 未指定を 0dB とみなすと、

静かな語が「基準どおり」として拾われる

日本のテレビのテロップから採る型(要調査)

字幕とテロップは別物として設計されている。押さえる点だけ挙げる。

  • 文字は太く、縁を厚く。背景がどんな絵でも読めることを最優先にする
  • 強調は色より大きさが先に効く。色は 1 本の中で 1 系統に絞る
  • 出しっぱなしにしない。 パワーワードは声に当てて、言い終わりで消す
  • 位置は固定しない。人物の顔と被らせない

この節は文献調査をしていない。 実際の番組から採寸して数値を入れる作業が残っている。

現状の数値(黄 #ffe000 / 縁 12px / 5 字)は浅尾の指示を書き写したもので、実測ではない。


5. 現行の実装(2026-08-12 更新)
段実装状態
T1字幕トラック実装済み
T2tools/resolve-adapter/bake_titles.py → .comp実装済み。 強調は範囲付き。ただし1 comp = 単一 RGB なのでラン内の色分けはできない(下記)
T2動画レンダラ(ffmpeg / ASS)ラン内の色分けは実装済み、供給元が未接続。 lib/render/ffmpeg/ass.ts の formatAssEmphasis が区間ごとに escape してから \c を外側で組む。契約の口は RenderRequest.hook.emphasis。`lib/production/spec` の `hookOverlay` がまだこの値を作らない
T3bake_titles.py → <part>_<id>_power.comp実装済み。 字数と回数は fitsPowerWord が決める。縁は --outline(下記の理由で既定 off)
強調語の選定(L2-15 段 3)lib/shorts/emphasis.ts の planEmphasis実装済み。 本番経路は lib/analysis/candidates/editorial-gate.ts の evaluateEditorialGate(pipeline.ts:482)と scripts/plan-telop.mts(bake_titles.py から)と app/api/short-titles
強調語の抽出(AI)claude-transport.ts の emphasisCandidates実装済み。 タイトルを作る tool schema に順位つきの候補を足した。新しい経路は作っていない(§5-5)
既存 610 本への強調語の付け直しscripts/judge-title-kinds.mts実装済み。 型と強調語を同じ 1 回で取る。出力は plan-telop.mts の items[].emphasisCandidates にそのまま渡せる
タイトルの型の判定(AI)lib/analysis/candidates/title-kind-judge.ts実装済み。 tool schema で titleKind / reason / confidence を必須にして 1 回で束ねて判定する。呼び出しは lib/ai/claude.ts の callClaudeMessage、語彙は contracts.ts の DECLARED_TITLE_KINDS。新しい経路は作っていない
型の合流(AI + 字面)editorial-gate.ts の judgeTitleGate実装済み。 落とす/残すの判断は従来どおり。変えたのは「段 3 へ渡す型」で、judged(字面だけ)から kinds(申告 + 字面)にした。出どころは titleKindSource(llm / markers / llm+markers / none)に残す
既存 610 本への型の付け直しscripts/judge-title-kinds.mts実装済み。 --plan-dir で構成案から集め、--out に書く。出力は plan-telop.mts の items[].titleKind にそのまま渡せる
テンプレートtext.telop(プロファイル)実装済み。 lib/shorts/telopSettings.ts が組み込み + telop.presets を合成する。スキーマは docs/schemas/shorts-profile.schema.json の definitions.telop

T2 の行組みは lib/subtitle/breakPoints.ts の layoutLinesDetailed を通る

(scripts/layout-lines.mts 経由)。幅はプロファイルのパラメータで、規則違反は出さない。

5-1 目印は語ではない

judgeTitle の目印(TITLE_KIND_MARKERS)は「型が立つか」を見る正規表現なので、

当たった範囲をそのまま赤にすると語にならない。 実測 2026-08-12(Part1 の先頭 20 本):

直す前直した後
1.5億 が 1. と 5億 に割れる1.5億
終わってる が 終わ(活用語尾の分断。L1-01 §4 の禁則)終わってる
スパイダーマン2で から 2で出さない(1 字の数字は語にならない)
から しかない が赤になり T3 まで昇格出さない

emphasis.ts が入れた門は 3 つ。

  • 型の印であって語でないものは候補にしない。 gap(なのに・実は)/ causal(から・ので)/

condition(ないと)を外す。§2 の型別表が挙げるのは 数字・落差・タブー・一手の核・異常さの中心 だけ

  • 数字は 1 つにまとめ、単位を 1 つ取り込む(§2 の 300万 12時間 を語として扱うため)。

活用の途中で終わるものは続くひらがなを 3 字まで取り込む

  • ひらがなだけのランは出さない。 やばい えぐい のように、ひらがなだけでも強調したい語は

プロジェクトの辞書に登録する(§2 規則 1「辞書に当たった語だけ」)

単位の一覧は実データから出す(2026-08-12 に直した)

門 2 の単位表(NUMBER_UNITS)が薄く、数字が単位の手前で切れていた。

610 本のタイトルで数字の直後に来る文字を全部数えて作り直した(頻度は

時17 / 人13 / 日13 / 分13 / 万12 / 週間6 / ヶ月6 / 歳6)。

直す前直した後
予定22+時 / 12時と20+時22時 12時 20時
19+歳 21+歳19歳 21歳
1週 ← 1週間 / 2週 ← 2週間1週間 2週間
5時起き から 1 字の 5 → FRAGMENT で消える5時
4リットル 3列 1周 1ヶ月 2カメ 600キロ が数字 1 字で消える語として出る

一致は長いものから当てる(resolveUnit)。並び順に依存させない。

で と まで は単位ではないので採らない(スパイダーマン2で の 2で を出さないため)。

効き(出荷経路 `layout-lines.mts` -> `plan-telop.mts` で実測): 数字だけのラン 19 → 1。

変わったタイトルは 42 本で、42 本とも「数字が単位まで届いた」方向の変化。逆向きは 0 件。

5-2 まだできないこと
  • T2 のラン内色分け(Text+)はできない。 1 comp = 単一 Text+ = 単一 RGB なので、

行の一部だけを赤にできない。いまはランがある行は行全体が `emphasisColor` になる。

bake_titles.py はそうなった行数を毎回数えて出す。

直すには sMerge で Text+ を重ねる。未着手。

ffmpeg / ASS の側は同じことができる(formatAssEmphasis)

  • Text+ の縁は入力名だけ実測、値は未検証。

Enabled2 ElementShape2 Thickness2 Red2/Green2/Blue2 という綴りは、

要素番号を後ろに付ける規則を Blackmagic 同梱の

Support/Developer/Fusion Fuse/FuseExamples.comp(Enabled2 Type2 Red1 Alpha3 が実在)で、

基底名を fusionsystem.dll の文字列(ElementShape Thickness Softness JoinStyle)で確かめたもの。

`ElementShape2 = 1` が「Outline」かどうかは画で確かめていない。

Resolve で縁付きの Text+ を 1 つ作って ExportFusionComp し、突き合わせるまで既定は off

  • 感情のピーク(笑い・驚き・沈黙の直後)との一致(§2 規則 2)は未実装。

段 3 の入口にその時刻が渡っていない

  • 段 3 が使うテロップの型は、本番の門ではまだ既定(`plain-white`)。

evaluateEditorialGate に telop を渡す口はあるが、lib/analysis/candidates/pipeline.ts が

プロジェクトのプロファイルを持っていないので渡していない。字数の上限だけが影響を受ける

  • 目印(`TITLE_KIND_MARKERS`)が語でない範囲を当てる欠陥が 3 型残っている(§5-4 の precision)。

何を【作るか】 / デー【ゼロ】 / 血の【飛び】方。

§5-1 の門 3 つは数字と機能語を止めるもので、目印の側は直していない。未着手

5-3 辞書を実データから作り直した(2026-08-12)

lib/katto/generationSettings.ts の emphasisKeywords は 4 語

(低予算 映画制作 グーチョキデッド 葛藤)だった。**うち 3 語は 610 本のタイトルに

1 度も出ない**(映画制作 は書き起こし 229,379 字にも 1 回だけ)。効いていたのは 低予算 の 1 語。

KATTO_EMPHASIS_LEXICON として 36 語へ作り直した。候補は実データから出す。

610 本のタイトル(dev/short-plan/*.json の shorts[].title)を BudouX の文節と

同一字種のランに割って候補を作り、次を全部満たすものだけ採る。**採用条件と根拠は

語ごとにソースへ書いてある。**

E1タイトル 2 本以上に出る(1 本だけの言い回しは辞書にしない)
E2書き起こしに 3 回以上出る(実際に話された語であることの裏付け)
E3§2 の型別表に当たる。番組の日常語(映画 現場 監督 撮影 予算)は「異常さ」ではない
E4部分一致で語を割らない(プロ←プロデューサー / 古着←古着屋 / 監督←撮影監督)

頻度だけで採ると普通の語が混ざる。 実測: 頻度上位は した った から 現場 映画 で、

E3 を課さないと辞書が「番組の目次」になる。逆に E1 を 1 本へ緩めると候補が 237 → 758 に膨らみ、

増える語は 未来 苦手 免許 飯の のような一回きりの一般名詞だった。E1=2 が境目。

例外は 3 つで、どれも語ごとに理由を書いた。①lib/katto/dictionary.ts に既にある固有名詞は

E1 を 1 本へ緩める(裏付けが既にあり、辞書に無い固有名詞をここで作らないための縛りでもある)。

②正本が名指しした語(§2 の 逆算、§5-1 の やばい)。③E4 の保護形

(背水→背水の陣、廃墟→廃墟ドットコム、イカゲーム→イカゲーム3)。

保護形は resolveOverlaps が長い方を採る性質を使って割れを止めるもので、

タイトルに実在する表記をそのまま使うだけ。新しい表記を決めていない。

効き(出荷経路 layout-lines.mts -> plan-telop.mts で実測)
辞書 4 語辞書 36 語
強調が付いたタイトル(全 610 本)167(27.4%)185(30.3%)
強調が付いたタイトル(Part1 先頭 20 本)8(40%)9(45%)
強調ランの総数208246
うち辞書由来139
うち目印由来207207(増減なし)
T3 が出た本数163180

目印由来のランは 1 本も増減していない。辞書だけが動いた。

辞書由来 39 本を全部目で読んで、断片・活用語尾の分断・固有名詞の内部での切れは 0 件。

天井は辞書ではなく型だった(2026-08-12 に外した)

辞書を何語にしても 41.8% を超えなかった。 planEmphasis は kinds が空だと

NO_TITLE_KIND で先に返すので、judgeTitle が型を読めない 355 本(58.2%)は

辞書に当たる語を持っていても 0 件のままになる。

上限の実測: E3/E4 を外した 210 語(タイトル中の内容語ほぼ全部)を辞書に入れても

237 本(38.9%)で、型が立つ 255 本(41.8%)が天井だった。

この天井は §5-4 の「型を LLM に切り替える」で外した。

型が付いたタイトルは 255(41.8%)→ 553(90.7%)。ただし

強調が付いたタイトルは 30.3% → 36.4% までしか動かない。理由は §5-4 の表。


5-4 型を AI に切り替えた(浅尾確定 2026-08-12)

正本 §5 の注記「型を人/LLM が入れる」を実装した。判定は LLM が先、字面は検算。

どこを直したか
  • 本番の生成経路では LLM が既に `titleKind` を申告していた

(claude-transport.ts の tool schema で required)。

ところが evaluateEditorialGate は段 3 へ judged(字面だけ)を渡していたので、

申告があっても字面が黙るタイトルでは NO_TITLE_KIND で 0 件になっていた。

judgeTitleGate に kinds(申告 + 字面)を持たせ、そちらを渡すよう直した

  • 既存 610 本には申告が無い(titleKind を入れる前に作られた)。

scripts/judge-title-kinds.mts で後から付ける

  • `judgeTitle`(字面)は捨てていない。落とす/残すの判断は従来どおりで、

両方が「型なし」のときだけ型なし(judgeTitleGate の既存設計)

  • 型の出どころを titleKindSource(llm / markers / llm+markers / none)に記録する。

根拠(reason)と確信度(confidence)は tool schema で必須

効き(出荷経路 layout-lines.mts -> plan-telop.mts で実測。610 本)
前単位の修正だけ単位 + AI の型
型が付いたタイトル255(41.8%)255(41.8%)553(90.7%)
型が付かない355(58.2%)355(58.2%)57(9.3%)
強調が付いたタイトル185(30.3%)198(32.5%)222(36.4%)
強調ランの総数246270295
うち目印由来207231231(AI では 1 本も増えない)
うち辞書由来393964
T3 が出た本数180191212
数字だけのラン(欠陥)1911

目印由来のランは AI の型を入れても 1 本も増えない。 当然で、judgeTitle が

読まなかった型とは「その型の目印がタイトルに無い」ということだから、型だけ足しても

目印は発火しない。AI が型を入れて動くのは辞書の側だけ。

型の出どころ本数強調が付いた割合
llm(字面は黙った)298248.1%
llm+markers(両方が型あり)23118981.8%
markers(申告は none、字面が拾った)24937.5%
none(どちらも型なし)5700%

新しい天井は辞書。 型が付いた 298 本のうち 274 本は、型が付いても

36 語の辞書に当たる語を 1 つも持っていない。「AI にしたら 90% 強調が付く」ではない。

この天井は §5-5 で外した(辞書を増やすのではなく、語そのものを AI に出させる)。

AI と字面の食い違い(全 124 件。無作為 30 件を目で読んだ)

食い違いの定義は両方が何か言っていて中身が違うもの。

字面が黙っただけ(298 件)は食い違いに数えない。

件数
split(両方が型ありと言い、どの型かで割れた)100
markers-only(AI が none、字面が型あり)24

等間隔に抜いた 30 件を 1 件ずつ読んだ結果:

件数中身
AI が正しい14字面の当たりが機能語だったもの。から(わからない の中の から まで拾う)/ 作る(何を作るか の 作る)/ 絶対に / なければ。§2 の「一手の核」ではない
字面が正しい3AI が none と言ったが型がある(3列シートのインパラが現場に来た / なぜ監督よりプロデューサーが重いか)と、型の取り違え 1 件
どちらも正しい13数字があり、かつ状況が異常。union で両方入るので争いにならない

AI が正しいと決めつけていない。3 件は字面が正しかった。

ただしその 3 件で強調は 1 つも壊れていない。設計上、

①none の申告は型を上書きしない(字面の型がそのまま残る)

②割れたときは union なので、どちらの型の目印も生きる —— の 2 つで吸収される。

precision / recall

別に測る。「AI にしたら増えた」は成果ではない。

値測り方
強調の precision(全体)27/30 = 90%全 295 ランから等間隔に 30 件抜いて目で読み、L2-16 §2 と L1-01 §4 に照らす
強調の precision(今回増えた分)67/67 = 100%変わった 67 ラン(単位 42 + 辞書 25)を全部読んだ。断片・活用語尾の分断・固有名詞内部の切れ 0 件
型の precision(食い違いのみ)14/17 = 82%上の 30 件のうち、どちらかが明確に正しい 17 件で AI が正しかった割合
型の recall99.0〜99.6%型が付かなかった 57 本を全部読み、型があるのに落ちたものを数えた。明確 2 件(メインキャストの衣装はメルカリ / じゃんけんの机は廃墟で拾ったもの)+境界 4 件
強調の recall58.7% → 70.5%強調 0 件の 388 本から 25 本を等間隔で抜き、§2 に当たる語があるかを目で読んだ。明確な取りこぼし 6/25(24%)→ 全体で約 93 本。分母 222+93=315

残っている precision の欠陥 3 件は今回の変更で入ったものではない(前も後も同じ本数)。

何を【作るか】(作る の目印 + ひらがな 3 字の取り込み)、

デー【ゼロ】(デーゼロ の内部で切れる)、血の【飛び】方(連用形の分断)。

どれも目印(`TITLE_KIND_MARKERS`)側の問題で、辞書でも型でもない。未着手。

測っていないもの(§5-4)
  • `bake_titles.py` はまだ `titleKind` を渡していない。plan-telop.mts の口は開いたが、

Python 側が構成案に申告を持たないので、Resolve へ焼く経路は当面 undeclared(= 従来どおり)


5-5 強調語も AI に切り替えた(浅尾確定 2026-08-12)
なぜ辞書だ? 内容を毎回、構成やタイトルを作る際に AI で抽出したらいいだろ。 パワーワードランキングをな。

§5-4 で型を AI にしたが、強調は 36.4% で止まった。天井が「型」から「辞書」へ移っただけで、

型が付いた 298 本のうち 274 本は 36 語の辞書に 1 語も当たらなかった。

辞書は静的なので、新しい Part の内容に追いつけない。

どこを直したか
  • 既存の LLM 呼び出しに相乗りした。 claude-transport.ts の tool schema に

emphasisCandidates([{ text, rank }]、0〜3 件、required)を足しただけ。

型(titleKind)を足したときと同じやり方で、新しい経路は作っていない

  • 既存 610 本には scripts/judge-title-kinds.mts が後付けする。

型と強調語を同じ 1 回で取る(別々に聞くと「その型だと言った根拠の語」と

「大きくする語」が別の呼び出しの産物になる)

  • 辞書は捨てていない。役割を変えた。AI の候補が主で、辞書は

「必ず強調する語」の固定ピン。上限で削るときは辞書 → AI の順位 → 目印 の順で残る。

ひらがなだけの語(やばい)と、境界の門を通らない形(低予算|映画)は

いまも辞書からしか出せない

「囲う」を止める(浅尾指示 2026-08-12「ピックアップするだけだぞ」)

最初の実装は節を返させていた。628 ラン中 281 件(37.4%)が 6 字以上で、

強調のある行の 27.1% はランが行の 8 割以上を覆っていた。

§5-2 のとおり 1 comp = 単一 RGB なので、それは「行が丸ごと赤い」ということで、

抜いたのではなく囲っただけになる。直したのは 2 か所。

  • prompt を「抜く」側へ書き直した。 contracts.ts の emphasisContractPrompt。

*Extract, do not enclose* を柱にして、実データから採った対比例を置いた

(齟齬を埋められるかが俳優の実力 -> 齟齬、血の飛び方を夜に100回練習した -> 100回)。

助詞・接続・活用語尾を両端から落とすことも明示した。

文言は 1 か所に置いて生成経路と後付け経路で共有する(2 箇所に書くと必ず食い違う)

  • 字数はプロジェクト設定から prompt に入れる。`lib/analysis/` に数を焼かない。**

目安は T3 の maxChars、上限は T2 のランの字数。葛藤なら 5 / 14。

渡さなければ prompt から字数の行が消えるだけで、既定値は作らない

(案件ごとに違う値を共有コードに焼くと、他の案件で静かに間違う)

prompt だけに頼らない。実装側にも同じ目安を置く。

  • 超えた候補は切り詰めず、順位を下げる(PRIORITY_OVER_TARGET)。

切り詰めると語の途中で切れる(L1-01 §4)

  • 重なりの解消でも目安を先に見る。 長さだけで決めると、

齟齬を埋められるか が 齟齬 を飲み込んで節が勝つ。

ただし固定ピンは対象外(廃墟ドットコム 背水の陣 イカゲーム3 は

長いことに意味がある登録なので、短い形に負けさせない)

  • 目安に収まる候補が 1 つも無ければ、長いものが残る。落とすのはハードの上限だけ

(OVER_MAX_CHARS)。「短くしたら件数が減った」を避けるため

発明をどう止めるか(門は 3 つ。lib/shorts/emphasis.ts)

正本 §2 規則 1 の趣旨は幻覚の防止なので、辞書を外すなら別の方法で同じ保証を付ける。

門落ちたもの落ちた理由コード
① タイトルの本文に字面で存在すること。位置まで確定できること0 件NOT_IN_TITLE
② 語の境界で切れていること30 件SPLITS_A_WORD
③ 既存の門を全部通ること(断片・ひらがなだけの機能語・字数上限・1 枚 0〜2 ラン・1 本 0〜1 パワーワード)—従来どおり

門 ② は行組みの正本を使い回す。 切ってよい位置は

lib/subtitle/breakPoints.ts の findBreakPoints が返すものを使う(音節の切れ目は除く)。

同じ禁則を 2 箇所に書くと片方だけ直る。正本との差は 2 つで、どちらも行頭の都合。

  • ランの始まりは行頭と同じ条件でよい(文頭・約物の直後・正本が言う位置)
  • ランの終わりは行頭の条件を当てられない。行は助詞で始められないが、

赤は助詞の手前で終わってよいし、逆算|した 100回|練習 のように後ろへ語が続いてもよい。

ここは禁則に当たるものだけ落とす(数を割る/カタカナ複合語の内部/英字の内部/

登録語の内部/送り仮名・活用語尾の内部)

門 ② は 3 回作り直した。実測しないと外し方が分からなかった。

作り方落とした数何が壊れたか
両端「同じ字種が続いたら不可」131100回(`回練習)カット割り(りを`)まで落ちた。大半が正しい語
両端 findBreakPoints だけ134`150億。 22時、 東大病院まで 逆算した` が落ちた
始まりは正本・終わりは禁則だけ30`決めとかな(とか を助詞表に入れていた)と 終わってる(って` を入れていた)が通った。両方を表から外して塞いだ

助詞表の教訓: 補助用言の頭になれる字は 1 字でも助詞と見なさない。

を が は へ は送り仮名に出ないので無条件、に で と も の や か ね よ は

その次が語頭(漢字・カタカナ・数字・約物・文末)のときだけ助詞と見る。

効き(出荷経路 layout-lines.mts -> plan-telop.mts で実測。610 本)
型だけ AI(§5-4)強調語も AI(囲う版)強調語も AI(抜く版)
強調が付いたタイトル225(36.9%)497(81.5%)508(83.3%)
強調ランの総数298751760
うち AI 由来0628598
うち辞書由来675874
うち目印由来2316588
T3 が出た本数215350469
6 字以上のラン(目安 5 字超)—281(37.4%)77(10.1%)
行の 8 割以上を覆うラン—171(27.1%)52(8.4%)
幻覚(本文に無い語)—0 / 7290 / 701(0.0%)

短くして、かつ付く割合も上げた。 節を返さなくなったぶん 1 枚に 2 ランが入るようになり、

T3 も 350 → 469 に増えた(節は T3 の 5 字に入らないので、以前は捨てられていた)。

辞書由来が 58 → 74 に増えたのは、型に依存しなくなった固定ピン(§2)が効いている。

※ §5-4 の表の 36.4% と 36.9% の差は、型の判定を測り直したため(llm 298 → 290 本)。

「囲う版」と「抜く版」は prompt を変えて判定をやり直しているので、型の内訳も少し動く

(llm 290 → 303 本)。同じ 610 本・同じ出荷経路で測っている。

precision / recall
値測り方
幻覚率0 / 701 = 0.0%AI が返した全候補を出荷経路で字面照合。judge-title-kinds.mts の生の照合でも 0 件
強調の precision変わった 363 本を全件目視断片・活用語尾の分断・固有名詞内部の切れ・数字の単位落ちを数えた。見つけた 4 件は全部その場で直した(下)
強調の recall約 93%強調 0 件の 102 本を読み、§2 に当たる語があるかを数えた。前回(113 本)と同じ測り方
辞書との比較AI は辞書の語の 50%(38/76)しか挙げない36 語の辞書がタイトルに実在する 76 箇所を、AI が候補に挙げたか

目視で見つけて直した 4 件(どれも回帰テストにした)。

出たもの何が壊れていたか直し方
1本1万円 → 1本1万単位が 2 つ続く形で 円 が落ちた単位の取り込みを繰り返す。目印の側にも同じ規則を使う(この件は目印由来だった)
4日間 → 4日日間 が単位表に無く 間 が取り残された日間 年間 を追加。末尾が単位の一部なら長い方へ伸ばす
プチっと切れる → プチっ促音の直後の と を助詞と誤認っと は助詞と見ない(擬態語)
1周回る → 1周回上の単位修正が 回 を単位と誤認単位の次がひらがななら取り込まない(用言の語幹)

辞書を残す根拠が実測で出た。 AI は 低予算映画は「英語でしか成功しえない」 で

成功しえない を挙げ、低予算 を挙げない。通訳が入ってから現場が変わった では

候補を 1 つも出さない。**AI は「その文で目立つ言い回し」を選び、

「この案件で毎回赤くしたい語」を選ばない。**固定ピンはその差を埋めるためにある。

残っている欠陥と未着手
  • 漢字・カタカナの複合語はまだ割れる。 60着が200リットルのゴミ袋2つ から ゴミ が出る

(ゴミ|袋 は字種が変わるので、字面の門では語の切れ目と見分けが付かない)。

直そうとして測ったが割に合わなかった。 ランの終わりにも

「正本が切ってよいと言う位置であること」を課すと、一千万|借りて 八百万|消えた

三時間|遅れて 四万|以上 半年|契約 が落ちて 13 本が強調を失い 83.3% → 82.8% になる。

捕まえられるのは ゴミ袋 型だけなので入れていない。未着手

  • 残る 10.1%(77 ラン)は目安を超えている。 目安に収まる候補が他に無かったもので、

設計どおり落とさずに残している(直りかけている 英語できひん)

  • §5-4 から残る目印側の欠陥 3 件(何を【作るか】 デー【ゼロ】 血の【飛び】方)は未着手。

実測でも デーゼロに12時間かけて確認した から ゼロ が出ている

  • 門の入口の辞書が 2 つある。 plan-telop.mts は KATTO_EMPHASIS_LEXICON(36 語の

厳選)を渡すが、evaluateEditorialGate は Context Bundle の固有名詞辞書全部を渡す。

ピンを型から外したので、この差が出力に出るようになった。どちらを固定ピンにするかは未決

測っていないもの
  • `--declarations` を使わない生の LLM 呼び出しは、この環境では 1 度も走っていない。

上の 610 本の型と強調語は、buildTitleKindRequest が実際に組んだ system prompt

(プロファイルから引いた 5 / 14 が入ったもの)を書き出して、別プロセスの Claude

(Sonnet、40 本 × 16 バッチ = 本番と同じ束ね方)に判定させ、

parseTitleKindDeclarations(同じ schema)と judgeTitleGate(同じ合流)と

planEmphasis(同じ門)へ通して測った。API 呼び出しそのものの往復は未検証

  • `bake_titles.py` はまだ `emphasisCandidates` を渡していない。plan-telop.mts の口は

開いたが、Python 側が構成案に候補を持たないので、Resolve へ焼く経路は当面 従来どおり

  • 順位を T3(パワーワード)の選定には使っていない。T3 はいまもいちばん長いランを採る
ディレクターズ葛藤 — 登録ポインタ

実体フォルダ: D:\zFilms Dropbox\z Films\Directors'Katto\


制作資料の所在(Dropbox 直参照。ダウンロード不要)

D:/zFilms Dropbox/z Films/ 配下。

用途パス
クレジット(英)FeatureFilm_Allez!/Assets/Credits/Credits.jpg
クレジット(日)FeatureFilm_Allez!/Assets/Credits/Credits_Ja.jpg
日本語の脚本FeatureFilm_Allez!/Documents/Allez_Script_Ja_1030.pdf
企画書(日)FeatureFilm_Allez!/Documents/Allez!_Deck_Ja_1030.pdf
あらすじ(日)FeatureFilm_Allez!/Documents/Synopsis_Ja_1019.pdf
請求書(漢字表記の一次資料)FeatureFilm_Allez!/Bill/
キャスト(役名_俳優名)FeatureFilm_Allez!/CastFiles/
ロケ地FeatureFilm_Allez!/Documents/Location_Scounting - コピー、Location_Share
別系統の書き起こしDirectors'Katto/Contexts/*_書き起こし_*.json / .txt(13 回中 5 本)
校正済み字幕Directors'Katto/Subtitles/*_claude.srt(8 本。.tmp. は無視)
本編の最終音声Directors'Katto/Final/Audio/PartN.mp3
DRP(時間軸の正本)Directors'Katto/drp/ディレクターズ葛藤.drp

クレジットは縦 24,516px の長い画像。 Read でそのまま開くと縮小されて読めない。Python (PIL) で帯ごとに切り出して拡大する。

Bill/ の内訳: Casts/(キャスト別)、Makeup/、Recording_Jo/、Special Effects_Ohira/、LuckyLighting/、CubeFilm/、Audio_SoundFactory/、ChibaSportsPlaza/、金井ペイント.pdf、フード請求書 黒岩.pdf。業者名と個人名が漢字で書かれている唯一の材料。


概要

映画『グーチョキデッド』(英題: Rock, Paper, Death!) の制作過程を、素人 3 人チーム(あさお / ふじい / なべ)が振り返るトークドキュメンタリー YouTube シリーズ。

フル尺本編(Part1〜Part8+)、ショート(1-1〜3-5)、英語字幕、Podcast を展開。


主要 MD ファイル(実体)
ファイルパス役割
プロジェクト概要・固有名詞辞書Claude/PROJECT_REFERENCE.md(Dropbox: Directors'Katto/Claude/PROJECT_REFERENCE.md)番組概要・出演者・作品・固有名詞辞書(マスター)
字幕生成ルールClaude/SUBTITLE_RULES.md(Dropbox: Directors'Katto/Claude/SUBTITLE_RULES.md)Katto 固有の字幕運用(1行13字, ショート10字, Step1-4 ワークフロー)
コンテンツ生成指示Claude/CONTENT_GENERATION.md(Dropbox: Directors'Katto/Claude/CONTENT_GENERATION.md)スプレッドシート構造・SNS 配信設定・ショート企画
字幕処理マニュアルSUBTITLE_MANUAL.md(Dropbox: Directors'Katto/SUBTITLE_MANUAL.md)パイプライン詳細・スクリプトパス・実行コマンド
英語字幕ガイドSubtitles/Subtitles_En/README_EN_SUBTITLE_INSTRUCTIONS.md(Dropbox: Directors'Katto/Subtitles/Subtitles_En/README_EN_SUBTITLE_INSTRUCTIONS.md)英語字幕の指示書

外部リソース(このプロジェクトのユニーク URL)
種別URL / ID
Notion DB(ショート + SNS 投稿管理)https://www.notion.so/zfilms/Subtitle_Shorts-35dbd1f2cc818097b45ad8eba877b3b1
Google Spreadsheet(コンテンツ生成)https://docs.google.com/spreadsheets/d/1kK14gdwjUd2myIpvbgLtZlELUHEUlyQOxeND-P03hh0/edit
Spreadsheet ID1kK14gdwjUd2myIpvbgLtZlELUHEUlyQOxeND-P03hh0
YouTube チャンネル(実体側参照)
Spotify Podcast(実体側参照)
Dropbox 共有名z Films Dropbox

環境変数 / API キー参照
値は記録しない。環境変数名のみ。
環境変数名用途設定済み
ELEVENLABS_API_KEY書き起こし(Scribe API)✅ User scope
ANTHROPIC_API_KEYClaude API(多面修正)都度 export

参照方法は [07_elevenlabs_workflow.md](../L1_subtitle/07_elevenlabs_workflow.md) 参照。


SubtitleMaker からの差分(重要な上書き)

L1 / L2 マスターからの主な逸脱:

項目マスター(L1/L2)Katto
1 行の文字数(フル尺)13 文字同じ(13 文字)
1 行の文字数(ショート)10 文字同じ(10 文字)
CPS 上限(同言語トーク)8 / 10 許容同じ
句読点不使用不使用
話者ラベル(YouTube 本編)プロジェクト依存なし
話者ラベル(Podcast)プロジェクト依存あり
ASR—ElevenLabs Scribe(character-level JSON)
パイプライン主体—semantic_srt.py(gap-based セマンティック分割)
1 Part あたりショート企画本数素材1分につき1本以上Partの素材尺から機械的に算出
キャラクター訳サブ ([04b](../L1_subtitle/04b_film_translation_subs.md))映画ドラマ用適用しない(実在人物のトーク番組)

→ 詳細は実体側 Claude/SUBTITLE_RULES.md(Dropbox: Directors'Katto/Claude/SUBTITLE_RULES.md) を参照。


固有名詞辞書(実体側にマスター)

→ Claude/PROJECT_REFERENCE.md(Dropbox: Directors'Katto/Claude/PROJECT_REFERENCE.md) §4-1〜4-11

主要カテゴリ:

  • 制作チーム(あさお / ふじい / なべ / トニー / ケニス / リナ 等)
  • 番組・作品名(ディレクターズ葛藤 / グーチョキデッド / Rock, Paper, Death!)
  • 参照映画(グラディエーター / イカゲーム / レザボア・ドッグス 等)
  • 業界用語(プリプロ / シーンブレイクダウン / Tiffcom 等)

ASR 誤変換の典型(Katto 固有)

→ SUBTITLE_MANUAL.md(Dropbox: Directors'Katto/SUBTITLE_MANUAL.md) §固有名詞辞書

主要パターン:

誤変換正
大金 / 長尾 / 鍋なべ
浅野 / 浅部 / 浅宝 / 浅田 / アサオあさお
フジイふじい
グーチョキゼット / ぐーちょきぜっとグーチョキデッド
エルマリアヤッツエル・マリアッチ
抵抗しえない成功し得ない
間垣予算マーケ予算

ローカルパス
種別パス
プロジェクトルートD:\zFilms Dropbox\z Films\Directors'Katto\
校正済み SRTSubtitles\
ショート SRTSubtitles\Shorts\
英語 SRTSubtitles\English\
ASR JSONContexts\
音声Final\Audio\
QA スクリプトScripts\
Claude 指示書Claude\
パイプラインスクリプト(生成系)C:\Users\<user>\AppData\Local\Temp\semantic_srt.py 等

ショート構成のプロジェクト値

グローバルルールは [../L2_sns/15_shorts_composition_workflow.md](../L2_sns/15_shorts_composition_workflow.md)。

この案件で上書きするのは次の値だけ。

値この案件置き場
1 行の文字数本編 13 / ショート 10lib/subtitle/thresholds.ts
1 枚の文字数本編 26 / ショート 20同上
CPS 理想 / 上限8 / 12同上
焼き込みタイトルの 1 行14(画で未計測)templates/shorts/katto/_base.json の text.maxCharsPerLine
タイトルの本文色 / 強調ラン#040000 / #aa0000(実測)同 text
パワーワード(T3)黄 + 黒縁・最大 5 字・1 本に 0〜1 回未実装。[../L2_sns/16_telop_emphasis.md](../L2_sns/16_telop_emphasis.md) §4
強調語の辞書低予算 映画制作 グーチョキデッド 葛藤lib/katto/generationSettings.ts
固有名詞辞書—lib/katto/dictionary.ts
除外の具体例内輪ネタ等lib/katto/shortsRubric.ts(補完への転換は反映済み。2026-08-11 に生成側へ接続 — lib/analysis/candidates/editorial-gate.ts が detectSupplements() を本番の門から呼ぶ。当たっても落とさず印を付けて残す)
門の Score の床70。尺度は LLM rubric 8 軸の等重み平均(Project rubric の editorialPolicy.rubricScoreThreshold = RUBRIC_SCORE_THRESHOLD)ShortYieldPolicy.minRubricScore。本番の門として効き、未満は RUBRIC_SCORE_BELOW_MINIMUM で落ちる(→ [../L2_sns/15_shorts_composition_workflow.md](../L2_sns/15_shorts_composition_workflow.md) §8-1)。Project Settings に編集フォームはまだ無い
門の軸の重み8 軸すべて 1(=等重み平均)KATTO_SHORT_YIELD_POLICY.rubricWeights。hook を門で重く見たくなったらここを上げる。`shortsScoring.ts` の重みを持ち込まない
順位付けの Score(門ではない)70(PRODUCTION_SCORE_THRESHOLD、hook 0.24 の重み付き)SHORTS_EDITORIAL_POLICY.productionScoreThreshold。rankShortCandidates → lib/studio/cut-level-plan.ts。落とさない。Studio の並び順と `adopt` / `review` の表示だけ。同じ 70 が 2 尺度に当たっていた問題は決着済み(浅尾確定 2026-08-12: 実装が正)

上の「黄 + 黒縁・5 字」は浅尾の指示を書き写した値で、画に出して測っていない。


話者マッピング
⚠️ この表は 2026-08-10 の実測と矛盾している。信用して使わない。 ある Part で speaker_2 が誤検出、speaker_1 が Nabe、speaker_3 が Fujii だった。 下の表は ASR のラベルをそのまま人名へ対応づけたもので、 [../L1_subtitle/07_elevenlabs_workflow.md](../L1_subtitle/07_elevenlabs_workflow.md) 「話者ラベリング」の手順(話者別マイクとの突き合わせ・一致率の記録)を通していない。 どの Part の実測かも記録が無い。 全 Part をマイク音源から取り直し、 一致率つきで置き換えるまで candidate 扱いとする。
ファイルspeaker_0speaker_1speaker_2speaker_3
Part1asaofujiinabe—
Part2asaofujiinabenabe
Part3asao(不明)nabefujii
Part4(不明)asaoasao—
Part5asaoasaofujiinabe
Part6fujiiasaonabe—
Part7asaoasaofujiinabe
Part8asaoasaofujiinabe
Shorts 1-1, 2-3, 3-1, 3-3, 3-4asaonabe——
Shorts 2-1, 2-2, 3-2, 3-5asaofujii——

SNS 投稿管理運用
スプレッドシート(コンテンツ生成)
  • URL: https://docs.google.com/spreadsheets/d/1kK14gdwjUd2myIpvbgLtZlELUHEUlyQOxeND-P03hh0/edit
  • 列構成: CONTENT_GENERATION.md §1(Dropbox: Directors'Katto/Claude/CONTENT_GENERATION.md) 参照
  • 書き込み手順(Chrome DevTools MCP 経由)も同ファイル §4
Notion DB(Subtitle_Shorts)
  • URL: https://www.notion.so/zfilms/Subtitle_Shorts-35dbd1f2cc818097b45ad8eba877b3b1
  • 用途: ショート企画 + 各プラットフォーム投稿の一元管理
  • 投稿テーブルフォーマット: [06_shorts_sns_table.md](../L2_sns/06_shorts_sns_table.md)

利用フロー(Claude / LLM への指示)

新規セッションで Katto の作業を始めるとき:

1. SubtitleMaker マスター読込:
   D:\zFilms Dropbox\z Films\App\SubtitleMaker\README.md
   D:\zFilms Dropbox\z Films\App\SubtitleMaker\00_base\<必要なもの>
   D:\zFilms Dropbox\z Films\App\SubtitleMaker\10_content\<必要なもの>

2. Katto 登録ポインタ(このファイル):
   D:\zFilms Dropbox\z Films\App\SubtitleMaker\20_projects\directors_katto.md

3. Katto 実体側 MD:
   D:\zFilms Dropbox\z Films\Directors'Katto\Claude\PROJECT_REFERENCE.md
   D:\zFilms Dropbox\z Films\Directors'Katto\Claude\SUBTITLE_RULES.md (字幕作業時)
   D:\zFilms Dropbox\z Films\Directors'Katto\Claude\CONTENT_GENERATION.md (SNS作業時)
   D:\zFilms Dropbox\z Films\Directors'Katto\SUBTITLE_MANUAL.md (パイプライン詳細)
典型ワークフロー
作業主に参照する MD
音声 → 書き起こし[07_elevenlabs_workflow.md](../L1_subtitle/07_elevenlabs_workflow.md)
書き起こし多面修正[08_multipass_correction.md](../L1_subtitle/08_multipass_correction.md) + Katto 辞書
SRT 検証[06_quality_review_workflow.md](../L1_subtitle/06_quality_review_workflow.md)
ショート企画[02_shorts_planning_framework.md](../L2_sns/02_shorts_planning_framework.md)(8〜12 本)
SNS 投稿生成[06_shorts_sns_table.md](../L2_sns/06_shorts_sns_table.md) → Notion / スプレッドシートへ
X ポスト[05_x_post_style.md](../L2_sns/05_x_post_style.md)

*最終更新: 2026-05-11 — Notion URL / Spreadsheet / ELEVENLABS_API_KEY 反映、L1 04b 適用外フラグ追加*


規則値(自動生成)

この節の表は `lib/rules/registry.ts` から生成される。手で書き換えると `yarn rules:docs --check` が落ちる。

値を変えるときは実定数を変え、yarn rules:docs で吐き直す。

全体の既定から外す場合は、このマニュアルに根拠を書いてから authorizedBy を埋める(正本 §7)。

<!-- generated:rules:subtitle-full-ja -->

字幕(本編・日本語 / `EPISODE_JA_DEFAULT`) — この表は lib/rules/registry.ts から生成される。手で書き換えない(yarn rules:docs)。

値いま全体の既定認可根拠
maxCharsPerLine13 字6〜16 の範囲manual/L1_subtitle/01_subtitle_format_jp.md §1—
maxLines2 行1〜3 の範囲manual/L1_subtitle/01_subtitle_format_jp.md §1—
maxTotalChars26 字———
cpsMax18 字/秒12 字/秒manual/L1_subtitle/01_subtitle_format_jp.md §CPS 上限の上書き浅尾 2026-08-18 — 本編で実測。上限超過 28 件の分布 12.1〜17.3 / 中央 14.0。本編経路では検証の閾値としてしか使われない
minDuration0.4 秒0.5 秒manual/L1_subtitle/01_subtitle_format_jp.md §最小表示時間の上書き浅尾 2026-08-18 — Part1 実測: 本編 5 件・ショート 16 件が 0.5 秒未満、最小 0.196 秒
maxDuration7 秒7 秒——

<!-- /generated:rules:subtitle-full-ja -->

題に出してよい固有名詞(浅尾確定 2026-08-20)

一般に通じるものだけ。知らない人には意味の無い文字列になるので、迷ったら外す。

規則は [../L2_sns/03_thumbnail_title.md](../L2_sns/03_thumbnail_title.md) §固有名詞。

使ってよい理由
スパイダーマン本編に 9 回。いちばん強い。誰でも知っている
ヴェノムスパイダーマン系で通じる
カメ止め / カメラを止めるな邦画の低予算ヒットとして一般に通じる
ゾンビジャンル名として通じる
少年ジャンプ / ジャンプ通じる
デスゲームジャンルとして定着している
タコス一般名詞

外したもの(この案件の題では使わない。中身では使ってよい):

ロドリゲス(ロバート・ロドリゲス)/ 巨人の星 / パラノーマル /

ブレアウィッチ / zFilms / 鮨バトルジャンプ / メキシカン寿司。

L5-01 会話音声のマスタリング

全 Project・全 Part に適用するグローバルルール。 浅尾指示 2026-08-10。

案件ごとに変わるのは目標値と素材の癖だけで、鎖の順序は動かさない。


1. なぜ目標値が要るか

配信先は必ず正規化する。 YouTube はおおよそ −14 LUFS へ寄せるので、

それより大きく作っても音量は上がらず、クリップの歪みだけが残る。

実測 2026-08-10(Final/Audio/):

PartI (LUFS)LRA (LU)True Peak (dBFS)
Part1−12.53.4+6.1
Part2−10.72.6+9.0
Part3−10.62.8+11.0
Part4−11.23.0+10.4
Part5−11.22.9+7.7
Part6−11.43.2+10.8
Part7−11.43.2+9.9
Part8−11.22.9+11.0
Part9——音声が書き出されていない
Part10−19.39.5−1.1
Part11−19.49.4+1.8
Part12−17.18.1+2.6

2 つの群に分かれている。 Part1〜8 は −11 LUFS / LRA 3 前後(強い圧縮)、

Part10〜12 は −19 LUFS / LRA 9 前後(ほぼ素)。同じ番組で 8 dB 違う。

True Peak が全部プラス。 Part3 と Part8 は +11.0 dBFS で、波形が潰れている。

配信基準(−1.0 dBFS 以下)を大きく超えており、これは録り直さないと戻らない歪みである。

副作用として、この強い圧縮が包絡の相互相関を潰し、

話者ラベリングの同期を止めていた(docs/PLAN.md の P5)。

音の工程を後追いにすると、後段の解析まで止まる。


2. 目標値
用途ITrue PeakLRA
YouTube / SNS−14 LUFS−1.0 dBFS9 LU
放送・配信(参考)−23 LUFS−1.0 dBFS11 LU

LRA を詰めすぎない。会話は抑揚が消えると聞き疲れる。


3. 鎖の順序(動かさない)

1 本ずつ(stem)整えてから混ぜる。 マイクごとにノイズ床が違うので、

混ぜてから処理すると全部のノイズをまとめて相手にすることになる。

1 本ずつ
#処理既定なぜその順か
1修復デクリップ(TP>0のみ)/ デクリック歪んだままEQすると歪みまで持ち上がる
2ローカット75 Hz / 12dB/oct成人男性の基本周波数は 85Hz 前後から。下は空調・振動・扱いノイズ
3ノイズ低減8 dB帯域を削ってから。12dB は声の裾が削れて水っぽくなるので減らした
4下方展開−6 dB @ −40dB喋っていない区間を沈める。ゲートで切ると部屋鳴りが出入りして不自然
5引くEQ250 Hz −2dB / 450 Hz −1.5dB近接効果の濁りと部屋の箱鳴り。足す前に引く
6均す2:1 @ −24dB / attack 20msゆるいレベラー。抑揚を残したまま底上げ
7締める3:1 @ −12dB / attack 3ms子音の飛び出しだけ抑える
8足すEQ3 kHz +2dB / 10 kHz +1dB明瞭度は 2〜5kHz で決まる。10kHz は空気感
9ディエッサー0.358 で持ち上げた歯擦音を抑える
10ハイカット16 kHz会話の明瞭度に効くのは 8kHz まで

6 と 7 を分けるのが要点。 1 段で強く掛けると平坦になる。

ゆっくり均してから速く止める、の 2 段にすると抑揚が残る。

混ぜてから
#処理既定なぜ
11バス圧縮1.5:1 @ −16dB3 本を1 つの音像にまとめる。深く掛けない
12ラウドネス2 パス・線形1 パスの動的正規化は抑揚を潰す
13リミッタ−1.0 dBFSloudnorm の TP 推定は完全ではない

数値は業界の標準的な出発点であって、この素材で測った値ではない。

耳で確かめて素材ごとに直す。実装は lib/audio/mastering.ts の

trackChain() / busChain() が唯一で、単体マスタリングもセッションのミックスも同じものを通る。

4. 検算

設定を書き戻して一致を見るのは検算にならない。 出力を測り直す。

「揃えた値」を並べ直すのも検算にならない。 2026-08-12 の監査で見つかった

2 件はどちらもこの形だった。①区切りの境目の検査が

chainedVoicedMedianDb + gainDb の比較で、gainDb の定義から恒等的に定数になる

(証跡でも 3 人とも小数 14 桁まで同一)。②被覆率の分母が

「出すと決めた秒」で、出さなかったぶんが分子からも分母からも同時に消えて

比が構造上 1 になる。どちらも「合格」を出し続けていた。

検算になる条件は 1 つ。判定の入力が、判定される側とは独立に得られていること。

上の 2 件はどちらも入力が判定対象の設計値そのものだった。

直したあとはどちらもレンダリング済みの出力と写像が引いている秒という

外から来る量になっている。

scripts/master-part-audio.mts は 2 パス目のあとに ebur128 で測り直し、

I が目標 ±0.5 LU、TP が上限 +0.1 dB を超えたら FAIL として報告する。

実測 2026-08-10(Part1):

前: I=-12.5  LRA=3.4  TP=+6.1   ** クリップあり **
後: I=-14.1  LRA=3.5  TP=-1.0   PASS

LRA は 3.4 → 3.5 でほぼ変わっていない。 元素材が既に潰れているので、

ここから抑揚は戻らない。戻したいなら生マイクから組み直すしかない。


5. 原本を触らない
  • 出力は Final/Audio/_mastered/<Part>.wav(48kHz / 24bit)。元の mp3 は残す
  • 1 Part あたり約 500 MB。11 Part で約 5.5 GB
  • 既定は DRY RUN。--go を付けたときだけ書く

6. 生マイクから 1 本を作る(音源生成の正本)

§1〜§5 は既に潰れた mp3 の手当てで、クリップも抑揚も戻らない。

配信に使う音はこちらで作る。 完成 Part の音声は音楽が乗っているので使わない。

6-0. 出力の時間軸を先に決める。2 つある

同じ素材から 2 種類の音が出る。用途が違うので、どちらを作るのかを先に決める。

scripts/build-part-mix.mts --axis <source|timeline>。既定は `source`。

軸出力が載る時間出力ファイル用途
`source`(既定)生収録の連続した通し。編集で抜かれた部分も入っている<Part>_mix_source.wavResolve のマルチカムに差し替える素材
timeline本編(編集後)。写像の隙間は無音<Part>_mix.wav完成尺にそのまま貼る

差し替え用途で `timeline` を使ってはいけない。 差し替え先のマルチカムは連続した収録で、

編集は Resolve のタイムラインが既に持っている。こちらが編集後の尺だと、

カットの段差が二重に入って頭から全部ずれる。

編集の段差は、オフセットを出すためだけに使う。`source` では出力から抜かない。

段差から読めるのは「各マイクが共通の基準に対してどれだけずれているか」という定数だけで、

その定数さえ出れば、あとは生収録の通しをそのまま並べればよい。

`source` では頭出しを必ず確認する。 出力の頭が基準マイクの何秒目から始まるかを

印字と JSON(sourceAxis.startInReferenceMicSec)に出している。

写像が指していた素材(カメラ本体音など)についても同じ頭出しを出す(clockAnchors)。

ここがずれると、差し替えた瞬間に全部ずれる。

`source` では被覆率の意味が変わる。同じ名前で読まない。

名前意味関門
coverRatio(timeline のみ)全員そろう時間 ÷ 本編の長さ90% 未満は作らない
`deliveredRatio`(source のみ)本編が引いている素材のうち、出力に載った割合98% 未満は作らない
allPresentRatio(source のみ)全員そろう区間 ÷ 生収録の広がり関門にしない
intactRatio(source のみ)全員そろう区間のうち、実際に全員のマイクが在る割合関門にしない(下)

allPresentRatio を関門にしないのは、**誰かのレコーダが次の Part まで回っていれば

分母だけが伸びる**から。低い値が欠陥を意味しない(実測 Part3 = 68.4%。

Fujii の 2 本目だけ 30 分ぶん余っている)。

`intactRatio` は関門から外した(2026-08-11)

あれは欠陥を捕まえられない。 出荷済みの Part1 は intactRatio 100% で通っているのに、

本編が使っている 195.2 秒が丸ごと出力に入っていなかった(§6-0d)。

出力を「全員そろう区間」で打ち切っていたので、**載らなかったものが分母からも消えて

100% に見えていた**。トートロジーである。

さらに intactRatio は「1 人だけ欠ける区間」を欠陥として数えるが、

その人がそこで喋っていないなら欠けても問題ない。

coverage.thinRegions に、全員はそろわない区間ごとに

「本編がその区間で欠けている人のマイクを引いているか」(missingUsedSec)を実測して残す。

0 なら喋っていないので欠陥ではない。

いまの関門は本編を基準に測る(deliveredRatio)。写像が「この本編の秒はこのマイクのここ」と

言っている秒のうち、出力のその位置に本当に素材が載っているかを数える。

同じ数え方を出荷前の Part1 に当てると 88.84% で、関門に掛かる

(--no-clock-bridge で再現できる。載らなかった本編の区間 1569.9〜1765.1s と

ファイル名まで印字される)。

同じ形が一段上で再発していた(2026-08-12 → 直したのは 08-13)

2026-08-12 に「3 人そろわないから出さないと決めたぶんは分母から抜く」という変更を入れた

(intendedRecordSec = mapped − refused)。これで関門は原理的に落ちなくなった。

出さなかった秒は分子からも分母からも同時に消えるので、比は構造上 1 になる。

Part写像載った出さないと決めた旧 ratio(分母 = mapped−refused)新 `ratio`(分母 = mapped)
Part124982.8s4425.0s557.8s1.00000.8880 → FAIL
Part72328.3s2279.3s49.0s1.00000.9790 → FAIL
Part42116.4s2096.6s19.8s1.00000.9906
Part22289.9s2285.7s4.2s1.00000.9982
Part1 / 3 / 5 / 6 / 9〜11——0〜0.1s1.00001.0000

12 Part 全部が 1.0000 だった。「出さないと決めた」が 557.8 秒あっても 1.0000 である。

intactRatio を関門から外したときと同じトートロジーで、直す場所が一段上がっただけだった。

いまの分母は `mapped`。refused も分母に残す。 抜けるのは

--approve-refusal "<理由の一部>" で理由を個別に承認したときだけ。

理由ごとの秒数(delivery.refusedByReason)と、承認したぶん(excusedRecordSec)/

していないぶん(unexcusedRecordSec)を必ず印字する。

intendedRatio(refused を全部抜いた比)は参考値として残すが関門ではない。

出力の区間は「全員そろう区間」ではない

1 人欠けるのは「その人が居ない」であって「素材が無い」ではない。

以前は人ごとの範囲の共通部分を出力にしていたので、誰か 1 人のマイクが切れた時点で

打ち切られていた。いまは 2 つの実測値の和を採る(sourceAxis.spanFromSec〜spanToSec)。

  • 全員そろう区間
  • 写像が実際に引いている区間(本編が生収録のどこを使ったか)

生収録の広がりそのものは採らない。 誰かのレコーダが次の Part まで回っていると

分母だけが伸びる。頭打ちにするだけ。

3 人未満で中止と、`meetsTarget` の合否判定は軸によらず同じ。

リタイム(`MediaTimemapBA`)はどちらの軸でも読めない(lib/nle/drp/edit-map.ts ヘッダ)。

速度を変えた区間があるとその区間だけずれるが、検出できない。

実測 2026-08-11(--axis source --go。260215 のセッション)。

Part1 / Part7 は同日に作り直した(§6-0d / §6-8)ので下の表とは別。

新しい値は §6-9 の表を見る。Part2〜6 / Part8 はまだ古い経路の出力。

Part出力の尺頭出し(基準マイクの何秒目)allPresent / intact残差(検算した本数)I / LRA / TP判定
Part11749.06skatto_part1_Asao.wav の 3.090s99.5% / 100%10ms(3 本)−14.0 / 4.6 / −1.0PASS
Part22546.33skatto_part2-1_Fujii.wav の 0.000s97.6% / 100%20ms(6 本)−14.0 / 4.7 / −1.0PASS
Part32465.31skatto_part3-1_Fujii.wav の 0.000s68.4% / 99.95%30ms(7 本)−14.1 / 4.6 / −1.0PASS
Part42615.89sTX02_MIC011 が始まる 815.740s 前71.5% / 100%未検算−14.1 / 5.5 / −1.0PASS
Part52425.68sTX02_MIC013 の 0.000s71.5% / 100%未検算−14.1 / 5.1 / −1.0PASS
Part62596.22sTX02_MIC015 の 0.000s97.6% / 100%未検算−14.1 / 5.4 / −1.0PASS
Part72494.82sTX02_MIC018 の 0.000s95.6% / 100%未検算−14.1 / 5.5 / −1.0PASS
Part82479.83sTX01_MIC024 の 0.000s98.3% / 100%未検算−14.1 / 5.1 / −1.0PASS

各マイクの位置は冗長な経路で検算する(同じマイクを 2 本以上の辺が指していたら、

どの辺から計算しても同じ位置に来るはず)。**辺が 1 本しか無いマイクは「残差 0」ではなく

「未検算」として出す**(residualSec: null)。包絡が 50Hz なので分解能は 20ms。

Part4〜8 は「未検算」。合格ではなく、測っていない。 その Part の写像が指した clock が

カメラ 1 本(例 Part5 は C0013.MP4)しか無く、どのマイクへも経路が 1 本しか通らないため。

clock が複数ある Part(Part1〜3、および 260411 の Part9〜12)でだけ残差が出る。

clock を増やせば検算できる(--max-clock-media)。

6-0b. 成立条件(先に読む)

**この 6 段は「単一のオフセットで生マイクと完成 Part が揃う」ことを前提にしている。

その前提が成り立たない Part がある。**

  • 単一のオフセットが成り立つのは、その Part に編集の切れ目が無い場合だけ。

完成 Part は編集済みなので、本編側で区間が抜かれていれば生マイクとのずれは区間ごとに変わる。

段 3 の「2 窓以上・ばらつき 50ms 以内」は、この前提が崩れた Part では通らないのが正しい。

通らないものを平均で埋めない。

  • 切れ目がある Part は区間ごとの写像が要る。 単一の Δ では表せない。

同じ話は [../../docs/PLAN.md](../../docs/PLAN.md) の同期の項にもある

(Part4 / 260215 で 5 点の Δ が 1357 / −164 / −215 / −629 / −383 と散り、

「単一のオフセットは存在しない」と結論している)。

監査の実測(2026-08-10)でも、Part3 で 3 人の独立マイクが揃って 6.120 秒ずれた。

3 本が同じ値でずれるのはマイク側の問題ではなく、本編側で約 6 秒が抜かれている証拠。

  • いまの既定は区間写像。単一オフセットは落ち先。 Resolve の書き出し

(<Part>_Master.drt)から区間ごとの写像を読み、そこで測った定数で組む

(lib/audio/part-edit-map.ts)。書き出しが無い Part でだけ単一オフセットへ落ちる

(--no-edit-map で強制もできる)。

  • 成立を確かめたのは Part1〜12。 Part1〜8 は --go まで通して出荷済み、

Part9〜12 は DRY RUN で関門まで通した(§6-0c)。Special-1 と 1-1〜3-5 は

まだ通っていない(Special-1 は 4 群見つかるが、窓のばらつきで全部落ちて 0 人になる)。

6-0c. Dialogue が実素材を指していない Part がある(Part9〜12)

写像は読めているのに、その先が収録素材ではない。 実測 2026-08-11、

drp/export-20260807/ の .drt を読んだ結果:

PartDialogue(A1)が指す先実体
Part1〜8マルチカムの音声 → カメラ本体音(例 C0013.MP4)在る
Part9Part9_Audio(コンパウンドでもマルチカムでもなく、MediaFilePath が空)21 区間辿れない
Part10 / 11マルチカム → Part10_tempAudio.mp3 / Part11_tempAudio.mp3無い
Part12Part12_1_tempAudio.mp3 / Part12_2_tempAudio.mp3無い

編集時に書き出した仮ミックスを Dialogue に置いたまま完成させた、という編集の癖。

このとき clock(ずれを測る相手)が 1 本も採れず、

「検算の通ったずれが 1 本も無く、生収録の軸を組めない」で落ちる。

素材が無いのではない。 260411 の生マイクは 47 本ある。写像の入口が違うだけ。

どのトラックから写像を読むかは、名前で決め打ちしない。 同じ timeline の全トラックを

評価して「**収録セッション(Clips_Raw/<session>/)の実ファイルを指している本編秒が

いちばん長いもの**」を採る(PlanOptions.trackFallback)。段は 2 つ:

  • まず音声・映像の各トラックを、マルチカムの音声トラックだけ展開して評価する
  • Dialogue が勝てなかったときだけ、マルチカムの映像トラック(アングルの素材)も

候補に足してもう一度評価する(MulticamVariantOptions.includeVideoTracks)。

アングルの素材はそれ自体が音を持った収録なので clock に使える

段 2 を「Dialogue が壊れているときだけ」に限るのは、**壊れていない Part の出力を

壊れた Part の都合で動かさないため**。Part1〜8 は段 1 で確定するので何も変わらない。

これを使ってよいのは `source` 軸だけ。 source 軸は写像の区間を出力に使わない

(使うのは「素材 → マイクのずれ」という定数だけ)ので、どのトラックから clock を

拾っても並びは変わらない。timeline 軸では回さない(trackFallback: 'none')。

あちらは写像の区間がそのまま出力の並びになるので、Dialogue 以外を代わりに使うと

別の並びの音を出してしまう。

実測 2026-08-11(修正後の DRY RUN。どれも 3 群そろって関門を通る):

Part採ったトラックclock載った / 載らない出力の尺allPresent / intact残差
Part9映像 V1(段 1 で決着。マルチカム 0411本編 の音声に生マイクが入っている)10 本(カメラ 2・Wireless 2・TX 6)8 / 04181s99.7% / 100%20ms(8 本)
Part10A1(段 2。アングル素材を足して初めて届いた)20260411_C0354.MP4 / C1187.MP45 / 03500s98.0% / 100%0ms(2 本)
Part11A1(段 2)20260411_C0355.MP4 / C1188.MP45 / 02632s99.2% / 100%10ms(4 本)
Part12映像 V1(段 2)カメラ 6 本6 / 33596s90.1% / 100%10ms(3 本)

Part12 の 3 本(17:34 開始のチャンク)はどの素材とも検算の通ったずれが取れず、載せていない。

sourceAxis.unplaced に理由付きで残る。無音で埋めない。

それでも直らなかった読み方も残す。 アングルを足す前は Part10〜12 の clock が

Clips_Raw/260411/20260411_katto/2026-04-11 14-00-07.mov 等の別撮りしか無く、

相関は 0.55〜0.76 と高いのに窓どうしのばらつきが 600〜1900 秒あって全滅した。

相関が高いことと、同じ時間軸に乗っていることは別。高い相関を「合った」と読まない。

人の名前は 260411 では読めない。 Clips_Raw/260411/Audio/ の下は

TX_MIC001_20250217_025120 / TX_MIC001_20260211_073914 / Wireless1 / Wireless2 という

レコーダ単位のフォルダで、260215 の Asao / Fujii / Nabe のような人単位ではない。

person は置き場のフォルダ名から引いているので、Part9〜12 の話者ラベルは機材名になる。

3 人未満の関門は「3 つの独立した収録が載った」ことしか保証していない。

誰が誰かは人が確認する(正本 グローバル「人物ラベルはプロジェクトと Part の二層で持つ」)。

6-0d. 1 人の収録が複数チャンクに割れている(浅尾指摘 2026-08-11)

浅尾指摘:「あさおの音声が part1 ちゃんとできてなさそうだ。なんか遠くに聞こえる気がする。

(…)それぞれ途中でカメラカット入っている場合は複数個ある場合あるからな」。

そのとおりだった。 実測 2026-08-11、Part1 の Asao(レコーダ TX01):

ファイル収録開始尺本編が使う量
TX01_MIC005(改名 katto_part1_Asao.wav)11:55:461757.47s1553.25s
— 誰も録っていない —12:25:0358.53s—
TX01_MIC00612:26:02604.12s195.21s

出力にはこの後半 195.21 秒が 1 秒も入っていなかった。

だから Part1 の出力(1749.06s)が本編(1794.42s)より短かった。

なぜ落ちていたか

生収録の軸は、素材どうしの包絡の相関で位置を配る。相関が立つのは

同じ時間帯に鳴っていた素材どうしだけなので、収録の切れ目でグラフが千切れる。

Part1 は 2 つの塊に割れていた。

塊中身繋がる clock
A3 人の 1 本目C0004.MP4
B3 人の 2 本目C0005.MP4

A と B の間には辺が 1 本も無い。 幅優先探索は A からしか配らないので、

B の 3 本は「検算の通ったずれがどの素材とも取れない」として捨てられていた。

素材が無いのではない。探索が届いていなかった。

繋げる根拠は、同じレコーダのファイル名の時刻しかない

切れ目の 58.5 秒は誰も録っていないので、重なりが 1 サンプルも無い。

音では原理的に繋がらない。 残る手がかりは

TX01_MIC006_20260215_122602_orig.wav の 20260215_122602 だけ。

グローバル正本は「時計は『同じ撮影か』の粗い当たり付けにしか使わない」と戒めているが、

あれは機材をまたぐ話。ここでやるのは同じ 1 台のレコーダの中だけなので、

比べているのは同一の時計であり、機材どうしのずれは入らない。

残る誤差は秒への丸め(±1 秒)だけ。

それでも 1 台では信用しない。独立なレコーダ 2 台以上が同じ橋の値を出したときだけ採る。

人が 3 人いれば 3 台が独立に「同じだけ離れている」と言うはずで、

票の食い違い(`spreadSec`)がそのまま誤差の実測値になる。

実測 2026-08-11(Part1。相関で組んだ塊 B の中の位置と突き合わせた。TX01 の 2 本目を 0 とした相対):

時計から相関から差
TX02(Fujii)2 本目−48.91s−49.10s0.19s
TX03(Nabe)2 本目+0.27s+1.00s0.73s

1 秒以内で揃った。 丸めの理論値どおりで、これ以上の精度は名前からは出ない。

本番の橋は 3 台の票が 0.92s に収まった。

±1 秒の誤差はどこへ行くか

出力 wav と、その wav を Resolve へ戻す写像は同じ値を使う。

wav の中でそのチャンクが始まる位置も、build-audio-swap-drt.mts が書く In も、

どちらも同じ axisFromSec から出る。同じ定数なので相殺する。

ずれるのは「誰も録っていない隙間の長さ」だけで、そこは無音である。

基準マイク(`startInReferenceMicSec` の拠り所)には橋渡しで載せたものを選ばない。

頭出しに ±1 秒の値を置かない。

架けられないとき
  • Rode Wireless PRO(`00034_Wireless_PRO.WAV`)は名前に時刻を持たない。

null を返す。推測で埋めない(橋が架からないだけで、嘘の位置は作らない)

  • 票が 1 台しか無い / 票どうしが 2 秒を超えて食い違う → 架けない。

sourceAxis.unbridged に理由付きで残り、deliveredRatio が落ちて関門に掛かる

  • 以前の挙動を再現したいときは --no-clock-bridge

実装は lib/audio/recorder-clock.ts と buildSourceAxis の段 3。

6-1. 手順

下の 6 段は落ち先(単一オフセット)の手順。 既定の区間写像では段 2〜3 が

「写像から区間を読み、素材どうしの相関で定数を測る」に置き換わる(§6-0b)。

実行は yarn audio:part-mix --part <Part> --axis <source|timeline> --go

(package.json の audio:part-mix)。

段やること落ちる条件
1生マイクを集める — セッションを跨いで走査。20MB 未満は捨てる—
2その Part の素材か判定 — 完成 Part を物差しに包絡の相関。0.35 未満は不採用3 本揃わなければ中止
3相対のずれを出す — 全マイクが 0.35 を超えた窓だけを使う。2 窓以上・ばらつき 50ms 以内満たさなければ中止
4共通区間を切る — 全マイクが存在する範囲だけ。無音で埋めない—
51 本ずつ鎖を掛け、−23 LUFS へ揃える — 静的ゲインなので抑揚は変わらない—
6オートミキサーで混ぜ、バスの鎖へ—

マイク同士を直接合わせない。 ラベリアは装着者の声しか拾わず、席が離れていると

被りが無いので相関が立たない(実測 Asao↔Fujii 0.17〜0.29)。

共通の物差しは完成 Part の音声。 詳細は lib/audio/sync.ts の冒頭。

3 本未満では作らない。 2 本で作れてしまうと、欠けている人がいることが音でしか分からない。

6-2. 1 本ごとの鎖

デクリップ → ローカット 85Hz → ノイズ低減 6dB → 下方展開 →

濁り 300Hz −4dB / 箱鳴り 600Hz −3dB(← こもりの正体)→ レベラー → ピークコンプ →

厚み 100Hz +6dB → 低域コンプ 120Hz 比4 → 明瞭度 3.5k +5dB → 子音 6.5k +3.5dB →

エキサイタ 5k → 空気感 12k +4dB → ディエッサー → ハイカット 18kHz

引いてから足す。 低中域を引かずに高域を足すと、うるさくなるだけでこもりは取れない。

低音は静的ブーストだけにしない。 大きい音節で膨らみ小さい音節で消える。

低域を独立に潰して初めて「マイクに近い」密度になる。

閾値は auto=adaptive。話者ごとに声の重さが違うので固定値だと誰かに合わない。

6-3. 混ぜ方

素直に足さない。 同じ部屋の N 本を足すと、喋っている人の声が自分のマイクと

他人のマイクの両方から入り、櫛形の干渉になる。**風呂場のように鳴るのはこれで、

同期を 1 サンプルまで詰めても消えない。**

オートミキサー(Dugan 方式)。鋭さ 1.0 / 下限 −12dB。

これより締めると相槌が消え、声が途切れて聞こえる。

「合計を 1 に保つ」とは書かない(§6-3b)。

バスは バスコンプ → パラレルコンプ → リミッタ → 静的ゲイン → リミッタ。

リミッタをラウドネス調整の前に置かない。 目標に届かなくなる。

6-3b. 「合計 1」は主張だけで、一度も測っていなかった(2026-08-12 の欠陥)

lib/audio/automix.ts のヘッダは「開いているゲインの合計を 1 に保つので、

何本開いても部屋鳴りが増えない」と書いていた。合計は 1 ではない。

実測(AutomixReport.gainSumMean): Part1 1.326 / Part10 1.323 / Part11 1.345。

欠陥は「1.33 だったこと」ではなく、「一度も測らずにコメントで断言していたこと」。

1 にならない理由は 2 つあり、大きいのは平滑化のほう

Part11 で切り分けた(設計値 targetSumMean と実測 gainSumMean を別々に出した):

設計値の合計実際の合計
既定(床の再正規化 off)1.0521.345(+2.58dB)
床の再正規化 on1.0001.246(+1.91dB)
  • 床(`floorGain`) — Math.max(floorGain, share) は合計を必ず増やす。1.000 → 1.052
  • 平滑化の非対称(速く開き、ゆっくり閉じる)— 切り替わりの間だけ超える。1.05 → 1.35

差の大半は 2 番目。 床を直しても実際の合計は 1.25 で、1 にはならない。

直したが、既定 off(部屋鳴りは減らなかった)

床のあとに再正規化すれば設計値の合計はきっちり 1.000 になる。

Part11 を --out を分けて 2 本作り、同じ方法で測った:

語間 − 発話減衰支配率途切れ ≥3dB語頭 50ms有声 p05
off(既定)−19.76dB−11.6 dB/s27/30/42%基準基準基準
on−19.80dB−12.0 dB/s27/30/42%(同一)0.0 件/分−0.43dB−1.02dB

主指標が 0.04dB しか動かない(ノイズ合わせを off にしたときの 0.07dB と同じ水準で、

判定下限 0.5dB を大きく下回る)。理由もはっきりしている。

合計は全マイクに共通の係数なので、直接音と部屋鳴りの比を動かさない。

支配率が 1% も動いていないのがその証拠で、総量はバスの 2 パスが戻す。

代償のほうは出た。語頭 50ms が −0.43dB(§6-8c の表では 0.00〜+0.04dB を

「アタックは削られていない」としている。桁が違う)、有声 p05 が −1.02dB。

途切れ(≥3dB、差分法)は 0.0 件/分なので穴は開いていない。

効きが無くて代償だけがあるので入れない。 --automix-normalize-after-floor on で試せる。

残したもの
  • 毎回測って出す。 targetSumMean(設計値)/ gainSumMean / gainSumMeanDb を

印字と証跡(automixGainSum)へ。主張をコメントの中に置いたままにしない

  • ヘッダから「合計を 1 に保つので部屋鳴りが増えない」を消し、実測値と理由を書いた
  • 反響を減らすのは「合計」ではなく `floorGain` と鎖の下方展開(§6-8c で膝を測ってある)。

合計の話は反響の容疑者から外す

  • 無音のフレームで floorGain * n が 1 を超える件は、再正規化 on のときだけ

min(floorGain, 1/n) で頭打ちにした(現行の Part は全部 3 人 = 0.75 なので当たらない)

  • 検査は tests/lib/audio-automix.test.ts(合計の実測 / on で 1.000 / 比が変わらない /

既定が off であること)

まだ直していない: 平滑化の +2.14dB

床を直しても実際の合計は 1.25 で、残りは平滑化の非対称(速く開き、ゆっくり閉じる)。

**ここを 1 に戻すには平滑化後のゲインを毎フレーム正規化することになるが、

それは「ゆっくり閉じる」の意味を相対値の上で消してしまう**ので、

上と同じ手続き(同じ Part を 2 本作って語間・途切れ・語頭・支配率を測る)を

踏んでから決める。測っていないので入れていない。

6-4. 目標と検算
ラウドネス−14 LUFS
真のピーク−1.0 dBFS
抑揚5 LU 前後

天井は目標より 1dB 下に置く。 alimiter はサンプル値しか見ないので、

サンプル間のピークはそれを超える(実測: −1.0 で止めて TP=0.0 dBFS)。

出力を測り直す。 設定の書き戻しは検算にならない。

ラウドネスが通っても「全員が入っている」ことは分からない。 I / TP は 1 本でも 3 本でも

同じ値に揃う。出力そのものと生マイクを突き合わせる。 各マイクが軸のどこに載るかは

JSON(sourceAxis.placements[].axisFromSec)に入っているので、探索せずにその位置で

包絡の相関を取れる。窓を全長に等間隔で置き、どこかの窓だけ 0 に落ちれば、

そこでその人が欠けている。

実測 2026-08-11(60 秒窓 × 12、10Hz 包絡。範囲外の窓は数えない):

PartAsaoFujiiNabe
Part10.40〜0.680.56〜0.780.32〜0.62
Part30.34〜0.670.60〜0.740.28〜0.64
Part5(再生成後)0.51〜0.750.47〜0.750.35〜0.68

0.2 を下回る窓は 1 つも無い。 3 本とも全長にわたって鳴っている。

Fujii が高く Nabe が低いのは席とマイクの当たりの差で、欠落ではない。

`ok` は 1 個ではない。4 つの AND(2026-08-13)

2026-08-12 まで、証跡の ok は meetsTarget の戻り値そのものだった。

ラウドネスが合ったという意味しか無いものが最上位に置かれていた。

実測 Part12_mix_source.json は ok: true / reasons: [] なのに、同じ JSON の

programCoverage が「ダイジェストを 0 と置いても 3.22 分足りない」と書いていた。

書いてある欠陥が合否に効いていない。

関門何を見るか落ちる意味
ok.loudnessmeetsTarget(I の誤差と TP の超過)音量・ピークが目標から外れている
ok.deliverydeliveredRatio ≥ 98%(分母は mapped)素材を取りこぼしている
ok.coverageprogramCoverage.verdict === 'covers'編集点を抜いている / 判定できていない
ok.verification境目を出力そのもので測り、上限以内か飛んでいる / 測っていない

ok.all はこの 4 つの AND で、落ちたら非ゼロで終わる。

`unknown` と「測っていない」は合格にしない。「完成」と言えるのは

その工程の規則を同時に全部通したときだけで、測っていない項目があるなら未完成である。

組み立ては lib/audio/mastering.ts の combineGates(検査は tests/lib/audio-mastering.test.ts)。

6-5. AI の前段は入れない
判断
Resemble Enhance 等の生成モデル使わない。 声を作り直すので安っぽくなる
DeepFilterNet 等の非生成(マスク)モデル安全側だが Rust が必要で未導入

基本編集の鎖だけで作る。 残る「遠さ」は部屋鳴りの比率で、

ゲートとオートミキサーの下限で詰められるが、どちらも締めすぎると声が途切れる。

6-6. 並列で回すときの一時ファイル

一時ファイルの名前を Part 名だけで付けない。実行ごとに別のフォルダを切る。

実測 2026-08-11、13 Part を 16 並列で回したときに起きたこと。

一時ファイルは %TEMP%/zfilms-partmix/<Part>.<n>.asm.f32 のように Part 名だけで

名前を付けていたので、同じ Part が 2 つ同時に走ると同じパスを取り合った。

Part落ち方起きたこと
Part1 / Part3ENOENT open <Part>.decode.f32相手の後片付け(rmSync)に消された
Part5EBUSY unlink <Part>.0.asm.f32相手が書き込み用に開いている最中だった

片方が落ちるだけでは済まない。 生き残ったほうも、相手に切り詰められた

scratch(openSync(path, 'w') は既存を 0 に切る)を読んで混ぜている可能性がある。

名前がぶつかった実行の出力は、落ちたほうも生き残ったほうも信用しない。

直したのは 2 点。

  • 実行ごとに専用の一時フォルダ(<Part>-<軸>-<pid>-<乱数>/)。同じ Part を

何本同時に走らせてもぶつからない。終了時(異常終了も含む)に丸ごと消す

  • 後片付けで実行を落とさない。 rmSync は maxRetries 付きで、失敗しても

件数を印字して先へ進む。消せないことは結果に影響しない。

Part5 は wav を作り終えたあとの rmSync で落ちていた

同じ Part を二重に投げない仕組みは入れていない。 呼び出し側(一括実行の

スクリプト)の責任として残っている。ぶつかっても壊れなくなっただけ。

6-6b. 完全性の検算 — 区切り × 話者数(浅尾指示 2026-08-11)
もとの drp で使ってるファイルから予測して何カット分あるかはわかるから、 そこからファイルを探してだいたい x3 以上あるよな、そのカット数に対して

写像は「その Part が素材のどの区切りを使っているか」を持っている。

区切り N 個 × 話者 M 人 = 生マイクは NM 本あるはず。

足りなければ素材を取りこぼしている。毎回この突き合わせを走らせ、印字と JSON に残す。

完全性の検算: 素材の区切り 3 個 × 話者 3 人 = 期待 9 本 / 実際に見つけた 6 本  ← **3 本足りない**

区切り = 別々の実体。カット数ではない。

同じ 1 ファイルが何回に分けて置かれているかはカット数であって区切りではない(グローバル正本)。

マルチカムのアングル(カメラ本体音 + 話者ぶんのラベリア)は**同じ本編時刻を指すので

1 つの区切りにまとめる。区切りの切れ目は本編時刻が重ならないこと**で決める。

話者数は「解決できた人」だけで数える。置き場の数ではない。

読めなければ 3 を仮に置き、personSource に仮だと書く。

6-6c. 置き場 = 人ではない(2026-08-13 に切り離した)

以前は `Clips_Raw/<session>/Audio/<dir>/` の `<dir>` をそのまま人にしていた。

260411 で成り立たない(実測):

置き場中身人
TX_MIC001_20250217_025120/TX02_*.wav 15 本(2026-04-11)+ TX04_*.wav 4 本(2025-02-17)あさお。機材は 2 台
TX_MIC001_20260211_073914/TX01_*.wav 18 本なべ
Wireless1/00034〜00040_Wireless_PRO.WAVふじい
Wireless2/00018〜00022_Wireless_PRO.WAV同じふじい(別レコーダ)

置き場は 4 つ、人は 3 人、機材は 5 台。 3 つとも別の数で、どれも他の代わりにならない。

置き場を人として読んでいたので、次の 3 つが同時に壊れていた。

  • 完全性の検算が恒常的に偽陽性。 Part10 / Part11 は毎回

personCount:4 / expectedMics:4 / foundMics:3 / missingPersons:["Wireless2"]。

Wireless2 は 10:11〜13:35 で止まっていて Part10(13:59〜)に居ないだけ。

常に落ちる関門は誰も読まない

  • `MIN_PERSONS = 3` が人数ではなく置き場の数を数えていた
  • --noise-group person と --dynamics-match の束ねる単位が、

別レコーダ 2 台を 1 票にしうる

3 層に分けた(lib/speakers/mic-identity.ts)。

何どこから引けないとき
置き場 dirフォルダ名—
機材 recorderファイル名の TXnn。改名済みなら改名表から復元置き場の名前
人 person台帳 dev/knowledge/speaker-people.json(Part 層 → プロジェクト層)。キーは機材 → 置き場の順`unknown:<recorder>` のまま残す。既定の人へ落とさない

`unknown:` の付いたものは人として数えない。 数えると置き場を数えていた頃と同じ偽陽性に戻る。

MIN_PERSONS・完全性の検算・buildSourceSegments の 3 つとも、

数えるのは解決できた人だけ。人を引けなかった機材は

unresolvedRecorders に別に残す(「引けなかった」ことは消さない)。

台帳が `person: null` と書いてあるキーは「決めていない」という決定として扱い、

そこで止める(粗いキーへ落ちない)。

置き場に機材が 2 台以上あるのに人を置き場から引いたときは印を付ける

(ambiguousDir)。台帳の単位が置き場なので同じ人を当てているが、

機材ごとの裏取りはしていない。実測 Part10 の印字:

260411/TX_MIC001_20250217_025120  TX02  15 本  Asao  …(キー TX_MIC001_20250217_025120)。
  **この置き場には機材が 2 台ある**(TX02 / TX04)。台帳の単位が置き場なので同じ人を当てたが、機材ごとの裏取りはしていない
260411/TX_MIC001_20250217_025120  TX04   4 本  Asao  …(同上)
260411/TX_MIC001_20260211_073914  TX01  16 本  Nabe
260411/Wireless1                  Wireless1  7 本  Fujii
260411/Wireless2                  Wireless2  5 本  Fujii
-> 人 3 人(Asao / Fujii / Nabe)/ 置き場 4 / 機材 5

裏取りするなら台帳へ機材のキー(TX02 / 260411/TX02)を足す。

機材のキーは置き場のキーより優先される。

足りないときは Clips_Raw/ を大きさで絞らずに走査して候補を挙げる

(findMissingChunkCandidates)。本編時刻から予測した収録開始との差で並べ、

下限バイト数で候補から外れていたものはそう書く。

見つからなければ「見つからなかった」と書く。推測で素材を作らない。

6-6d. 「素材が無い」ではなく「窓を置いた場所が悪い」(2026-08-13)
素材は確実にあるよ。だって作ってるもん。(浅尾 2026-08-13)

そのとおりだった。 Part7 と Part12 が delivery の下限(98%)で止まっていた原因は、

素材の不在ではなく探し方だった。

何が起きていたか

区間写像(A の道)は、素材(カメラ本体音)の中に等間隔に窓を置いてマイクを探す

(planPartFromEditMap。窓 90 秒 × 20 本)。素材が 4009 秒なら窓の間隔は約 203 秒。

素材の終わり際にだけ重なるマイクには、窓が 1 本しか入らない。

重なりが 90 秒未満なら1 本も入らない。resolveOffsetFromWindows は

「2 窓以上で一致」を要求するので、当たっていても落ちる。 実測 2026-08-13:

Partマイク素材最大相関当たった窓実際
Part12TX02_MIC019(あさお)20260411_C0358.MP40.921/13居る
Part12TX01_MIC049(なべ)20260411_C0358.MP40.761/13居る
Part1200039_Wireless_PRO.WAV(ふじい)20260411_C0356.MP40.671/20居る
Part7TX02_MIC020(ふじい)C0017.MP40.230/20居る(重なり 49.8 秒)

どれもレコーダの次のチャンク(MIC018 → MIC019、MIC048 → MIC049)。

収録は切れていない。窓が届いていなかっただけ。

もう 1 つの取りこぼしが走査の下限バイト数(既定 20MB = 139 秒)。

Part7 のあさお TX01_MIC023_20260215_182301_orig.wav は 8.6MB(59 秒)で、

候補にすら入っていなかった。

どう直したか(判定は 1 つも緩めていない)

変えたのは窓を置く場所だけ。lib/audio/part-edit-map.ts。

  • 末尾に短い窓を 2 本足す(window / 4、下限 15 秒)。

これは種を取るためだけ。 上の 90 秒の窓と重なるので

resolveOffsetFromWindows へは渡さない(「重なった窓は独立した検算にならない」を破らない)。

  • 窓を置き直す第 2 走査(focusedClockOffset)。種の k から

「マイクと素材が重なっている範囲」を割り出し、その中にだけ重ならない窓を 2 本以上置く。

窓は長いほうを優先し、minWindows に届かないときだけ縮める(下限 FOCUS_MIN_WINDOW_SEC = 15 秒)。

合否はここが出す。閾値は 1 回目と同じ(相関 0.35 / 2 窓以上 / ばらつき 50ms 以内)。

  • 種が無いマイクには走らせない。総当たりにすると偶然の山を掴む。

FOCUS_MIN_WINDOW_SEC(15 秒)は PLACEMENT_VERIFY_DEFAULTS.minWindowSec(8 秒)より

厳しい。あちらは本編相手で「写像が予測した位置に山が立つか」を見るが、

こちらは位置を予測せずに素材の中を探すので、短い窓ほど偶然の山を掴む。

実測(Part7 のふじい、重なり 48.7 秒。窓長を総当たり): 短くするほど偽の山が増える。

24s は 2 本中 1 本が正解(偽 0.36)、20s も 1 本(正解 0.90)、

16s は 3 本中 1 本(偽 0.26 / 0.42)、12s は 4 本中 1 本で偽が 3 本(0.34〜0.44)。

--focus-thin-scans に相当する切り口は PlanOptions.focusThinScans(既定 true)。

false にすると 2026-08-12 までと同じ値になる。比較のためだけに残してある。

時間は増える。 種は「1 回目でどこかに当たった」だけなので、

居ないマイクにも立つ(実測 Part3: 種を持つ未検証ペアが 73 組)。

そのぶん第 2 走査が走り、素材の切り出しが増える。

偽の位置が通ることは無い(重ならない窓 2 本が 50ms 以内で揃う必要がある)が、

1 Part あたり数分〜十数分伸びる。

効いた量(実測 2026-08-13、本番経路の出力)
Part前後拾ったマイク
Part1288.80%100.00%TX01_MIC047(なべ)/ 00039_Wireless_PRO(ふじい)/ TX02_MIC019(あさお)/ TX01_MIC049(なべ)
Part797.89%97.89%(変わらず)TX01_MIC023(あさお、8.6MB。下限を 5MB へ下げて拾った)。ふじいは下を見よ

Part12 で落ちていた 557.8 秒(本編 3489.5〜3859.5s の 370.0s と 5383.6〜5565.7s の 182.1s)が

全部載った。 拾った 4 本はどれもレコーダの次のチャンクで、

verifyPlacementsAgainstProgram(本編相手の独立な検算)も 4〜5/5 窓で通っている。

第 2 走査は冗長な辺も増やす(同じマイクを 2 本目の素材にも合わせられる)。

Part12 の「相関の辺」は 47 → 123 本、「冗長な経路で検算できたマイク」は

15/27 → 42/63 本、残差の最大は 10 → 11ms。独立な裏付けの数が増えた。

Part12 は `--go` で 9 本(99.13 分)を書き出した。 ただし

4 つの AND(§6-4)は通っていない。 通ったのは loudness と delivery の 2 つ。

関門判定理由
loudnessPASSI=−14.2 LUFS / TP=−1 dBFS(目標 −14 / −1)
deliveryPASS100.00%(下限 98%)
coverageFAIL判定できない。 Part12 の DRT にコンパウンドが 1 つも無く、ダイジェストの尺が不明。0 と置けば 6.36 分の余裕がある(§6-12)
verificationFAIL区切り 5 → 7 の境目を出力から測れない(区切り 7 が 2.32 秒しかなく、有声フレームが 16 個。最低 30)。未検証であって不一致ではない

どちらも今回の第 2 走査とは関係が無い。 新しく載った 4 本が関わる境目

(9→11 / 11→12 / 12→14 / 14→15)は全部 PASSで、

出力を測った有声中央値の差は −0.18 / −5.40 / +3.68 / −3.06dB(上限 6dB)。

探す器(scripts/find-missing-mics.mts)

「素材が無い」と書く前に、探した範囲を数えるための道具。組まない。測って並べるだけ。

npx tsx scripts/find-missing-mics.mts --part Part7,Part12

<Part>_mix_source.attempt.json の refusedRanges(落ちた区間)を読み、

その区間の中にだけ窓を置き直して Clips_Raw/**/Audio/ を下限 0MB で走査する。

物差しは 2 本。どちらも同じ形(`kSec` = マイクの秒 − 素材の秒)で答えを出す。

物差し何を切り出すかいつ効くか
P(本編)`Final/<Part>.mp4\.mov\.mp3` の落ちた区間全員の声が入っているので、誰のマイクでも探せる
C(素材)写像がその区間で指しているカメラクリップ素材どうしなので山が高い。本編より長いので窓が置ける
F(置き直し)上の 2 つで種が取れたときに focusedClockOffset を呼ぶマイクが区間の途中から始まっているとき

本編の 1 箇所 = 1 票。 マルチカムの変種は同じカメラクリップの同じ区間を何度も返すし、

1 つの箇所を Cam1 と Cam2 の両方が指していることもある。それは物差しが 2 本ある

という意味であって、票が 2 つあるという意味ではない。

(最初の実装はここを重複させていて、1 つの窓が 2 票になり

「2 窓で一致」が 113 本中 60 本以上で成立した。検算を黙って緩めていた。)

出力は dev/knowledge/missing-mics-20260813.json。走査したディレクトリと本数、

落ちた区間ごとの合格・検算不能・不一致の内訳、窓 1 つずつの相関と k を全部残す。

precision は 100% ではない。窓数と相関で読む

実測 2026-08-13(Part12、窓 30 秒 × 5 本)で、明らかな偽陽性が 3 件混ざった。

窓相関ばらつき判定
TX01_MIC047(なべ、実在)4/50.890ms真
00039_Wireless_PRO(ふじい、実在)5/50.8360ms真
TX01_MIC034(別時間帯)2/50.3240ms偽
TX03_MIC017(別セッション 260215)2/50.370ms偽
TX02_MIC020(別セッション 260215)2/50.39200ms偽

分かれ目は「窓の数」と「相関」で、ばらつきではない。

偽陽性は全部 2/5 窓・相関 0.3〜0.5。真のものは 4〜5/5 窓・相関 0.77〜0.90。

この器の出力をそのまま採らない。 採否は本番(planPartFromEditMap の

第 2 走査 → verifyPlacementsAgainstProgram)が決める。この器は候補を挙げるだけ。

「素材が無い」と「検算できない」を混ぜない(Part7 のふじい)

Part7 の落ちた区間(本編 2296.2〜2345.2s、49.0s)は、あさおの TX01_MIC023 を

第 2 走査が見つけて 1/3 人 → 2/3 人になった。だがふじいは通っていない。

素材は在る。TX02_MIC020_20260215_182303_orig.wav(874 秒)で、

k = −2492.26(マイクの 0 秒目 = C0017.MP4 の 2492.26 秒目)。

独立な 2 系統が同じ値を出している。

物差し窓相関k
C0017.MP4(カメラ) source 2493.3 / 24s10.79−2492.26
C0017.MP4 source 2513.3 / 20s10.90−2492.26
Final/Part7.mp3(本編) record 2320.7 / 24.5s10.76−2492.31

それでも合格にしない。 重なり(source 2492.3〜2542.0、49.8 秒)に

重ならない窓を 2 本置くと、必ず片方が外れる。 実測(窓長を 10〜24 秒で総当たり):

窓長置けた本数正解した本数相関 0.35 以上
24s212(もう 1 本は偽の山 0.36)
20s211
16s312
12s414(偽の山 0.34〜0.44)

区間の後半(source 2517 以降)はエンディングで、ふじいのラベリアに合う音が無い。

短くするほど偽の山が増えるだけで、独立な 2 点にならない。

BWF の time_reference も 0(カウンタが停止で戻る型)なので第 2 系統が使えない

(lib/audio/recorder-timecode.ts §A)。

よって `unmeasurable`。合格とも不合格とも書かない。 Part7 の delivery は

97.89% のままで、落ちているのは本編 2296.2〜2345.2s の 49.0 秒。

採るなら浅尾の判断(--approve-refusal で理由を承認する)が要る。

同じ TX02_MIC020_20260215_... が Part12 の探索では偽陽性として出ている

(別セッション 260411 の Part に 260215 のファイルが 2/5 窓・相関 0.39 で当たった)。

同じファイルが、ある Part では真で、別の Part では偽。

found の 1 行を見て採らない理由がこれ。

6-7. 機材が違うマイクの音色を揃える(--tone-match)

浅尾指示 2026-08-11:「パート9以降は複数の機材で収録しているから、そのクオリティも合わせるんだ」。

収録セッションで素材の性格が違う。

セッションPart置き場の単位形式機材
260215Part1〜8人(Audio/Asao 等)pcm_s24le 48kHz mono同型ラベリア 3 本(TX01/02/03)
260411Part9〜12機材(Audio/TX_MIC001_* / Wireless1 / Wireless2)pcm_f32le 48kHz monoTX ラベリアと Rode Wireless PRO の混在

BALANCE_LUFS(−23)は音量しか揃えていない。周波数特性・ノイズフロア・

ゲイン構造は機材ごとに違うので、混ぜると「マイクが変わったのが分かる」音になる。

そこで鎖の手前に、トラックごとのスペクトルを基準へ寄せる段を足した

(lib/audio/tone-match.ts。差し込み口は mastering.ts の trackChainWithPre)。

何をどう測るか

声が鳴っている区間だけの長時間平均スペクトル(LTAS)。無音を混ぜると、

静かなトラックほどノイズフロアの形が平均を支配して「低域が足りない」と誤診する。

  • 13 バンド、2/3 オクターブ間隔(中心 63 / 100 / 160 / 250 / 400 / 630 / 1k /

1.6k / 2.5k / 4k / 6.3k / 10k / 16kHz)。境界は隣り合う中心の幾何平均なので隙間も重なりも無い

  • 補正するのは 100Hz〜10kHz の 11 バンドだけ。 63Hz は鎖のローカット(85Hz)の下、

16kHz は hiss しか無い。測って報告はするが触らない

  • 測り方は ffmpeg で生 PCM にしてから自前 FFT。ffmpeg の bandpass を使わないのは、

2 極バイキャッドのスカートでは隣のバンドの漏れが支配的になり、

「バンドごとの差」を測るという目的が崩れるため。加えて astats では無音を除外できない

基準の選び方(勝手に平均を採らない)
mode何を基準にするか
`median`(既定)機材ごとに束ねてから、バンドごとの中央値
longestいちばん長く声が鳴っている機材の実測スペクトル
file--tone-reference <file> で人が指した 1 本

既定を中央値にしたのは、1 本を基準にするとその 1 本の癖(席・装着・その人の声)まで

全員に配ることになるから。束ねる単位をクリップではなく機材にしたのは、

同じ機材が 2 本あると票が二重に入るため(実測 Part9: TX 6 本に対し Wireless 2 本)。

選んだ mode と理由は印字と JSON(`toneMatch.reference.reason`)に必ず残る。

寄せ方
  • 基準 − 実測(どちらも補正バンドの平均を 0dB に正規化してから)
  • 平均を引く。 補正で音量が動くと BALANCE_LUFS と喧嘩する
  • --tone-strength 倍(既定 0.7)。完全一致を目指さない。その機材の良さも消える
  • ±6dB で切る(--tone-max-db)。切ったバンドは clippedBands に記録して印字する
  • 隣と均す([0.25, 0.5, 0.25])。段差のある補正は不自然に鳴る

ベルの Q は 2.5。測って決めた。 最初は「重ねたほうが滑らか」と考えて 1.4 に

していたが、ホワイトノイズへ通して実測すると大幅に出しすぎていた

(2026-08-11、60 秒。頼んだ値と実現値の差、最大 / 平均 dB):

Q傾き山高域落ち
1.03.55 / 2.115.01 / 2.72—
1.42.28 / 1.263.15 / 1.68—
2.01.02 / 0.561.38 / 0.790.85 / 0.32
2.150.80 / 0.471.09 / 0.640.76 / 0.28
2.50.92 / 0.320.66 / 0.341.13 / 0.21
3.01.28 / 0.280.27 / 0.131.54 / 0.23

Q が低いと隣り合うベルが足し合わさり、Q=1.4 では +5.0 を頼んだ山の頂点に +8.1 が出た。

これは「7 割寄せる」という約束を黙って壊す。EQ は組んだだけでは効いた量が分からない。

掛けるかどうか(既定は「同一機材なら素通し」)

--tone-match auto(既定)が掛けるのは「**名前から機材を読めたファイルだけで構成され、

かつ機材が 2 種類以上ある**」ときだけ。読めないファイルが 1 つでもあれば掛けない。

チャンネル番号は機材の型ではない。 TX01 / TX02 / TX03 は同じ送信機の別チャンネルで、

まとめて TX-lavalier にする。ここを分けると 260215 の Part4〜8 が「3 機材」に見えて、

承認済みの音に補正が掛かる。

構成判定
Part1〜3 katto_partN_<人名>.wav(型を読めない)素通し
Part4〜8 TX01/02/03_MIC*(1 機材)素通し
Part9〜12 TX + *_Wireless_PRO.WAV(2 機材)掛ける

スペクトルの差で判定してはいけない。 実測 2026-08-11 で、

同型ラベリア 3 本の Part1 のほうが、機材が混在する Part10 より差が大きかった。

人が違えば同じマイクでもスペクトルは違う。**揃えるのは機材の差だけで、

話者の声の違いは潰さない**ので、判定は機材で行う。

実測 2026-08-11

Part1(260215、同型機材)は既定で 1 バイトも変わらない。

--out を分けて再生成し、出荷済みの wav と SHA-256 を突き合わせた。

出荷済み Part1_mix_source.wav791df2dcde8004b166b7f3269b7f76ebbdb6ce16c6e179c1a5d5830efaae0031
新しい段を入れて再生成同じ
I / LRA / TP−14.0 / 4.6 / −1.0(PASS。§6-0 の表と一致)

Part1 のトラック間バンド差は 最大 6.61dB / 平均 2.47dB(素通しなので前後で不変)。

同型機材でもこれだけ違う。 だから差の大きさで掛ける掛けないを決めない。

Part10(`--axis source --go`、`--tone-match auto` で発動)。

トラック間のバンド差(各トラックは補正バンドの平均を 0dB に揃えてから比べている):

バンド寄せる前寄せた後
63Hz (補正外)6.806.50
100Hz1.790.83
160Hz1.671.04
250Hz0.960.53
400Hz1.521.06
630Hz1.230.71
1000Hz2.811.39
1600Hz2.051.33
2500Hz0.990.71
4000Hz1.971.71
6300Hz2.101.44
10000Hz1.910.80
16000Hz (補正外)3.953.81
補正バンドの最大 / 平均2.81 / 1.731.71 / 1.05

最大で 39%、平均で 39% 縮んだ。±6dB の上限に当たったバンドは 1 つも無い

(実際に掛かった最大は 1.01dB)。寄せ切ってはいない。7 割寄せなので差は残る。

補正外の 63Hz / 16kHz がわずかに動いているのは、隣のベル(100Hz / 10kHz)の裾が届くため。

出力は I=−14.2 / LRA=7.3 / TP=−1.0 → PASS。

機材ごとの素性(鎖の前の素材を測ったもの。組み立ての埋め草は統計から外してある):

file機材I(LUFS)LRATPノイズフロア声持上げ補正
TX02_MIC011TX-lavalier−34.518.7−1.8−66.2−33.9+13.94 バンド 最大 0.37dB
TX02_MIC012TX-lavalier−34.418.1−4.0−66.1−34.1+14.38 バンド 最大 0.61dB
TX01_MIC041TX-lavalier−41.717.7−8.7−68.1−41.2+18.310 バンド 最大 0.95dB
TX01_MIC042TX-lavalier−37.717.2−3.3−67.7−37.1+16.07 バンド 最大 1.01dB
00036_Wireless_PRORode Wireless PRO−40.316.6−4.6−63.5−39.3+18.22 バンド 最大 0.25dB

この段が揃えるのは音色だけ。 他の 3 つは測って出すだけで、直していない。

  • ノイズフロアは Wireless PRO が TX より 2.6〜4.6dB 高い。

本番経路とは独立に生ファイルの中盤 600 秒を測ると、差は高域でもっと開く

(6.3kHz の床 −91.5 対 −96.6 / −98.9、10kHz の床 −93.1 対 −98.4 / −100.8。5〜8dB)。

いまの一律 noiseReductionDb(podcast-loud は 6dB)はここを揃えない。

機材ごとにノイズ低減量を変える段は入れていない(プロファイルの数値は浅尾承認済み)

  • ダイナミクスは LRA 16.6〜18.7 と 2.1 LU 以内。

内部リミッタが掛かっている機材があるという証拠は出なかった

  • ゲイン構造の差(持ち上げ量 +13.9〜+18.3dB、4.4dB)は機材の差とは限らない。

260215(同型機材)でも Part1 の生の I は −26.5 / −38.3 / −28.3 LUFS(11.8dB 差)で、

持ち上げ量は +10.0 / +16.3 / +11.4dB。装着位置と声量でも同じだけ動く。

持ち上げ量が大きいトラックはノイズも一緒に上がるので、値は JSON

(toneMatch.tracks[].balanceGainDb / noiseFloorDb)に必ず残す

測定で見つかった別の欠陥を 2 つ残す。

  • Part1 の生素材は True Peak が +0.3 / +0.1 dBFS(Asao / Nabe)で既にクリップしている。

build-part-mix.mts は trackChain(o, false) を呼んでおりデクリッパを通していない

  • 組み立てた素材は、そのマイクが覆っていない区間が厳密なゼロで埋まっている。

最初これを「静かなフレーム」に数えていたので、Part10 の TX 4 本のノイズフロアが

−200dBFS になっていた。デジタル無音は録音のノイズではない

(silenceFloorDb、既定 −100dBFS で外す)

6-7b. Part を跨ぐ音色の基準(--tone-reference-mode program)

浅尾指摘 2026-08-11:「Part1、反響が割とありそうな気がする。もっと近くでポッドキャスト風に

立っていたと思うが、7 は」 → 測った → 判断:「b でいこう」(= Part7 の発話スペクトルを全 Part の基準にする)。

測ってから決めた(反響ではなかった)

scripts/measure-reverb.mts で Part1 と Part7 の完成出力を同じ方法で測った。

指標Part1Part7読み
語間 − 発話(主指標)−21.42dB−20.98dB差 0.44dB。0.5dB 未満なので「差は測れなかった」
減衰の傾き−13.1 dB/s−11.9 dB/sPart1 のほうが速く減衰(乾いている)
発話直後 +150ms の落ち11.5dB12.4dBほぼ同じ

反響は出なかった。 出たのは発話そのものの帯域(音量差を正規化した曲線、Part1 − Part7):

2500Hz  −0.94dB    4000Hz  −2.65dB    10000Hz  +3.13dB

2.5〜4kHz(プレゼンス帯)が弱い。 これが「遠い / 立っていない」と聞こえる正体で、

部屋鳴りではない。素材の段階で既にそうなっている(生マイク、正規化済み 2.5kHz):

2500Hz4000Hz
katto_part1_Asao.wav(11:55 収録)−4.16−8.63
TX01_MIC006(12:26)−1.20−7.88
TX01_MIC021(Part7、17:40)+0.17−8.30

ファイル形式は同一(pcm_s24le 48kHz mono)で、mic-rename-manifest.json によれば

katto_part1_Asao.wav の正体は TX01_MIC005_20260215_115546_orig.wav = 同じ TX ラベリア。

劣化経路ではなく、午前のピン位置が午後より遠かったと読める。

なぜ Part 内の基準では埋まらないか

median / longest / file はどれもその Part の中から基準を選ぶ。

Part1 は 3 本とも同じだけ暗いので、中央値を採っても暗いまま。Part どうしの差は原理的に埋まらない。

そこで `program` を足した。基準を台帳に固定して、全 Part をそこへ寄せる。

  • 台帳: dev/knowledge/tone-reference-part7-20260811.json
  • 中身: Part7 の発話区間の長時間平均スペクトル(正規化済み)、バンド定義、測った素材、測定日
  • 毎回 Part7 を測り直さない。 測り直すと基準が揺れて、同じ入力から同じ出力が出なくなる
  • 測る位置は鎖の手前(組み立て → デクリップの直後)。音色合わせが実際に掛かるのと同じ場所。

完成 mix(鎖の出口)を基準にすると、EQ・コンプ・エキサイタが掛かった後の形を

掛かる前の素材に要求することになり、二重に掛かる

  • 作り方(本番と同じ入口から入れて同じ場所で測る):

```bash

npx tsx scripts/build-part-mix.mts --part Part7 --tone-match on --go --out <scratch>

npx tsx scripts/build-tone-reference.mts --from <scratch>/Part7_mix_source.json --go

```

  • 読み込み時にバンド定義を検算する。中心周波数が 1 つでも違えば投げる

(同じ番号のバンドが別の帯域を指すと、補正が黙って別の場所へ掛かる)

発動条件を変えた

auto の 2 つの見送り条件(機材名が読めない / 機材が 1 種類)は、どちらも

「Part 内の中央値へ寄せても意味が無い」ことを言っている。基準が外にあるなら前提が変わる。

実測 Part1 と Part7 はどちらも TX ラベリア 1 種類だが、発話の 4kHz が 2.65dB 違う。

--tone-reference-mode program を渡したときは、機材構成によらず掛ける。

検証(2026-08-11、全 Part を回す前に通した)

1. Part7 自身を基準に掛けると素通しになるか。

Part 全体のバンド差(重み付きパワー平均 − 台帳)は 13 バンドすべて 0.0000dB。

掛けた後も最大 0.153dB。基準と自分の差はゼロという定義どおり。

測る側の定義を 1 度間違えた。 最初はトラックごとに正規化してから平均していて、 Part7 対自分で 100Hz +1.27dB / 4kHz −1.35dB の見かけの差が出た。 正規化は平均を引く非線形な操作なので、平均と交換できない。 chooseToneReference と同じ順序(生のバンド値をパワー平均 → 最後に 1 回正規化)へ直したら 0 になった。 補正そのものは最初から正しかった。

なお program モードで各トラックに掛かる量は median モードと完全に一致する

(Part7 の台帳は Part7 の median そのものなので)。1.92 / 1.71 / 2.36 / 1.11 / 0.63 / 1.70 dB。

2. Part1 の 2.5kHz / 4kHz は縮んだか。 独立な 2 系統で測った(strength 0.70 のとき)。

測り方2500Hz4000Hz
鎖の手前・Part 全体 − 台帳(referenceGapAt)−1.13 → −0.27−2.93 → −1.98
完成 mix・Part1 − Part7(measure-reverb)−1.47 → −0.43−2.93 → −1.54

縮んだ。 ただしゼロにはならない(strength 0.7・平均引き・平滑化のぶん)。

4kHz は 1.5〜2.0dB 残る。「Part7 と同じになった」とは書かない。

3. 反響は悪化していないか。(プレゼンスを上げると部屋鳴りも上がる)

語間 − 発話減衰
Part1 前(連結版)−21.42dB−13.1 dB/s
Part1 後・区切り 1−21.13dB−18.1 dB/s
Part1 後・区切り 2−23.31dB−5.9 dB/s

区切り 1 は語間が 0.29dB 上がったが判定の下限 0.5dB 未満で、減衰はむしろ速くなった。

悪化していないので strength は 0.7 のまま。

(区切り 2 は切れ目が 20 本しかなく、減衰の標本が薄い)

4. 上限に当たったバンド。 Part1 / Part7 とも 0 バンド。

掛かった最大は Part1 で 3.81dB(TX03_MIC005)、Part7 で 2.36dB(TX02_MIC018)。

変えていないもの(設計思想)
  • 上限 ±6dB。 当たったバンドは toneMatch.tracks[].correction.clippedBands に残す
  • `strength` は 0.85。実測で決めた(浅尾判断 2026-08-11「トレードオフが検出されていない以上、

選ぶ余地が無い」)。Part1 を 0.70 / 0.85 で作って比べた:

2.5k 残差4k 残差mix の 2.5kmix の 4k語間−発話減衰
0.70−0.27−1.98−0.43−1.57−21.01−16.7
0.85−0.10−1.78−0.16−1.28−21.33−18.0

全指標が良い側にしか動かず、上限に当たったバンドも 0。

TONE_CORRECTION_DEFAULTS.strength(0.7)はライブラリの既定として残す

(Part を跨ぐ基準を使わない呼び出し側を動かさないため)。完全一致は目指さない

  • Q = 2.5 のまま(§6-7 の実測。Q=1.4 では +5.0dB のつもりが +8.1dB 出た)
  • 隣接バンドで平滑化 → もう一度平均を引く(音量を動かさない)
  • podcast-loud の値は 1 つも変えていない
6-7c. 機材の違いは、まず測って出す(2026-08-13)

浅尾指示 2026-08-13:

機材の違いを消さなくてもいいが、ダイナミクスとかが違うだろうから一応検証した方がいいのでは? ぐらいのイメージだ。32bit float のものとそうでないものが混じってたりするからな

目的は「消す」ではなく「測って出す」。 自動で寄せるのは、寄せる根拠が実測で出たときだけ。

比較の単位を先に作る(改名でファミリが読めなくなっていた)

recorderFamily は名前でしか型を読めない。**Part1〜3 の katto_partN_<人名>.wav は

TX01_MIC005_... の改名後**なので型が読めず、Part1 の 6 本が

unknown:katto_part1_Asao.wav / …Fujii / …Nabe / TX-lavalier の 4 群に割れていた

(うち 3 群は 1 人 1 群)。比較の単位が機材から人へ落ちていた。

改名表(dev/knowledge/mic-rename-manifest.json)を渡すと復元する。

改名表そのものは中身の bext(改名しても書き換わらない)と 15/15 一致しているので、

名前と独立な根拠で裏が取れている(checkRenameAgainstTimecode)。

直す前  機材 unknown:katto_part1_Asao.wav×1 / …Fujii×1 / …Nabe×1 / TX-lavalier×3   (4 群)
直した後 機材 TX-lavalier×6(うち 3 本は改名表で型を復元した)                      (1 群)

復元の目的は「束ねて寄せる」ではなく「比較の単位を作る」。 どれとどれが同じ機材か

分からないと、差が機材由来か人由来かを切り分けられない。

何を測るか(gearProfile。掛けたかどうかと関係なく毎回出す)

鎖に入る前の素材を、Part ごと・トラックごとに測る。印字と JSON(evidence.gearProfile)の両方。

見るもの出す値
形式codec / bits / numberFormat(int か float か)/ sampleRateHz。元の wav を ffprobe する(組み立て後の f32 を測ると全部 float に見える)
ゲイン構造積分 I(LUFS)、声のレベル、声の中央値、−23LUFS までの持ち上げ量
ダイナミクスLRA、真のピーク、声区間の p10 / p50 / p95
ノイズ無音区間の RMS(床)、SNR
スペクトル群の中のバンド差 / 群どうしのバンド差

1 つの Part の中で形式が混ざっていたら名指しで出す(formatMixed)。

実測 2026-08-13(形式)

Clips_Raw/ を全部 ffprobe した。混在は 1 か所だけで、そこは Part に載っていない。

セッション置き場形式本数
260215Asao / Fujii / Nabepcm_s24le 24bit 整数19 / 19 / 18
260411TX_MIC001_20250217_025120pcm_f32le 32bit float(TX02)15
260411同上`pcm_s24le` 24bit 整数(TX04、2025-02-17 の残り物)4
260411TX_MIC001_20260211_073914pcm_f32le 32bit float(TX01)16
260411Wireless1 / Wireless2pcm_f32le 32bit float7 / 5

同じ置き場の中で形式が割れているのは `TX_MIC001_20250217_025120` だけ。

割れているのは中に機材が 2 台入っているためで(§6-6c)、

TX04 の 4 本は 2025-02-17 の収録なので、Part9〜12 のどれにも載っていない

(相関が立たないので識別で落ちる)。したがって

出荷される Part の中では形式は混在していない(Part1 = 24bit 整数のみ、

Part10 = 32bit float のみ)。Part を跨ぐと違うので、Part を跨ぐ基準

(--tone-reference-mode program、Part7 = 24bit 整数)を使うときはそれを承知で使う。

実測 2026-08-13(ゲイン構造とダイナミクス)

32bit float の 260411 は、24bit 整数の 260215 より 10dB 前後低いところに録れている。

−23LUFS まで持ち上げる量(preGainDb):

Part形式素材の I(LUFS)−23 までの持ち上げ声の p50(鎖の出口)
Part124bit 整数−25.7 〜 −38.3+2.5 〜 +16.1dB−31.5 〜 −34.4
Part1032bit float−34.3 〜 −47.3+12.0 〜 +18.9dB−32.8 〜 −33.6

ただし、ばらつきは形式ではなく人と装着で決まっている。

Part1 は 6 本とも同じ TX ラベリア・同じ 24bit 整数なのに、持ち上げ量が 13.6dB ばらつく

(katto_part1_Fujii.wav +16.1 対 TX03_MIC005 +2.5)。SNR も 24.7 〜 36.7dB で 12.0dB 割れる。

同じ機材・同じ形式でこれだけ割れるので、形式の違いを持ち上げ量の説明にしない。

持ち上げは既に鎖の手前でトラックごとに掛かっている(§6-8 の欠陥 1)ので、

形式が違っても鎖へ入る絶対レベルは揃っている。ここは既に直っている。

実測 2026-08-13(差の切り分け)

同じ機材の中の差 = 人の声由来。群どうしの差 = 機材(+その群に居る人)由来。

Part群群の中の差(人由来)群どうしの差(機材由来)
Part1TX-lavalier ×6(1 群)最大 6.77dB / 平均 3.95dB測れない(群が 1 つ)
Part10TX-lavalier ×4最大 2.43dB / 平均 1.45dB最大 1.18dB / 平均 0.66dB
Part10Rode-Wireless-PRO ×2最大 1.47dB / 平均 0.82dB同上

**機材が混在している Part10 でも、機材由来の差(1.18dB)は

同じ機材の中の人由来の差(2.43dB)より小さい。**

「機材が違うから音色を寄せる」という動機は、この案件のこの構成では実測に支えられていない。

束ねる単位(--tone-group)。既定を `gear` にした(浅尾判断 2026-08-13)
オーディオはまぁ音量ベースだけ同じであればとは同じでよさそうやね

= 音量が揃っていれば十分。音色まで人ごとに寄せなくてよい。

音量は別の段で揃えてある(鎖の手前の −23LUFS = preGainDb、鎖の出口の声の中央値 = --balance)。

音色でやるのは機材の群どうしを寄せるところまで。

gear(既定)track(2026-08-13 までの既定)
何を測って補正を作るか機材の群を束ねたパワー平均トラック 1 本
同じ機材の中の差(= 人の声)残る潰れる

--tone-group track で戻せる。**機材の型を読めないファイルは機材の個体

(unknown-gear:<recorder>)で束ねる**ので、束ねる相手が居なければ結果は track と同じ。

人へは落とさない。

実測 Part1(--tone-reference-mode program、strength 0.85。両方を `--go` で作って測った):

trackgear(既定)
Asao の補正 2.5k / 4k+2.04 / +2.37 dB+1.27 / +1.31 dB(6 本とも同じ値)
Part − 台帳 の残差 2.5k−1.134 → −0.096−1.134 → +0.003
Part − 台帳 の残差 4k−2.950 → −1.780−2.950 → −1.879
トラック間のバンド差6.77 → 2.89dB6.77 → 6.82dB(動かさない)
出力 I / LRA / TP−14.1 / 5.1 / −1.0−14.1 / 5.0 / −1.0

Part1 の 6 本は全部同じ TX ラベリアなので、6.77dB は定義上すべて人の声の差。

track はそのうち 3.88dB を潰していた。gear は潰さず、

Part 全体と台帳の差は 2.5kHz でむしろ小さくなる(−0.096 → +0.003)。

4kHz は 0.1dB 悪くなる(−1.780 → −1.879)。ラウドネスは動かない。

Part10(機材が混在する側)でも 機材由来 1.18dB < 同じ機材の中の人由来 2.43dB なので、

「機材が違うから寄せる」で動かしてよい量はもともと小さい。

6-8. ボリューム感とダイナミクス感を揃える(--dynamics-match)

浅尾指示 2026-08-11:

ちゃんと機材ごとに本当にしっかり合わせて欲しいのだよ、全く同じエフェクトをかける 前提としてのボリューム感であったりダイナミクス感を合わせないと最終のエフェクトかけても ズレるだけだろ

正しい。3 つの欠陥があった。

欠陥 1: 音量合わせが鎖のあとだった(これが本体)

鎖の閾値は全部が絶対 dBFS である。

段閾値
ノイズ低減 afftdnnf=-45 dBFS
下方展開 agate−40 dBFS
レベラー−24 dBFS
ピークコンプ−12 dBFS

そこへ入る素材のレベルはばらばらだった。実測 2026-08-11、Part1 の声の中央値:

声 p50
Asao−30.3 dBFS
Nabe−36.0 dBFS
Fujii−41.7 dBFS

11.4dB 違う。 同じ nf=-45 でも Fujii にとっては床のすぐ上、Asao にとっては遥か下。

同じ −24dB のレベラーでも跨ぐ回数がまるで違う。

「全く同じエフェクトをかける前提」がここで崩れていた。

直し方: `BALANCE_LUFS`(−23)を鎖の手前へ移した。

鎖の出口に残る食い違いは、そのあとで詰める。鎖の値そのものは 1 つも変えていない。

欠陥 2: デクリッパを飛ばしていた

build-part-mix.mts は trackChain(o, false) を呼んでおり、

真のピークが 0 を超えている素材にもデクリップを掛けていなかった(§6-7 の測定で発覚)。

Part1 の生素材は Asao +0.3 / Nabe +0.1 dBFS で既にクリップしている。

歪んだまま EQ すると歪みまで持ち上がる(§3 の段 1)。

いまは組み立て済みの素材を測った真のピークが 0 を超えたトラックにだけ掛け、

掛けたかどうかを dynamicsMatch.tracks[].declipped に残す。

デクリップは独立した段にして、いちばん先に置く。順序が効く。

adeclip は窓ごとに |サンプル| のヒストグラムを作って頭打ちを探すので、

±1.0 に正規化されている前提で動く。欠陥 1 の直しで音量合わせを鎖の手前へ移したが、

それを先に掛けるとヒストグラムが飽和して検出が壊れる(Part1 の Asao は +10dB 前後

持ち上がるので、頭打ちが ±3 付近に来てビンから溢れる)。

だから鎖の中でも音量合わせの後でもなく、音色にも音量にも触る前に単独で通す。

フィルタの値は mastering.ts の DECLIP_FILTER が唯一。

段の順序:

組み立て → **デクリップ** → 音色補正 EQ → **音量合わせ(−23 LUFS)**
        → **ダイナミクス合わせ** → 共通の鎖 → 残差の詰め → オートミキサー → バス
欠陥 3: 揃えていたのが積分ラウドネスというスカラ 1 個だけ

BALANCE_LUFS は「無音を含む全体の量」しか揃えない。抑揚の形は揃わない。

LRA が 18 のトラックと 12 のトラックに同じコンプを掛ければ、出てくるものは別物になる。

測るのは声が鳴っているフレームのレベル分布(p10 / p50 / p95)。

実測 2026-08-11、Part1(鎖の前):

file声 p10声 p50声 p95広がりLRA
katto_part1_Asao−41.2−30.3−20.221.013.5
TX01_MIC006(Asao 2 本目)−42.1−32.0−18.723.517.8
katto_part1_Fujii−48.4−41.7−32.316.112.6
TX02_MIC005(Fujii 2 本目)−49.4−41.5−29.719.717.4
katto_part1_Nabe−44.9−36.0−22.122.816.4
TX03_MIC005(Nabe 2 本目)−43.2−32.7−19.723.517.7

トラック間で広がりが 7.40dB 食い違う。

LRA だけでは足りない。 LRA 13.5 の Asao と 12.6 の Fujii は 1 LU 差しかないのに、

声の広がりは 21.0 対 16.1 で 4.9dB 違う。LRA は無音を含む全体の指標なので、

喋る割合が違うトラックでは声の抑揚を表さない。 両方を測って両方出す。

どう揃えるか(lib/audio/dynamics-match.ts)

`tone-match.ts` と同じ形にしてある。 基準を決める / 上限を置く /

完全一致を目指さない / 発動条件を明示する。違うのは測る量だけ。

音色(§6-7)ダイナミクス(ここ)
測る量バンドごとのレベル(LTAS)声のフレームのレベル分布
束ねる単位機材(同じ型は同じ周波数特性)人(抑揚は人に付く。同じ人の 2 チャンクを二重に数えない)
発動条件機材が 2 種類以上(スペクトルの差で決めない)実測の食い違いが 1dB 以上
基準機材ごとに束ねた中央値人ごとに束ねた中央値
寄せる割合0.70.7
上限±6dB / バンド±3dB / アンカー

発動条件が音色と逆なのは、直したいものが違うから。

音色は「人が違えば同じマイクでもスペクトルは違う」ので機材で判定する

(話者の声の違いを潰さないため)。ダイナミクスは**原因が機材でも人でも、

揃っていなければ同じ鎖が別物を出す**。直したいのは「同じコンプを通したときに

結果がずれること」そのものなので、実測の食い違いで判定してよい。

中央値(p50)は動かさない。 音量は鎖の手前の静的ゲインの仕事で、

ここが音量まで触ると 2 つが喧嘩する(音色補正が平均を引いているのと同じ理屈)。

動かすのは中央値からの広がりだけ。

ffmpeg の compand に5 点の伝達曲線を渡す(acompressor は「広げる」ができない)。

  • `delay=0`。 compand の delay は先読みのため出力を遅らせるが ffmpeg は戻さない。

1 サンプルでもずらしたら同期が壊れる

  • attacks=0.35 / decays=1.2。遅くする。 揃えたいのは段落ごとの声の大きさで、

音節ではない。速くすると鎖の中のコンプと二重になる

  • 傾きが 0.25〜4 の外に出る補正は掛けない。理由を残す
  • 上限に当たったトラックは clippedBands ならぬ clipped に記録して印字する
音量は「積分」か「声の中央値」か(--balance)

実測で比べてから決める(compareBalanceBases)。積分ラウドネスは無音を含む

全体の量なので、喋る割合が違うトラックでは声そのものの大きさが揃わない。

残る食い違いは voiced − integrated のばらつきで、どちらを軸にしても同じ量。

だから「小さいほう」では決まらない。**耳が聞くのは声なので、1dB 以上割れるなら

声の中央値を揃える。**1dB 未満なら出荷済みの音を動かさないほうを採る。

検算

「掛けた」ことではなく「差が縮んだ」ことだけが成果。

鎖の出口(=混ざる直前)でもう一度測り、

トラック間の食い違いが縮んだかを dynamicsMatch.spread に残す。

ここで測るのは「同じコンプを通したあとに揃っているか」という、直したかった当のもの。

`spread.before → after` は A/B ではない(2026-08-13)

spread の before は鎖の前(素材)、after は鎖の出口である。

測定点が違うので、この 2 つを引き算しても補正の効果は出ない。

出てくるのは「補正 + 鎖」の合計で、鎖(コンプ・レベラー・下方展開)のほうが

効きは大きい。実測 Part1 で記録されている唯一の数字は

span 7.26 → 9.71、p10 側 4.43 → 7.35 という悪化だが、

これは補正のせいだとは言えない(掛けていなくても鎖は通る)。

決めるには off と on を同じ Part で作り、同じ点で比べる。

ノイズ合わせを既定 off にしたときと同じ手続き(§6-8b「実測: 効かなかった」)。

npx tsx scripts/build-part-mix.mts --part <Part> --go --out <A> --dynamics-match on
npx tsx scripts/build-part-mix.mts --part <Part> --go --out <B> --dynamics-match off

比べるのは**両方の dynamicsMatch.spread.after***(どちらも鎖の出口)と、

完成 mix の measure-reverb / measure-gate-chop。

縮まなければ既定 off にする。

実測(Part11、2026-08-13)。通ったので既定は on(`auto`)のまま

同じ Part を --out だけ変えて 2 本作り、同じ測定点(鎖の出口)で比べた。

鎖の出口のトラック間の食い違いonoff
中央値から下(p10 側)3.76dB5.37dB
中央値から上(p95 側)1.19dB1.53dB
p95 − p104.23dB6.08dB

直したかった当のものが 6.08 → 4.23dB(−1.85dB)縮んでいる。 鎖の前はどちらも同じ

(3.63 / 2.65 / 4.19dB。掛ける前なので当然)。

完成 mix(measure-reverb / measure-gate-chop、base = on):

語間 − 発話途切れ(自己基準 ≥6dB)途切れ(差分 ≥3dB)語頭 50ms
on−19.80dB108.3 件/分基準基準
off−22.06dB146.7 件/分33.7 件/分 / 0.60 秒/分−1.13dB

off は語間が 2.26dB 静かだが、途切れが増え語頭が 1.13dB 削れる。

補正を外すと広がりの大きいトラックが鎖のゲートを深く踏むためで、

§6-8c の判断(声を欠くほうが害が大きい)をそのまま当てると on が勝つ。

2026-08-12 まで記録されていた「悪化」(span 7.26 → 9.71)は測定点の取り違えだった。

鎖そのものが広がりを開くので、before(鎖の前)→ after(鎖の出口)を引くと

掛けていなくても悪化する。A/B にすると符号が逆になる。

6-8b. ノイズフロアを揃える(--noise-match)

浅尾指摘 2026-08-11(プレゼンス帯を Part7 基準へ上げた直後):

だいぶいい感じだと思うが、eq をもうちょっと効かしたほうがいいのかな。 雑音が多くて、ちょっと環境音が目立つぐらい

この 2 つは同じ原因。 2.5〜4kHz を上げたぶん、その帯域の雑音も上がる。

EQ をさらに効かせる前に、ノイズ処理をトラックごとに変えないと、上げるほど環境音が目立つ。

揃えるのは絶対のフロアではなく SNR

音量合わせ(BALANCE_LUFS)が鎖の手前にあるので、各トラックは +2.7〜+15.3dB

ばらばらに持ち上がり、フロアも同じだけ上がる(実測 Part1)。

絶対のフロアを揃えても、混ざった後の雑音の量は揃わない。

静的ゲインで動かない量は SNR(声の中央値 − フロア)だけ。 これを揃える。

束ねる単位は 人(機材ではない)。測ってから決めた

指示は「機材ごと」だったが、実測が合わなかった。Part7 は TX ラベリア 1 種類だけなのに:

声床SNR
TX03_MIC019−25.6−61.836.2dB
TX02_MIC018−35.7−60.324.6dB

11.6dB 割れている。 声に対する床の位置は、機材の型ではなく

装着位置・服・プリのゲインで決まるので、同じ型でも収録ごとに違う。

機材で束ねると 1 群になり、この差に何もできない。

人で束ねるのは、収録が 2 チャンクに割れている人の票が二重に入るのを防ぐため

(dynamics-match と同じ理由)。--noise-group gear で機材へ戻せる。

寄せ先は いちばん SNR の良いトラック(中央値ではない)

音色は中央値へ寄せるが、ここは最良へ寄せる。理由は 2 つ:

  • 雑音は足せない。 中央値を狙うと、良いトラックには「増やす」補正が要る
  • 苦情は「雑音が多い」。全体として下げる方向でなければ答えになっていない
何を動かすか。プロファイルは触らない

出すのはトラックごとの上乗せ(dB)だけ。鎖を組むときに

podcast-loud の noiseReductionDb(6dB)へ足す。

afftdn=nr=(6 + 上乗せ):nf=-45:tn=1

上乗せが 0 なら ffmpeg へ渡す引数は 1 文字も変わらない。

承認済みの構成で発動しない Part は 1 バイトも動かない。

  • strength 0.7(完全一致を目指さない)
  • 上限 6dB(プロファイルの既定と同じ。最悪でも 2 倍を超えない)。

削りすぎると声が痩せるので、上限に当たったトラックは clipped に記録する

  • 発動条件は「群間の SNR の食い違いが 1.5dB 以上」。揃っていれば素通し
  • 実装 lib/audio/noise-match.ts
実測: 効かなかった。既定は off(2026-08-11)

Part1 で実際に掛けた(群間 SNR 7.85dB 割れ → 発動。

Fujii +6.00dB(上限に当たった)/ TX02_MIC005 +5.49dB / Nabe +2.92dB / 残り 3 本は素通し):

指標offon
完成 mix の 語間 − 発話−21.08dB−21.01dB(0.07dB。判定下限 0.5dB 未満)
下側の広がり(p10−p50)4.40 → 6.83dB4.40 → 6.75dB
SNR の広がり(鎖の出口)—9.80 → 9.46dB
いちばん悪い SNR—19.01 → 16.57dB(悪化)

苦情に直結する指標が 0.07dB しか動かない。

afftdn の上乗せは「環境音が目立つ」に効かない。効くのは下方展開(§6-8c)。

浅尾判断 2026-08-11:「効きが測れないものを残すと、後で原因の切り分けができなくなる」

→ 既定 off。 コードは残してある(--noise-match on)。同じことを二度やらないためにここへ書いた。

6-8c. 下方展開を膝で決める(--downward-expansion-db、既定 4dB)

浅尾指示 2026-08-11:「音の反響とかの判断とかは基本機械的にやったほうが精度高いだろ」

「途切れとは発話中にゲートが閉じることなので、そのまま数えられる」。

途切れの測り方(候補 3 つから選んだ理由)

採った: 「同じ素材・同じ経路で設定だけ変えた 2 本の差」(scripts/measure-gate-chop.mts)。

duck(t) = 出力X のフレームレベル − 基準(--downward-expansion-db 0)のフレームレベル
  • `agate` の前後を別々に測らない。 それだとオートミキサーが入らない。

podcast-loud のコメントが警告しているのは二重掛けなので、

出力どうしの差でなければ実態にならない

  • 語頭 50ms のエネルギー減も併せて出す(attackLossDb)。

アタックだけ削られる壊れ方は中央値では見えない

  • 「有声区間に挿入された無音の長さと本数」は採らない。 「無音」の閾値を決める必要があり、

その閾値がそのまま答えを決めてしまう。差分なら閾値は「何 dB 落ちたら途切れか」の 1 つで済む

  • バスの静的ゲインは設定ごとに変わるので、**いちばん大きい有声フレーム(上位 30%。

−40dB の閾値から遥か上でゲートが絶対に触らない)の中央値**で引き算して打ち消す

実測(Part1、区切り 1)
下方展開語間−発話減衰途切れ ≥3dB≥6dB≥10dB最長(≥6dB)語頭50ms
0−19.50dB−12.0 dB/s—————
2(プロファイル)−21.17−18.00.7 件/分0.10.00.02s+0.00dB
4(この経路の既定)−23.15−24.44.60.20.00.02s+0.02
6−25.02−30.66.41.60.10.03s+0.04
8−26.83−37.77.52.80.10.06s+0.06

膝は「耳に届く深さ(≥6dB)の途切れ」に出る。 0.1 → 0.2 → 1.6(×8) → 2.8。

立ち上がる直前は 4dB。そこでの途切れは 0.2 件/分・1 件 20ms(2 フレーム)で、

真の脱落(≥10dB)は 0.0 件/分。語頭のアタックは削られていない(+0.02dB)。

環境音は 1.98dB 下がる。声を欠くほうが害が大きいのでここで止める。

環境音のほうには膝が無い(−1.98 / −1.87 / −1.81 とほぼ一直線)。

決めているのは途切れの側なので、そちらの膝を採る。

プロファイルは書き換えていない

podcast-loud.downwardExpansionDb は 2 のまま。上げているのは

scripts/build-part-mix.mts の上書き層(profileOverrides)で、--downward-expansion-db 2 で戻せる。

浅尾承認済みの数値は残す。

効いたかどうかの指標

「掛けた」ではなく「揃った / 下がった」だけが成果。鎖の出口(混ざる直前)で測り直す。

見るもの
spread.beforeSnrSpreadDb → afterトラック間の SNR の食い違い
spread.beforeWorstSnrDb → afterいちばん悪いトラックの SNR
dynamicsMatch.spread.beforeLowDb → after中央値から下の広がり(§6-9 で 4.26 → 6.91dB と悪化していた指標)
measure-reverb の 語間 − 発話環境音がどれだけ目立つか。 苦情に直接対応する
6-9. 作り直した結果(Part1 / Part7、2026-08-11)
Part1 前Part1 後Part7 前Part7 後
出力の尺1749.06s2415.17s2494.82s2546.85s
本編(DRT)との差−45.36s+620.75s+149.61s+201.64s
載った生マイク3 本6 本6 本6 本
deliveredRatio88.84%100.00%100%100%
デクリップ0/6 本6/6 本0/6 本3/6 本
I / LRA / TP−14.0 / 4.6 / −1.0−14.1 / 5.0 / −1.0−14.1 / 5.5 / −1.0−14.1 / 5.1 / −1.0
判定PASSPASSPASSPASS

Part1 は本編より長くなった。 壁時計の橋(§6-0d)で 12:26 の収録が載ったため。

出力の頭は katto_part1_Asao.wav の 3.090 秒目(前と同じ)。

クリップの実測(鎖の前の真のピーク、dBFS)。全部プラス = 全部歪んでいた。

Part1TPPart7TP
katto_part1_Asao+0.3TX01_MIC021+0.0
TX01_MIC006+0.5TX01_MIC022+0.1
katto_part1_Fujii+3.4TX02_MIC018−9.1
TX02_MIC005+0.3TX02_MIC019−5.3
katto_part1_Nabe+0.1TX03_MIC018+0.7
TX03_MIC005+1.1TX03_MIC019+0.6

鎖へ入るレベルが揃った(欠陥 1 の直し。単位 dB):

Part1Part7
鎖の手前で当てたゲイン+2.7〜+15.3(幅 12.6)+2.6〜+13.1(幅 10.5)
鎖の出口に残った差(詰めた量)−1.8〜+0.8(幅 2.6)−0.8〜+1.9(幅 2.7)

以前はこの 12.6dB / 10.5dB が鎖の入口に生のまま入っていた。

音量の揃え方は 2 本とも `voiced-median` が選ばれた(実測で決めた。§6-8)。

積分ラウドネスで揃えると声の中央値が Part1 で 1.87dB、Part7 で 2.60dB 割れる。

ダイナミクスの食い違い(鎖の前 → 鎖の出口、トラック間の dB)
Part1Part7
中央値から上(p95−p50)4.37 → 2.353.18 → 2.29
中央値から下(p10−p50)4.26 → 6.914.85 → 5.64
p95−p107.40 → 9.265.98 → 7.93

⚠ この表の読み方は 2026-08-13 に訂正した。§6-8「`spread.before → after` は A/B ではない」を先に読む。

before は鎖の前、after は鎖の出口で、間に鎖が丸ごと入っている。

「下が 4.26 → 6.91 に開いた」のは鎖が開いたぶんを含んでいて、補正のせいではない。

同じ Part を off / on で作って同じ点(鎖の出口)で比べると符号が逆になる

(Part11: off 6.08 → on 4.23dB)。この表からは補正の効果を読み取れない。

下が開くこと自体は本当で、原因は鎖の下方展開(−6dB @ −40dB)と

ノイズ低減(nf=−45)がノイズフロアに対して効くから。床は Part1 で

−60.5〜−63.2dBFS、Part7 で −60.1〜−61.8dBFS とトラックごとに違い、

声の分布を揃えてもこの差は残る。機材ごとにノイズ低減量を変える段は入れていない

(§6-7 の末尾に同じ指摘がある)。次に直すならここ。

6-11. 区切りごとに別ファイルで出す(既定。--concat で 1 本)

浅尾指示 2026-08-11:「複数クリップがある場合は勝手に連結せずに複数クリップで用意しとけ」。

収録は途中で止まって再開する。実測 Part1:

区切り本編素材マイク
10〜1569.9sC0004.MP4katto_part1_{Asao,Fujii,Nabe}.wav
21569.9〜1765.1sC0005.MP4TX01_MIC006 / TX02_MIC005 / TX03_MIC005
31765.1〜1794.4sC0007.MP40/3 本(§6-10)

これを 1 本に繋ぐにはレコーダのファイル名の時計で橋を架けるしかない(§6-0d)。

切れ目の間は誰も録っていないので、相関では原理的に検証できない

(実測 Part1 の票のばらつき 0.92 秒)。この経路で唯一の未検証な推定がそこだった。

区切りごとに別ファイルで出せば、その推定が消える。 各区切りの中は

サンプル単位で合っている。Resolve 側もマルチカムのクリップ単位で差し替えるので、

分かれているほうが素直。

  • 出力は <Part>_mix_source_1.wav / _2.wav …(本編の早い順に振る)
  • 壁時計の橋は既定 off(--clock-bridge で明示的に有効化。--concat のときだけ意味がある)
  • 3 人そろわない区切りは出さない。 理由は skippedSegments に残す(無音混じりを作らない)
  • 証跡(outputs[])に本編の範囲・DRT のどの素材か・その wav の頭がそのファイルの何秒目かを書く
鎖の設定は Part 全体で 1 回決める

区切りごとに測り直すと境目で音量と音色が変わる。 決めるのは 1 回、当てるのは全区切り:

段何を Part 全体で決めるか
音色(§6-7 / §6-7b)基準スペクトル。台帳(program)なら Part すら跨いで固定
ダイナミクス(§6-8)dynamicsReference。トラック全部の分布から 1 回
音量合わせvoicedTarget(全トラックの声の中央値の中央値)
バスのラウドネス全区切りを繋いだ 1 本を測って `gainDb` を 1 つ決め、全区切りに当てる

繋ぐのは測るためだけで、出力は分かれたまま。積分ラウドネスはゲート付きの総和なので、

区切りごとの値を平均しても Part 全体の I にはならない。

境目の検査(合否は出力そのものを測った値で出す)

2026-08-12 まで、この検査は恒等式だった。

finalVoiced(i) = chainedVoicedMedianDb[i] + gainDb[i]
gainDb[i]      = voicedTarget − chainedVoicedMedianDb[i]
→ finalVoiced(i) ≡ voicedTarget(定数)。voicedDeltaDb は構造上つねに 0

証跡でも Part1 の 3 人が −32.89240815967751 で完全同一、差は 0.00 だった。

出力を 1 サンプルも測っていない。「揃えたつもりの値」を並べていただけである。

Part10 はさらに voicedMedianAfterDb: null(NaN)・beforeFile === afterFile(自分と自分を比較)

で ok: true になっていた。しかも Part10_mix_source_2.wav は 1.63 秒なのに

-sseof -10 で 10 秒窓を切り、その結果を「境界前後 10s」と書いていた。

いまは 3 系統。合否は 3 だけが持つ。

何を見るか合否
1. 計算値(perPerson.planned*)chainedVoicedMedianDb + gainDb使わない。plannedIsIdentity: true と明記して残すだけ
2. 音色(perPerson.maxBandDeltaDb)2 本の素材を別々に測った正規化バンド曲線の差参考(実測ではある)
3. 出力(`boundaries[].output`)レンダリング済み wav の境界前後の有声フレーム中央値これ
  • 窓は `--boundary-sec`(既定 10 秒)を上限に、短い側の区切りに合わせて縮める。

実際に使った長さは output.windowSec、縮んだ理由は windowLimitedBy に出る

  • `--boundary-min-window-sec`(既定 1.0 秒)を割るなら測らない。

null を返して合格側へ流さず、measured: false と reason を書く。

測っていないものは `ok.verification` を落とす(未検証は合格ではない)

  • 上限は 6.0dB(--boundary-max-voiced-delta-db)。内容だけで動く量を先に測って決めた。

同じ 1 本の中で隣り合う窓を 24 組取ると(Part1_mix_source_1.wav、1024 点フレーム):

窓\Δ有声中央値\p50p90max
2s1.332.262.67
5s2.044.105.34
10s1.662.733.38
30s1.342.804.21

境目が無いところで 5.34dB 動く。 ここより下に閾値を置くと飛んでいない境目が落ちる。

これは「揃っている」ことの証明ではない。「飛んでいれば必ず出る」だけ(分解能が 6dB ということ)

  • I(積分)とノイズフロアも出すが合否に使わない。フロアは同じ測り方で

内容だけで最大 16.5dB 動いた(2s 窓)ので、指標として使えない

6-11b. 番号は「最初の断片」ではなく「本体」で振る(2026-08-13)

浅尾指摘:「part3-1 とかは、パート2 のあとのストーリーのように聞こえるが?」

実際にそうなっていた。 Part3_mix_source_1.wav の中身は本編 27 分めである。

なぜ 1 番になったか

区切りの番号は recordFromSec(= 本編のいちばん最初の断片の頭)順に振っていた。

冒頭のダイジェスト / OP がこれを壊す。 実測 Part3 の当該区切りの recordRanges:

[[0.0, 9.1], [1614.3, 1925.6]]
  ^^^^^^^^ OP の 9.1 秒       ^^^^^^^^^^^^^^^ 本体の 311.3 秒

OP は本編の頭に置かれるが、素材は後半のチャンクから引かれている。

9.1 秒の断片のせいで recordFromSec = 0.0 になり、445 秒の後半チャンクが 1 番へ来た。

音で裏を取った(証跡の数字ではなく出力そのもの)

Part3_mix_source_1.wav から 30 秒窓を 8 か所抜き、完成 Part3.mp3 と相関を取った:

窓(wav 秒)完成 Part の秒相関
0.01614.30.882
59.31673.60.876
118.61732.90.865
177.91792.20.898
237.11851.40.859
296.4 以降立たない(0.21〜0.25)編集で抜かれた素材

ずれは 1614.30s で一定。「1 番」のファイルは本編 27 分めから始まっていた。

直し方(scripts/build-part-mix.mts)

並べ替えの key を `bodyStartSec`(いちばん長い本編区間の頭) にした。

同点なら coverageMedianSec(本編秒で重み付けした中央値)。測ってから決めた

(9 Part 40 区切り):

並べ方結果
最初の断片の頭(〜2026-08-12)10 Part 中 6 Part で本体の順と食い違う
いちばん長い区間の頭(いま)Part3 は 2,3,6,1,8 → 番号を振り直して 1,2,5,7,8
本編秒の重心上と 40 区切り全部で完全一致(独立な 2 つの key が同じ順を出した)

番号が変わった数(2026-08-13 の DRY RUN。Part7 / Part12 は別作業中で回していない):

Part変わった数いちばんひどいもの
Part1 / Part2 / Part5 / Part90—
Part42旧 1(最初の断片 0.0s / 本体 600.7s)→ 新 2
Part112旧 1(最初の断片 3600.0s / 本体 5269.5s)→ 新 2
Part105旧 1(最初の断片 3600.0s / 本体 3641.7s)→ 新 2
Part66旧 2(最初の断片 12.3s / 本体 1679.9s)→ 新 7
Part86旧 2(最初の断片 23.3s / 本体 1699.2s)→ 新 7
Part37旧 1(最初の断片 0.0s / 本体 1614.3s)→ 新 7
  • 番号は出さない区切りも含めて通しで振る(歯抜けになるのは従来どおり)
  • 本体が本編秒の 50% 未満の区切りは「順番の根拠が弱い」と印字する

(本編の離れた 2 か所を同じくらい覆っている = どちらを本体と呼んでも恣意的)

番号を決めるのは 1 箇所だけ

正本は `lib/audio/part-edit-map.ts` の `buildSourceSegments` の `drafts.sort`。

key は SourceSegment.bodyStartSec、同点なら coverageMedianSec。どちらも同関数が計算して返す。

2026-08-13 の一時期、別作業が part-edit-map.ts を持っていたため

scripts/build-part-mix.mts 側で番号を振り直していた。番号が 2 箇所で定義されていた。

同日中にライブラリへ移して仮置きを消した。script 側へ振り直しを書き戻さない。

6-11c. `recordFromSec`〜`recordToSec` は連続した区間ではない(2026-08-13)

3 つの別の量が「本編 X〜Y」の 1 行に潰れていた。

量何Part3 の区切り(新 7)
lengthSec(尺)生収録の秒。source 軸なので編集で抜かれたぶんも入る445.00s
recordSec写像が実際に引いている本編秒の和320.44s
recordFromSec〜recordToSecrecordRanges の端から端まで(穴を含む span)0.0〜1925.6s

印字が 3 つめだけを出していたので、次の 2 つが同時に起きていた:

  • 尺と合わない(445.00s の区切りに「本編 0.0〜1925.6s」と書かれる)
  • 区切りどうしが重なって見える(Part3 / Part4 / Part6 / Part8 / Part11)

実際の重なりはほぼ無い。 recordRanges そのもので数えると

9 Part 40 区切りで 1 組・0.06 秒だけ(Part3 の新 1 と新 7 が 9.1s 付近で接している)。

applyAudioSwap(§7)の重なり拒否が正しく、生成側が壊れていたのではなく、

span を区間として読むと壊れて見えるだけだった。

  • 印字は 本編 <recordSec>s(N 区間、範囲 A〜B = a1〜b1 + a2〜b2 …) に変えた
  • 証跡の outputs[] に recordSec と recordRangesAreTheTruth: true を足した。

下流は `recordRanges` を使う。`recordFromSec`〜`recordToSec` を 1 本の区間として使わない

6-11d. 前の実行の出力を先に消す(--go のときだけ。2026-08-13)

浅尾指摘:「part2 のめちゃ短いのとかは全部ミスやし」。

区切りの番号は実行のたびに変わる。番号が変わると上書きされないので、前の実行のファイルが残る。

08/12 の実行  Part2 は 区切り 1 / 3 / 5 / 7 を出した
08/13 の実行  Part2 は 区切り 1 / 3 / 4 / 6 を出した
→ 5 と 7 が消えずに残り、4 と 6 と混ざっていた

Final/Audio/_partmix に全 Part で 13 本の残骸があった。

  • --go の書き出し直前に ^<Part><軸の接尾辞>(_\d+)?(\.attempt)?\.(wav|json)$ を完全一致で消す
  • DRY RUN では消さない(作らない実行が既にある出力を壊さない)
  • 軸は混ぜない。 _mix(timeline)の掃除が _mix_source_* を巻き込まないよう番号まで照合する。

Part1 の掃除が Part10_... を巻き込まないのは接尾辞の _ が境界になるため

  • 消せなかったものは止めずに印字して証跡へ(staleOutputsRemoved.failed)。

これから書く番号と同じなら上書きで解決する

  • 実測 2026-08-13(Part3、番号が 1,2,3,6,8 から 1,2,5,7,8 へ変わる実行):

6 本(wav 5 + attempt.json)を消してから書き、旧番号 3 / 6 は残らなかった

6-11e. 区切りの下限は測って決める(2026-08-13。まだ既定を変えていない)

浅尾指示:「『10 秒未満』は私が目視で切った線であって根拠が無い。

積分ラウドネスのゲートが成立する最小の長さと、検算窓が置ける最小の長さの

両方を実測して、大きいほうを採れ」。

出荷経路の出力(Part1_mix_source_1.wav / Part5_mix_source_1.wav / Part9_mix_source_10.wav)

から、長さを変えた窓を各 12 か所ずつ切って測った(計 36 窓/長さ):

窓\I − 全体の I\p50p90maxI が出た検算窓が置けた
0.5s1.9011.3028.1036/360%
1s1.508.7027.2036/3628%
1.5s1.303.9021.8036/3658%
2s1.403.4018.6036/3675%
3s1.403.0010.1036/3683%
4s0.802.104.7036/3692%
5s0.802.005.3036/3697%
6s0.802.006.0036/36100%
10s0.602.306.0036/36100%
30s0.501.102.7036/36100%
60s0.401.301.7036/36100%

読み方

  • `ebur128` は 0.5 秒でも I を返す。36/36 で必ず数字が出る。

ゲートは「成立しない」と言ってくれない。黙って誤った値を返す。

だから「短い区切りの I が −10.2 だった」は鎖が壊れている証拠にならない。

同じく正しくマスタリングされた長い出力から 4.5 秒を切っても、

全体の I から最大 4.7dB ずれる。観測された +3.8〜+4.1dB はこの範囲の中

  • 積分ラウドネスの膝は 4 秒(max が 10.10 → 4.70 に落ち、そこから 30 秒まで改善しない)
  • 検算窓(measureLevelsOfRawFile、1024 点・有声 30 フレーム)が確実に置けるのは 6 秒(5 秒で 97%)
  • 大きいほうは 6 秒。 ただし ±1dB の I が欲しいなら 60 秒でも足りない

(max 1.70dB)。短い区切りの I を目標値と比べること自体に意味が無い。

合否は Part 全体(全区切りを繋いだ 1 本)で出す設計が正しい

浅尾判断 2026-08-13: 吸収。 6 秒未満は隣へ merge する。

実装と、寄せる向きの比較(6 通り。2026-08-14 に位置と人数を足した)は §6-11g。

6-11g. 短い区切りは隣へ吸収する(浅尾判断 2026-08-13)

buildSourceSegments の absorbShorterThanSec(既定 6 秒)と absorbInto。

CLI は --absorb-shorter-than / --absorb-into。0 を渡すと吸収しない(旧挙動)。

  • 吸収できるのは軸で隣り合う窓だけ。 出力の wav は軸の連続した切り出しなので、

本編で隣でも軸で離れていれば 1 本にできない

  • 連鎖する。 いちばん短いものから処理し、6 秒未満が無くなるまで繰り返す。

吸収した結果また 6 秒未満なら次の回でさらに寄る

  • 両隣が無い(塊に窓が 1 つ)ものは吸収できない。unabsorbed に残し minSegmentSec の判定へ落ちる
  • 本編のどこにも当たらない区切り(recordSec = 0)は吸収の有無によらず出さない。

実測 Part11 の 2.46s がこれで、差し替え先が無いのに出ていた

どちらの隣へ寄せるか(向きを固定しない。浅尾指示 2026-08-14)
3 は内容から予測できるだろ。それぞれのパートの冒頭かラストかはわかるだろ。

2026-08-13 は向きを next に固定していた。柔らかい指標では `previous` のほうが良いのに、

Part7 が `next` でしか組めないという 1 点で next を選んでいた。

向きを固定するのをやめ、その区切りが塊のどこにあるか(位置)と、

隣が `minPersons` に届くか(人数)で決める。

値決め方
previous / next向きを固定(旧)
same-source素材の一致が多いほう
position塊の中の位置。 冒頭寄り(< 50%)なら後ろへ、末尾寄りなら前へ
persons寄せないと `minPersons` に届かない隣を助ける。 決まらなければ same-source
`auto`(既定)人数 → 位置。 人数で決まらなければ位置で決める

位置は軸(生収録)の時刻で測る。本編の時刻ではない。吸収できるのは軸で隣り合う窓だけ

なので、位置も軸で測らないと「隣」と食い違う(冒頭のダイジェスト / OP を含む塊では

本編の順と軸の順がずれる。実測 Part3 の塊は本編 [[0,9.1],[1614.3,1925.6]])。

番号(pick / N)ではなく時刻で測るのは、1.86 秒の窓と 1500 秒の窓を同じ重さにしないため。

全通り 12 Part で回した(出荷経路の DRY RUN。scripts/build-part-mix.mts)
吸収先吸収出す出さない1 対 1 崩れ崩れた人delivery 平均最悪組めた Part
previous55379152099.602%97.90%11/12
next(旧既定)53408192999.777%98.22%12/12
same-source55379151899.602%97.90%11/12
position(位置だけ)55379131799.602%97.90%11/12
persons(人数だけ)55388141799.777%98.22%12/12
`auto`(採用)55388121699.777%98.22%12/12

(same-source の行は 2026-08-13 の同じコード経路での実測。2026-08-14 の変更は

previous / next / same-source の挙動を変えていない。next / previous は

2026-08-14 に回し直して 2026-08-13 と同じ数を得た。)

境目の飛びは Part9 をレンダリングして測った(どれも verification PASS):

吸収先出す区切り境目の数最大 \Δ有声中央値\
previous / same-source321.38dB
next432.15dB
`auto`321.04dB(−0.23 / +1.04)

auto の Part9 は 4 関門すべて PASS(loudness / delivery / coverage / verification)。

実行は --absorb-into auto --go、出力は scratch へ(出荷物には触っていない)。

位置と人数のどちらが効くか

人数。位置だけでは Part7 が落ちる。

Part7 の末尾の窓は Fujii のレコーダが止まっていて 2/3 人しか居ない。

その手前に Fujii と Nabe が居る 1.86 秒の窓がある。この窓は塊の 98% の位置

にあるので、position の「末尾寄りなら前へ」は逆へ寄せる。

末尾は 2 人のままで 49.0 秒がまるごと落ち、delivery 97.90% で関門(98%)を割り、

Part7 は 1 本も出力しないで終わる(作らない で非ゼロ終了)。

persons は「寄せないと 3 人に届かない隣」を見るので、同じ窓を後ろへ寄せる。

実際のログ(auto / Part7):

軸 2494.82〜2497.22s(尺 2.40s / 本編 0.00s / Asao,Fujii,Nabe)を **後ろ** へ
  寄せないと 3 人に届かない隣(Asao,Nabe)を助けるので 後ろへ
軸 1754.63〜1754.64s(尺 0.01s / 本編 0.01s / Asao,Fujii,Nabe)を **前** へ
  塊の 69% の位置(末尾寄り)なので前へ(人数では決まらない: 前 3 人 / 後ろ 3 人)

位置は 2 段目で効く。 人数はたいてい決め手にならない(両隣とも 3 人そろっている)ので、

そこから先を位置で決めると 1 対 1 の崩れが減る。`persons` 14 区 / 17 人 → `auto` 12 / 16。

採ったのは auto(人数 → 位置)

`next` に対して、hard な指標は同じで soft な指標が良い。

  • 組めた Part 12/12(next と同じ)
  • delivery 平均 99.777% / 最悪 98.22%(next と同じ。関門にいちばん近いのは Part2 で、

これは吸収の向きと無関係な素材欠落)

  • 1 対 1 の崩れ 19 区 / 29 人 → 12 区 / 16 人(区切りで 37% 減、人で 45% 減)
  • 出す区切り 40 → 38(境目が 2 つ減る)。Part9 の境目の飛びは 2.15dB → 1.04dB

`previous` / `position` / `same-source` は採れない。 どれも Part7 が組めない(11/12)。

`persons` 単独も採れない。12/12 は満たすが崩れが auto より多い(14 / 17 対 12 / 16)。

まだ測っていないもの。 Part9 以外の Part の境目の飛び(レンダリングが要る)。

auto で作り直した全 Part の 4 関門(2026-08-13 の next では 11/12 PASS。

落ちたのは Part12 の coverage で、これは吸収と無関係)。

記録(全件)
  • 印字と segmentAbsorption.absorbed に、何を・どちらへ・なぜ(reason)
  • segmentAbsorption.unabsorbed に、吸収できなかったものと理由
  • `sourceFiles` は吸収前の全ファイルを持つ。 1 対 1 が崩れたことが分かる形。

startInOutputSec に「その wav の何秒目からそのファイルに変わるか」が入る

  • 崩れた区切りは印字で **素材の 1 対 1 が崩れた区切り N 個** と出し、人ごとの

ファイルA(wav の 0.00s から)-> ファイルB(wav の 1793.71s から) を並べる

入れたあとの実測(2026-08-13。全 Part を作り直した)

`verification` が落ちなくなった。 短い区切りが消えたので、境目の検算窓が置ける。

吸収の前(10 Part)吸収の後(全 12 Part)
4 関門すべて PASS5/1011/12
落ちた理由全部 verification(短い区切りに窓が置けない)Part12 の coverage だけ(別件。§6-12 のダイジェスト不明)
verification が落ちた Part50
出力の本数3940(Part7 / Part12 を含む)

Part12 の `coverage` FAIL は吸収と無関係。 DRT にコンパウンドが 1 つも無くダイジェストの尺が読めないため、

judgeProgramCoverage が unknown を返している(ダイジェストを 0 と置けば 6.36 分の余裕がある)。

「測っていないものは合格にしない」という設計どおりの落ち方(§6-12)。

1 対 1 が崩れた区切りは 12 Part で 19 個(Part1 だけ 0)。全部印字と証跡に出る。

境目の飛び(レンダリング済みの出力を測った有声中央値の差)。上限 6dB。

残っているいちばん大きいもの:

Part境目差
Part51→2−5.67dB
Part62→3−5.14dB
Part11→2+4.38dB
Part82→3−3.89dB
Part101→2+3.62dB

Part1 の 1→2 は吸収で消えなかった。+5.73 → +4.38dB。

Part1 は gainDb を落としたクリップが 0 本なので、§6-11f の欠陥とは無関係。

別の原因が残っている(内容の差か、区切りをまたぐ人の声そのものの差)。まだ切り分けていない。

Part5 の −5.67dB は吸収で増えた(前は −1.51 / +1.45 / +0.10 と、測れない境目が 1 つ)。

吸収でできた 8.60 秒の区切りが隣と 5.67dB 違う。上限の中には居るが、いちばん近い。

6-11f. 短い区切りが大きいのは、揃え方の法則が黙って切り替わるから(2026-08-13)

浅尾指摘:「part2 のめちゃ短いのとかは全部ミスやし」。

窓が短いから I がばらついている、ではない。+8dB 余計に持ち上がっている。

scripts/build-part-mix.mts の音量合わせは、クリップごとにこう決めていた:

gainDb = 声の中央値が測れる ? voicedTarget − 鎖出口の声の中央値
                            : BALANCE_LUFS(−23) − 鎖出口の積分I

2 つの法則は基準点が違う。 声の中央値の基準(voicedTarget)は約 −32 dBFS、

積分の基準(BALANCE_LUFS)は −23 LUFS。鎖の出口はどちらの尺度でも −30〜−32 付近に来るので、

  • 測れたクリップ → −32 −(−32) ≈ 0dB
  • 測れないクリップ → −23 −(−31) ≈ +8dB

測れないのは短いクリップだけ(measureLevelsOfRawFile は有声 60 フレーム必要)。

つまり短い区切りだけが別の目標へ揃えられていた。

実測 2026-08-13(出荷経路の証跡 <Part>_mix_source.json の balance[]):

声の中央値「残差の詰め」gainDb出力の I
Part2 区切り 1(1793.71s)測れる+0.47 / 0.00 / +1.80−14.1
Part2 区切り 3(4.55s)測れない+7.90 / +8.30 / +6.70−10.1
Part2 区切り 4(1.88s)測れない+9.00 / +8.20 / +7.80−10.1
Part2 区切り 6(746.18s)測れる−0.09 / −1.08 / +0.62−14.1
Part3 区切り 1(1793.71s)測れる−0.17 / −1.33 / 0.00−14.4
Part3 区切り 2(2.74s)測れない+8.40 / +8.30 / +6.90−9.9
Part3 区切り 5(3.68s)測れない+8.50 / +8.30 / +8.60−10.7
Part3 区切り 7(445.00s)測れる+1.19 / −0.67 / +0.50−13.4

2 Part とも同じ形。 短い区切りは目標 −14 に対して 3.3〜4.1dB 大きい。

§6-11e で測った「窓が短いこと自体による I のばらつき」は 4.5 秒で max 4.7dB だが、

これはそれとは別で、トラックの段で実際に +8dB 掛かっている。

これは「ゲートが働かない」ではなく、同じ Part の中で 2 つの目標が混ざっている

(§6-11 の「鎖の設定は Part 全体で 1 回決める」に反している)。

採ったのは B(浅尾判断 2026-08-13)
推定値を当てるより、何もしないほうが安全
voiced median が測れる(有声 60 フレーム以上) → gainDb = voicedTarget − 実測
測れない                                      → gainDb = 0

`BALANCE_LUFS(−23LUFS)` − 積分 への落ち方は消した。 基準点の違う 2 つの式が

黙って切り替わるのが欠陥だった。鎖の手前で既に −23LUFS へ揃えてあるので、

0 にしてもそのトラックが野放しになるわけではない。

案何をする犠牲
A測れないクリップは同じ Part の同じ人の測れたクリップの `gainDb` を流用同じ人が Part の途中で機材を替えると donor が決まらない(実測 Part3 の Asao は 3 本に割れていて、どれを選ぶかで結果が変わる)残してある
B測れないクリップは `gainDb = 0`その人だけ僅かにずれる。+8dB が消える採用
Cそもそも短い区切りを出さない(§6-11e の 6 秒)本編に穴。delivery の関門に響く保留

A を消していない。 scripts/build-part-mix.mts の balance.forEach の直前のコメントに

A へ差し替えるコードそのものを書いてある。測って比べた結果 B を採った、という記録つき。

0dB にしたことを黙らせない。

  • 表の行に **声の中央値を測れず 0dB(案 B。L5-01 §6-11f)** を付ける
  • 0dB にした区切りを一覧で出す(区切り番号 / 尺 / 何本 / 誰 / 以前の式なら何 dB 乗っていたか)
  • 証跡 balanceFallback に policy / rows[].previousLawGainDb / worstAvoidedAbsDb を残す。

`0dB` は「揃っていた」ではなく「測れなかった」という意味であることを meaning に書く

B を入れて測った結果(2026-08-13)

短い区切りの I(目標 −14)。+8dB が消えた:

前(−23LUFS へ落ちる)後(gainDb = 0)
Part2 区切り 3(4.55s)−10.1−13.4
Part2 区切り 4(1.88s)−10.1−13.6
Part3 区切り 2(2.74s)−9.9−13.0
Part3 区切り 5(3.68s)−10.7−13.6

境目の飛び(出力そのものを測った有声中央値の差):

前後
Part2 区切り 4→6−4.00dB−0.72dB
Part3(最悪)−6.63dB−0.28dB
Part4(最悪)−11.18dB−1.18dB

残っているいちばん大きい飛びは Part1 の 1→2 で +5.73dB(上限 6dB のすれすれ)。

Part1 は gainDb = 0 にしたクリップが 0/9 なので、これは別の原因(内容の差か、

区切りをまたぐ人の声そのものの差)。まだ切り分けていない。次に大きいのは Part8 の 5→7 で −4.14dB。

`integrated` 基準にも同じ穴がある。ただし「0dB」にはできない(2026-08-13)

compareBalanceBases は積分と声の中央値の食い違いが 1dB 未満なら integrated を選ぶ。

実測 Part10 / Part11 がそれで、そのとき全クリップが BALANCE_LUFS − 積分。

これは落ち先ではなく設計どおりの経路だが、短いクリップの積分は当てにならない

(§6-11e: 2 秒窓で最大 18.6dB ずれる)。Part10 の 1.63s は +9.7〜+10.0 で

長い区切り(+9.8〜+10.2)とたまたま揃っただけだった。

`voiced-median` と同じ「0dB」にはできない。基準が違う:

基準典型的な gainDb測れないときに 0 を当てると
voiced-medianvoicedTarget が測れたぶんの中央値なので定義上 0 付近正しい
integrated絶対値 −23LUFS 基準。実測 Part10 / Part11 は +10 付近10dB 小さくなる

なので「測れないクリップにはその Part の典型のゲインを当てる」に統一した。

典型 = 測れたクリップの gainDb の中央値。

`voiced-median` ではこれが恒等的に 0 なので、浅尾判断の案 B とまったく同じ動きになる

(voicedTarget が中央値である以上、gainDb の中央値は 0)。

実際に当てた値は毎回 balanceFallback.fallbackGainDb に出るので、0 かどうかを目で見られる。

境目の検査は 6dB 上限なのでこれを見逃す。 Part2 の区切り 4→6 は

声の中央値が −4.00dB 飛んでいるのに OK と出た(上限 6dB)。

上限は「内容だけで 5.34dB 動く」から下げられない(§6-11 の実測)。

この欠陥は境目の検査では捕まえられない。 捕まえるのは

「同じ Part の中で gainDb が人ごと・区切りごとにどれだけ割れているか」のほう。

6-12. 「本当の本編の尺」を物差しにする(lib/audio/program-length.ts)

浅尾指摘 2026-08-11:「本編 drt は間違ったファイルが繋がってる部分もある」

「ただ本編はダイジェストが冒頭にあるからな」。

以前は editMap.recordSeconds(.drt の写像の最後尾)を分母にして

「本編を覆えているか」を判定していた。これが間違い。

原因は「間違ったファイル」ではなく、開始タイムコードだった
Part写像の最後尾実効(原点を引く)出荷実体
Part97435.21s3835.21sFinal/Part9.mp4 3835.22s
Part106680.42s3080.42sFinal/Part10.mp4 3080.43s
Part116059.79s2459.79sFinal/Part11.mp4 2459.80s
Part129165.67s5565.67sFinal/Audio/Part12.mp3 5565.72s

Part9〜12 の timeline は 01:00:00:00 始まりで、原点に 3600 秒が乗っていた。

引くと12 Part すべてで出荷実体と 1 秒以内に一致する。Part1〜8 は 00:00:00:00 なので

この誤差はそこには出ていなかった。原点は sequenceOriginSec() で読める。

  • PartEditMapPlan.recordOriginSec / programSeconds を足した
  • recordSeconds の意味は変えていない(写像の最後尾)。分母には `programSeconds` を使う
  • --axis timeline も原点から並べるよう直した(以前は頭に 1 時間の無音が入っていた)
出荷実体の尺は台帳から読む

台帳 dev/knowledge/program-lengths-20260811.json。毎回 YouTube を叩かない。

強い順に youtube > Final/<Part>.mp4|.mov > Final/<Part>.mp3 > Final/Audio/<Part>.mp3。

取れたものは全部記録し、食い違いも残す(丸めの範囲は食い違いにしない)。

  • `Final/Audio/` の mp3 は版が古いことがある。 実測: Part7 は −26.96s、Part8 は −34.70s で

どちらもダイジェスト無しの旧版。Part10 / Part11 は別版(−39.7s / +44.2s)

  • YouTube の videoId は docs/archive/YOUTUBE_CAPTION_PIPELINE.md(archive) の表と

scripts/put-episode-part10.mts / lib/ops/bulkOperations.ts から取った。

タイトルから機械的に推測していない。 裏付けとして 11 本すべて DRT の実効尺と 1 秒以内で一致

  • Part2 だけ食い違う(YouTube 2326s / 手元 2344.61s = 18.6s)。公開されているのは別の版
  • Part12 は videoId が取れていない。 0 や推測で埋めていない
ダイジェストは DRT のコンパウンドから出す

構造で拾って、名前で分ける。

  • 構造: MediaRef が timeline 種別のメディアプール項目を指している区間(= コンパウンド)。

compoundTimelinePlacements()

  • 名前: その timeline の名前に digest が含まれるか。classifyDigestCompounds()

実測の付き方は 4 通りに割れていて、前方一致でも後方一致でも全部は拾えない:

Part1_digest / digest_2〜5 / Digest_6_2・Digest_7・Digest_8 / Part9〜11_Digest。

OP と LogoMotion copy はどちらにも当たらないので落ちる。

同じダイジェストが V1 と A1 の両方に載っているので、区間の和集合で数える(二重に足さない)。

Part12 はコンパウンドが 1 つも無い。 これは「ダイジェスト 0」ではなく「不明」として扱う

(平坦化されて普通のクリップとして置かれている可能性を否定できない)。

判定
実効の本編 = 出荷実体の尺 − ダイジェスト長
判定       = 生成した通しの尺(全区切りの合計)が 実効の本編 を超えているか

超えていれば「編集点を抜いていない」ことの証拠になる。

ダイジェストが不明な Part、出荷実体が取れない Part は判定しない(verdict: 'unknown')。

黙って旧基準(DRT の尺)へ落ちない。

一覧は npx tsx scripts/program-length-report.mts。

6-12b. 錨と派生を分ける(dev/knowledge/audio-lineage-20260812.json)

浅尾指摘 2026-08-12:「それぞれ mp4 の元データとかと比較しているか?」

「一回元データを加工にしたやつを、いちど mp3 で書き出しているから、その辺りを考慮したほうがいい」。

何が問題だったか

Final/Audio/<Part>.mp3 を物差しに使っていたが、**あれは加工済みの派生物であって

出荷された実体ではない。** 書き出し工程で頭が落ちたり、別の版になったりする。

錨(anchor)と派生(derived)
何精度
錨Final/<Part>.mp4 / .mov1ms。手元にある出荷実体
錨YouTube の公開尺1 秒。本当に出荷されたことの証拠
派生Final/<Part>.mp3—
派生Final/Audio/<Part>.mp3—
派生Final/Audio/_mastered/<Part>.wav—

錨が 2 つあるときは精度の高いほう(final-video)を採る。ただし YouTube と

丸めの範囲で一致しているときだけ。ずれていたら別の版なので YouTube を採り、食い違いを残す。

Part1〜7 と Part12 には `Final/<Part>.mp4|.mov` が無い。 Part1〜7 は YouTube が錨。

Part12 は錨が無い(mp4 も YouTube も無い)。DRT の実効尺 5565.67s と mp3 5565.72s が

0.05s で一致するので mp3 は現行版と読めるが、出荷された実体そのものではない。

実測(2026-08-12、scripts/audit-audio-lineage.mts)

錨から等間隔に 60 秒の窓を切り、派生側の ±180 秒を探す。

隣り合う窓で値が変われば、そこが段差。平均で埋めない。

Part錨Audio/<Part>.mp3 の差写像
Part8Part8.mov 2218.048s−34.648s単一オフセット −34.66s(相関 1.00 / 17 窓)
Part9Part9.mp4 3835.221s±0.000s同版(_mastered/Part9.wav も一致)
Part10Part10.mp4 3080.427s−39.651s単一オフセット −41.62s(相関 0.98〜0.99 / 25 窓)
Part11Part11.mp4 2459.797s+44.171s2 区間に割れている(下)

Part11 の段差(浅尾の「88.4 秒の段差」の正体):

本編 120〜1020s   mp3 = 本編 − 44.16s   窓 8 個   相関 0.97〜0.99
本編 1080〜2340s  mp3 = 本編 + 44.18s   窓 11 個  相関 0.93〜0.98
段差は本編 1050s 付近で +88.34s

内訳の検算: 頭で −44.16s(ダイジェスト 27.25s + OP ≈ 16.9s が mp3 に無い)、

途中で +88.34s(mp4 に無い塊が mp3 に在る)。合計 +44.18s が実測の尺差 +44.171s と一致する。

単一のオフセットでは表せない。

音を使わない検算を必ず併用する

相関だけだと段差をまたいで誤った値を掴む(Part11 がまさにそれ)。

DRT の実効尺は 12 Part すべてで錨と −0.01s で一致した。YouTube は +0.20〜+0.95s(秒への丸め)。

どこを直したか
  • scripts/build-part-mix.mts の物差し(partFile)は `Final/<Part>.mp4` を先に見る。

以前は Final/Audio/<Part>.mp3 が先だった

  • 物差しとの差は `programSeconds`(原点を引いた書き出しの尺)に対して測る。

以前は recordSeconds と比べており、Part9〜12 で 3600 秒ずれていた

  • 証跡に ruler.kind / ruler.isAnchor / rulerSec / recordOriginSec を出す。

`scripts/confirm-speakers.mts` はこれを読むので、あちらの軸もここで直る

  • 台帳 dev/knowledge/program-lengths-20260811.json の各出典に role(anchor / derived)を付けた
6-10. やり残していること
  • `--axis timeline` は実機で確かめていない。 原点を引くよう直した(§6-12)が、

Part9〜12 の timeline 軸の出力は作り直していない

  • 区切りをまたぐ位置関係は測っていない。 --concat で 1 本にするときだけ

壁時計の橋(±1 秒)が要る。既定の区切り出力にはこの推定が入らない(§6-11)

  • `build-audio-swap-drt.mts` は区切りごとの出力に対応していない。 1 本の wav を前提にしている
  • Part1 の 3 つ目の区切り(本編 1765.1〜1794.4s、29.3s)はまだ載っていない。

3 人ぶんの実物(95s / 63s / 47s)は Clips_Raw に在るが、

`MIN_MIC_BYTES`(20MB = 139 秒)で候補から外れている。

--min-mic-bytes 6000000 で試すと Asao の 1 本だけが同期して載る

(Fujii 63s / Nabe 47s は 35 秒窓を 2 本置けず、2 窓以上 の関門を通らない)。

1/3 人で載せてよいかは人の判断。 既定は 20MB のままにしてある

  • `build-audio-swap-drt.mts` は基準マイクの区間しか書かない。

収録が複数チャンクに割れている Part では、2 本目以降が `.drt` に入らない。

Part1 で新たに載った 195.2 秒はここを直さないと Resolve へ戻らない

  • Part1 / Part7 の `<Part>_AudioSwap.drt` は作り直しが要る。 軸が変わった
  • Part7 の本編 2296.2〜2345.2s(49.0s)は、ふじいが載っていない。

素材は在る(TX02_MIC020_20260215_182303_orig.wav、k=−2492.26)が、

重ならない窓を 2 本置けないので unmeasurable(§6-6d)。delivery は 97.89%。

採るなら --approve-refusal で理由を承認するか、その 49 秒を 2/3 人で出す判断が要る。

どちらも人の判断。

  • 第 2 走査(§6-6d)を入れたあと、Part1 / Part2 / Part4 / Part8 は回していない。

この 4 本は delivery が 98.35 / 99.82 / 99.06 / 99.99% で、

同じ形の取りこぼしが残っている可能性がある(Part1 の 29.3 秒は上の項目)。

第 2 走査は検算の通ったマイクを増やすだけなので下がることは無いはずだが、

測っていない。 回したら区切りの数と出力が変わりうる

  • Part12 の本編 3859.5〜4365.4s(505.9s)は写像に区間が無い。

DRT の Dialogue が Part12_2_tempAudio.mp3(実体なし)を指しており、

そこだけどのトラックからも実素材へ辿れない。delivery の分母(写像 4982.8s)に

入っていないので 100% は通るが、本編の 5565.7 秒のうち 582.9 秒は写像が無い

(77.0s + 505.9s)。source 軸の出力には生収録として入っているが、

「本編のどこか」は言えていない


7. 作った音を Resolve へ戻す(<Part>_AudioSwap.drt)

§6 が作るのは wav 1 本。それをタイムラインへ手で貼らずに済ませる。

yarn tsx scripts/build-audio-swap-drt.mts --part Part1          # DRY RUN
yarn tsx scripts/build-audio-swap-drt.mts --parts Part1..Part8 --go

原本を上書きしない。 入力の <Part>_Master.drt は読むだけで、出力は

drp/audio-swap-20260811/<Part>_AudioSwap.drt と、同フォルダの audio-swap-report.json。

7-1. やっていること

ゼロから `.drt` を組まない。本編の `.drt` を読んで、要素を 2 つ足すだけ。

  • メディアプールに wav の項目を 1 つ。既存のモノラル wav 項目を複製して、

パス・尺・サンプル数・ID だけ差し替える

  • 音声トラック 1 本に、区間ごとのクリップ。既存の素材直参照クリップを複製して、

Start / Duration / In / MediaFilePath / MediaRef だけ差し替える

空いている音声トラックがあればそこへ入れる(実測 Part1〜8 は全部 A4 が空いていた)。

無ければ末尾に 1 本足す。元のクリップは 1 本も消さない。

時刻は 2 つの実測値だけから出す。推定しない。

値どこから意味
editMap.placements[基準マイク].intervals<Part>_mix_source.json本編の record 秒 ↔ 基準マイクの秒
startInReferenceMicSec同上基準マイクの何秒目が wav の 0 秒か

つまり In(秒)= sourceFromSec − startInReferenceMicSec。

7-2. wav は本編の全長を埋めない(実測)

基準マイクが覆うのは本編の一部だけ。残りは元の Dialogue のまま。

Part区間埋まる / 本編置き先
Part1131553.3s / 1794.4s(86.6%)A4
Part2111556.6s / 2344.1s(66.4%)A4
Part371588.3s / 1966.1s(80.8%)A4
Part4151533.7s / 2133.3s(71.9%)A4
Part5211578.7s / 2156.3s(73.2%)A4
Part6191656.1s / 2301.8s(71.9%)A4
Part7171665.7s / 2345.2s(71.0%)A4
Part8191670.9s / 2218.0s(75.3%)A4

埋まらない区間は印字と audio-swap-report.json の plan.uncovered に必ず出る。

大半は OP / ダイジェスト / 別セッションのマイクへ切り替わった尾で、欠陥ではない。

7-3. 検算(3 系統。同じコードで書いて読むだけにしない)
系統何を見るか結果(Part1〜8)
TypeScript readResolveSequencesクリップ数・パス・in/out・他トラックが無傷か区間 122 本すべて一致。重なり 0、読めなかったクリップ 0、他の音声・映像トラックは変化なし
Python tools/resolve-adapter/drt_decode.py(zipfile + ElementTree)別実装で zip と XML が開けるか、値が同じか8 本すべて開けた。Start / Duration / In / パス / MediaRef が 122 本すべて一致
音そのもの(log 包絡の相互相関、独立系統)書いた位置が本当にその音か27 点でずれ 0ms(包絡 50Hz なので分解能 20ms)。相関 0.47〜0.94

音の検算は 2 通り当てた。本編 mp3 と当てられるのは Part1〜6 だけで、

Part7 / Part8 は `Final/Audio/PartN.mp3` が別版(ruler.deltaToEditMapSec が

+26.9s / +34.6s)なので本編では測れない。そこはカメラの内蔵マイク

(C0017.MP4 / C0018.MP4。ラベリアとは別のマイク)で当てて 0ms。

版が違う Part を本編で測ると「一致していない」と出る。写像の誤りと読み違えない。

7-4. 書き出しに入っていないもの(この経路で直せない)
  • トラックのミュート / 有効無効。 Mute Enable にあたるキーが project.xml にも

SeqContainer にも 1 つも無い(UTF-16BE で全走査した)。だから

元の Dialogue は鳴ったまま。足したトラックと元のどちらを鳴らすかは Resolve 上で人が切り替える

  • マルチカムのどのアングル / マイクが選ばれていたか。 書き出しに残らない
  • リタイム(`MediaTimemapBA`)の中身。 足すクリップには等速の 1 値だけを書く
  • `In` の小数部を読み手が落とす。 readResolveSequences の frameInteger は

352|9f9999999999c93f の | 以降を捨てる。書いたファイルには入っているが、

読み返しの数値は最大 35ms(1 フレーム未満)小さく出る

  • `EffectFiltersBA`(音量・パン)。 足すクリップでは空=既定値
  • Part4〜8 はモノラルの雛形がその `.drt` に無い。 2ch の項目を複製しているので、

Resolve 側でチャンネル構成を必ず見る(印字の「注意」に出る)

7-5. Resolve 実機では開いていない

NLE 互換は未検証。 読み返しは TypeScript と Python の 2 系統だけで、

「Resolve が開けるか」はどちらも保証しない。プロジェクトの正本ルール

([../../CLAUDE.md](../../CLAUDE.md))どおり、完了扱いにしない。

なお zip 層は読んでそのまま書くとバイト単位で一致することを確かめてある

(export-20260807 の .drt 14 本すべて。エントリ順・deflate の生バイト・

署名つきデータディスクリプタ・DOS 時刻・外部属性まで保つ)。

XML 内の二進ブロブも、Part1_Master.drt に入っている全件で decode → encode が

バイト単位で一致する(BA 1,834 件 / コンテナ 2,423 件 / protobuf 1,450 件)。

一致するのは「壊していない」ことの証明であって、Resolve が開ける証明ではない。


8. 実装
鎖と目標値lib/audio/mastering.ts
生マイクから 1 本を作る`scripts/build-part-mix.mts`
作った音を Resolve へ戻す`lib/nle/drp/audio-swap.ts` / `scripts/build-audio-swap-drt.mts`
区間写像 → マイクの置き場所 / 生収録の軸lib/audio/part-edit-map.ts
.drt / .drp の読み取りと合成lib/nle/drp/edit-map.ts
機材が違うマイクの音色を揃える`lib/audio/tone-match.ts`
Part を跨ぐ音色の基準(台帳)`dev/knowledge/tone-reference-part7-20260811.json` / scripts/build-tone-reference.mts
ダイナミクスを揃えるlib/audio/dynamics-match.ts
「本当の本編の尺」と判定`lib/audio/program-length.ts` / dev/knowledge/program-lengths-20260811.json
全 Part の一覧`scripts/program-length-report.mts`
反響・音色の実測(2 本を比べる)`scripts/measure-reverb.mts`
オートミキサーlib/audio/automix.ts
同期lib/audio/sync.ts
包絡と相関lib/audio/envelope.ts
完成 mp3 の手当てscripts/master-part-audio.mts
生マイクの結合scripts/join-mic-chunks.mts
2026-08-15: Part12の「素材不足」訂正とcoverage物差し

Part12の7459.500〜7965.375秒について、旧監査は外部マイクを発見できず coverage を保留していた。しかし再走査で TX02_MIC017_20260411_171840_orig.wav(Asao)、TX01_MIC047_20260411_171842_orig.wav(Nabe)、00039_Wireless_PRO.WAV(Fujii)を発見した。13本の配置検証はすべて合格し、最大残差は11msだった。これは「未検証の別音源を採用した」事例ではなく、探索窓の不足を修正した事例である。

Part12_mix_source.json の現在値は次のとおり。

項目実測
生成音声5本 / 5947.61s
loudness / delivery / coverage / verification全てPASS
DRT本編尺5565.6667s
出荷台帳 Part12.mp35565.72s(差0.05s)
digest不明(コンパウンドなし。0にはしない)
measuredProgramUsedtrue

コンパウンドが無いのでdigestは不明のままにする。代わりに、DRTの主タイムライン尺と出荷台帳の尺が明示的な許容差1秒以内で一致する場合だけ、DRT本編尺を測定物差しとして judgeProgramCoverage に渡す。digestSec=0 とする実装ではない。

なお coverageAudit.ok=false は、旧 Part12_Master.drt が実体のない Part12_2_tempAudio.mp3 を参照していることを示すsource-map監査である。新たに発見した生マイクの配置検証や4関門のcoverage PASSとは別の欠陥なので、旧参照を消して合格扱いにはしない。ResolveのAudioSwapは Part12_mix_source_1.wav〜_5.wav とそのmanifestを使用する。

L5-02 生マイクから配信用の音を作る(手順と根拠の正本)

次の案件でも同じ手順で回すための文書。 何を根拠に何を決めるか、案件ごとに測り直す値はどれか、

そして外れた仮説をここに残す。同じ間違いを二度やらないための唯一の手段が、外れた記録である。

0. この文書の範囲(01_dialogue_mastering.md との分担)
何の正本か
[01_dialogue_mastering.md](01_dialogue_mastering.md)鎖と品質。 目標値、鎖の順序、1 段ずつの実測、合否の定義。数値の根拠はすべてあちら
この文書(02)手順と根拠。 一連の流れ、各段の判断材料、案件ごとに変わる値、外れた仮説、人にしかできないこと

数値の実測表は 01 にある。ここでは同じ表を写さない。§ 番号でリンクする。

実装の正本は scripts/build-part-mix.mts(ヘッダに設計思想)と lib/audio/*.ts の各ヘッダ。

文書と実装が食い違ったら実装を読む。 食い違いは §7 に列挙してある。


1. 一連の流れ
生マイクを集める → 素材を同定する → 位置を確定する → 区切りに割る
→ 1 本ずつ整える → 混ぜる → 仕上げる → 測って合否を出す → Resolve へ戻す

正規入口は 1 つ。yarn audio:part-mix。--project と --media-registry を必須とし、

Media Registry が hash で束縛した audioPartMix manifest の logical artifact key から、

生素材、NLE 書き出し、台帳、出力先を ProjectArtifactLocator で解決する。

環境変数や案件名から置き場を推測しない。同名 Part でも Project が違えば read/write key は交差しない。

段 1〜4 は --go の手前で終わる。作れないと分かったら作らずに止まる。

yarn audio:part-mix --project <storage-key> --project-id <uuid> \
  --media-registry <registry.json> --part Part7        # 下見(書き出さない)
yarn audio:part-mix --project <storage-key> --project-id <uuid> \
  --media-registry <registry.json> --part Part7 --go   # 書き出す

出力は既定で <Part>_mix_source_<n>.wav(区切りごとに別ファイル)と、

同じ場所の <Part>_mix_source.json(全部の証跡)。

段 1. 生マイクを集める

根拠にするもの。 Clips_Raw/<session>/Audio/ の実ファイル。

決めること。 走査に入れる wav の集合。

  • scanRawMics(lib/audio/part-edit-map.ts)が <session>/Audio/ を歩く
  • 下限バイト数で落ちる(既定 20MB = 48kHz/24bit モノラルで 139 秒)。

ここが素材の取りこぼしを作る唯一の場所。実測で「在って使える」と確かめた Part だけ

MIN_MIC_BYTES_BY_PART で下げてある(§5 の表)

  • 落ちたものは黙って消えない。 段 3 の完全性の検算(01 §6-6b)が毎回数え直す
段 2. 素材を同定する(置き場・機材・人を 3 つとも別に持つ)

根拠にするもの。 ファイル名 → 改名表 → 台帳、の順。

決めること。 そのファイルの dir / recorder / person / family。

量どこから引くか引けないとき
dir 置き場フォルダ名—
recorder 機材の個体TX01_MIC005_... の TX01 → 改名表 → 置き場置き場へ落ちる
person 人台帳 speaker-people.json を <session>/<recorder> → <recorder> → <dir> の順で引く`unknown:<recorder>` のまま残す。人名へ寄せない
family 機材の型recorderFamily(lib/audio/tone-match.ts)。TX-lavalier / Rode-Wireless-PROunknown:<file>

置き場 = 人にしない(01 §6-6c)。実測 260411 は置き場 4 つに対して人 3 人で、

置き場を人として数えると完全性の検算が恒常的に偽陽性になっていた。

常に落ちる関門は誰も読まない。

`TXnn` は型ではなく個体。`MICnnn` は個体ではなくチャンク。 3 つを混ぜない

(lib/speakers/mic-identity.ts のヘッダ)。

段 3. 位置を確定する(道は 2 本。写像が読めるなら写像)

根拠にするもの。 Resolve の書き出し <Part>_Master.drt と、素材どうしの相関。

決めること。 「本編の t 秒 = このマイクの t+k 秒」の k。

A. 区間写像(既定)
  • .drt の音声トラックから「本編の時刻 → 素材ファイルと、その中の時刻」を区間ごとに読む

(lib/nle/drp/edit-map.ts)。編集で抜かれた段差はここに全部入っている

  • 写像が指した素材(カメラ本体音 / マルチカムのマイク)と生マイクのずれは定数。

どちらも連続した収録なので編集の影響を受けない。これを包絡の相関で測る

  • 相関 0.35 以上・重ならない窓 2 本以上・ばらつき 50ms 以内で合格。1 本でも欠けたら不合格
  • 端で窓が届かないマイクには第 2 走査(focusedClockOffset)。

重なり範囲の中にだけ窓を置き直す。閾値は 1 回目と同じ(01 §6-6d)

この道では完成 Part の mp3 を使わない。 物差しが古い版でも影響を受けない。

Dialogue トラックで決め打ちしない。 Part9〜12 の A1 は書き出し済みの仮ミックスを指していて

実素材へ辿れない。source 軸では同じ timeline の別トラックから採る(01 §6-0c)。

B. 単一オフセット(写像が無いときの落ち先)

物差しは完成 Part の音声。マイク同士は直接合わせない

(ラベリアは装着者の声しか拾わないので、席が離れていると相関が立たない)。

ただしこの道は編集の段差を吸収できない。 既定ではない。

検算(採用したものを、独立な根拠でもう一度見る)
  • verifyPlacementsAgainstProgram — 出荷実体(錨)を相手にした独立な検算。

「本編のこの秒 = このファイルのこの秒」と写像が言っている位置に、本当に山が立つか

  • 相関の値の高さを根拠にしない。「予測した位置に山が立つか」が根拠

(本編は全員 + 音楽なので相関は 0.2〜0.5 しか出ない)

  • 窓を重ねない。 重なった窓は独立した検算にならない
  • 窓を置けるだけの長さが無いものは「検算不能」。 合格とも不合格とも書かない
第 2 系統(機材のカウンタ)は別のツール

BWF bext.TimeReference と Sony rec-run TC は lib/audio/recorder-timecode.ts が読み、

台帳 dev/knowledge/timecode-20260813.json に出る。器は scripts/inspect-timecode.mts。

この経路は `build-part-mix.mts` から呼ばれていない。(実測: build-part-mix.mts が

recorder-timecode.ts から import しているのは readAudioFormat だけ)

出荷経路は相関 1 系統のまま。 カウンタは今のところ監査の道具である。

段 4. 区切りに割る

根拠にするもの。 マイクと素材の辺で繋がった連結成分と、生ファイルの境目。

決めること。 出力を何本に分けるか、それぞれの番号と範囲。

  • buildSourceSegments が連結成分ごとに独立した軸を作り、生ファイルの境目でさらに割る
  • 6 秒未満の区切りは隣へ吸収する(既定 auto = 人数 → 位置。2026-08-14 に

next 固定から替えた。それ以前この行が書いていた same-source は一度も既定ではない)。

下限の根拠は実測で、積分ラウドネスの膝 4s と検算窓が置ける 6s の大きいほう(01 §6-11e)。

向きは固定しない。 隣が MIN_PERSONS に届かないならそちらを助け、

決まらなければ塊の中の位置で決める(01 §6-11g)

  • 番号は「最初の断片」ではなく「本体(いちばん長い本編区間)の頭」で振る(01 §6-11b)
  • 3 人未満では作らない(MIN_PERSONS = 3)。2 本で作れると、

欠けている人がいることが音でしか分からない。数えるのは解決できた人だけ

壁時計の橋(`--clock-bridge`)は既定で使わない。 区切りごとに別ファイルで出すので要らない。

橋は「相関では原理的に検証できない唯一の推定」(切れ目の間は誰も録っていない)。

連結版(--concat)を作るときだけ意味がある。

段 5. 1 本ずつ整える(順序が効く。動かさない)

実装は scripts/build-part-mix.mts の 段 1〜6。各値の根拠は 01 §6-2 / §6-7 / §6-8。

順何をなぜここか
1組み立てて、鎖の前に測る鎖は入力に依存して動く。出口で測ると機材の差と鎖の反応が混ざる
2デクリップ(真のピークが 0 を超えた素材だけ)adeclip は ±1.0 正規化前提。音量合わせを先に掛けるとヒストグラムが飽和して検出が壊れる
3音色を測って基準を決め、補正を作る基準は全トラックを測ってからでないと決まらない
4音色の補正 EQ を焼く補正が空なら ffmpeg の引数は 1 文字も変わらない
5ダイナミクスを測って揃える鎖の閾値は全部絶対 dBFS。ばらばらのレベルで入れると同じ鎖が別のことをする
5bノイズフロアを揃える(既定 off)実測で効かなかった(§3 の表)
6共通の鎖 → 音量合わせ(−23 LUFS)音量合わせは鎖の手前。 鎖の閾値が絶対値だから

束ねる単位が段によって違う。混ぜない。

段単位なぜ
音色機材(gear。既定)同じ型のマイクは同じ周波数特性を持つ。同じ機材の中の差 = その人の声なので動かさない
ダイナミクス人抑揚は人に付く。収録が 2 チャンクに割れた人の票を二重に数えない
ノイズ人床の位置は装着位置・服・プリのゲインで決まる。同じ型でも収録ごとに違う(実測 Part7 で 11.6dB 割れ)
段 6. 混ぜる
  • automixRawFiles(Dugan 式)。10ms ごとに各マイクのレベルを測り、比でゲインを配る
  • 素直に足さない。 同じ部屋の N 本を足すと櫛形の干渉になる。同期を詰めても消えない
  • 区切りごとに掛ける。 区切りをまたいで同時に鳴るクリップは無い
  • 設計値(`targetSumMean`)と実際に掛かった値(`gainSumMean`)を別々に出す。

平滑化が非対称なので合計は保存されない(§3 の表)

段 7. 仕上げる
  • ラウドネスは Part 全体で 1 回決める。 区切りごとに独立して正規化すると境目で音量が変わる
  • 全区切りを繋いだものを 1 度だけ測り、同じ静的ゲインを全区切りへ当てる。

繋ぐのは測るためだけで、出力は分かれたまま

  • バスの鎖は busChain。天井を先に当て、静的ゲインで目標へ持っていく

(loudnorm の線形モードは目標に届かない。01 §6-4)

  • 真のピークは繋いだものから採らない。ファイルごとの最大を採る

(繋ぎ目の段差が実在しないピークとして出る)

段 8. 測って合否を出す(`ok` は 1 個ではない。4 つの AND)
関門何を見るか落ちる意味
loudnessI の誤差と TP の超過(meetsTarget)音量・ピークが目標から外れている
delivery本編が引いている素材が出力に載ったか素材を取りこぼしている
coverage出荷実体の尺を超えているか編集点を抜いている / 判定できていない
verification本編時刻上で接している区切りの境目を出力そのもので測ったか飛んでいる / 測っていない

`unknown` と「測っていない」は合格にしない。 合格とは「その工程の全部の規則を同時に通した」こと。

source軸では、出力配列の隣同士が本編時刻上でも隣とは限らない。recordRanges の一方の終端と

他方の始端が実測許容内で接している境目だけを verification の対象にする。接していない

区間は真の gapであり、別の editorial cut として連結しない。そこを音量の境目として測らず、

applicable: false と理由を証跡へ残す。これにより、Part6 のように本編上は離れた区間を

出力順だけで比較して「音量が飛んだ」と誤判定しない。

分母は出荷実体 − ダイジェスト。`.drt` の尺を分母にしない

(Part9〜12 は開始タイムコードが 01:00:00:00 で 3600 秒ずれる。01 §6-12)。

段 9. Resolve へ戻す
  • scripts/build-audio-swap-drt.mts(lib/nle/drp/audio-swap.ts)が

出荷済みの .drt を読み、必要な要素だけを足して書き戻す

  • 入力の正本は <Part>_mix_source.json の outputs[]。

1 本目の値で全部を代表させない(実測 Part1 は 2 本目だけで本編 195.2 秒ぶんある)

  • 元のトラックは触らない。 空いている音声トラックへ置き、無ければ 1 本足す。消しも並べ替えもしない
  • 元の Dialogue のミュートはコードからできない(§6)

2. 検算そのものを疑う(規則)
検算が落ちないことを疑え。

「落ちない関門」は 2 種類ある。どちらも欠陥である。

  • 構造上つねに通る(トートロジー)。分子と分母から同じものを同時に引いている、

出力を 1 サンプルも見ずに計算値どうしを比べている、同じ窓を 2 回数えている

  • 構造上つねに落ちる(恒常的な偽陽性)。置き場を人として数えていた完全性の検算がこれ。

常に落ちる関門は誰も読まなくなるので、通らないのと同じ

新しい関門を足したら、まず「これはどうやったら落ちるか」を書く。 書けないなら関門ではない。

そして落ちる実例を 1 つ実測で通す(例: いまの delivery は出荷前の Part1 に当てると 88.8%)。


3. 実測で否定された仮説(消すな)

同じ間違いを二度やらないための台帳。 親エージェントが誤って報告したものも含む。

#仮説実測出典
1民生機にタイムコードは無いBWF `bext.TimeReference` にサンプル単位のカウンタが在った。 260411 TX01 は隣接チャンクの差が実尺と誤差 0 サンプルで一致。素材 169 本・機材 13 台で sample-exact 51 組(うち bwf 46 組は残差 0 目盛り)recorder-timecode.ts ヘッダ
2automix の合計が 1.326 なので部屋鳴りが +2.4dB 増えている支配率が 1% も動かない。 合計を 1 に戻しても語間 − 発話は 0.04dB(判定下限 0.5dB 未満)しか変わらず、支配率は 27/30/42% で同一。全マイク共通の係数は直接音と部屋鳴りの比を動かさない。代償だけがあった(語頭 50ms −0.43dB、有声 p05 −1.02dB)ので既定 offautomix.ts L24-27 / 01 §6-3b
3ダイナミクス合わせが悪化させている(7.26→9.71)測定点の取り違え。 同じ点で測ると 6.08→4.23 と縮む。spread.before → after は A/B ではない01 §6-8
4機材が違うと音色が違うから寄せるべき機材差 1.18dB < 同じ機材の中の人の差 2.43dB(実測 Part10)。トラック単位で寄せると Part1 で人の差 6.77dB のうち 3.88dB を潰していた。束ねる単位を機材にした01 §6-7c / tone-match.ts L765-769
5ノイズ低減を機材ごとに変えれば環境音が減る0.07dB。 いちばん悪い SNR は 19.01 → 16.57dB と悪化した。効いたのは下方展開(0→4dB で語間 −19.50 → −23.15dB、−5.85dB)。既定 off へ01 §6-8b / §6-8c
6Part7 / Part12 は素材が無い在った。窓を置く場所が悪かった。 90 秒の窓を等間隔に 20 本置くと、素材の終わり際にだけ重なるマイクには窓が 1 本しか入らない。2 窓要求で落ちていただけ。第 2 走査を足して Part12 は 88.80% → 100.00%(拾った 4 本は全部レコーダの次のチャンク)01 §6-6d
7短い区切りが +8dB なのは窓のばらつきゲインの式が黙って切り替わっていた。 声の中央値が測れると voicedTarget(−32dBFS) 基準、測れないと BALANCE_LUFS(−23LUFS) 基準へ落ちる基準点の違う 2 式。測れないのは短いクリップだけなので、短い区切りだけが別の目標へ揃えられていた。測れないときは gainDb = 0 にして +8dB が消えた01 §6-11f
8尺と本編範囲が合わないのは不具合合わないのが正しい。 source 軸の lengthSec は生収録の秒で、編集で抜かれたぶんも入る。原因は3 つの量(生収録の尺 / 本編の秒の合計 / 本編の端から端までの span)を混ぜて印字していたこと。recordFromSec〜recordToSec は連続区間ではなく穴を含む span01 §6-11c
9番号と素材が逆なのは素材の取り違え9.1 秒の OP が 445 秒の後半チャンクを 1 番へ引きずり上げていた。 番号を「最初の断片の頭」で振っていたため。10 Part 中 6 Part で順が狂っていた。本体(いちばん長い本編区間)の頭で振り直した01 §6-11b
10低い相関 = 素材が違う誤診。 ラベリアは装着者の声しか拾わないので、席が離れていると被りが無い(Asao↔Nabe 0.63〜0.81 に対し Asao↔Fujii 0.17〜0.29)。Fujii の素材は正常で同じ収録だった。識別と同期で閾値を分ける(0.25 / 0.35)契機になったsync.ts ヘッダ
11ファイル名の時刻が同じなら同時刻260215 で Asao と Nabe は同じ 11:12:39 だが、真のずれは −130.3ms(約 3 フレーム)。時計は「同じ撮影か」の粗い当たり付けにしか使わないsync.ts ヘッダ
12カメラの TC で橋を架けられるrec-run TC は実時間ではない。 収録していない間は進まない。カメラ 4 台の隣接 45 組がすべて `rec-run-only`。Part1 でこの橋を架けるとレコーダ 3 台の壁時計より 29.5 秒ずれたrecorder-timecode.ts / recorder-clock.ts
13機材のカウンタが名前の時計を置き換える半分だけ。 260411 の TX は走り続ける型で橋が架かる(壁時計と 0.29 秒一致)が、260215 の TX は停止のたびに 0 へ戻る(run 数 15/12/12)。Part1 の橋は名前の時計のまま。「±1 秒が消えた」とは書けないrecorder-clock.ts L48-74
14隣り合うベルは重ねたほうが滑らか(音色 EQ の Q = 1.4)大幅に出しすぎていた。 ホワイトノイズ 60 秒で実測すると、Q=1.4 は +5.0 を頼んだ山の頂点で +8.1 が出た。Q=2.5 で平均誤差最小・最大誤差 1.13dBtone-match.ts L779-801
15正規化してから平均しても同じ順序に意味がある。 正規化は非線形なので平均と交換できない。トラックごとに正規化してから平均すると、Part7 を自分自身の基準に掛けたときに 100Hz +1.27dB / 4kHz −1.35dB の見かけの差が出た。生のバンド値をパワー平均 → 最後に 1 回正規化で 0 になったtone-match.ts L577-585
16残差の小さいほうの基準を採ればよい(音量)どちらを軸にしても残る食い違いは同じ量(片方を揃えればもう片方がその差だけ割れる)。「小さいほう」では決まらない。耳が聞くのは声なので声の中央値を揃えるdynamics-match.ts L477-483
17尺が合わないのは間違ったファイルが繋がっている開始タイムコードだった。 Part9〜12 の timeline は 01:00:00:00 始まりで recordSeconds は 0 から数えていた。原点を引くと出荷実体と 0.06 秒以内で一致。Part1〜8 は 00:00:00:00 なので誤差が出ていなかったprogram-length.ts L17-21
18音色 EQ の鋭さは 2.0 前後が実用(automix の sharpness)2.0 は耳で否定された(「みんなの声がとぎれとぎれに聞こえる」)。既定は 1.0automix.ts L83-88

4. 構造上そう見えていただけの検算(消すな)

「落ちない」は「欠陥が無い」ではない。 以下は全部、実際に欠陥を抱えたまま通っていた。

#検算どう空だったかいまどうなっているか
1delivery.ratio分母を mapped − refused にしていたので、出さないと決めた秒が分子からも分母からも同時に消える。比は必ず 1 になる。実測 Part12 は 557.8 秒を出さなかったのに 100.00%。12 Part 全部が 1.0000 だった分母は mapped。抜けるのは --approve-refusal で個別に承認した理由だけ。Part12 は 0.8880 → FAIL に変わった
2区切りの境目の検査finalVoiced = chainedVoicedMedianDb + gainDb、gainDb = voicedTarget − chainedVoicedMedianDb なので 恒等的に `voicedTarget`。差は構造上つねに 0。証跡でも Part1 の 3 人が −32.89240815967751 で完全同一。出力を 1 サンプルも測っていない合否はレンダリング済みの wav を測った値で出す(boundaries[].output)。計算値は plannedIsIdentity: true を付けて残し、合否には使わない
3intactRatio出力を「全員そろう区間」で打ち切っていたので、載らなかったものが分母からも消えて 100% に見えていた。出荷済みの Part1 は 100% で通っているのに、本編が使う 195.2 秒が丸ごと出力に入っていなかった関門から外して報告だけにした。代わりに delivery(本編を基準に数える)を関門にした
4写像が直接指したファイルの位置method: 'edit-map'、k=0 で windowsScanned: 0 のまま `verified: true`。改名表の引き直しが間違っていれば黙って別人のマイクを載せるverifyPlacementsAgainstProgram が出荷実体(錨)を相手に独立に検算する。direct フラグで「以前は 1 窓も測っていなかった経路」だと分かるようにした
5マルチカムの変種の票composeToMediaVariants はトラックごとに写像を返すので、同じカメラクリップの同じ区間が変種の数だけ出てくる。重複を残すと1 つの窓が 2 票になり「2 窓で一致した」が成立する。実測で 113 本中 60 本以上がこれで通っていた「本編の 1 箇所 = 1 票」。positionIndex で数え、同じ区間を 2 度数えない
6完全性の検算(置き場 = 人)置き場を人として数えていたので、Part10 / Part11 が personCount:4 / foundMics:3 で恒常的に落ちていた(Wireless2 は Part10 の時間帯に居ないだけ)。常に落ちる関門は誰も読まない置き場・機材・人を切り離した(lib/speakers/mic-identity.ts)。数えるのは解決できた人だけ
7最上位の okmeetsTarget の戻り値そのもの。ラウドネスが合ったという意味しか無いものが最上位に置かれていた。 Part12 は同じ JSON に「ダイジェストを 0 と置いても 3.22 分足りない」と書きながら ok: true4 つの AND(§1 段 8)。書いてある欠陥が合否に効くようにした
8窓の重なり90 秒の窓を 31 秒おきに置いたら 65% が同じ音になり、同じ収録ですらない別チャンクが 4 窓そろって「一致」した。同じ needle を 4 回見ていただけ窓を重ねない。重なった窓は独立した検算にならない
9辺が 1 本のマイクの残差経路が 1 本しか無いものを「残差 0」と書いていた「測っていない」と書く。null を 0 と書かない

5. グローバル / プロジェクト値の境界(再現性の要)
5-1. どの案件でも同じ(グローバル。触らない)
値実体根拠
配信目標 −14 LUFS / TP −1.0 dBFSMASTERING_TARGETS.youtube / sns各社の公表値。推測で置かない(sns は個別の公表値が無いので YouTube と同値に置いてある=仮定)
鎖の順序(修復 → ローカット → NR → 下方展開 → 引く EQ → レベラー → ピーク → 足す EQ → ディエッサ → ハイカット)trackChain01 §3。動かさない
プロファイル podcast-loud の全数値MASTERING_PROFILES浅尾承認済み。書き換えない。この経路で上書きするのは下方展開だけ
下方展開 4dB経路の既定(プロファイルの 2dB は据え置き)膝を実測(01 §6-8c)。耳に届く深さ(≥6dB)の途切れが 0.2 → 1.6 件/分(×8)に立ち上がる直前
音色 EQ の Q = 2.5TONE_CORRECTION_DEFAULTS.qホワイトノイズで実測(3 曲線で平均誤差最小、最大誤差 1.13dB)
音色バンド 13 本(2/3 オクターブ、50Hz〜20.2kHz)、補正は 100Hz〜10kHz の 11 バンドTONE_BANDS / TONE_CORRECT_FROM_HZ / TONE_CORRECT_TO_HZ幾何平均で切ると必ず連続する。48kHz のナイキストに収まる
識別 0.25 / 同期 0.35 の二重閾値IDENTIFY_MIN / SYNC_MIN「相関が立たない = 素材が違う」という誤診を構造的に防ぐ
位置の合格条件(重ならない窓 2 本以上・ばらつき 50ms 以内)DEFAULT_WINDOW_SCAN_POLICY01 §6-6d。第 2 走査でも同じ閾値
区切りの下限 6 秒ABSORB_SHORTER_THAN積分ラウドネスの膝 4s と検算窓が置ける 6s の大きいほう
3 人未満では作らないMIN_PERSONS = 3欠けている人がいることが音でしか分からなくなる
delivery 98% / coverage は出荷実体 − ダイジェストMIN_DELIVERED / judgeProgramCoverage01 §6-4
境目の許容 6.0dBBOUNDARY_MAX_VOICED_DELTA_DB境目が無いところで同じ統計が max 5.34dB 動くのを先に測ってから決めた
束ねる単位(音色 = 機材 / ダイナミクス = 人 / ノイズ = 人)--tone-group / dynamicsTracks.group / --noise-group§1 段 5 の表
ノイズ合わせは既定 off--noise-match off実測で効かなかった(§3 #5)
壁時計の橋は既定 off--clock-bridge区切りごとに別ファイルで出すので要らない
区切りごとに別ファイルSEGMENTED(--concat で 1 本)各区切りが自分の素材にサンプル単位で合う。橋の推定がゼロになる
5-2. 案件ごとに測り直す(プロジェクト値。次の案件では作り直す)
値いまの中身(Directors' Katto)どこで作るか
素材の置き場Kattoの既存実体は D:/zFilms Dropbox/z Films/Directors'KattoProjectMediaRegistry.audioPartMix がhash束縛したmanifestの projectRootDirectory / rawDirectory。環境変数や--rawでは渡さない
セッション名と機材構成260215 = TX ラベリア 3 本(pcm_s24le 24bit 整数)/ 260411 = TX 系 + Rode Wireless PRO(pcm_f32le 32bit float)実測(readAudioFormat)
人の台帳dev/knowledge/speaker-people.json(置き場 or 機材 → 人名 / 表示名 / 画面に映るか)scripts/infer-speaker-names.mts。`sources` に `manual` が入る行は再生成で上書きしない
改名表dev/knowledge/mic-rename-manifest.json(Part1〜3 の katto_partN_<人名>.wav ↔ TX01_MIC005_...)手動。中身の bext と 15/15 一致を実測済み(checkRenameAgainstTimecode)
音色の基準dev/knowledge/tone-reference-part7-20260811.json(Part7 の発話スペクトル)scripts/build-tone-reference.mts。毎回測り直さない
出荷実体の尺dev/knowledge/program-lengths-20260811.json(YouTube 公開尺 / Final/<Part>.mp4 など)scripts/program-length-report.mts --refresh
錨と派生の系譜dev/knowledge/audio-lineage-20260812.jsonscripts/audit-audio-lineage.mts
機材カウンタの台帳dev/knowledge/timecode-20260813.jsonscripts/inspect-timecode.mts
Part 単位の `--min-mic-bytes`Part1 / 2 / 5 / 7 = 5MB、他は既定 20MB下げるのは「その Part で素材が在って使える」と実測で確かめた場合だけ。証跡 dev/knowledge/chunk-gaps-20260812.json / missing-mics-20260813.json
ダイジェストのコンパウンド名Part1_digest / digest_N / Digest_N / PartN_Digest の 4 通り。Part12 はコンパウンドが 1 つも無いclassifyDigestCompounds(既定 /digest/i)。識別できなければ `null`。0 と書かない
音色の寄せ幅 0.85経路の既定(ライブラリ既定 0.7 は据え置き)Part1 を 0.70 / 0.85 で作って比べた実測。次の案件では測り直す
話者数と人名あさお / ふじい / なべ の 3 人手動登録を必ず用意する。ゲストが毎回変わる案件がある

次の案件で最初にやること。 5-2 の台帳を全部空から作り直す。5-1 は触らない。

5-1 の値を変えたくなったら、それは実測を伴う変更であってプロジェクト設定ではない。


6. 人にしかできないこと(機械では判定できない理由つき)
やることなぜ機械で決められないか
音を聴く合否 4 つは「規則に従ったか」しか測っていない。sharpness 2.0 が否定されたのも、podcast-loud の低域が上がったのも、耳が根拠だった。数値が全部通っていても、聴かずに出荷しない
元 Dialogue のミュート`.drt` にキーが無い。 Mute / Enable にあたるキーが project.xml にも SeqContainer にも 1 つも無いことを UTF-16BE で全走査して確認済み。実機の読み返しでも 12 Part すべて全段 enabled だった。測定された不在であって未実装ではない。足したトラックと元のトラックのどちらを鳴らすかは Resolve 上で人が切り替える
チャンネル構成の確認実機の Audio Ch は本物の 1ch でも `2` を返すので、この値では判定できない。Part4〜8 と Part10 / Part11 は 1ch のプール雛形が無く 2ch を複製している
人名の確定台帳に無い機材は unknown:<recorder> のまま残る。推測で人名を当てない。画面に映らない声は(カッコ)付き
`--min-mic-bytes` を下げる判断下げると候補が増えて承認済みの Part の組み方まで動きうる。「その Part で素材が在って使える」を実測で確かめた人だけが下げる
`--approve-refusal` の承認「出さないと決めた」を分母から抜く操作。機械が自分で承認できるなら関門ではない
マルチカムのアングル / マイクの選択書き出しに残らない。写像は音声トラックごとに作り、素材ファイルの一致という独立な根拠で選ぶが、決まらないものは「未確定」で残す
リタイムの中身速度カーブは解けない。既定形(等速)から外れたクリップは写像へ載せずに止める。「等速でない」以上のことは言わない

7. 実装との食い違い(発見時の記録。解消状況は §8)

書いてあることと動いていることが割れている箇所。読むときの前提として残す。

#場所食い違い
1lib/audio/automix.ts:182段 2 のインラインコメントが「合計が 1 になるよう正規化するので部屋鳴りが増えない」のまま。ヘッダ(L13-16)が実測 1.326 / 1.323 / 1.345 で撤回した主張と同じ文
2lib/audio/automix.ts:52AutomixOptions.sharpness の doc が「2.0 前後が実用」。既定(L83-88)は 2.0 を耳で否定して 1.0
3lib/audio/automix.ts:143attackSec: 0.003 は frameSec: 0.01 より短く、Math.max(1, attackSec/frameSec) で潰れる。3ms と 10ms が区別できない
4lib/audio/mastering.ts:313-316「天井は目標より 1dB 下に置く」と書いてあるが、コードは alimiter に target.truePeak をそのまま渡す。直後の 192kHz オーバーサンプリングの段(L317-328)が実際の答えで、古いコメントが上に残っている
5lib/audio/mastering.ts:20ヘッダの鎖の説明が air = 8kHz。DIALOGUE_DEFAULTS.airHz は 10000、根拠文(L143)も 10kHz
6lib/audio/mastering.ts:348-353プロファイル一覧表に `podcast-loud` が無い。実際にはこれが build-part-mix.mts の既定で、低域コンプを持つ唯一のプロファイル
7lib/audio/sync.ts:232if (good.length < 2) が固定値。並びの resolveOffsetFromWindows は policy.minWindows で可変
8lib/audio/dynamics-match.ts:170-174, 196-201doc は束ねる単位を機材(recorderFamily)と書くが、同じファイルの証跡文(L215)と build-part-mix.mts:2758(group: clip.person)は人。動いているのは人
9lib/audio/recorder-clock.ts:73 vs :246-248同じカメラ橋の失敗の実測が、ヘッダは「1951.26s に対して 1921.76s(29.5 秒)」、インラインは「1921.757s に対して 1892.06s(29.70 秒)」。ずれ量は近いが基準値が 59.2 秒違う。どちらかが古い
10lib/audio/noise-match.ts:99-103median が偶数個のとき上側を採る。tone-match.ts:697-702 と dynamics-match.ts:189-194 は 2 つの平均。同じ名前で別の定義
11package.json音声の器で登録されているのは audio:part-mix / audio:session-mix / audio:master-part の 3 本だけ。find-missing-mics / inspect-timecode / build-tone-reference / build-audio-swap-drt / program-length-report / audit-audio-lineage は手打ち(npx tsx scripts/...)
12機材カウンタの経路recorder-timecode.ts の buildTimecodeIndex / crossCheckAxisWithTimecode と recorder-clock.ts の createLayeredStampReader を呼ぶのは scripts/inspect-timecode.mts だけ。build-part-mix.mts が recorder-timecode から使うのは readAudioFormat のみ。出荷経路は相関 1 系統のままで、カウンタは監査の道具
13TONE_CORRECTION_DEFAULTS.strengthライブラリ既定 0.7 と build-part-mix.mts 既定 0.85 が別。意図的(ライブラリの呼び出し側を動かさないため)だが、「音色の寄せ幅」と言ったときにどちらか決まらない
14lib/audio/mastering.ts:151DIALOGUE_DEFAULTS は export されているが、リポジトリ内のどこからも参照されていない(scripts/ lib/ app/ tests/ を grep して 0 件)。「この数値は業界の標準的な出発点であって、この素材で測った値ではない」(L29-30, L148-149)という断り書きごと、死んだ既定として残っている
15入口ごとに既定プロファイルが違うbuild-part-mix.mts は `podcast-loud`(−14 LUFS / LRA 5、低域コンプあり)、master-part-audio.mts は `podcast-highend`(−16 LUFS / LRA 7、低域コンプなし)。同じ素材を 2 つの入口へ通すと別の音が出る。 どちらが正かは未決

8. 2026-08-14 解消記録

§7 の表は、発見した仮説と経緯を消さないため残す。以下は実装へ反映したもの。

  • automix の sharpness / gain 合計 / attack の説明を実装に合わせた。
  • measureSync の有効窓数を minWindows(既定 2)で判定するようにした。
  • ノイズ中央値の偶数個を tone / dynamics と同じ上下平均へ統一した。
  • master-part-audio.mts の既定プロファイルを podcast-loud へ統一した。
  • package.json に swap DRT / timecode / tone reference / speaker confirmation の入口を追加した。
  • --clock-bridge の build-part-mix.mts が BWF / rec-run TC と改名表を読み、--concat の source 軸へ橋として渡すようにした。segmented の既定出力は独立軸のまま。Part1 DRY RUN は 9/9 本 indexed、3 系統、橋 1 本。

未解消は、ライブラリ既定 strength 0.7 と本番既定 0.85 の境界、DIALOGUE_DEFAULTS の扱い、音を聴く判断である。承認済みの本番値は変更しない。

9. 未確認(この文書を書いた時点で確かめていないこと)
  • 段 9 の実機検証。 .drt を書き戻したあと Resolve で開いた記録は docs/PLAN.md(2026-08-12)にあるが、

この文書では読み返していない。埋まるのは本編の 78〜99.5% とされている

  • 他案件での再現。 上の手順は Directors' Katto の 12 Part でしか回していない。

別案件で 1 本通すまで「再現できる」とは書けない

  • build-session-mix.mts が今の鎖と同じ経路かどうかは未確認(master-part-audio.mts は

プロファイルが違うことまで確認した。§7 #15)

  • §5-1 の「グローバル」は、この案件 1 件でしか確かめていない値を含む。

下方展開 4dB・区切りの下限 6 秒・境目の許容 6.0dB・音色 EQ の Q 2.5 は

Directors' Katto の素材で測った膝である。別の収録環境で膝が同じ位置に来る保証は無い(未測定)

10. 音声完成物の制作契約(2026-08-14)

音声の最新版はブランチ名やファイルの新しさだけで決めない。現行の承認済み固定チェーン(podcast-loud、Part7基準の音色、strength 0.85、下方展開4dB、ノイズ合わせoff)を正本とし、実装差分と実測結果をこの契約へ照合する。

  • 完成音声の正本・納品検算物はPart単位の1本の PartN_mix.wav とする。ただし、Resolveの現行ショート生成経路は生素材軸の PartN_mix_source_<n>.wav と PartN_mix_source.json をAudioSwap DRTへ参照させる。したがって *_mix_source_* は、今回の最終DRP・代表再生・書き出し検証が終わるまでlegacyとして削除してはならない。
  • PartN_mix.wav は現行ショートDRTが直接参照するMedia Pool音声ではないが、4関門・Part全体の連続音声・納品マスターとして必要である。*_mix_source_* だけでもショートDRT自体は組めるが、正本のPart音声完成を証明するものではない。両系統を同じ生成条件・同じmanifestで揃える。
  • 聴感・軽い手動EQ/コンプ確認用の軽量proxyは scripts/build-audio-proxies.mts で可逆FLACへ作ってよい。FLACはWAVと同じサンプル列なので編集用としては安全だが、BWF TimeReference を独立した正本にしない。サンプル単位の位置、LUFS、True Peak、BWF TimeReference は24bit WAV/BWFとJSON manifestで検算し、FLACは試聴・軽編集専用とする。MP3は持ち運びだけが必要な場合に限る。
  • 各Partで、生成物の存在だけでなくLUFS、True Peak、delivery、coverage、verificationの4関門を実測する。ok は4つのANDであり、未測定を合格へ置き換えない。
  • Part8は聴感、LUFS、True Peak、境界を最新版の品質基準として再確認する。全Partの音声完成は、12 Partすべての生成物と実測記録がそろうまで needs-evidence のままにする。
  • Resolveの元Dialogueミュートと1ch/2ch構成は、.drtの静的情報だけで確定できない。新規プロジェクト上での人手確認を完了条件に残す。
2026-08-15 Part12再走査の適用結果

旧DRTの Part12_2_tempAudio.mp3 は実体がなく、source-map監査では未解決参照として残る。一方、実録音の再走査で該当区間の3系統(TX02_MIC017 / TX01_MIC047 / 00039_Wireless_PRO)を発見し、13/13配置検証を通過したため、未検証音源を許可したわけではなく、探索結果を更新して本番manifestへ反映した。

Part12の本番再生成は次の条件で行う。

yarn audio:part-mix --project <storage-key> --project-id <uuid> \
  --media-registry <registry.json> --part Part12 \
  --axis source --absorb-into auto --require-verified --go

出力は Part12_mix_source_1.wav〜_5.wav と Part12_mix_source.json。2026-08-15実測では4関門が全てPASSした。DRTにコンパウンドが無いのでdigestは unknown のまま保持し、DRT主タイムライン5565.6667秒と出荷台帳5565.72秒(差0.05秒)の独立突合を測定物差しとしてcoverageを判定する。digestを0へ置くこと、旧 tempAudio 参照を成功扱いにすることは禁止する。