mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-07 18:49:45 +00:00
ksplayer word splitting fix
This commit is contained in:
parent
0ab85ec870
commit
ecaaaa66ed
3 changed files with 289 additions and 291 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -54,7 +54,7 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
formattedSegments,
|
formattedSegments,
|
||||||
}) => {
|
}) => {
|
||||||
if (!useCustomSubtitles || !currentSubtitle) return null;
|
if (!useCustomSubtitles || !currentSubtitle) return null;
|
||||||
|
|
||||||
const inverseScale = 1 / zoomScale;
|
const inverseScale = 1 / zoomScale;
|
||||||
const bgColor = subtitleBackground ? `rgba(0, 0, 0, ${Math.min(Math.max(backgroundOpacity, 0), 1)})` : 'transparent';
|
const bgColor = subtitleBackground ? `rgba(0, 0, 0, ${Math.min(Math.max(backgroundOpacity, 0), 1)})` : 'transparent';
|
||||||
let effectiveBottom = bottomOffset;
|
let effectiveBottom = bottomOffset;
|
||||||
|
|
@ -65,23 +65,26 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
}
|
}
|
||||||
effectiveBottom = Math.max(0, effectiveBottom);
|
effectiveBottom = Math.max(0, effectiveBottom);
|
||||||
|
|
||||||
|
// Prepare content lines
|
||||||
|
const lines = String(currentSubtitle).split(/\r?\n/);
|
||||||
|
|
||||||
|
// Detect RTL for each line
|
||||||
|
const lineRTLStatus = lines.map(line => detectRTL(line));
|
||||||
|
const hasRTL = lineRTLStatus.some(status => status);
|
||||||
|
|
||||||
// When using crisp outline, prefer SVG text with real stroke instead of blur shadow
|
// When using crisp outline, prefer SVG text with real stroke instead of blur shadow
|
||||||
const useCrispSvgOutline = outline === true;
|
// However, SVG text does not support complex text shaping (required for Arabic/RTL),
|
||||||
|
// so we must fallback to standard Text component for RTL languages.
|
||||||
|
const useCrispSvgOutline = outline === true && !hasRTL;
|
||||||
|
|
||||||
const shadowStyle = (textShadow && !useCrispSvgOutline)
|
const shadowStyle = (textShadow && !useCrispSvgOutline)
|
||||||
? {
|
? {
|
||||||
textShadowColor: 'rgba(0, 0, 0, 0.9)',
|
textShadowColor: 'rgba(0, 0, 0, 0.9)',
|
||||||
textShadowOffset: { width: 2, height: 2 },
|
textShadowOffset: { width: 2, height: 2 },
|
||||||
textShadowRadius: 4,
|
textShadowRadius: 4,
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
// Prepare content lines
|
|
||||||
const lines = String(currentSubtitle).split(/\r?\n/);
|
|
||||||
|
|
||||||
// Detect RTL for each line
|
|
||||||
const lineRTLStatus = lines.map(line => detectRTL(line));
|
|
||||||
|
|
||||||
const displayFontSize = subtitleSize * inverseScale;
|
const displayFontSize = subtitleSize * inverseScale;
|
||||||
const displayLineHeight = subtitleSize * lineHeightMultiplier * inverseScale;
|
const displayLineHeight = subtitleSize * lineHeightMultiplier * inverseScale;
|
||||||
const svgHeight = lines.length * displayLineHeight;
|
const svgHeight = lines.length * displayLineHeight;
|
||||||
|
|
@ -89,14 +92,14 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
// Helper to render formatted segments
|
// Helper to render formatted segments
|
||||||
const renderFormattedText = (segments: SubtitleSegment[], lineIndex: number, keyPrefix: string, isRTL?: boolean, customLetterSpacing?: number) => {
|
const renderFormattedText = (segments: SubtitleSegment[], lineIndex: number, keyPrefix: string, isRTL?: boolean, customLetterSpacing?: number) => {
|
||||||
if (!segments || segments.length === 0) return null;
|
if (!segments || segments.length === 0) return null;
|
||||||
|
|
||||||
// For RTL, use a very small negative letter spacing to stretch words slightly
|
// For RTL, use a very small negative letter spacing to stretch words slightly
|
||||||
// This helps with proper diacritic spacing while maintaining ligatures
|
// This helps with proper diacritic spacing while maintaining ligatures
|
||||||
const effectiveLetterSpacing = isRTL ? (displayFontSize * -0.02) : (customLetterSpacing ?? letterSpacing);
|
const effectiveLetterSpacing = isRTL ? (displayFontSize * -0.02) : (customLetterSpacing ?? letterSpacing);
|
||||||
|
|
||||||
// For RTL, adjust text alignment
|
// For RTL, adjust text alignment
|
||||||
const effectiveAlign = isRTL && align === 'left' ? 'right' : (isRTL && align === 'right' ? 'left' : align);
|
const effectiveAlign = isRTL && align === 'left' ? 'right' : (isRTL && align === 'right' ? 'left' : align);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text key={`${keyPrefix}-line-${lineIndex}`} style={{
|
<Text key={`${keyPrefix}-line-${lineIndex}`} style={{
|
||||||
color: textColor,
|
color: textColor,
|
||||||
|
|
@ -156,7 +159,7 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
const isRTL = lineRTLStatus.every(status => status);
|
const isRTL = lineRTLStatus.every(status => status);
|
||||||
let anchor: 'start' | 'middle' | 'end';
|
let anchor: 'start' | 'middle' | 'end';
|
||||||
let x: number;
|
let x: number;
|
||||||
|
|
||||||
if (isRTL) {
|
if (isRTL) {
|
||||||
// For RTL, always use 'end' anchor to position from right edge
|
// For RTL, always use 'end' anchor to position from right edge
|
||||||
anchor = 'end';
|
anchor = 'end';
|
||||||
|
|
@ -165,20 +168,20 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
anchor = align === 'center' ? 'middle' : align === 'left' ? 'start' : 'end';
|
anchor = align === 'center' ? 'middle' : align === 'left' ? 'start' : 'end';
|
||||||
x = align === 'center' ? 500 : (align === 'left' ? 0 : 1000);
|
x = align === 'center' ? 500 : (align === 'left' ? 0 : 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseFontSize = displayFontSize;
|
const baseFontSize = displayFontSize;
|
||||||
const lineHeightPx = displayLineHeight;
|
const lineHeightPx = displayLineHeight;
|
||||||
const strokeWidth = Math.max(0.5, outlineWidth);
|
const strokeWidth = Math.max(0.5, outlineWidth);
|
||||||
// For RTL, use a very small negative letter spacing to stretch words slightly
|
// For RTL, use a very small negative letter spacing to stretch words slightly
|
||||||
// This helps with proper diacritic spacing while maintaining ligatures
|
// This helps with proper diacritic spacing while maintaining ligatures
|
||||||
const effectiveLetterSpacing = isRTL ? (baseFontSize * -0.02) : letterSpacing;
|
const effectiveLetterSpacing = isRTL ? (baseFontSize * -0.02) : letterSpacing;
|
||||||
|
|
||||||
// Position text from bottom up - last line should be at svgHeight - small margin
|
// Position text from bottom up - last line should be at svgHeight - small margin
|
||||||
// Add descender buffer so letters like y/g/p/q/j aren't clipped
|
// Add descender buffer so letters like y/g/p/q/j aren't clipped
|
||||||
const descenderBuffer = baseFontSize * 0.35 + (strokeWidth * 0.5);
|
const descenderBuffer = baseFontSize * 0.35 + (strokeWidth * 0.5);
|
||||||
const lastLineBaselineY = svgHeight - descenderBuffer;
|
const lastLineBaselineY = svgHeight - descenderBuffer;
|
||||||
const startY = lastLineBaselineY - (lines.length - 1) * lineHeightPx;
|
const startY = lastLineBaselineY - (lines.length - 1) * lineHeightPx;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Stroke layer */}
|
{/* Stroke layer */}
|
||||||
|
|
@ -239,7 +242,7 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
const effectiveLetterSpacing = isRTL ? (subtitleSize * inverseScale * -0.02) : letterSpacing;
|
const effectiveLetterSpacing = isRTL ? (subtitleSize * inverseScale * -0.02) : letterSpacing;
|
||||||
// For RTL, adjust text alignment
|
// For RTL, adjust text alignment
|
||||||
const effectiveAlign = isRTL && align === 'left' ? 'right' : (isRTL && align === 'right' ? 'left' : align);
|
const effectiveAlign = isRTL && align === 'left' ? 'right' : (isRTL && align === 'right' ? 'left' : align);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text style={[
|
<Text style={[
|
||||||
styles.customSubtitleText,
|
styles.customSubtitleText,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export const safeDebugLog = (message: string, data?: any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add language code to name mapping
|
// Add language code to name mapping
|
||||||
export const languageMap: {[key: string]: string} = {
|
export const languageMap: { [key: string]: string } = {
|
||||||
'en': 'English',
|
'en': 'English',
|
||||||
'eng': 'English',
|
'eng': 'English',
|
||||||
'es': 'Spanish',
|
'es': 'Spanish',
|
||||||
|
|
@ -81,10 +81,10 @@ export const formatLanguage = (code?: string): string => {
|
||||||
if (!code) return 'Unknown';
|
if (!code) return 'Unknown';
|
||||||
const normalized = code.toLowerCase();
|
const normalized = code.toLowerCase();
|
||||||
const languageName = languageMap[normalized] || code.toUpperCase();
|
const languageName = languageMap[normalized] || code.toUpperCase();
|
||||||
|
|
||||||
// If the result is still the uppercased code, it means we couldn't find it in our map.
|
// If the result is still the uppercased code, it means we couldn't find it in our map.
|
||||||
if (languageName === code.toUpperCase()) {
|
if (languageName === code.toUpperCase()) {
|
||||||
return `Unknown (${code})`;
|
return `Unknown (${code})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return languageName;
|
return languageName;
|
||||||
|
|
@ -103,8 +103,8 @@ export const getTrackDisplayName = (track: { name?: string, id: number, language
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the track name contains detailed information (like codec, bitrate, etc.), use it as-is
|
// If the track name contains detailed information (like codec, bitrate, etc.), use it as-is
|
||||||
if (track.name && (track.name.includes('DDP') || track.name.includes('DTS') || track.name.includes('AAC') ||
|
if (track.name && (track.name.includes('DDP') || track.name.includes('DTS') || track.name.includes('AAC') ||
|
||||||
track.name.includes('Kbps') || track.name.includes('Atmos') || track.name.includes('~'))) {
|
track.name.includes('Kbps') || track.name.includes('Atmos') || track.name.includes('~'))) {
|
||||||
return track.name;
|
return track.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,7 +159,7 @@ export const formatTime = (seconds: number) => {
|
||||||
const hours = Math.floor(seconds / 3600);
|
const hours = Math.floor(seconds / 3600);
|
||||||
const mins = Math.floor((seconds % 3600) / 60);
|
const mins = Math.floor((seconds % 3600) / 60);
|
||||||
const secs = Math.floor(seconds % 60);
|
const secs = Math.floor(seconds % 60);
|
||||||
|
|
||||||
if (hours > 0) {
|
if (hours > 0) {
|
||||||
return `${hours}:${mins < 10 ? '0' : ''}${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
return `${hours}:${mins < 10 ? '0' : ''}${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -189,14 +189,14 @@ export const detectRTL = (text: string): boolean => {
|
||||||
// Arabic Presentation Forms-B: U+FE70–U+FEFF
|
// Arabic Presentation Forms-B: U+FE70–U+FEFF
|
||||||
// Hebrew: U+0590–U+05FF
|
// Hebrew: U+0590–U+05FF
|
||||||
// Persian/Urdu use Arabic script (no separate range)
|
// Persian/Urdu use Arabic script (no separate range)
|
||||||
const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/;
|
const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/g;
|
||||||
|
|
||||||
// Remove whitespace and count characters
|
// Remove whitespace and count characters
|
||||||
const nonWhitespace = text.replace(/\s/g, '');
|
const nonWhitespace = text.replace(/\s/g, '');
|
||||||
if (nonWhitespace.length === 0) return false;
|
if (nonWhitespace.length === 0) return false;
|
||||||
|
|
||||||
const rtlCount = (nonWhitespace.match(rtlRegex) || []).length;
|
const rtlCount = (nonWhitespace.match(rtlRegex) || []).length;
|
||||||
|
|
||||||
// Consider RTL if at least 30% of non-whitespace characters are RTL
|
// Consider RTL if at least 30% of non-whitespace characters are RTL
|
||||||
// This handles mixed-language subtitles (e.g., Arabic with English numbers)
|
// This handles mixed-language subtitles (e.g., Arabic with English numbers)
|
||||||
return rtlCount / nonWhitespace.length >= 0.3;
|
return rtlCount / nonWhitespace.length >= 0.3;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue