diff --git a/voicevox-remotion-template/src/data/pizza-oven-project-01/script.ts b/voicevox-remotion-template/src/data/pizza-oven-project-01/script.ts index 7ecbc64..6dd1fb4 100644 --- a/voicevox-remotion-template/src/data/pizza-oven-project-01/script.ts +++ b/voicevox-remotion-template/src/data/pizza-oven-project-01/script.ts @@ -37,7 +37,7 @@ wait(1), say("pizza-oven-project-01-sayo-005", "sayo", "まずはblender上で、耐熱レンガの寸法を元に積み方を設計することにしました。"), say("pizza-oven-project-01-sayo-006", "sayo", "使うレンガの数がこれでわかります。"), - say("pizza-oven-project-01-sayo-007", "sayo", "通常サイズのレンガが91個、半分にしたレンガが8個必要ですね。"), + say("pizza-oven-project-01-sayo-007", "sayo", "通常サイズのレンガが91個、\n半分にしたレンガが8個必要ですね。"), say("pizza-oven-project-01-sayo-008", "sayo", "レンガを割って半分にするので、合計95個の耐熱レンガを買うことにしました。"), say("pizza-oven-project-01-sayo-009", "sayo", "次に、レンガを積む土台を作ります。"), say("pizza-oven-project-01-sayo-010", "sayo", "ホームセンターで、コンクリートブロックを8個買ってきました。"), diff --git a/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQSpeechSubtitle.tsx b/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQSpeechSubtitle.tsx index b90a239..40291b5 100644 --- a/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQSpeechSubtitle.tsx +++ b/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQSpeechSubtitle.tsx @@ -1,5 +1,6 @@ import React from "react"; import {interpolate} from "remotion"; +import {splitVQSubtitleLines} from "../subtitleText"; const clampInterpolation = { extrapolateLeft: "clamp", @@ -53,6 +54,7 @@ }) => { const opacity = interpolate(progress, [0, 1], [0, 1], clampInterpolation); const translateY = interpolate(progress, [0, 1], [16, 0], clampInterpolation); + const subtitleLines = splitVQSubtitleLines(text); return (
) : null} -
{text}
+
+ {subtitleLines.map((line, index) => ( + + {index > 0 ?
: null} + {line} +
+ ))} +
); }; diff --git a/voicevox-remotion-template/src/lib/VQRemotionLib/index.ts b/voicevox-remotion-template/src/lib/VQRemotionLib/index.ts index 2933bf3..4287eea 100644 --- a/voicevox-remotion-template/src/lib/VQRemotionLib/index.ts +++ b/voicevox-remotion-template/src/lib/VQRemotionLib/index.ts @@ -1,6 +1,7 @@ export * from "./types"; export * from "./avatarAnimations"; export * from "./scenario"; +export * from "./subtitleText"; export * from "./timeline"; export * from "./components/VQCaptionOverlay"; export * from "./components/VQCharacterAvatar"; diff --git a/voicevox-remotion-template/src/lib/VQRemotionLib/subtitleText.ts b/voicevox-remotion-template/src/lib/VQRemotionLib/subtitleText.ts new file mode 100644 index 0000000..838e7f9 --- /dev/null +++ b/voicevox-remotion-template/src/lib/VQRemotionLib/subtitleText.ts @@ -0,0 +1,13 @@ +const vqSubtitleLineBreakPattern = /\r\n|\r|\n/g; + +// 用途: 字幕テキストを明示改行ごとの表示行に分割する。 +// 使用方法: VQSpeechSubtitleなどの字幕描画前に splitVQSubtitleLines(text) として呼び出す。 +// オプションや引数詳細: text 内の \n・\r\n・\r を改行シンボルとして扱い、空行も表示行として残す。 +export const splitVQSubtitleLines = (text: string) => + text.split(vqSubtitleLineBreakPattern); + +// 用途: VOICEVOX読み上げに渡す前に字幕用の明示改行を取り除く。 +// 使用方法: say(...) や音声生成前の読み上げテキスト正規化で stripVQSubtitleLineBreaks(text) として呼び出す。 +// オプションや引数詳細: text 内の \n・\r\n・\r を削除し、読み上げ内容に改行シンボルが残らないようにする。 +export const stripVQSubtitleLineBreaks = (text: string) => + text.replace(vqSubtitleLineBreakPattern, ""); diff --git a/voicevox-remotion-template/src/lib/VQRemotionLib/timeline.ts b/voicevox-remotion-template/src/lib/VQRemotionLib/timeline.ts index 45d6083..2242f02 100644 --- a/voicevox-remotion-template/src/lib/VQRemotionLib/timeline.ts +++ b/voicevox-remotion-template/src/lib/VQRemotionLib/timeline.ts @@ -1,3 +1,5 @@ +import {stripVQSubtitleLineBreaks} from "./subtitleText"; + export type VQSpeechOptions = Readonly<{ subtitle?: string; readAs?: string; @@ -125,13 +127,24 @@ character: CharacterId, text: string, options: VQSpeechOptions = {} -): VQSpeechEvent => ({ - type: "say", - id, - character, - text, - ...options, -}); +): VQSpeechEvent => { + const strippedText = stripVQSubtitleLineBreaks(text); + const readAs = + options.readAs === undefined + ? strippedText === text + ? undefined + : strippedText + : stripVQSubtitleLineBreaks(options.readAs); + + return { + type: "say", + id, + character, + text, + ...options, + ...(readAs === undefined ? {} : {readAs}), + }; +}; // 用途: 静止画をタイムラインイベントとして定義する。 // 使用方法: script.ts の timeline 内で still("id", "path", options) として呼び出す。