From 093125f51378b9bd2593a1fe072b273e5409e538 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 16 Oct 2024 04:06:39 -0700 Subject: [PATCH] add filtering --- src/app/(core)/v/[slug]/player.tsx | 199 +++++++++++++++++++++-------- 1 file changed, 145 insertions(+), 54 deletions(-) diff --git a/src/app/(core)/v/[slug]/player.tsx b/src/app/(core)/v/[slug]/player.tsx index 66a520f..23d13dc 100644 --- a/src/app/(core)/v/[slug]/player.tsx +++ b/src/app/(core)/v/[slug]/player.tsx @@ -11,6 +11,127 @@ import type { VOD } from "~/utils/twitch-server"; import { Player } from "~/utils/types/twitch-player"; dayjs.extend(duration); +function parseOffsetValue(value: string): number | undefined { + // if there are no colons, assume its seconds + if (/^\d+$/.test(value)) return parseInt(value, 10); + + // Supports HH:MM:SS, MM:SS, SS + // If it's not in the format, return undefined + if (!/^([0-5]?[0-9]:){0,2}[0-5][0-9]$/.test(value)) return undefined; + + return value + .split(":") + .reduce((acc, cur) => (acc = acc * 60 + parseInt(cur, 10)), 0); +} + +// Example marker labels +// "Talking about Chrome" - no metadata, just label +// "START: Talking about Chrome" - start marker, same as above +// "END: Talking about Chrome" - end marker, tagged accordingly so it can be filtered out +// "End of Talking about Chrome" - end marker, same as above +// "OFFSET 00:40:31" - offset marker + +const START_LABELS = ["START:", "START OF", "START"]; +const END_LABELS = ["END:", "END OF", "END"]; +const OFFSET_LABELS = ["OFFSET", "OFFSET:"]; +const LABELS = [...START_LABELS, ...END_LABELS, ...OFFSET_LABELS]; + +function parseMetadataFromMarker(marker: string) { + for (const tl of START_LABELS) { + if (marker.toLowerCase().startsWith(tl.toLowerCase())) { + return { + type: "start", + label: marker.slice(tl.length).trim(), + }; + } + } + + for (const tl of END_LABELS) { + if (marker.toLowerCase().startsWith(tl.toLowerCase())) { + return { + type: "end", + label: marker.slice(tl.length).trim(), + }; + } + } + + for (const tl of OFFSET_LABELS) { + if (marker.toLowerCase().startsWith(tl.toLowerCase())) { + return { + type: "offset", + label: marker.slice(tl.length).trim(), + }; + } + } + + return { + type: "start", + label: marker, + }; +} + +function parseMarkers(props: { vod: VOD; offset?: { totalSeconds: number } }) { + const videoDuration = getDurationFromTwitchFormat( + (props.vod as any)?.duration ?? "0h0m0s" + ); + + const mockedMarkers = [ + { position_seconds: 0, id: "start", description: "Intro" }, + ...props.vod.markers, + ]; + + const OFFSET = props.offset?.totalSeconds ?? 0; + + const taggedMarkers = mockedMarkers.map((marker, id) => { + let endTime = + (mockedMarkers[id + 1]?.position_seconds ?? + (videoDuration as duration.Duration)?.asSeconds?.()) - OFFSET; + + endTime += EXPORT_BUFFER; + + if (endTime < 0) endTime = 1; + + const startTime = Math.max( + marker.position_seconds - OFFSET - EXPORT_BUFFER, + 0 + ); + + const duration = dayjs + .duration(endTime * 1000 - startTime * 1000) + .format("HH:mm:ss"); + + const taggedDescription = parseMetadataFromMarker(marker.description); + + return { + startTime, + endTime, + duration, + ...taggedDescription, + }; + }); + + const filteredMarkers = taggedMarkers.filter((m) => m.type === "start"); + + const ytChapters = filteredMarkers.reduce((acc, marker) => { + const startTime = new Date(marker.startTime * 1000); + const timeStr = startTime.toISOString().substr(11, 8); + return `${acc}${marker.label} - ${timeStr}\n`; + }, ""); + + const csv = filteredMarkers + .map((marker) => { + return `${marker.startTime},${marker.endTime},${marker.label}`; + }) + .join("\n"); + + return { + taggedMarkers, + filteredMarkers, + ytChapters, + csv, + }; +} + // Converts "8h32m12s" format into dayjs duration // I absolutely hate this function and would love ANYTHING better const getDurationFromTwitchFormat = (input: string) => { @@ -81,15 +202,6 @@ export const VodPlayer = (props: { id: string; vod: VOD }) => { return cleanup; }, [props.id]); - const videoDuration = props.vod - ? getDurationFromTwitchFormat((props.vod as any)?.duration ?? "0h0m0s") - : "n/a"; - - const mockedMarkers = [ - { position_seconds: 0, id: "start", description: "Intro" }, - ...props.vod.markers, - ]; - const [offset, setOffset] = useState<{ presentational: string; totalSeconds: number; @@ -97,46 +209,18 @@ export const VodPlayer = (props: { id: string; vod: VOD }) => { presentational: "0", totalSeconds: 0, }); - const csv = mockedMarkers.flatMap((marker, id) => { - let endTime = - (mockedMarkers[id + 1]?.position_seconds ?? - (videoDuration as duration.Duration)?.asSeconds?.()) - - offset.totalSeconds; - endTime += EXPORT_BUFFER; + const parsedMarkers = useMemo(() => { + if (!props.vod) return; - if (endTime < 0) endTime = 1; + return parseMarkers({ + vod: props.vod, + offset: offset, + }); + }, [props, offset]); - const startTime = Math.max( - marker.position_seconds - offset.totalSeconds - EXPORT_BUFFER, - 0 - ); - - if (marker.description.toLowerCase().startsWith("end of")) return []; - - return [`${startTime},${endTime},${marker.description.replace(",", "")}`]; - }); - - const ytChapters = mockedMarkers.reduce((acc, marker) => { - const startTime = new Date( - (marker.position_seconds - offset.totalSeconds) * 1000 - ); - const timeStr = startTime.toISOString().substr(11, 8); - return `${acc}${marker.description} - ${timeStr}\n`; - }, ""); - - function parseOffsetValue(value: string): number | undefined { - // if there are no colons, assume its seconds - if (/^\d+$/.test(value)) return parseInt(value, 10); - - // Supports HH:MM:SS, MM:SS, SS - // If it's not in the format, return undefined - if (!/^([0-5]?[0-9]:){0,2}[0-5][0-9]$/.test(value)) return undefined; - - return value - .split(":") - .reduce((acc, cur) => (acc = acc * 60 + parseInt(cur, 10)), 0); - } + if (!parsedMarkers) return null; + const { taggedMarkers, filteredMarkers, ytChapters, csv } = parsedMarkers; return (
@@ -168,9 +252,7 @@ export const VodPlayer = (props: { id: string; vod: VOD }) => { { {props.vod && (
    - {mockedMarkers.map((marker, index) => { + {filteredMarkers.map((marker, index) => { return ( -
  • +