Merge branch 'feature/subtitle-customizations' into develop

This commit is contained in:
Fredrik Burmester
2025-10-01 08:33:54 +02:00
6 changed files with 280 additions and 6 deletions

View File

@@ -22,6 +22,12 @@ import { BITRATES } from "@/components/BitrateSelector";
import { Text } from "@/components/common/Text"; import { Text } from "@/components/common/Text";
import { Loader } from "@/components/Loader"; import { Loader } from "@/components/Loader";
import { Controls } from "@/components/video-player/controls/Controls"; import { Controls } from "@/components/video-player/controls/Controls";
import {
OUTLINE_THICKNESS,
OutlineThickness,
VLC_COLORS,
VLCColor,
} from "@/constants/SubtitleConstants";
import { useHaptic } from "@/hooks/useHaptic"; import { useHaptic } from "@/hooks/useHaptic";
import { usePlaybackManager } from "@/hooks/usePlaybackManager"; import { usePlaybackManager } from "@/hooks/usePlaybackManager";
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache"; import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
@@ -98,7 +104,7 @@ export default function page() {
/** Playback position in ticks. */ /** Playback position in ticks. */
playbackPosition?: string; playbackPosition?: string;
}>(); }>();
useSettings(); const { settings } = useSettings();
const offline = offlineStr === "true"; const offline = offlineStr === "true";
const playbackManager = usePlaybackManager(); const playbackManager = usePlaybackManager();
@@ -557,8 +563,34 @@ export default function page() {
? allSubs.indexOf(chosenSubtitleTrack) ? allSubs.indexOf(chosenSubtitleTrack)
: [...textSubs].reverse().indexOf(chosenSubtitleTrack); : [...textSubs].reverse().indexOf(chosenSubtitleTrack);
initOptions.push(`--sub-track=${finalIndex}`); initOptions.push(`--sub-track=${finalIndex}`);
}
// Add VLC subtitle styling options from settings
const textColor = (settings.vlcTextColor ?? "White") as VLCColor;
const backgroundColor = (settings.vlcBackgroundColor ??
"Black") as VLCColor;
const outlineColor = (settings.vlcOutlineColor ?? "Black") as VLCColor;
const outlineThickness = (settings.vlcOutlineThickness ??
"Normal") as OutlineThickness;
const backgroundOpacity = settings.vlcBackgroundOpacity ?? 128;
const outlineOpacity = settings.vlcOutlineOpacity ?? 255;
const isBold = settings.vlcIsBold ?? false;
// Add subtitle styling options
initOptions.push(`--freetype-color=${VLC_COLORS[textColor]}`);
initOptions.push(`--freetype-background-opacity=${backgroundOpacity}`);
initOptions.push(
`--freetype-background-color=${VLC_COLORS[backgroundColor]}`,
);
initOptions.push(`--freetype-outline-opacity=${outlineOpacity}`);
initOptions.push(`--freetype-outline-color=${VLC_COLORS[outlineColor]}`);
initOptions.push(
`--freetype-outline-thickness=${OUTLINE_THICKNESS[outlineThickness]}`,
);
initOptions.push(`--sub-text-scale=${settings.subtitleSize}`);
initOptions.push("--sub-margin=40");
if (isBold) {
initOptions.push("--freetype-bold");
}
}
if (notTranscoding && chosenAudioTrack) { if (notTranscoding && chosenAudioTrack) {
initOptions.push(`--audio-track=${allAudio.indexOf(chosenAudioTrack)}`); initOptions.push(`--audio-track=${allAudio.indexOf(chosenAudioTrack)}`);
} }

View File

@@ -40,7 +40,6 @@ export const StorageSettings = () => {
}; };
const calculatePercentage = (value: number, total: number) => { const calculatePercentage = (value: number, total: number) => {
console.log("usage", value, total);
return ((value / total) * 100).toFixed(2); return ((value / total) * 100).toFixed(2);
}; };

View File

