pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

improve subtitle selection

Pas 9b12beaa 61c3b4ac

+126 -17
+2
src/components/Icon.tsx
··· 78 78 SUPPORT = "support", 79 79 TMDB = "tmdb", 80 80 IMDB = "imdb", 81 + EAR = "ear", 81 82 } 82 83 83 84 export interface IconProps { ··· 173 174 support: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-message-circle-question"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15.02 19.52c-2.341 .736 -5 .606 -7.32 -.52l-4.7 1l1.3 -3.9c-2.324 -3.437 -1.426 -7.872 2.1 -10.374c3.526 -2.501 8.59 -2.296 11.845 .48c1.649 1.407 2.575 3.253 2.742 5.152" /><path d="M19 22v.01" /><path d="M19 19a2.003 2.003 0 0 0 .914 -3.782a1.98 1.98 0 0 0 -2.414 .483" /></svg>`, 174 175 tmdb: `<svg width="2em" height="2em" fill="currentColor" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 190.24 81.52"><defs><style>.cls-1{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" y1="40.76" x2="190.24" y2="40.76" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#90cea1"/><stop offset="0.56" stop-color="#3cbec9"/><stop offset="1" stop-color="#00b3e5"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M105.67,36.06h66.9A17.67,17.67,0,0,0,190.24,18.4h0A17.67,17.67,0,0,0,172.57.73h-66.9A17.67,17.67,0,0,0,88,18.4h0A17.67,17.67,0,0,0,105.67,36.06Zm-88,45h76.9A17.67,17.67,0,0,0,112.24,63.4h0A17.67,17.67,0,0,0,94.57,45.73H17.67A17.67,17.67,0,0,0,0,63.4H0A17.67,17.67,0,0,0,17.67,81.06ZM10.41,35.42h7.8V6.92h10.1V0H.31v6.9h10.1Zm28.1,0h7.8V8.25h.1l9,27.15h6l9.3-27.15h.1V35.4h7.8V0H66.76l-8.2,23.1h-.1L50.31,0H38.51ZM152.43,55.67a15.07,15.07,0,0,0-4.52-5.52,18.57,18.57,0,0,0-6.68-3.08,33.54,33.54,0,0,0-8.07-1h-11.7v35.4h12.75a24.58,24.58,0,0,0,7.55-1.15A19.34,19.34,0,0,0,148.11,77a16.27,16.27,0,0,0,4.37-5.5,16.91,16.91,0,0,0,1.63-7.58A18.5,18.5,0,0,0,152.43,55.67ZM145,68.6A8.8,8.8,0,0,1,142.36,72a10.7,10.7,0,0,1-4,1.82,21.57,21.57,0,0,1-5,.55h-4.05v-21h4.6a17,17,0,0,1,4.67.63,11.66,11.66,0,0,1,3.88,1.87A9.14,9.14,0,0,1,145,59a9.87,9.87,0,0,1,1,4.52A11.89,11.89,0,0,1,145,68.6Zm44.63-.13a8,8,0,0,0-1.58-2.62A8.38,8.38,0,0,0,185.63,64a10.31,10.31,0,0,0-3.17-1v-.1a9.22,9.22,0,0,0,4.42-2.82,7.43,7.43,0,0,0,1.68-5,8.42,8.42,0,0,0-1.15-4.65,8.09,8.09,0,0,0-3-2.72,12.56,12.56,0,0,0-4.18-1.3,32.84,32.84,0,0,0-4.62-.33h-13.2v35.4h14.5a22.41,22.41,0,0,0,4.72-.5,13.53,13.53,0,0,0,4.28-1.65,9.42,9.42,0,0,0,3.1-3,8.52,8.52,0,0,0,1.2-4.68A9.39,9.39,0,0,0,189.66,68.47ZM170.21,52.72h5.3a10,10,0,0,1,1.85.18,6.18,6.18,0,0,1,1.7.57,3.39,3.39,0,0,1,1.22,1.13,3.22,3.22,0,0,1,.48,1.82,3.63,3.63,0,0,1-.43,1.8,3.4,3.4,0,0,1-1.12,1.2,4.92,4.92,0,0,1-1.58.65,7.51,7.51,0,0,1-1.77.2h-5.65Zm11.72,20a3.9,3.9,0,0,1-1.22,1.3,4.64,4.64,0,0,1-1.68.7,8.18,8.18,0,0,1-1.82.2h-7v-8h5.9a15.35,15.35,0,0,1,2,.15,8.47,8.47,0,0,1,2.05.55,4,4,0,0,1,1.57,1.18,3.11,3.11,0,0,1,.63,2A3.71,3.71,0,0,1,181.93,72.72Z"/></g></g></svg>`, 175 176 imdb: `<svg width="2em" height="2em" fill="currentColor" viewBox="0 0 32 32" id="Camada_1" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <path d="M8.4,21.1H5.9V9.9h3.8l0.7,4.7h0.1L11,9.9h3.8v11.2h-2.5v-6.7h-0.1l-0.9,6.7H9.4l-1-6.7h0L8.4,21.1L8.4,21.1z"></path> <path d="M15.8,9.8c0.4,0,3.2-0.1,4.7,0.1c1.2,0.1,1.8,1.1,1.9,2.3c0.1,2.2,0.1,4.4,0.1,6.6c0,0.2,0,0.5-0.1,0.8 c-0.2,0.9-0.7,1.4-1.9,1.5c-1.5,0.1-3,0.1-4.4,0.1c0,0-0.1,0-0.2,0V9.8z M18.8,11.9v7.2c0.5,0,0.8-0.2,0.8-0.7c0-1.9,0-3.9,0-5.9 C19.6,12,19.4,11.8,18.8,11.9z"></path> <path d="M2,21.1V9.9h2.9v11.2H2z"></path> <path d="M29.9,14.1c-0.1-0.8-0.6-1.2-1.4-1.4c-0.8-0.1-1.6,0-2.3,0.7V9.9h-2.8v11.2H26c0.1-0.2,0.1-0.4,0.2-0.5c0,0,0,0,0.1,0 c0.1,0.1,0.2,0.2,0.3,0.3c0.7,0.5,1.5,0.6,2.3,0.3c0.7-0.3,1-0.9,1-1.6c0-0.8,0.1-1.7,0.1-2.6C30,16,30,15,29.9,14.1L29.9,14.1z M27.1,19.1c0,0.2-0.2,0.4-0.4,0.4s-0.4-0.2-0.4-0.4v-4.3c0-0.2,0.2-0.4,0.4-0.4s0.4,0.2,0.4,0.4V19.1z"></path> </g> </g></svg>`, 177 + ear: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-ear" viewBox="0 0 16 16"> <path d="M8.5 0A5.5 5.5 0 0 0 3 5.5v7.047a3.453 3.453 0 0 0 6.687 1.212l.51-1.363a4.6 4.6 0 0 1 .67-1.197l2.008-2.581A5.34 5.34 0 0 0 8.66 0zM7 5.5v2.695q.168-.09.332-.192c.327-.208.577-.44.72-.727a.5.5 0 1 1 .895.448c-.256.513-.673.865-1.079 1.123A9 9 0 0 1 7 9.313V11.5a.5.5 0 0 1-1 0v-6a2.5 2.5 0 0 1 5 0V6a.5.5 0 0 1-1 0v-.5a1.5 1.5 0 1 0-3 0"/></svg>`, 176 178 }; 177 179 178 180 function ChromeCastButton() {
+97 -17
src/components/player/atoms/settings/CaptionsView.tsx
··· 1 1 import classNames from "classnames"; 2 - import { type DragEvent, useRef, useState } from "react"; 2 + import { type DragEvent, useEffect, useMemo, useRef, useState } from "react"; 3 3 import { useTranslation } from "react-i18next"; 4 4 import { convert } from "subsrt-ts"; 5 5 ··· 23 23 onClick?: () => void; 24 24 error?: React.ReactNode; 25 25 flag?: boolean; 26 + subtitleUrl?: string; 27 + subtitleType?: string; 28 + // subtitle details from wyzie 29 + subtitleSource?: string; 30 + subtitleEncoding?: string; 31 + isHearingImpaired?: boolean; 26 32 }) { 33 + const [showTooltip, setShowTooltip] = useState(false); 34 + const tooltipTimeoutRef = useRef<NodeJS.Timeout | null>(null); 35 + 36 + const tooltipContent = useMemo(() => { 37 + if (!props.subtitleUrl && !props.subtitleSource) return null; 38 + 39 + const parts = []; 40 + 41 + if (props.subtitleSource) { 42 + parts.push(`Source: ${props.subtitleSource}`); 43 + } 44 + 45 + if (props.subtitleEncoding) { 46 + parts.push(`Encoding: ${props.subtitleEncoding}`); 47 + } 48 + 49 + if (props.isHearingImpaired) { 50 + parts.push(`Hearing Impaired: Yes`); 51 + } 52 + 53 + if (props.subtitleUrl) { 54 + parts.push(`URL: ${props.subtitleUrl}`); 55 + } 56 + 57 + return parts.join("\n"); 58 + }, [ 59 + props.subtitleUrl, 60 + props.subtitleSource, 61 + props.subtitleEncoding, 62 + props.isHearingImpaired, 63 + ]); 64 + 65 + const handleMouseEnter = () => { 66 + if (tooltipTimeoutRef.current) { 67 + clearTimeout(tooltipTimeoutRef.current); 68 + } 69 + tooltipTimeoutRef.current = setTimeout(() => setShowTooltip(true), 500); 70 + }; 71 + 72 + const handleMouseLeave = () => { 73 + if (tooltipTimeoutRef.current) { 74 + clearTimeout(tooltipTimeoutRef.current); 75 + } 76 + setShowTooltip(false); 77 + }; 78 + 79 + // Cleanup timeout on unmount 80 + useEffect(() => { 81 + return () => { 82 + if (tooltipTimeoutRef.current) { 83 + clearTimeout(tooltipTimeoutRef.current); 84 + } 85 + }; 86 + }, []); 87 + 27 88 return ( 28 - <SelectableLink 29 - selected={props.selected} 30 - loading={props.loading} 31 - error={props.error} 32 - onClick={props.onClick} 89 + <div 90 + className="relative" 91 + onMouseEnter={handleMouseEnter} 92 + onMouseLeave={handleMouseLeave} 33 93 > 34 - <span 35 - data-active-link={props.selected ? true : undefined} 36 - className="flex items-center" 94 + <SelectableLink 95 + selected={props.selected} 96 + loading={props.loading} 97 + error={props.error} 98 + onClick={props.onClick} 37 99 > 38 - {props.flag ? ( 39 - <span data-code={props.countryCode} className="mr-3 inline-flex"> 40 - <FlagIcon langCode={props.countryCode} /> 41 - </span> 42 - ) : null} 43 - <span>{props.children}</span> 44 - </span> 45 - </SelectableLink> 100 + <span 101 + data-active-link={props.selected ? true : undefined} 102 + className="flex items-center" 103 + > 104 + {props.flag ? ( 105 + <span data-code={props.countryCode} className="mr-3 inline-flex"> 106 + <FlagIcon langCode={props.countryCode} /> 107 + </span> 108 + ) : null} 109 + <span>{props.children}</span> 110 + {props.subtitleType && ( 111 + <span className="ml-2 px-2 py-0.5 rounded bg-video-context-hoverColor bg-opacity-80 text-video-context-type-main text-xs font-semibold"> 112 + {props.subtitleType.toUpperCase()} 113 + </span> 114 + )} 115 + {props.isHearingImpaired && ( 116 + <Icon icon={Icons.EAR} className="ml-2" /> 117 + )} 118 + </span> 119 + </SelectableLink> 120 + {tooltipContent && showTooltip && ( 121 + <div className="absolute z-50 left-1/2 -translate-x-1/2 bottom-full mb-2 px-3 py-2 bg-black/80 text-white/80 text-xs rounded-lg backdrop-blur-sm w-60 break-all whitespace-pre-line"> 122 + {tooltipContent} 123 + </div> 124 + )} 125 + </div> 46 126 ); 47 127 } 48 128
+6
src/components/player/atoms/settings/OpensubtitlesCaptionsView.tsx
··· 64 64 } 65 65 onClick={() => startDownload(v.id)} 66 66 flag 67 + subtitleUrl={v.url} 68 + subtitleType={v.type} 69 + // subtitle details from wyzie 70 + subtitleSource={v.source} 71 + subtitleEncoding={v.encoding} 72 + isHearingImpaired={v.isHearingImpaired} 67 73 > 68 74 {v.languageName} 69 75 </CaptionOption>
+6
src/components/player/atoms/settings/SourceCaptionsView.tsx
··· 96 96 } 97 97 onClick={() => startDownload(v.id)} 98 98 flag 99 + subtitleUrl={v.url} 100 + subtitleType={v.type} 101 + // subtitle details from wyzie 102 + subtitleSource={v.source} 103 + subtitleEncoding={v.encoding} 104 + isHearingImpaired={v.isHearingImpaired} 99 105 > 100 106 {v.languageName} 101 107 </CaptionOption>
+1
src/components/player/display/base.ts
··· 532 532 id: track.id.toString(), 533 533 language: track.lang ?? "unknown", 534 534 url: track.url, 535 + type: "vtt", // HLS captions are typically VTT format 535 536 needsProxy: false, 536 537 hls: true, 537 538 };
+7
src/components/player/utils/captions.ts
··· 101 101 id: v.id, 102 102 language: v.language, 103 103 url: v.url, 104 + type: (v as any).type, 104 105 needsProxy: v.hasCorsRestrictions, 105 106 opensubtitles: v.opensubtitles, 107 + // subtitle details from wyzie 108 + display: (v as any).display, 109 + media: (v as any).media, 110 + isHearingImpaired: (v as any).isHearingImpaired, 111 + source: (v as any).source, 112 + encoding: (v as any).encoding, 106 113 })); 107 114 }
+7
src/stores/player/slices/source.ts
··· 53 53 id: string; 54 54 language: string; 55 55 url: string; 56 + type?: string; 56 57 needsProxy: boolean; 57 58 hls?: boolean; 58 59 opensubtitles?: boolean; 60 + // subtitle details from wyzie 61 + display?: string; 62 + media?: string; 63 + isHearingImpaired?: boolean; 64 + source?: string; 65 + encoding?: string; 59 66 } 60 67 61 68 export interface AudioTrack {