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.

update skip times

Pas 372cf3ff e95222bb

+103 -112
+102 -110
src/components/player/hooks/useSkipTime.ts
··· 9 9 10 10 // Thanks Nemo for this API 11 11 const THE_INTRO_DB_BASE_URL = "https://api.theintrodb.org/v1"; 12 - // const QUICKWATCH_BASE_URL = "https://skips.quickwatch.co"; 13 12 const FED_SKIPS_BASE_URL = "https://fed-skips.pstream.mov"; 14 - // const VELORA_BASE_URL = "https://veloratv.ru/api/intro-end/confirmed"; 15 13 const INTRODB_BASE_URL = "https://api.introdb.app/intro"; 16 14 const MAX_RETRIES = 3; 17 15 18 16 // Track the source of the current skip time (for analytics filtering) 19 - let currentSkipTimeSource: 20 - | "fed-skips" 21 - | "introdb" 22 - | "theintrodb" 23 - // | "quickwatch" 24 - | null = null; 17 + let currentSkipTimeSource: "fed-skips" | "introdb" | "theintrodb" | null = null; 25 18 26 19 export function useSkipTimeSource(): typeof currentSkipTimeSource { 27 20 return currentSkipTimeSource; ··· 41 34 const febboxKey = usePreferencesStore((s) => s.febboxKey); 42 35 43 36 useEffect(() => { 37 + // Validate segment data according to rules 38 + // eslint-disable-next-line camelcase 39 + const validateSegment = ( 40 + type: "intro" | "recap" | "credits", 41 + // eslint-disable-next-line camelcase 42 + start_ms: number | null, 43 + // eslint-disable-next-line camelcase 44 + end_ms: number | null, 45 + ): boolean => { 46 + // eslint-disable-next-line camelcase 47 + const start = start_ms ?? 0; 48 + // eslint-disable-next-line camelcase 49 + const end = end_ms; 50 + 51 + if (type === "intro") { 52 + // Intro: end_ms is required, duration must be 0 or 5-200 seconds 53 + if (end === null) return false; 54 + const duration = (end - start) / 1000; 55 + if (duration === 0) return true; // No intro is valid 56 + return duration >= 5 && duration <= 200; 57 + } 58 + 59 + if (type === "recap") { 60 + // Recap: end_ms is required, duration must be 0 or 5-1200 seconds 61 + if (end === null) return false; 62 + const duration = (end - start) / 1000; 63 + if (duration === 0) return true; // No recap is valid 64 + return duration >= 5 && duration <= 1200; 65 + } 66 + 67 + if (type === "credits") { 68 + // Credits: start_ms is required 69 + // If end_ms is provided, duration must be at least 5 seconds 70 + // If end_ms is null, credits extend to end of video (valid) 71 + // eslint-disable-next-line camelcase 72 + if (start_ms === null) return false; 73 + if (end === null) return true; // Credits to end of video is valid 74 + const duration = (end - start) / 1000; 75 + return duration >= 5; 76 + } 77 + 78 + return false; 79 + }; 80 + 44 81 const fetchTheIntroDBSegments = async (): Promise<SegmentData[]> => { 45 82 if (!meta?.tmdbId) return []; 46 83 ··· 58 95 59 96 const fetchedSegments: SegmentData[] = []; 60 97 61 - // Add intro segment if it has data 62 - if (data?.intro && data.intro.submission_count > 0) { 98 + // Add intro segment if it has valid data 99 + if ( 100 + data?.intro && 101 + data.intro.submission_count > 0 && 102 + validateSegment("intro", data.intro.start_ms, data.intro.end_ms) 103 + ) { 63 104 fetchedSegments.push({ 64 105 type: "intro", 65 106 start_ms: data.intro.start_ms, ··· 69 110 }); 70 111 } 71 112 72 - // Add recap segment if it has data 73 - if (data?.recap && data.recap.submission_count > 0) { 113 + // Add recap segment if it has valid data 114 + if ( 115 + data?.recap && 116 + data.recap.submission_count > 0 && 117 + validateSegment("recap", data.recap.start_ms, data.recap.end_ms) 118 + ) { 74 119 fetchedSegments.push({ 75 120 type: "recap", 76 121 start_ms: data.recap.start_ms, ··· 80 125 }); 81 126 } 82 127 83 - // Add credits segment if it has data 84 - if (data?.credits && data.credits.submission_count > 0) { 128 + // Add credits segment if it has valid data 129 + if ( 130 + data?.credits && 131 + data.credits.submission_count > 0 && 132 + validateSegment("credits", data.credits.start_ms, data.credits.end_ms) 133 + ) { 85 134 fetchedSegments.push({ 86 135 type: "credits", 87 136 start_ms: data.credits.start_ms, ··· 98 147 } 99 148 }; 100 149 101 - // const fetchVeloraSkipTime = async (): Promise<number | null> => { 102 - // if (!meta?.tmdbId) return null; 103 - 104 - // try { 105 - // let apiUrl = `${VELORA_BASE_URL}?tmdbId=${meta.tmdbId}`; 106 - // if (meta.type !== "movie") { 107 - // apiUrl += `&season=${meta.season?.number}&episode=${meta.episode?.number}`; 108 - // } 109 - // const data = await proxiedFetch(apiUrl); 110 - 111 - // if (data.introSkippable && typeof data.introEnd === "number") { 112 - // return data.introEnd; 113 - // } 114 - 115 - // return null; 116 - // } catch (error) { 117 - // console.error("Error fetching velora skip time:", error); 118 - // return null; 119 - // } 120 - // }; 121 - 122 - // const fetchQuickWatchTime = async (): Promise<number | null> => { 123 - // if (!meta?.title || meta.type === "movie") return null; 124 - // if (!meta.season?.number || !meta.episode?.number) return null; 125 - 126 - // try { 127 - // const encodedName = encodeURIComponent(meta.title); 128 - // const apiUrl = `${QUICKWATCH_BASE_URL}/api/skip-times?name=${encodedName}&season=${meta.season.number}&episode=${meta.episode.number}`; 129 - 130 - // const data = await proxiedFetch(apiUrl); 131 - 132 - // if (!Array.isArray(data) || data.length === 0) return null; 133 - 134 - // // Find the first result with intro or credits data 135 - // for (const item of data) { 136 - // if (item.data) { 137 - // // Check for intro end time 138 - // if ( 139 - // item.data.intro?.end && 140 - // typeof item.data.intro.end === "number" 141 - // ) { 142 - // // Convert milliseconds to seconds 143 - // return Math.floor(item.data.intro.end / 1000); 144 - // } 145 - // // Check for credits start time (use as intro end) 146 - // if ( 147 - // item.data.credits?.start && 148 - // typeof item.data.credits.start === "number" 149 - // ) { 150 - // // Convert milliseconds to seconds 151 - // return Math.floor(item.data.credits.start / 1000); 152 - // } 153 - // } 154 - // } 155 - 156 - // return null; 157 - // } catch (error) { 158 - // console.error("Error fetching QuickWatch time:", error); 159 - // return null; 160 - // } 161 - // }; 162 - 163 150 const fetchFedSkipsTime = async (retries = 0): Promise<number | null> => { 164 151 if (!meta?.imdbId || meta.type === "movie") return null; 165 152 if (!conf().ALLOW_FEBBOX_KEY) return null; ··· 231 218 232 219 // Try TheIntroDB API first (supports both movies and TV shows with full segment data) 233 220 const theIntroDBSegments = await fetchTheIntroDBSegments(); 234 - if (theIntroDBSegments.length > 0) { 221 + const hasIntroSegment = theIntroDBSegments.some( 222 + (s) => s.type === "intro", 223 + ); 224 + const nonIntroSegments = theIntroDBSegments.filter( 225 + (s) => s.type !== "intro", 226 + ); 227 + 228 + // If we have a valid intro from TIDB, use all TIDB segments 229 + if (hasIntroSegment) { 235 230 currentSkipTimeSource = "theintrodb"; 236 231 setSegments(theIntroDBSegments); 237 232 return; 238 233 } 239 234 240 - // QuickWatch API disabled 241 - // const quickWatchTime = await fetchQuickWatchTime(); 242 - // if (quickWatchTime !== null) { 243 - // currentSkipTimeSource = "quickwatch"; 244 - // setSegments([ 245 - // { 246 - // type: "intro", 247 - // start_ms: 0, // Assume starts at beginning 248 - // end_ms: quickWatchTime * 1000, // Convert seconds to milliseconds 249 - // confidence: null, 250 - // submission_count: 1, 251 - // }, 252 - // ]); 253 - // return; 254 - // } 235 + // If TIDB doesn't have a valid intro, try fallbacks to get intro data 236 + // But keep any valid recap/credits segments from TIDB 237 + let fallbackIntroSegment: SegmentData | null = null; 255 238 256 - // Fall back to Fed-skips if TheIntroDB and QuickWatch don't have anything 239 + // Fall back to Fed-skips if TheIntroDB doesn't have intro 257 240 // Note: Fed-skips only supports TV shows, not movies 258 241 if (febboxKey && meta?.type !== "movie") { 259 242 const fedSkipsTime = await fetchFedSkipsTime(); 260 243 if (fedSkipsTime !== null) { 261 244 currentSkipTimeSource = "fed-skips"; 262 - setSegments([ 263 - { 264 - type: "intro", 265 - start_ms: 0, // Assume starts at beginning 266 - end_ms: fedSkipsTime * 1000, // Convert seconds to milliseconds 267 - confidence: null, 268 - submission_count: 1, 269 - }, 270 - ]); 271 - return; 245 + fallbackIntroSegment = { 246 + type: "intro", 247 + start_ms: 0, // Assume starts at beginning 248 + end_ms: fedSkipsTime * 1000, // Convert seconds to milliseconds 249 + confidence: null, 250 + submission_count: 1, 251 + }; 272 252 } 273 253 } 274 254 275 255 // Last resort: Fall back to IntroDB API (TV shows only, available to all users) 276 - const introDBTime = await fetchIntroDBTime(); 277 - if (introDBTime !== null) { 278 - currentSkipTimeSource = "introdb"; 279 - setSegments([ 280 - { 256 + if (!fallbackIntroSegment) { 257 + const introDBTime = await fetchIntroDBTime(); 258 + if (introDBTime !== null) { 259 + currentSkipTimeSource = "introdb"; 260 + fallbackIntroSegment = { 281 261 type: "intro", 282 262 start_ms: 0, // Assume starts at beginning 283 263 end_ms: introDBTime * 1000, // Convert seconds to milliseconds 284 264 confidence: null, 285 265 submission_count: 1, 286 - }, 287 - ]); 266 + }; 267 + } 268 + } 269 + 270 + // Combine fallback intro with any valid TIDB segments (recap/credits) 271 + const finalSegments: SegmentData[] = []; 272 + if (fallbackIntroSegment) { 273 + finalSegments.push(fallbackIntroSegment); 274 + } 275 + // Add any valid recap/credits segments from TIDB 276 + finalSegments.push(...nonIntroSegments); 277 + 278 + if (finalSegments.length > 0) { 279 + setSegments(finalSegments); 288 280 } 289 281 }; 290 282
+1 -2
src/components/player/internals/Backend/SkipTracker.tsx
··· 19 19 startTime: number; 20 20 endTime: number; 21 21 hasBackwardMovement: boolean; 22 - skipTimeSource: "fed-skips" | "introdb" | "theintrodb" | null; // | "quickwatch" 22 + skipTimeSource: "fed-skips" | "introdb" | "theintrodb" | null; 23 23 timer: ReturnType<typeof setTimeout>; 24 24 } 25 25 ··· 78 78 if ( 79 79 pendingSkip.skipTimeSource === "fed-skips" || 80 80 pendingSkip.skipTimeSource === "introdb" 81 - // pendingSkip.skipTimeSource === "quickwatch" 82 81 ) { 83 82 // Send analytics 84 83 sendSkipAnalytics(pendingSkip.skip, adjustedConfidence);