@@ -16,6 +16,8 @@ import { useMedia } from "./MediaContext";
interface Props extends ViewProps {} interface Props extends ViewProps {}
import { OUTLINE_THICKNESS, VLC_COLORS } from "@/constants/SubtitleConstants";
export const SubtitleToggles: React.FC<Props> = ({ ...props }) => { export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
const isTv = Platform.isTV; const isTv = Platform.isTV;
@@ -25,6 +27,15 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
const cultures = media.cultures; const cultures = media.cultures;
const { t } = useTranslation(); const { t } = useTranslation();
// Get VLC subtitle settings from the settings system
const textColor = pluginSettings?.vlcTextColor ?? "White";
const backgroundColor = pluginSettings?.vlcBackgroundColor ?? "Black";
const outlineColor = pluginSettings?.vlcOutlineColor ?? "Black";
const outlineThickness = pluginSettings?.vlcOutlineThickness ?? "Normal";
const backgroundOpacity = pluginSettings?.vlcBackgroundOpacity ?? 128;
const outlineOpacity = pluginSettings?.vlcOutlineOpacity ?? 255;
const isBold = pluginSettings?.vlcIsBold ?? false;
if (isTv) return null; if (isTv) return null;
if (!settings) return null; if (!settings) return null;
@@ -147,6 +158,148 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
onUpdate={(subtitleSize) => updateSettings({ subtitleSize })} onUpdate={(subtitleSize) => updateSettings({ subtitleSize })}
/> />
</ListItem> </ListItem>
<ListItem title={t("home.settings.subtitles.text_color")}>
<Dropdown
data={Object.keys(VLC_COLORS)}
keyExtractor={(item) => item}
titleExtractor={(item) =>
t(`home.settings.subtitles.colors.${item}`)
}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{t(`home.settings.subtitles.colors.${textColor}`)}
</Text>
<Ionicons
name='chevron-expand-sharp'
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.subtitles.text_color")}
onSelected={(value) => updateSettings({ vlcTextColor: value })}
/>
</ListItem>
<ListItem title={t("home.settings.subtitles.background_color")}>
<Dropdown
data={Object.keys(VLC_COLORS)}
keyExtractor={(item) => item}
titleExtractor={(item) =>
t(`home.settings.subtitles.colors.${item}`)
}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{t(`home.settings.subtitles.colors.${backgroundColor}`)}
</Text>
<Ionicons
name='chevron-expand-sharp'
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.subtitles.background_color")}
onSelected={(value) =>
updateSettings({ vlcBackgroundColor: value })
}
/>
</ListItem>
<ListItem title={t("home.settings.subtitles.outline_color")}>
<Dropdown
data={Object.keys(VLC_COLORS)}
keyExtractor={(item) => item}
titleExtractor={(item) =>
t(`home.settings.subtitles.colors.${item}`)
}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{t(`home.settings.subtitles.colors.${outlineColor}`)}
</Text>
<Ionicons
name='chevron-expand-sharp'
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.subtitles.outline_color")}
onSelected={(value) => updateSettings({ vlcOutlineColor: value })}
/>
</ListItem>
<ListItem title={t("home.settings.subtitles.outline_thickness")}>
<Dropdown
data={Object.keys(OUTLINE_THICKNESS)}
keyExtractor={(item) => item}
titleExtractor={(item) =>
t(`home.settings.subtitles.thickness.${item}`)
}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{t(`home.settings.subtitles.thickness.${outlineThickness}`)}
</Text>
<Ionicons
name='chevron-expand-sharp'
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.subtitles.outline_thickness")}
onSelected={(value) =>
updateSettings({ vlcOutlineThickness: value })
}
/>
</ListItem>
<ListItem title={t("home.settings.subtitles.background_opacity")}>
<Dropdown
data={[0, 32, 64, 96, 128, 160, 192, 224, 255]}
keyExtractor={String}
titleExtractor={(item) => `${Math.round((item / 255) * 100)}%`}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>{`${Math.round((backgroundOpacity / 255) * 100)}%`}</Text>
<Ionicons
name='chevron-expand-sharp'
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.subtitles.background_opacity")}
onSelected={(value) =>
updateSettings({ vlcBackgroundOpacity: value })
}
/>
</ListItem>
<ListItem title={t("home.settings.subtitles.outline_opacity")}>
<Dropdown
data={[0, 32, 64, 96, 128, 160, 192, 224, 255]}
keyExtractor={String}
titleExtractor={(item) => `${Math.round((item / 255) * 100)}%`}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>{`${Math.round((outlineOpacity / 255) * 100)}%`}</Text>
<Ionicons
name='chevron-expand-sharp'
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.subtitles.outline_opacity")}
onSelected={(value) => updateSettings({ vlcOutlineOpacity: value })}
/>
</ListItem>
<ListItem title={t("home.settings.subtitles.bold_text")}>
<Switch
value={isBold}
onValueChange={(value) => updateSettings({ vlcIsBold: value })}
/>
</ListItem>
</ListGroup> </ListGroup>
</View> </View>
); );

View File

