アプリンの書庫

The Library of Aplin · Engineering

制作の手順と考察(Technical Insight)

管理者(Aplin)による検証記録。符号化パラメータと字幕パイプラインを、仕様として固定する。

Technical Insight

本稿は、管理者(Aplin)が映像資産と字幕資産を整備する際に採用した手順を、検証結果とともに記録したエンジニアリング・レポートである。情緒的な叙述は意図的に排し、設定値・アルゴリズム・失敗条件のみを列挙する。

1. 映像符号化の論理

1950–60 年代台の銀幕記録を現行プレーヤへ移植する場合、可変ビットレート(VBR)二往復(2-Pass)符号化は、フレーム間の情報密度差を均すための最低条件である。UniConverter および Filmora の出力ダイアログにおいて、管理者は次の判定基準を固定した。

  • 第 1 パスでシーンごとの複雑度曲線を収集し、第 2 パスで目標ファイルサイズに収束させる。単一パス CBR は、階調の細いシーンでビットの浪費を生じ、反対に動きの大きいシーンでブロッキングを誘発する。
  • 映像トラック平均ビットレートは、1920×1080・23.976 fps・ソースが粒状ノイズを含む前提において、5–8 Mbps を初期レンジとした。上限はデコーダ負荷と CDN 転送コストの積で決まり、下限は主観的品質閾値との ABX 試聴により決定した。
  • 音声は AAC-LC、ステレオ、320 kbps を上限とし、モノラル・歴史的録音のみ 192 kbps へ降格することを許容した。

図 1 — 2-Pass 目標ビットレートとピークキャップの対応(掲載予定)

# 論理モデル(擬似設定表記)
mode = VBR_2PASS
resolution = 1920x1080
frame_rate = 23.976
pass[1] = ANALYZE(complexity_map=True, grain_preserve=HIGH)
pass[2] = ENCODE(
  target_avg_bitrate_Mbps = clamp(5.0, measured_complexity * k, 8.0),
  peak_bitrate_Mbps = 1.25 * target_avg_bitrate_Mbps,
  b_frames = 3,
  ref_frames = 4,
  entropy_coding = CABAC
)
# k はソースの粒状度係数。フィルム・スキャン由来では k > 1 を検証した。

ビットレート配分は、低動きの対話シーンへビットを再分配するプロファイルが最も妥当であると判断した。固定 QP 運用は一見して帯域を節約するが、デジタル時代のプレーヤが採用するループ・フィルタと相性が悪く、縁取り状の圧縮偽物が残存した。

2. 字幕同期のアルゴリズム

Subtitle Edit に読み込んだ波形と、拡張子 .srt の暫定タイムコードから出発した。波形のゼロクロス近傍に発話立ち上がりが存在する仮説の下、エネルギー窓を短く積分し、閾値越えをキューとして採用した。人手で確定させたアンカー区間のみを信頼し、それ以外は補間しない方針である。

図 2 — アンカー点と自動シフトの検証ログ(掲載予定)

2.1 WebVTT および MD への変換

中間表現として内部毫秒カウンタを保持し、出力段で VTT の HH:MM:SS.mmm へ整列した。MD 側は物語原稿との突合用に、同一キーを持つブロック列へ射影した。

# 論理の骨格(Python 風擬似コード)
from dataclasses import dataclass

@dataclass
class Cue:
    t0_ms: int
    t1_ms: int
    text: str

def to_vtt(cues: list[Cue]) -> str:
    lines = ["WEBVTT", ""]
    for i, c in enumerate(cues, start=1):
        lines += [str(i), f"{ms_to_ts(c.t0_ms)} --> {ms_to_ts(c.t1_ms)}", c.text, ""]
    return "\n".join(lines)

def to_md_blocks(cues: list[Cue], key_prefix: str) -> str:
    # 原稿側のアンカーと 1:1 で突合するためのキーを付与する。
    parts = []
    for idx, c in enumerate(cues):
        parts.append(f"<!-- {key_prefix}:{idx} {c.t0_ms}-{c.t1_ms} -->\n> {c.text}\n")
    return "\n".join(parts)

def ms_to_ts(ms: int) -> str:
    h, rem = divmod(ms, 3_600_000)
    m, rem = divmod(rem, 60_000)
    s, x = divmod(rem, 1000)
    return f"{h:02d}:{m:02d}:{s:02d}.{x:03d}"

実運用では、無音区間の誤検出により終端が早期に閉じる事例が散見された。当該ケースは人手で終端を延長し、自動補正ルールには載せなかった。過剰な補正は、台詞と効果音の境界を破壊するためである。

結語

以上の手順は、再現性を優先した結果である。別ソースや別コーデック表紙では、閾値のみを再調整すべきであり、フレームワークそのものの置換は不要であると検証した。

技術報告(概要)へ アプリンの書庫へ