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) として呼び出す。