playsvideo 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/adapters/node-ffmpeg.d.ts +15 -0
- package/dist/adapters/node-ffmpeg.d.ts.map +1 -0
- package/dist/adapters/node-ffmpeg.js +35 -0
- package/dist/adapters/node-ffmpeg.js.map +1 -0
- package/dist/adapters/node-ffprobe.d.ts +12 -0
- package/dist/adapters/node-ffprobe.d.ts.map +1 -0
- package/dist/adapters/node-ffprobe.js +55 -0
- package/dist/adapters/node-ffprobe.js.map +1 -0
- package/dist/engine.d.ts +51 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +386 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline/adts-parse.d.ts +3 -0
- package/dist/pipeline/adts-parse.d.ts.map +1 -0
- package/dist/pipeline/adts-parse.js +36 -0
- package/dist/pipeline/adts-parse.js.map +1 -0
- package/dist/pipeline/audio-transcode.d.ts +42 -0
- package/dist/pipeline/audio-transcode.d.ts.map +1 -0
- package/dist/pipeline/audio-transcode.js +147 -0
- package/dist/pipeline/audio-transcode.js.map +1 -0
- package/dist/pipeline/codec-probe.d.ts +22 -0
- package/dist/pipeline/codec-probe.d.ts.map +1 -0
- package/dist/pipeline/codec-probe.js +70 -0
- package/dist/pipeline/codec-probe.js.map +1 -0
- package/dist/pipeline/demux.d.ts +23 -0
- package/dist/pipeline/demux.d.ts.map +1 -0
- package/dist/pipeline/demux.js +97 -0
- package/dist/pipeline/demux.js.map +1 -0
- package/dist/pipeline/mux.d.ts +15 -0
- package/dist/pipeline/mux.d.ts.map +1 -0
- package/dist/pipeline/mux.js +60 -0
- package/dist/pipeline/mux.js.map +1 -0
- package/dist/pipeline/pipeline.d.ts +22 -0
- package/dist/pipeline/pipeline.d.ts.map +1 -0
- package/dist/pipeline/pipeline.js +112 -0
- package/dist/pipeline/pipeline.js.map +1 -0
- package/dist/pipeline/playlist.d.ts +5 -0
- package/dist/pipeline/playlist.d.ts.map +1 -0
- package/dist/pipeline/playlist.js +72 -0
- package/dist/pipeline/playlist.js.map +1 -0
- package/dist/pipeline/segment-plan.d.ts +10 -0
- package/dist/pipeline/segment-plan.d.ts.map +1 -0
- package/dist/pipeline/segment-plan.js +55 -0
- package/dist/pipeline/segment-plan.js.map +1 -0
- package/dist/pipeline/subtitle.d.ts +17 -0
- package/dist/pipeline/subtitle.d.ts.map +1 -0
- package/dist/pipeline/subtitle.js +184 -0
- package/dist/pipeline/subtitle.js.map +1 -0
- package/dist/pipeline/types.d.ts +98 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +2 -0
- package/dist/pipeline/types.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export function generateVodPlaylist(spec) {
|
|
2
|
+
return generatePlaylist({ ...spec, endList: true });
|
|
3
|
+
}
|
|
4
|
+
export function generateEventPlaylist(spec) {
|
|
5
|
+
return generatePlaylist({ ...spec, endList: false });
|
|
6
|
+
}
|
|
7
|
+
function generatePlaylist(spec) {
|
|
8
|
+
const lines = [
|
|
9
|
+
'#EXTM3U',
|
|
10
|
+
'#EXT-X-VERSION:7',
|
|
11
|
+
`#EXT-X-TARGETDURATION:${spec.targetDuration}`,
|
|
12
|
+
`#EXT-X-MEDIA-SEQUENCE:${spec.mediaSequence}`,
|
|
13
|
+
`#EXT-X-PLAYLIST-TYPE:${spec.endList ? 'VOD' : 'EVENT'}`,
|
|
14
|
+
];
|
|
15
|
+
if (spec.mapUri) {
|
|
16
|
+
lines.push(`#EXT-X-MAP:URI="${spec.mapUri}"`);
|
|
17
|
+
}
|
|
18
|
+
for (const entry of spec.entries) {
|
|
19
|
+
if (entry.discontinuity) {
|
|
20
|
+
lines.push('#EXT-X-DISCONTINUITY');
|
|
21
|
+
}
|
|
22
|
+
lines.push(`#EXTINF:${entry.durationSec.toFixed(6)},`);
|
|
23
|
+
lines.push(entry.uri);
|
|
24
|
+
}
|
|
25
|
+
if (spec.endList) {
|
|
26
|
+
lines.push('#EXT-X-ENDLIST');
|
|
27
|
+
}
|
|
28
|
+
return `${lines.join('\n')}\n`;
|
|
29
|
+
}
|
|
30
|
+
export function parsePlaylist(m3u8) {
|
|
31
|
+
const lines = m3u8.split('\n').map((l) => l.trim());
|
|
32
|
+
let targetDuration = 0;
|
|
33
|
+
let mediaSequence = 0;
|
|
34
|
+
let endList = false;
|
|
35
|
+
let mapUri;
|
|
36
|
+
const entries = [];
|
|
37
|
+
let nextDisc = false;
|
|
38
|
+
let nextDuration = null;
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
if (line.startsWith('#EXT-X-TARGETDURATION:')) {
|
|
41
|
+
targetDuration = parseInt(line.split(':')[1], 10);
|
|
42
|
+
}
|
|
43
|
+
else if (line.startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
|
44
|
+
mediaSequence = parseInt(line.split(':')[1], 10);
|
|
45
|
+
}
|
|
46
|
+
else if (line === '#EXT-X-ENDLIST') {
|
|
47
|
+
endList = true;
|
|
48
|
+
}
|
|
49
|
+
else if (line.startsWith('#EXT-X-MAP:')) {
|
|
50
|
+
const match = line.match(/URI="([^"]+)"/);
|
|
51
|
+
if (match)
|
|
52
|
+
mapUri = match[1];
|
|
53
|
+
}
|
|
54
|
+
else if (line === '#EXT-X-DISCONTINUITY') {
|
|
55
|
+
nextDisc = true;
|
|
56
|
+
}
|
|
57
|
+
else if (line.startsWith('#EXTINF:')) {
|
|
58
|
+
nextDuration = parseFloat(line.slice(8));
|
|
59
|
+
}
|
|
60
|
+
else if (line && !line.startsWith('#') && nextDuration !== null) {
|
|
61
|
+
entries.push({
|
|
62
|
+
uri: line,
|
|
63
|
+
durationSec: nextDuration,
|
|
64
|
+
...(nextDisc ? { discontinuity: true } : {}),
|
|
65
|
+
});
|
|
66
|
+
nextDuration = null;
|
|
67
|
+
nextDisc = false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { targetDuration, mediaSequence, entries, endList, mapUri };
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=playlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playlist.js","sourceRoot":"","sources":["../../src/pipeline/playlist.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PlannedSegment } from './types.js';
|
|
2
|
+
export declare function normalizeKeyframeTimestamps(timestampsSec: number[], durationSec: number): number[];
|
|
3
|
+
export interface BuildSegmentPlanOptions {
|
|
4
|
+
keyframeTimestampsSec: number[];
|
|
5
|
+
durationSec: number;
|
|
6
|
+
targetSegmentDurationSec?: number;
|
|
7
|
+
sequenceStart?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function buildSegmentPlan(options: BuildSegmentPlanOptions): PlannedSegment[];
|
|
10
|
+
//# sourceMappingURL=segment-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment-plan.d.ts","sourceRoot":"","sources":["../../src/pipeline/segment-plan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIjD,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,MAAM,EAAE,EACvB,WAAW,EAAE,MAAM,GAClB,MAAM,EAAE,CAkCV;AAED,MAAM,WAAW,uBAAuB;IACtC,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,cAAc,EAAE,CAyBnF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const EPSILON_SEC = 1 / 1000;
|
|
2
|
+
export function normalizeKeyframeTimestamps(timestampsSec, durationSec) {
|
|
3
|
+
const duration = Number(durationSec);
|
|
4
|
+
if (!Number.isFinite(duration) || duration <= 0) {
|
|
5
|
+
throw new Error(`durationSec must be > 0 (received ${String(durationSec)})`);
|
|
6
|
+
}
|
|
7
|
+
const normalized = [...timestampsSec]
|
|
8
|
+
.map(Number)
|
|
9
|
+
.filter((v) => Number.isFinite(v) && v >= 0 && v <= duration + EPSILON_SEC)
|
|
10
|
+
.map((v) => Math.max(0, Math.min(duration, v)))
|
|
11
|
+
.sort((a, b) => a - b);
|
|
12
|
+
if (!normalized.length || normalized[0] > EPSILON_SEC) {
|
|
13
|
+
normalized.unshift(0);
|
|
14
|
+
}
|
|
15
|
+
const deduped = [];
|
|
16
|
+
for (const value of normalized) {
|
|
17
|
+
if (!deduped.length || Math.abs(value - deduped[deduped.length - 1]) > EPSILON_SEC) {
|
|
18
|
+
deduped.push(value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (duration - deduped[deduped.length - 1] > EPSILON_SEC) {
|
|
22
|
+
deduped.push(duration);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
deduped[deduped.length - 1] = duration;
|
|
26
|
+
}
|
|
27
|
+
if (deduped.length < 2) {
|
|
28
|
+
throw new Error('Not enough boundaries for segmentation.');
|
|
29
|
+
}
|
|
30
|
+
return deduped;
|
|
31
|
+
}
|
|
32
|
+
export function buildSegmentPlan(options) {
|
|
33
|
+
const durationSec = Number(options.durationSec);
|
|
34
|
+
const sequenceStart = Math.max(0, Math.floor(Number(options.sequenceStart) || 0));
|
|
35
|
+
const boundaries = normalizeKeyframeTimestamps(options.keyframeTimestampsSec, durationSec);
|
|
36
|
+
const plan = [];
|
|
37
|
+
let sequence = sequenceStart;
|
|
38
|
+
// Cut at every keyframe boundary — matches ffmpeg HLS behavior.
|
|
39
|
+
// ffmpeg cuts at each keyframe regardless of hls_time; the target duration
|
|
40
|
+
// only affects the M3U8 EXT-X-TARGETDURATION header, not segment boundaries.
|
|
41
|
+
for (let i = 0; i < boundaries.length - 1; i++) {
|
|
42
|
+
const startSec = boundaries[i];
|
|
43
|
+
const endSec = boundaries[i + 1];
|
|
44
|
+
const duration = Math.max(EPSILON_SEC, endSec - startSec);
|
|
45
|
+
plan.push({
|
|
46
|
+
sequence,
|
|
47
|
+
uri: `seg-${sequence}.m4s`,
|
|
48
|
+
startSec,
|
|
49
|
+
durationSec: duration,
|
|
50
|
+
});
|
|
51
|
+
sequence += 1;
|
|
52
|
+
}
|
|
53
|
+
return plan;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=segment-plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment-plan.js","sourceRoot":"","sources":["../../src/pipeline/segment-plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Input } from 'mediabunny';
|
|
2
|
+
import type { SubtitleData, SubtitleTrackInfo } from './types.js';
|
|
3
|
+
/** Discover subtitle tracks from a demuxed input. Cheap — reads only metadata, no cue extraction. */
|
|
4
|
+
export declare function getSubtitleTrackInfos(input: Input): Promise<SubtitleTrackInfo[]>;
|
|
5
|
+
/** Extract all cues from a subtitle track and return cleaned SubtitleData. */
|
|
6
|
+
export declare function extractSubtitleData(input: Input, trackIndex: number): Promise<SubtitleData>;
|
|
7
|
+
/**
|
|
8
|
+
* Convert SubtitleData to a WebVTT string suitable for a Blob URL.
|
|
9
|
+
* Works for any source codec — ASS override tags are stripped to plain text.
|
|
10
|
+
*/
|
|
11
|
+
export declare function subtitleDataToWebVTT(data: SubtitleData): string;
|
|
12
|
+
/**
|
|
13
|
+
* Parse a user-imported subtitle file into SubtitleData.
|
|
14
|
+
* Supports .srt, .vtt, .ass/.ssa files.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseSubtitleFile(text: string, filename: string): SubtitleData;
|
|
17
|
+
//# sourceMappingURL=subtitle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subtitle.d.ts","sourceRoot":"","sources":["../../src/pipeline/subtitle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAoB,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpF,qGAAqG;AACrG,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAgBtF;AAED,8EAA8E;AAC9E,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAwBjG;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAS/D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAgB9E"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { formatCuesToWebVTT } from 'mediabunny';
|
|
2
|
+
/** Discover subtitle tracks from a demuxed input. Cheap — reads only metadata, no cue extraction. */
|
|
3
|
+
export async function getSubtitleTrackInfos(input) {
|
|
4
|
+
const tracks = await input.getSubtitleTracks();
|
|
5
|
+
return tracks.map((track, i) => {
|
|
6
|
+
const d = track.disposition;
|
|
7
|
+
return {
|
|
8
|
+
index: i,
|
|
9
|
+
codec: track.codec ?? 'unknown',
|
|
10
|
+
language: track.languageCode,
|
|
11
|
+
name: track.name,
|
|
12
|
+
disposition: {
|
|
13
|
+
default: d.default,
|
|
14
|
+
forced: d.forced,
|
|
15
|
+
hearingImpaired: d.hearingImpaired,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/** Extract all cues from a subtitle track and return cleaned SubtitleData. */
|
|
21
|
+
export async function extractSubtitleData(input, trackIndex) {
|
|
22
|
+
const tracks = await input.getSubtitleTracks();
|
|
23
|
+
const track = tracks[trackIndex];
|
|
24
|
+
if (!track) {
|
|
25
|
+
throw new Error(`Subtitle track index ${trackIndex} not found`);
|
|
26
|
+
}
|
|
27
|
+
const codec = track.codec ?? 'unknown';
|
|
28
|
+
const rawCues = [];
|
|
29
|
+
for await (const cue of track.getCues()) {
|
|
30
|
+
rawCues.push(cue);
|
|
31
|
+
}
|
|
32
|
+
const cues = cleanCues(rawCues, codec);
|
|
33
|
+
// For ASS/SSA, try to get the header from exportToText
|
|
34
|
+
let header;
|
|
35
|
+
if (codec === 'ass' || codec === 'ssa') {
|
|
36
|
+
const exported = await track.exportToText();
|
|
37
|
+
header = extractAssHeader(exported);
|
|
38
|
+
}
|
|
39
|
+
return { cues, codec, header };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Convert SubtitleData to a WebVTT string suitable for a Blob URL.
|
|
43
|
+
* Works for any source codec — ASS override tags are stripped to plain text.
|
|
44
|
+
*/
|
|
45
|
+
export function subtitleDataToWebVTT(data) {
|
|
46
|
+
// If we have clean cues, use mediabunny's formatter
|
|
47
|
+
const mbCues = data.cues.map((c) => ({
|
|
48
|
+
timestamp: c.startSec,
|
|
49
|
+
duration: c.endSec - c.startSec,
|
|
50
|
+
text: stripAssTags(c.text),
|
|
51
|
+
settings: c.settings,
|
|
52
|
+
}));
|
|
53
|
+
return formatCuesToWebVTT(mbCues);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Parse a user-imported subtitle file into SubtitleData.
|
|
57
|
+
* Supports .srt, .vtt, .ass/.ssa files.
|
|
58
|
+
*/
|
|
59
|
+
export function parseSubtitleFile(text, filename) {
|
|
60
|
+
const ext = filename.split('.').pop()?.toLowerCase() ?? '';
|
|
61
|
+
if (ext === 'vtt') {
|
|
62
|
+
return parseWebVTT(text);
|
|
63
|
+
}
|
|
64
|
+
if (ext === 'srt') {
|
|
65
|
+
return parseSRT(text);
|
|
66
|
+
}
|
|
67
|
+
if (ext === 'ass' || ext === 'ssa') {
|
|
68
|
+
return { cues: [], codec: ext, header: text };
|
|
69
|
+
// For ASS, the full file IS the data — keep it opaque for JASSUB
|
|
70
|
+
// Could also parse into cues for WebVTT fallback
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Unsupported subtitle format: .${ext}`);
|
|
73
|
+
}
|
|
74
|
+
// --- Internal helpers ---
|
|
75
|
+
/** Strip tx3g 2-byte length prefix, filter empty gap cues. */
|
|
76
|
+
function cleanCues(raw, codec) {
|
|
77
|
+
const cleaned = [];
|
|
78
|
+
for (const cue of raw) {
|
|
79
|
+
let text = cue.text;
|
|
80
|
+
// tx3g samples have a 2-byte big-endian length prefix
|
|
81
|
+
if (codec === 'tx3g' && text.length >= 2) {
|
|
82
|
+
text = text.slice(2);
|
|
83
|
+
}
|
|
84
|
+
text = text.trim();
|
|
85
|
+
if (!text || cue.duration <= 0)
|
|
86
|
+
continue;
|
|
87
|
+
cleaned.push({
|
|
88
|
+
startSec: cue.timestamp,
|
|
89
|
+
endSec: cue.timestamp + cue.duration,
|
|
90
|
+
text,
|
|
91
|
+
settings: cue.settings,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return cleaned;
|
|
95
|
+
}
|
|
96
|
+
/** Strip ASS/SSA override tags like {\b1}, {\pos(x,y)}, {\an8} → plain text. */
|
|
97
|
+
function stripAssTags(text) {
|
|
98
|
+
return text
|
|
99
|
+
.replace(/\{\\[^}]*\}/g, '')
|
|
100
|
+
.replace(/\\N/g, '\n')
|
|
101
|
+
.replace(/\\n/g, '\n');
|
|
102
|
+
}
|
|
103
|
+
/** Extract the ASS header (everything before the first Dialogue: line). */
|
|
104
|
+
function extractAssHeader(fullText) {
|
|
105
|
+
const idx = fullText.indexOf('Dialogue:');
|
|
106
|
+
if (idx === -1)
|
|
107
|
+
return fullText;
|
|
108
|
+
return fullText.slice(0, idx).trimEnd();
|
|
109
|
+
}
|
|
110
|
+
function parseWebVTT(text) {
|
|
111
|
+
const cues = [];
|
|
112
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
113
|
+
const timeRegex = /([\d:.]+)\s+-->\s+([\d:.]+)(.*)/;
|
|
114
|
+
for (let i = 0; i < lines.length; i++) {
|
|
115
|
+
const match = timeRegex.exec(lines[i]);
|
|
116
|
+
if (!match)
|
|
117
|
+
continue;
|
|
118
|
+
const startSec = parseVTTTimestamp(match[1]);
|
|
119
|
+
const endSec = parseVTTTimestamp(match[2]);
|
|
120
|
+
const settings = match[3]?.trim() || undefined;
|
|
121
|
+
const textLines = [];
|
|
122
|
+
for (let j = i + 1; j < lines.length && lines[j].trim(); j++) {
|
|
123
|
+
textLines.push(lines[j]);
|
|
124
|
+
i = j;
|
|
125
|
+
}
|
|
126
|
+
if (textLines.length > 0) {
|
|
127
|
+
cues.push({ startSec, endSec, text: textLines.join('\n'), settings });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Extract preamble as header
|
|
131
|
+
const firstArrow = text.indexOf('-->');
|
|
132
|
+
let header;
|
|
133
|
+
if (firstArrow !== -1) {
|
|
134
|
+
const beforeFirstCue = text.slice(0, text.lastIndexOf('\n', text.lastIndexOf('\n', firstArrow) - 1));
|
|
135
|
+
if (beforeFirstCue.includes('WEBVTT')) {
|
|
136
|
+
header = beforeFirstCue.trim();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { cues, codec: 'webvtt', header };
|
|
140
|
+
}
|
|
141
|
+
function parseSRT(text) {
|
|
142
|
+
const cues = [];
|
|
143
|
+
const blocks = text.replace(/\r\n/g, '\n').split(/\n\n+/);
|
|
144
|
+
const timeRegex = /([\d:,]+)\s+-->\s+([\d:,]+)/;
|
|
145
|
+
for (const block of blocks) {
|
|
146
|
+
const lines = block.trim().split('\n');
|
|
147
|
+
if (lines.length < 2)
|
|
148
|
+
continue;
|
|
149
|
+
// Find the timing line (skip sequence number)
|
|
150
|
+
let timingIdx = 0;
|
|
151
|
+
for (let i = 0; i < lines.length; i++) {
|
|
152
|
+
if (timeRegex.test(lines[i])) {
|
|
153
|
+
timingIdx = i;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const match = timeRegex.exec(lines[timingIdx]);
|
|
158
|
+
if (!match)
|
|
159
|
+
continue;
|
|
160
|
+
const startSec = parseSRTTimestamp(match[1]);
|
|
161
|
+
const endSec = parseSRTTimestamp(match[2]);
|
|
162
|
+
const cueText = lines
|
|
163
|
+
.slice(timingIdx + 1)
|
|
164
|
+
.join('\n')
|
|
165
|
+
.trim();
|
|
166
|
+
if (cueText) {
|
|
167
|
+
cues.push({ startSec, endSec, text: cueText });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { cues, codec: 'srt' };
|
|
171
|
+
}
|
|
172
|
+
function parseVTTTimestamp(ts) {
|
|
173
|
+
const parts = ts.split(':');
|
|
174
|
+
if (parts.length === 3) {
|
|
175
|
+
return Number(parts[0]) * 3600 + Number(parts[1]) * 60 + Number(parts[2]);
|
|
176
|
+
}
|
|
177
|
+
return Number(parts[0]) * 60 + Number(parts[1]);
|
|
178
|
+
}
|
|
179
|
+
function parseSRTTimestamp(ts) {
|
|
180
|
+
const [time, ms] = ts.split(',');
|
|
181
|
+
const parts = time.split(':');
|
|
182
|
+
return Number(parts[0]) * 3600 + Number(parts[1]) * 60 + Number(parts[2]) + Number(ms) / 1000;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=subtitle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subtitle.js","sourceRoot":"","sources":["../../src/pipeline/subtitle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export interface KeyframeEntry {
|
|
2
|
+
timestamp: number;
|
|
3
|
+
sequenceNumber: number;
|
|
4
|
+
}
|
|
5
|
+
export interface KeyframeIndex {
|
|
6
|
+
duration: number;
|
|
7
|
+
keyframes: KeyframeEntry[];
|
|
8
|
+
}
|
|
9
|
+
export interface PlannedSegment {
|
|
10
|
+
sequence: number;
|
|
11
|
+
uri: string;
|
|
12
|
+
startSec: number;
|
|
13
|
+
durationSec: number;
|
|
14
|
+
}
|
|
15
|
+
export interface PlaylistEntry {
|
|
16
|
+
uri: string;
|
|
17
|
+
durationSec: number;
|
|
18
|
+
discontinuity?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface PlaylistSpec {
|
|
21
|
+
targetDuration: number;
|
|
22
|
+
mediaSequence: number;
|
|
23
|
+
entries: PlaylistEntry[];
|
|
24
|
+
endList: boolean;
|
|
25
|
+
mapUri?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface FfmpegRunner {
|
|
28
|
+
run(args: string[]): Promise<{
|
|
29
|
+
exitCode: number;
|
|
30
|
+
stderr: string;
|
|
31
|
+
}>;
|
|
32
|
+
writeInput(name: string, data: Uint8Array): Promise<void>;
|
|
33
|
+
readOutput(name: string): Promise<Uint8Array>;
|
|
34
|
+
deleteFile?(name: string): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export interface ProbeStream {
|
|
37
|
+
index: number;
|
|
38
|
+
codecType: 'video' | 'audio' | 'subtitle' | 'data';
|
|
39
|
+
codecName: string;
|
|
40
|
+
width?: number;
|
|
41
|
+
height?: number;
|
|
42
|
+
sampleRate?: number;
|
|
43
|
+
channels?: number;
|
|
44
|
+
duration?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface ProbeResult {
|
|
47
|
+
format: string;
|
|
48
|
+
duration: number;
|
|
49
|
+
bitRate?: number;
|
|
50
|
+
streams: ProbeStream[];
|
|
51
|
+
}
|
|
52
|
+
export interface AdtsFrame {
|
|
53
|
+
data: Uint8Array;
|
|
54
|
+
frameSize: number;
|
|
55
|
+
sampleRate: number;
|
|
56
|
+
channels: number;
|
|
57
|
+
}
|
|
58
|
+
/** Metadata for a discovered subtitle track (sent to main thread before extraction). */
|
|
59
|
+
export interface SubtitleTrackInfo {
|
|
60
|
+
/** Index within the subtitle tracks array (0-based). */
|
|
61
|
+
index: number;
|
|
62
|
+
/** Original codec in the container. */
|
|
63
|
+
codec: string;
|
|
64
|
+
/** ISO 639-2/T language code (e.g. 'eng', 'spa', 'und'). */
|
|
65
|
+
language: string;
|
|
66
|
+
/** User-visible track name, if any. */
|
|
67
|
+
name: string | null;
|
|
68
|
+
/** Container disposition flags — use to decide <track kind>. */
|
|
69
|
+
disposition: {
|
|
70
|
+
default: boolean;
|
|
71
|
+
forced: boolean;
|
|
72
|
+
hearingImpaired: boolean;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Source of a subtitle track — either embedded in the file or imported by the user.
|
|
77
|
+
* This is the internal representation; the renderer decides what to do with it.
|
|
78
|
+
*/
|
|
79
|
+
export interface SubtitleData {
|
|
80
|
+
/** The cue list (format-agnostic). */
|
|
81
|
+
cues: SubtitleCueEntry[];
|
|
82
|
+
/** Original codec so the renderer can choose strategy. */
|
|
83
|
+
codec: string;
|
|
84
|
+
/** Format-specific header (ASS [V4+ Styles] section, WebVTT preamble, etc). */
|
|
85
|
+
header?: string;
|
|
86
|
+
}
|
|
87
|
+
/** Single subtitle cue — mirrors mediabunny's SubtitleCue but cleaned up for our use. */
|
|
88
|
+
export interface SubtitleCueEntry {
|
|
89
|
+
/** Start time in seconds. */
|
|
90
|
+
startSec: number;
|
|
91
|
+
/** End time in seconds. */
|
|
92
|
+
endSec: number;
|
|
93
|
+
/** Cue text content (may contain format-specific markup like ASS override tags). */
|
|
94
|
+
text: string;
|
|
95
|
+
/** Optional VTT positioning/settings string. */
|
|
96
|
+
settings?: string;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "playsvideo",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Play any video file in the browser — client-side remuxing and audio transcode, no server required.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "vite",
|
|
20
|
+
"build": "vite build",
|
|
21
|
+
"build:lib": "rm -rf dist && tsc -p tsconfig.lib.json",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:unit": "vitest run tests/unit",
|
|
24
|
+
"test:integration": "vitest run tests/integration",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"test:e2e": "npx playwright test",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"format": "biome format --write .",
|
|
29
|
+
"format:check": "biome format .",
|
|
30
|
+
"lint": "biome lint .",
|
|
31
|
+
"check": "biome check --write .",
|
|
32
|
+
"download:ffmpeg-core": "node scripts/download-ffmpeg-core.mjs",
|
|
33
|
+
"setup": "npm install && npm run download:ffmpeg-core",
|
|
34
|
+
"deploy:worker": "cd worker && npx wrangler deploy",
|
|
35
|
+
"deploy:site": "npm run build && bash scripts/deploy.sh",
|
|
36
|
+
"deploy": "npm run deploy:site && npm run deploy:worker",
|
|
37
|
+
"prepublishOnly": "npm run build:lib"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@ffmpeg/ffmpeg": "^0.12.15",
|
|
41
|
+
"hls.js": "^1.5.0",
|
|
42
|
+
"mediabunny": "github:kzahel/mediabunny#integration"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@biomejs/biome": "^2.4.6",
|
|
46
|
+
"@playwright/test": "^1.58.2",
|
|
47
|
+
"@types/node": "^25.3.3",
|
|
48
|
+
"typescript": "^5.7.0",
|
|
49
|
+
"vite": "^6.0.0",
|
|
50
|
+
"vitest": "^3.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|