UNPKG

pupcaps

Version:

PupCaps! : A script to add stylish captions to your videos.

85 lines (73 loc) 3.38 kB
import {toMillis} from '../common/timecodes'; export type FilterType = 'indexes' | 'timecodes' | 'words'; export interface Filter { cssClass: string; type: FilterType; args: string[]; } export function normalizeTimecode(timecode: string): string { const [ hh, mm, ss, ms ] = timecode.split(/[^\d]+/); return `${hh}:${mm}:${ss}.${ms}`; } export abstract class AbstractDynamicCssRule { protected constructor(protected readonly targetSelectors: string[], public readonly appliedCssClass: string) { } public isApplied(target: HTMLElement, captionIndex: number, timeMs: number, words: string[]): boolean { let targetClasses = target.getAttribute('class')?.split(' ') || []; for (const targetSelector of this.targetSelectors) { if (targetSelector.startsWith('#')) { const idSelector = targetSelector.slice(1); if (target.getAttribute('id') != idSelector) { return false; } } else if (targetSelector.startsWith('.')) { const classSelector = targetSelector.slice(1); if (!targetClasses.includes(classSelector)) { return false; } } else { throw new Error(`Unsupported target selector: '${targetSelector}'`); } } return true; } } export class IndexesDynamicCssRule extends AbstractDynamicCssRule { constructor(targetSelectors: string[], appliedCssClass: string, private readonly startIndexInclusive: number, private readonly endIndexInclusive?: number) { super(targetSelectors, appliedCssClass); } isApplied(target: HTMLElement, captionIndex: number, timeMs: number, words: string[]): boolean { return super.isApplied(target, captionIndex, timeMs, words) && this.startIndexInclusive <= captionIndex && (this.endIndexInclusive ? this.endIndexInclusive >= captionIndex : true); } } export class TimecodesDynamicCssRule extends AbstractDynamicCssRule { constructor(targetSelectors: string[], appliedCssClass: string, private readonly startTimeMsInclusive: number, private readonly endTimeMsInclusive?: number) { super(targetSelectors, appliedCssClass); } isApplied(target: HTMLElement, captionIndex: number, timeMs: number, words: string[]): boolean { return super.isApplied(target, captionIndex, timeMs, words) && this.startTimeMsInclusive <= timeMs && (this.endTimeMsInclusive ? this.endTimeMsInclusive >= timeMs : true); } } export function createDynamicCssRule(targetSelectors: string[], filter: Filter): AbstractDynamicCssRule { switch (filter.type) { case 'indexes': const [ startIndex, endIndex ] = filter.args.map(arg => Number(arg)); return new IndexesDynamicCssRule(targetSelectors, filter.cssClass, startIndex, endIndex); case 'timecodes': const [ startMs, endMs ] = filter.args.map(normalizeTimecode).map(toMillis); return new TimecodesDynamicCssRule(targetSelectors, filter.cssClass, startMs, endMs); default: throw new Error(`Unknown filter type '${filter.type}'!`); } }