diff --git a/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/main-subtitles-style.tsx b/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/main-subtitles-style.tsx index b183edb8c..fb988da15 100644 --- a/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/main-subtitles-style.tsx +++ b/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/main-subtitles-style.tsx @@ -7,7 +7,7 @@ import { Card } from "@/components/ui/base-ui/card" import { Label } from "@/components/ui/base-ui/label" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/base-ui/tooltip" import { configFieldsAtomMap } from "@/utils/atoms/config" -import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SCALE, DEFAULT_FONT_WEIGHT, DEFAULT_SUBTITLE_COLOR } from "@/utils/constants/subtitles" +import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SCALE, DEFAULT_FONT_SHADOW_INTENSITY, DEFAULT_FONT_STROKE_WIDTH, DEFAULT_FONT_WEIGHT, DEFAULT_SUBTITLE_COLOR } from "@/utils/constants/subtitles" import { SubtitlesTextStyleForm } from "./subtitles-text-style-form" export function MainSubtitlesStyle() { @@ -21,6 +21,8 @@ export function MainSubtitlesStyle() { fontScale: DEFAULT_FONT_SCALE, color: DEFAULT_SUBTITLE_COLOR, fontWeight: DEFAULT_FONT_WEIGHT, + fontShadowIntensity: DEFAULT_FONT_SHADOW_INTENSITY, + fontStrokeWidth: DEFAULT_FONT_STROKE_WIDTH, }, }, })) diff --git a/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/subtitles-text-style-form.tsx b/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/subtitles-text-style-form.tsx index c0f474a0d..2fffe18e9 100644 --- a/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/subtitles-text-style-form.tsx +++ b/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/subtitles-text-style-form.tsx @@ -7,7 +7,7 @@ import { Field, FieldGroup, FieldLabel } from "@/components/ui/base-ui/field" import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/base-ui/select" import { Slider } from "@/components/ui/base-ui/slider" import { configFieldsAtomMap } from "@/utils/atoms/config" -import { MAX_FONT_SCALE, MAX_FONT_WEIGHT, MIN_FONT_SCALE, MIN_FONT_WEIGHT } from "@/utils/constants/subtitles" +import { MAX_FONT_SCALE, MAX_FONT_SHADOW_INTENSITY, MAX_FONT_STROKE_WIDTH, MAX_FONT_WEIGHT, MIN_FONT_SCALE, MIN_FONT_WEIGHT } from "@/utils/constants/subtitles" const FIELD_ROW_CLASS_NAME = "gap-0" const FIELD_ROW_CONTENT_CLASS_NAME = "flex flex-col gap-2 @xs/field-group:grid @xs/field-group:grid-cols-[8.5rem_minmax(0,1fr)] @xs/field-group:items-center @xs/field-group:gap-x-4" @@ -18,6 +18,9 @@ const FONT_FAMILY_OPTIONS: { value: SubtitlesFontFamily, label: string }[] = [ { value: "roboto", label: "Roboto" }, { value: "noto-sans", label: "Noto Sans" }, { value: "noto-serif", label: "Noto Serif" }, + { value: "misans", label: "MiSans" }, + { value: "ibm-plex", label: "IBM Plex" }, + { value: "tsuku-ard-gothic", label: "TsukuARdGothic Std" }, ] interface SubtitlesTextStyleFormProps { @@ -29,6 +32,8 @@ export function SubtitlesTextStyleForm({ type }: SubtitlesTextStyleFormProps) { const textStyle = videoSubtitlesConfig.style[type] const [draftFontScale, setDraftFontScale] = useState(textStyle.fontScale) const [draftFontWeight, setDraftFontWeight] = useState(textStyle.fontWeight) + const [draftShadowIntensity, setDraftShadowIntensity] = useState(textStyle.fontShadowIntensity) + const [draftStrokeWidth, setDraftStrokeWidth] = useState(textStyle.fontStrokeWidth) useEffect(() => { // eslint-disable-next-line react/set-state-in-effect @@ -40,6 +45,16 @@ export function SubtitlesTextStyleForm({ type }: SubtitlesTextStyleFormProps) { setDraftFontWeight(textStyle.fontWeight) }, [textStyle.fontWeight]) + useEffect(() => { + // eslint-disable-next-line react/set-state-in-effect + setDraftShadowIntensity(textStyle.fontShadowIntensity) + }, [textStyle.fontShadowIntensity]) + + useEffect(() => { + // eslint-disable-next-line react/set-state-in-effect + setDraftStrokeWidth(textStyle.fontStrokeWidth) + }, [textStyle.fontStrokeWidth]) + const handleChange = (style: Partial) => { void setVideoSubtitlesConfig(deepmerge(videoSubtitlesConfig, { style: { [type]: style } })) } @@ -128,6 +143,48 @@ export function SubtitlesTextStyleForm({ type }: SubtitlesTextStyleFormProps) { + + +
+ {i18n.t("options.videoSubtitles.style.shadowIntensity")} +
+ setDraftShadowIntensity(value as number)} + onValueCommitted={value => handleChange({ fontShadowIntensity: value as number })} + className="flex-1" + /> + + {draftShadowIntensity} + px + +
+
+
+ + +
+ {i18n.t("options.videoSubtitles.style.strokeWidth")} +
+ setDraftStrokeWidth(value as number)} + onValueCommitted={value => handleChange({ fontStrokeWidth: value as number })} + className="flex-1" + /> + + {draftStrokeWidth} + px + +
+
+
) } diff --git a/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/translation-subtitles-style.tsx b/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/translation-subtitles-style.tsx index 784817910..b768c92b1 100644 --- a/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/translation-subtitles-style.tsx +++ b/src/entrypoints/options/pages/video-subtitles/subtitles-style-settings/components/translation-subtitles-style.tsx @@ -7,7 +7,7 @@ import { Card } from "@/components/ui/base-ui/card" import { Label } from "@/components/ui/base-ui/label" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/base-ui/tooltip" import { configFieldsAtomMap } from "@/utils/atoms/config" -import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SCALE, DEFAULT_FONT_WEIGHT, DEFAULT_SUBTITLE_COLOR } from "@/utils/constants/subtitles" +import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SCALE, DEFAULT_FONT_SHADOW_INTENSITY, DEFAULT_FONT_STROKE_WIDTH, DEFAULT_FONT_WEIGHT, DEFAULT_SUBTITLE_COLOR } from "@/utils/constants/subtitles" import { SubtitlesTextStyleForm } from "./subtitles-text-style-form" export function TranslationSubtitlesStyle() { @@ -21,6 +21,8 @@ export function TranslationSubtitlesStyle() { fontScale: DEFAULT_FONT_SCALE, color: DEFAULT_SUBTITLE_COLOR, fontWeight: DEFAULT_FONT_WEIGHT, + fontShadowIntensity: DEFAULT_FONT_SHADOW_INTENSITY, + fontStrokeWidth: DEFAULT_FONT_STROKE_WIDTH, }, }, })) diff --git a/src/entrypoints/subtitles.content/ui/subtitle-lines.tsx b/src/entrypoints/subtitles.content/ui/subtitle-lines.tsx index 2d8cc27c4..7df7f60ae 100644 --- a/src/entrypoints/subtitles.content/ui/subtitle-lines.tsx +++ b/src/entrypoints/subtitles.content/ui/subtitle-lines.tsx @@ -12,11 +12,16 @@ interface SubtitleLineProps { } function getTextStyles(textStyle: SubtitleTextStyle) { + const hasShadow = textStyle.fontShadowIntensity > 0 + const si = textStyle.fontShadowIntensity return { fontFamily: SUBTITLE_FONT_FAMILIES[textStyle.fontFamily] || SUBTITLE_FONT_FAMILIES.system, fontSize: `${textStyle.fontScale / 100}em`, color: textStyle.color, fontWeight: textStyle.fontWeight, + textShadow: hasShadow ? `${Math.max(1, si * 0.25).toFixed(1)}px ${Math.max(1, si * 0.5).toFixed(1)}px ${si}px rgba(0,0,0,${(0.5 + (si / 8) * 0.5).toFixed(2)})` : "none", + WebkitTextStroke: textStyle.fontStrokeWidth > 0 ? `${textStyle.fontStrokeWidth}px rgba(0,0,0,0.8)` : "0", + paintOrder: "stroke fill" as const, } } diff --git a/src/entrypoints/subtitles.content/ui/subtitles-settings-panel/views/style.tsx b/src/entrypoints/subtitles.content/ui/subtitles-settings-panel/views/style.tsx index b92bea733..387aa8e2e 100644 --- a/src/entrypoints/subtitles.content/ui/subtitles-settings-panel/views/style.tsx +++ b/src/entrypoints/subtitles.content/ui/subtitles-settings-panel/views/style.tsx @@ -14,14 +14,20 @@ import { DEFAULT_DISPLAY_MODE, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SCALE, + DEFAULT_FONT_SHADOW_INTENSITY, + DEFAULT_FONT_STROKE_WIDTH, DEFAULT_FONT_WEIGHT, DEFAULT_SUBTITLE_COLOR, DEFAULT_TRANSLATION_POSITION, MAX_BACKGROUND_OPACITY, MAX_FONT_SCALE, + MAX_FONT_SHADOW_INTENSITY, + MAX_FONT_STROKE_WIDTH, MAX_FONT_WEIGHT, MIN_BACKGROUND_OPACITY, MIN_FONT_SCALE, + MIN_FONT_SHADOW_INTENSITY, + MIN_FONT_STROKE_WIDTH, MIN_FONT_WEIGHT, SUBTITLE_FONT_FAMILIES, } from "@/utils/constants/subtitles" @@ -153,6 +159,26 @@ function TextStyleGroup({ icon, title, textStyle, onChange, onReset, portalConta step={100} onChange={v => onChange({ fontWeight: v })} /> + + onChange({ fontShadowIntensity: v })} + /> + + onChange({ fontStrokeWidth: v })} + /> ) } @@ -162,6 +188,8 @@ const DEFAULT_TEXT_STYLE: SubtitleTextStyle = { fontScale: DEFAULT_FONT_SCALE, color: DEFAULT_SUBTITLE_COLOR, fontWeight: DEFAULT_FONT_WEIGHT, + fontShadowIntensity: DEFAULT_FONT_SHADOW_INTENSITY, + fontStrokeWidth: DEFAULT_FONT_STROKE_WIDTH, } export function StyleView() { diff --git a/src/locales/en.yml b/src/locales/en.yml index 7cd9a896d..63cc42b28 100644 --- a/src/locales/en.yml +++ b/src/locales/en.yml @@ -900,6 +900,8 @@ options: color: Color fontWeight: Font Weight backgroundOpacity: Background Opacity + shadowIntensity: Shadow + strokeWidth: Outline reset: Reset to Default aiSegmentation: title: AI Smart Segmentation diff --git a/src/locales/es.yml b/src/locales/es.yml index 73f639744..e2e967a28 100644 --- a/src/locales/es.yml +++ b/src/locales/es.yml @@ -900,6 +900,8 @@ options: color: Color fontWeight: Grosor de fuente backgroundOpacity: Opacidad del fondo + shadowIntensity: Sombra + strokeWidth: Contorno reset: Restablecer a predeterminado aiSegmentation: title: Segmentación inteligente de IA diff --git a/src/locales/ja.yml b/src/locales/ja.yml index 77acda38a..6bbfc5265 100644 --- a/src/locales/ja.yml +++ b/src/locales/ja.yml @@ -785,6 +785,8 @@ options: color: 色 fontWeight: フォントウェイト backgroundOpacity: 背景の透明度 + shadowIntensity: シャドウ + strokeWidth: 輪郭 reset: デフォルトにリセット aiSegmentation: title: AI スマート区切り diff --git a/src/locales/ko.yml b/src/locales/ko.yml index 4788d7f09..46834974e 100644 --- a/src/locales/ko.yml +++ b/src/locales/ko.yml @@ -785,6 +785,8 @@ options: color: 색상 fontWeight: 글꼴 굵기 backgroundOpacity: 배경 투명도 + shadowIntensity: 그림자 + strokeWidth: 윤곽선 reset: 기본값으로 재설정 aiSegmentation: title: AI 스마트 분할 diff --git a/src/locales/ru.yml b/src/locales/ru.yml index 06ff0b0a9..32f1d7e14 100644 --- a/src/locales/ru.yml +++ b/src/locales/ru.yml @@ -785,6 +785,8 @@ options: color: Цвет fontWeight: Толщина шрифта backgroundOpacity: Прозрачность фона + shadowIntensity: Тень + strokeWidth: Контур reset: Сбросить по умолчанию aiSegmentation: title: AI Умная сегментация diff --git a/src/locales/tr.yml b/src/locales/tr.yml index 4152b016b..aec5dfd7f 100644 --- a/src/locales/tr.yml +++ b/src/locales/tr.yml @@ -785,6 +785,8 @@ options: color: Renk fontWeight: Yazı Kalınlığı backgroundOpacity: Arka Plan Şeffaflığı + shadowIntensity: Gölge + strokeWidth: Anahat reset: Varsayılana Sıfırla aiSegmentation: title: AI Akıllı Bölümlendirme diff --git a/src/locales/vi.yml b/src/locales/vi.yml index ef22ad731..826879afc 100644 --- a/src/locales/vi.yml +++ b/src/locales/vi.yml @@ -785,6 +785,8 @@ options: color: Màu sắc fontWeight: Độ đậm chữ backgroundOpacity: Độ trong suốt nền + shadowIntensity: Đổ bóng + strokeWidth: Viền reset: Đặt lại về mặc định aiSegmentation: title: AI Phân đoạn thông minh diff --git a/src/locales/zh-CN.yml b/src/locales/zh-CN.yml index 9bba3f8f3..0dc093765 100644 --- a/src/locales/zh-CN.yml +++ b/src/locales/zh-CN.yml @@ -900,6 +900,8 @@ options: color: 颜色 fontWeight: 字体粗细 backgroundOpacity: 背景透明度 + shadowIntensity: 阴影 + strokeWidth: 描边 reset: 重置为默认 aiSegmentation: title: AI 智能断句 diff --git a/src/locales/zh-TW.yml b/src/locales/zh-TW.yml index 5379771a1..c402e16c9 100644 --- a/src/locales/zh-TW.yml +++ b/src/locales/zh-TW.yml @@ -785,6 +785,8 @@ options: color: 顏色 fontWeight: 字重 backgroundOpacity: 背景透明度 + shadowIntensity: 陰影 + strokeWidth: 描邊 reset: 重設為預設值 aiSegmentation: title: AI 智慧斷句 diff --git a/src/types/config/subtitles.ts b/src/types/config/subtitles.ts index 0ecfc539c..00a329428 100644 --- a/src/types/config/subtitles.ts +++ b/src/types/config/subtitles.ts @@ -1,16 +1,18 @@ import { z } from "zod" -import { MAX_BACKGROUND_OPACITY, MAX_FONT_SCALE, MAX_FONT_WEIGHT, MIN_BACKGROUND_OPACITY, MIN_FONT_SCALE, MIN_FONT_WEIGHT } from "@/utils/constants/subtitles" +import { MAX_BACKGROUND_OPACITY, MAX_FONT_SCALE, MAX_FONT_SHADOW_INTENSITY, MAX_FONT_STROKE_WIDTH, MAX_FONT_WEIGHT, MIN_BACKGROUND_OPACITY, MIN_FONT_SCALE, MIN_FONT_WEIGHT } from "@/utils/constants/subtitles" import { batchQueueConfigSchema, customPromptsConfigSchema, requestQueueConfigSchema } from "./translate" export const subtitlesDisplayModeSchema = z.enum(["bilingual", "originalOnly", "translationOnly"]) export const subtitlesTranslationPositionSchema = z.enum(["above", "below"]) -export const subtitlesFontFamilySchema = z.enum(["system", "roboto", "noto-sans", "noto-serif"]) +export const subtitlesFontFamilySchema = z.enum(["system", "roboto", "noto-sans", "noto-serif", "misans", "ibm-plex", "tsuku-ard-gothic"]) export const subtitleTextStyleSchema = z.object({ fontFamily: subtitlesFontFamilySchema, fontScale: z.number().min(MIN_FONT_SCALE).max(MAX_FONT_SCALE), color: z.string(), fontWeight: z.number().min(MIN_FONT_WEIGHT).max(MAX_FONT_WEIGHT), + fontShadowIntensity: z.number().min(0).max(MAX_FONT_SHADOW_INTENSITY), + fontStrokeWidth: z.number().min(0).max(MAX_FONT_STROKE_WIDTH), }) export const subtitleContainerStyleSchema = z.object({ diff --git a/src/utils/config/migration-scripts/v075-to-v076.ts b/src/utils/config/migration-scripts/v075-to-v076.ts new file mode 100644 index 000000000..17eee251e --- /dev/null +++ b/src/utils/config/migration-scripts/v075-to-v076.ts @@ -0,0 +1,36 @@ +/** + * Migration script from v075 to v076 + * - Adds fontShadowIntensity and fontStrokeWidth fields with defaults. + * + * IMPORTANT: All values are hardcoded inline. Migration scripts are frozen + * snapshots - never import constants or helpers that may change. + */ + +function migrateTextStyle(style: any): any { + if (!style || typeof style !== "object") + return style + return { + ...style, + fontShadowIntensity: style.fontShadowIntensity ?? 0, + fontStrokeWidth: style.fontStrokeWidth ?? 0, + } +} + +export function migrate(oldConfig: any): any { + const oldStyle = oldConfig?.videoSubtitles?.style + if (!oldStyle || typeof oldStyle !== "object") { + return oldConfig + } + + return { + ...oldConfig, + videoSubtitles: { + ...oldConfig.videoSubtitles, + style: { + ...oldStyle, + main: migrateTextStyle(oldStyle.main), + translation: migrateTextStyle(oldStyle.translation), + }, + }, + } +} diff --git a/src/utils/constants/config.ts b/src/utils/constants/config.ts index b0b264e1d..96f48e4be 100644 --- a/src/utils/constants/config.ts +++ b/src/utils/constants/config.ts @@ -7,7 +7,7 @@ import { DEFAULT_TRANSLATE_PROMPTS_CONFIG } from "./prompt" import { DEFAULT_PROVIDER_CONFIG_LIST } from "./providers" import { DEFAULT_SELECTION_OVERLAY_OPACITY } from "./selection" import { DEFAULT_SIDE_CONTENT_WIDTH } from "./side" -import { DEFAULT_BACKGROUND_OPACITY, DEFAULT_DISPLAY_MODE, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SCALE, DEFAULT_FONT_WEIGHT, DEFAULT_SUBTITLE_COLOR, DEFAULT_SUBTITLE_POSITION, DEFAULT_TRANSLATION_POSITION } from "./subtitles" +import { DEFAULT_BACKGROUND_OPACITY, DEFAULT_DISPLAY_MODE, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SCALE, DEFAULT_FONT_SHADOW_INTENSITY, DEFAULT_FONT_STROKE_WIDTH, DEFAULT_FONT_WEIGHT, DEFAULT_SUBTITLE_COLOR, DEFAULT_SUBTITLE_POSITION, DEFAULT_TRANSLATION_POSITION } from "./subtitles" import { DEFAULT_AUTO_TRANSLATE_SHORTCUT_KEY, DEFAULT_BATCH_CONFIG, DEFAULT_MIN_CHARACTERS_PER_NODE, DEFAULT_MIN_WORDS_PER_NODE, DEFAULT_PRELOAD_MARGIN, DEFAULT_PRELOAD_THRESHOLD, DEFAULT_REQUEST_CAPACITY, DEFAULT_REQUEST_RATE } from "./translate" import { TRANSLATION_NODE_STYLE_ON_INSTALLED } from "./translation-node-style" import { DEFAULT_TTS_CONFIG } from "./tts" @@ -18,7 +18,7 @@ export const GOOGLE_DRIVE_TOKEN_STORAGE_KEY = "__googleDriveToken" export const THEME_STORAGE_KEY = "theme" export const DEFAULT_DETECTED_CODE = "eng" as const -export const CONFIG_SCHEMA_VERSION = 75 +export const CONFIG_SCHEMA_VERSION = 76 export const DEFAULT_FLOATING_BUTTON_POSITION = 0.66 export const DEFAULT_FLOATING_BUTTON_SIDE: FloatingButtonSide = "right" @@ -144,12 +144,16 @@ export const DEFAULT_CONFIG: Config = { fontScale: DEFAULT_FONT_SCALE, color: DEFAULT_SUBTITLE_COLOR, fontWeight: DEFAULT_FONT_WEIGHT, + fontShadowIntensity: DEFAULT_FONT_SHADOW_INTENSITY, + fontStrokeWidth: DEFAULT_FONT_STROKE_WIDTH, }, translation: { fontFamily: DEFAULT_FONT_FAMILY, fontScale: DEFAULT_FONT_SCALE, color: DEFAULT_SUBTITLE_COLOR, fontWeight: DEFAULT_FONT_WEIGHT, + fontShadowIntensity: DEFAULT_FONT_SHADOW_INTENSITY, + fontStrokeWidth: DEFAULT_FONT_STROKE_WIDTH, }, container: { backgroundOpacity: DEFAULT_BACKGROUND_OPACITY, diff --git a/src/utils/constants/subtitles.ts b/src/utils/constants/subtitles.ts index 106a510dc..428f049ae 100644 --- a/src/utils/constants/subtitles.ts +++ b/src/utils/constants/subtitles.ts @@ -58,7 +58,13 @@ export const MAX_FONT_WEIGHT = 700 export const DEFAULT_FONT_WEIGHT = 400 export const MIN_BACKGROUND_OPACITY = 0 export const MAX_BACKGROUND_OPACITY = 100 -export const DEFAULT_BACKGROUND_OPACITY = 75 +export const DEFAULT_BACKGROUND_OPACITY = 0 +export const MIN_FONT_SHADOW_INTENSITY = 0 +export const MAX_FONT_SHADOW_INTENSITY = 8 +export const DEFAULT_FONT_SHADOW_INTENSITY = 3 +export const MIN_FONT_STROKE_WIDTH = 0 +export const MAX_FONT_STROKE_WIDTH = 8 +export const DEFAULT_FONT_STROKE_WIDTH = 2.5 export const DEFAULT_FONT_FAMILY = "system" as const export const DEFAULT_SUBTITLE_COLOR = "#FFFFFF" export const DEFAULT_DISPLAY_MODE = "bilingual" as const @@ -74,4 +80,7 @@ export const SUBTITLE_FONT_FAMILIES = { "roboto": "Roboto, sans-serif", "noto-sans": "\"Noto Sans\", \"Noto Sans SC\", \"Noto Sans JP\", \"Noto Sans KR\", sans-serif", "noto-serif": "\"Noto Serif\", \"Noto Serif SC\", \"Noto Serif JP\", \"Noto Serif KR\", serif", + "misans": "\"MiSans\", \"MiSans Latin\", \"MiSans TC\", \"MiSans L3\", \"MiSans Tibetan\", \"MiSans Arabic\", \"MiSans Devanagari\", \"MiSans Gurmukhi\", \"MiSans Thai\", \"MiSans Lao\", \"MiSans Myanmar\", \"MiSans Khmer\", sans-serif", + "ibm-plex": "\"IBM Plex Sans SC\", \"IBM Plex Sans TC\", \"IBM Plex Sans JP\", \"IBM Plex Sans KR\", \"IBM Plex Sans\", \"IBM Plex Sans Condensed\", \"IBM Plex Sans Arabic\", \"IBM Plex Sans Devanagari\", \"IBM Plex Sans Hebrew\", \"IBM Plex Sans Thai\", \"IBM Plex Sans Thai Looped\", sans-serif", + "tsuku-ard-gothic": "\"TsukuARdGothic Std\", \"筑紫A丸\", sans-serif", } diff --git a/src/utils/subtitles/fetchers/youtube/index.ts b/src/utils/subtitles/fetchers/youtube/index.ts index a66620f28..bbcc8afff 100644 --- a/src/utils/subtitles/fetchers/youtube/index.ts +++ b/src/utils/subtitles/fetchers/youtube/index.ts @@ -407,7 +407,11 @@ export class YoutubeSubtitlesFetcher implements SubtitlesFetcher { } } - const data = await response.json() + const text = await response.text() + if (!text) { + throw new Error("YouTube subtitle API returned an empty response") + } + const data = JSON.parse(text) const parsed = youtubeSubtitlesResponseSchema.safeParse(data) if (!parsed.success) { throw new Error("Invalid response format")