mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
Merge branch 'feature/subtitle-customizations' into develop
This commit is contained in:
@@ -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)}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
45
constants/SubtitleConstants.ts
Normal file
45
constants/SubtitleConstants.ts
Normal 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,
|
||||||
|
};
|
||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user