@@ -0,0 +1,45 @@
export type VLCColor =
| "Black"
| "Gray"
| "Silver"
| "White"
| "Maroon"
| "Red"
| "Fuchsia"
| "Yellow"
| "Olive"
| "Green"
| "Teal"
| "Lime"
| "Purple"
| "Navy"
| "Blue"
| "Aqua";
export type OutlineThickness = "None" | "Thin" | "Normal" | "Thick";
export const VLC_COLORS: Record<VLCColor, number> = {
Black: 0,
Gray: 8421504,
Silver: 12632256,
White: 16777215,
Maroon: 8388608,
Red: 16711680,
Fuchsia: 16711935,
Yellow: 16776960,
Olive: 8421376,
Green: 32768,
Teal: 32896,
Lime: 65280,
Purple: 8388736,
Navy: 128,
Blue: 255,
Aqua: 65535,
};
export const OUTLINE_THICKNESS: Record<OutlineThickness, number> = {
None: 0,
Thin: 2,
Normal: 4,
Thick: 6,
};

View File

@@ -111,11 +111,11 @@
}, },
"subtitles": { "subtitles": {
"subtitle_title": "Subtitles", "subtitle_title": "Subtitles",
"subtitle_language": "Subtitle Language", "subtitle_hint": "Configure how subtitles look and behave.",
"subtitle_language": "Subtitle language",
"subtitle_mode": "Subtitle Mode", "subtitle_mode": "Subtitle Mode",
"set_subtitle_track": "Set Subtitle Track From Previous Item", "set_subtitle_track": "Set Subtitle Track From Previous Item",
"subtitle_size": "Subtitle Size", "subtitle_size": "Subtitle Size",
"subtitle_hint": "Configure Subtitle Preference.",
"none": "None", "none": "None",
"language": "Language", "language": "Language",
"loading": "Loading", "loading": "Loading",
@@ -124,7 +124,38 @@
"Smart": "Smart", "Smart": "Smart",
"Always": "Always", "Always": "Always",
"None": "None", "None": "None",
"OnlyForced": "Only Forced" "OnlyForced": "OnlyForced"
},
"text_color": "Text Color",
"background_color": "Background Color",
"outline_color": "Outline Color",
"outline_thickness": "Outline Thickness",
"background_opacity": "Background Opacity",
"outline_opacity": "Outline Opacity",
"bold_text": "Bold Text",
"colors": {
"Black": "Black",
"Gray": "Gray",
"Silver": "Silver",
"White": "White",
"Maroon": "Maroon",
"Red": "Red",
"Fuchsia": "Fuchsia",
"Yellow": "Yellow",
"Olive": "Olive",
"Green": "Green",
"Teal": "Teal",
"Lime": "Lime",
"Purple": "Purple",
"Navy": "Navy",
"Blue": "Blue",
"Aqua": "Aqua"
},
"thickness": {
"None": "None",
"Thin": "Thin",
"Normal": "Normal",
"Thick": "Thick"
} }
}, },
"other": { "other": {

View File

@@ -168,6 +168,13 @@ export type Settings = {
defaultPlayer: VideoPlayer; defaultPlayer: VideoPlayer;
maxAutoPlayEpisodeCount: MaxAutoPlayEpisodeCount; maxAutoPlayEpisodeCount: MaxAutoPlayEpisodeCount;
autoPlayEpisodeCount: number; autoPlayEpisodeCount: number;
vlcTextColor?: string;
vlcBackgroundColor?: string;
vlcOutlineColor?: string;
vlcOutlineThickness?: string;
vlcBackgroundOpacity?: number;
vlcOutlineOpacity?: number;
vlcIsBold?: boolean;
// Gesture controls // Gesture controls
enableHorizontalSwipeSkip: boolean; enableHorizontalSwipeSkip: boolean;
enableLeftSideBrightnessSwipe: boolean; enableLeftSideBrightnessSwipe: boolean;
@@ -229,6 +236,13 @@ export const defaultValues: Settings = {
defaultPlayer: VideoPlayer.VLC_3, // ios-only setting. does not matter what this is for android defaultPlayer: VideoPlayer.VLC_3, // ios-only setting. does not matter what this is for android
maxAutoPlayEpisodeCount: { key: "3", value: 3 }, maxAutoPlayEpisodeCount: { key: "3", value: 3 },
autoPlayEpisodeCount: 0, autoPlayEpisodeCount: 0,
vlcTextColor: undefined,
vlcBackgroundColor: undefined,
vlcOutlineColor: undefined,
vlcOutlineThickness: undefined,
vlcBackgroundOpacity: undefined,
vlcOutlineOpacity: undefined,
vlcIsBold: undefined,
// Gesture controls // Gesture controls
enableHorizontalSwipeSkip: true, enableHorizontalSwipeSkip: true,
enableLeftSideBrightnessSwipe: true, enableLeftSideBrightnessSwipe: true,