@karsten_zhou/vite-plugin-hls 0.1.0
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 +124 -0
- package/dist/index.cjs +474 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +55 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +437 -0
- package/dist/index.js.map +1 -0
- package/package.json +98 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface HlsVariant {
|
|
4
|
+
height: number;
|
|
5
|
+
bitrate: string;
|
|
6
|
+
}
|
|
7
|
+
interface HlsCommonOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Explicit ffmpeg executable.
|
|
10
|
+
*
|
|
11
|
+
* Defaults to ffmpeg-static, then falls back to "ffmpeg".
|
|
12
|
+
*/
|
|
13
|
+
ffmpegPath?: string;
|
|
14
|
+
/**
|
|
15
|
+
* HLS segment duration in seconds.
|
|
16
|
+
*
|
|
17
|
+
* @default 4
|
|
18
|
+
*/
|
|
19
|
+
segmentDuration?: number;
|
|
20
|
+
/**
|
|
21
|
+
* HLS segment format.
|
|
22
|
+
*
|
|
23
|
+
* @default "fmp4"
|
|
24
|
+
*/
|
|
25
|
+
segmentType?: "fmp4" | "mpegts";
|
|
26
|
+
/**
|
|
27
|
+
* Directory inside Vite's output directory.
|
|
28
|
+
*
|
|
29
|
+
* @default "assets/hls"
|
|
30
|
+
*/
|
|
31
|
+
outputDir?: string;
|
|
32
|
+
/**
|
|
33
|
+
* FFmpeg encoding preset.
|
|
34
|
+
*
|
|
35
|
+
* @default "medium"
|
|
36
|
+
*/
|
|
37
|
+
preset?: string;
|
|
38
|
+
/**
|
|
39
|
+
* H.264 CRF.
|
|
40
|
+
*
|
|
41
|
+
* @default 23
|
|
42
|
+
*/
|
|
43
|
+
crf?: number;
|
|
44
|
+
}
|
|
45
|
+
type HlsPluginOptions = (HlsCommonOptions & {
|
|
46
|
+
mode?: "single";
|
|
47
|
+
variants?: never;
|
|
48
|
+
}) | (HlsCommonOptions & {
|
|
49
|
+
mode: "adaptive";
|
|
50
|
+
variants: HlsVariant[];
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
declare function hlsVideos(input?: HlsPluginOptions): Plugin;
|
|
54
|
+
|
|
55
|
+
export { type HlsPluginOptions, type HlsVariant, hlsVideos as default, hlsVideos };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface HlsVariant {
|
|
4
|
+
height: number;
|
|
5
|
+
bitrate: string;
|
|
6
|
+
}
|
|
7
|
+
interface HlsCommonOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Explicit ffmpeg executable.
|
|
10
|
+
*
|
|
11
|
+
* Defaults to ffmpeg-static, then falls back to "ffmpeg".
|
|
12
|
+
*/
|
|
13
|
+
ffmpegPath?: string;
|
|
14
|
+
/**
|
|
15
|
+
* HLS segment duration in seconds.
|
|
16
|
+
*
|
|
17
|
+
* @default 4
|
|
18
|
+
*/
|
|
19
|
+
segmentDuration?: number;
|
|
20
|
+
/**
|
|
21
|
+
* HLS segment format.
|
|
22
|
+
*
|
|
23
|
+
* @default "fmp4"
|
|
24
|
+
*/
|
|
25
|
+
segmentType?: "fmp4" | "mpegts";
|
|
26
|
+
/**
|
|
27
|
+
* Directory inside Vite's output directory.
|
|
28
|
+
*
|
|
29
|
+
* @default "assets/hls"
|
|
30
|
+
*/
|
|
31
|
+
outputDir?: string;
|
|
32
|
+
/**
|
|
33
|
+
* FFmpeg encoding preset.
|
|
34
|
+
*
|
|
35
|
+
* @default "medium"
|
|
36
|
+
*/
|
|
37
|
+
preset?: string;
|
|
38
|
+
/**
|
|
39
|
+
* H.264 CRF.
|
|
40
|
+
*
|
|
41
|
+
* @default 23
|
|
42
|
+
*/
|
|
43
|
+
crf?: number;
|
|
44
|
+
}
|
|
45
|
+
type HlsPluginOptions = (HlsCommonOptions & {
|
|
46
|
+
mode?: "single";
|
|
47
|
+
variants?: never;
|
|
48
|
+
}) | (HlsCommonOptions & {
|
|
49
|
+
mode: "adaptive";
|
|
50
|
+
variants: HlsVariant[];
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
declare function hlsVideos(input?: HlsPluginOptions): Plugin;
|
|
54
|
+
|
|
55
|
+
export { type HlsPluginOptions, type HlsVariant, hlsVideos as default, hlsVideos };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
// src/options.ts
|
|
2
|
+
import ffmpegStatic from "ffmpeg-static";
|
|
3
|
+
function resolveOptions(input = {}) {
|
|
4
|
+
const common = {
|
|
5
|
+
ffmpegPath: input.ffmpegPath ?? ffmpegStatic ?? "ffmpeg",
|
|
6
|
+
segmentDuration: input.segmentDuration ?? 4,
|
|
7
|
+
segmentType: input.segmentType ?? "fmp4",
|
|
8
|
+
outputDir: input.outputDir ?? "assets/hls",
|
|
9
|
+
preset: input.preset ?? "medium",
|
|
10
|
+
crf: input.crf ?? 23
|
|
11
|
+
};
|
|
12
|
+
if (input.mode === "adaptive") {
|
|
13
|
+
if (!input.variants || input.variants.length === 0) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"[vite-plugin-hls] Adaptive mode requires at least one variant."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
...common,
|
|
20
|
+
mode: "adaptive",
|
|
21
|
+
variants: input.variants
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
...common,
|
|
26
|
+
mode: "single"
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/resolve.ts
|
|
31
|
+
import { extname } from "path";
|
|
32
|
+
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
33
|
+
".mp4",
|
|
34
|
+
".mov",
|
|
35
|
+
".m4v",
|
|
36
|
+
".webm",
|
|
37
|
+
".mkv",
|
|
38
|
+
".avi"
|
|
39
|
+
]);
|
|
40
|
+
var VIRTUAL_PREFIX = "\0vite-plugin-hls:";
|
|
41
|
+
function isVideoSource(source) {
|
|
42
|
+
const pathname = source.split("?")[0] ?? source;
|
|
43
|
+
return VIDEO_EXTENSIONS.has(extname(pathname).toLowerCase());
|
|
44
|
+
}
|
|
45
|
+
function virtualIdForSource(source) {
|
|
46
|
+
return `${VIRTUAL_PREFIX}${encodeURIComponent(source)}`;
|
|
47
|
+
}
|
|
48
|
+
function sourceFromVirtualId(id) {
|
|
49
|
+
return decodeURIComponent(id.slice(VIRTUAL_PREFIX.length));
|
|
50
|
+
}
|
|
51
|
+
async function resolveVideoSource(source, importer, context) {
|
|
52
|
+
if (!isVideoSource(source)) {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
const resolved = await context.resolve(source, importer, {
|
|
56
|
+
skipSelf: true
|
|
57
|
+
});
|
|
58
|
+
if (!resolved || resolved.external) {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
return resolved.id.split("?", 1)[0];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/hls.ts
|
|
65
|
+
import { basename, extname as extname3, join as join4 } from "path";
|
|
66
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
67
|
+
|
|
68
|
+
// src/encoder.ts
|
|
69
|
+
import { mkdtemp, rm } from "fs/promises";
|
|
70
|
+
import { join } from "path";
|
|
71
|
+
import { tmpdir } from "os";
|
|
72
|
+
import { spawn } from "child_process";
|
|
73
|
+
|
|
74
|
+
// src/bitrate.ts
|
|
75
|
+
function bitrateToNumber(bitrate) {
|
|
76
|
+
const match = /^([\d.]+)\s*([kKmMgG]?)$/.exec(bitrate.trim());
|
|
77
|
+
if (!match) {
|
|
78
|
+
throw new Error(`[vite-plugin-hls] Invalid bitrate: ${bitrate}`);
|
|
79
|
+
}
|
|
80
|
+
const amount = Number(match[1]);
|
|
81
|
+
const suffix = (match[2] ?? "").toLowerCase();
|
|
82
|
+
const multiplier = suffix === "g" ? 1e9 : suffix === "m" ? 1e6 : suffix === "k" ? 1e3 : 1;
|
|
83
|
+
return Math.round(amount * multiplier);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/encoder.ts
|
|
87
|
+
function runFfmpeg(executable, args) {
|
|
88
|
+
return new Promise((resolvePromise, reject) => {
|
|
89
|
+
const child = spawn(executable, args, {
|
|
90
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
91
|
+
});
|
|
92
|
+
let stderr = "";
|
|
93
|
+
child.stderr.on("data", (data) => {
|
|
94
|
+
stderr += data.toString();
|
|
95
|
+
});
|
|
96
|
+
child.on("error", (error) => {
|
|
97
|
+
if ("code" in error && error.code === "ENOENT") {
|
|
98
|
+
reject(
|
|
99
|
+
new Error(
|
|
100
|
+
[
|
|
101
|
+
"[vite-plugin-hls] ffmpeg was not found.",
|
|
102
|
+
`Executable: ${executable}`,
|
|
103
|
+
"Set ffmpegPath or install ffmpeg."
|
|
104
|
+
].join("\n")
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
reject(error);
|
|
110
|
+
});
|
|
111
|
+
child.on("close", (code) => {
|
|
112
|
+
if (code === 0) {
|
|
113
|
+
resolvePromise();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
reject(
|
|
117
|
+
new Error(
|
|
118
|
+
[
|
|
119
|
+
`[vite-plugin-hls] ffmpeg failed with exit code ${code ?? "unknown"}.`,
|
|
120
|
+
stderr.trim()
|
|
121
|
+
].filter(Boolean).join("\n")
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async function encodeVariant(source, outputDirectory, options, variant = {}) {
|
|
128
|
+
const extension = options.segmentType === "fmp4" ? "m4s" : "ts";
|
|
129
|
+
const playlist = join(outputDirectory, "index.m3u8");
|
|
130
|
+
const args = [
|
|
131
|
+
"-y",
|
|
132
|
+
"-i",
|
|
133
|
+
source,
|
|
134
|
+
"-map",
|
|
135
|
+
"0:v:0",
|
|
136
|
+
"-map",
|
|
137
|
+
"0:a:0?"
|
|
138
|
+
];
|
|
139
|
+
if (options.mode === "single") {
|
|
140
|
+
args.push(
|
|
141
|
+
"-c:v",
|
|
142
|
+
"copy",
|
|
143
|
+
"-c:a",
|
|
144
|
+
"copy"
|
|
145
|
+
);
|
|
146
|
+
} else {
|
|
147
|
+
args.push(
|
|
148
|
+
"-c:v",
|
|
149
|
+
"libx264",
|
|
150
|
+
"-preset",
|
|
151
|
+
options.preset,
|
|
152
|
+
"-crf",
|
|
153
|
+
String(options.crf),
|
|
154
|
+
"-pix_fmt",
|
|
155
|
+
"yuv420p",
|
|
156
|
+
"-c:a",
|
|
157
|
+
"aac",
|
|
158
|
+
"-b:a",
|
|
159
|
+
"128k"
|
|
160
|
+
);
|
|
161
|
+
if (variant.height !== void 0) {
|
|
162
|
+
args.push(
|
|
163
|
+
"-vf",
|
|
164
|
+
`scale=-2:${variant.height}:force_original_aspect_ratio=decrease`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (variant.bitrate !== void 0) {
|
|
168
|
+
const bitrate = bitrateToNumber(variant.bitrate);
|
|
169
|
+
args.push(
|
|
170
|
+
"-b:v",
|
|
171
|
+
variant.bitrate,
|
|
172
|
+
"-maxrate",
|
|
173
|
+
variant.bitrate,
|
|
174
|
+
"-bufsize",
|
|
175
|
+
String(Math.round(bitrate * 1.5))
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
args.push(
|
|
180
|
+
"-f",
|
|
181
|
+
"hls",
|
|
182
|
+
"-hls_time",
|
|
183
|
+
String(options.segmentDuration),
|
|
184
|
+
"-hls_playlist_type",
|
|
185
|
+
"vod",
|
|
186
|
+
"-hls_segment_filename",
|
|
187
|
+
join(outputDirectory, `segment-%05d.${extension}`)
|
|
188
|
+
);
|
|
189
|
+
if (options.segmentType === "fmp4") {
|
|
190
|
+
args.push(
|
|
191
|
+
"-hls_segment_type",
|
|
192
|
+
"fmp4",
|
|
193
|
+
"-hls_fmp4_init_filename",
|
|
194
|
+
"init.mp4"
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
args.push(playlist);
|
|
198
|
+
await runFfmpeg(options.ffmpegPath, args);
|
|
199
|
+
}
|
|
200
|
+
async function createEncodeDirectory() {
|
|
201
|
+
return mkdtemp(join(tmpdir(), "vite-plugin-hls-"));
|
|
202
|
+
}
|
|
203
|
+
async function removeEncodeDirectory(directory) {
|
|
204
|
+
await rm(directory, {
|
|
205
|
+
recursive: true,
|
|
206
|
+
force: true
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/cache.ts
|
|
211
|
+
import { createHash } from "crypto";
|
|
212
|
+
import { mkdir, readFile as readFile2, rename, rm as rm2, stat, writeFile } from "fs/promises";
|
|
213
|
+
import { join as join3, resolve } from "path";
|
|
214
|
+
|
|
215
|
+
// src/fs.ts
|
|
216
|
+
import { readFile, readdir } from "fs/promises";
|
|
217
|
+
import { join as join2, relative } from "path";
|
|
218
|
+
async function collectFiles(directory) {
|
|
219
|
+
const result = /* @__PURE__ */ new Map();
|
|
220
|
+
async function visit(current) {
|
|
221
|
+
for (const entry of await readdir(current, { withFileTypes: true })) {
|
|
222
|
+
const filename = join2(current, entry.name);
|
|
223
|
+
if (entry.isDirectory()) {
|
|
224
|
+
await visit(filename);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
result.set(
|
|
228
|
+
relative(directory, filename).replaceAll("\\", "/"),
|
|
229
|
+
await readFile(filename)
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
await visit(directory);
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/cache.ts
|
|
238
|
+
async function getCacheKey(source, options) {
|
|
239
|
+
const sourceData = await readFile2(source);
|
|
240
|
+
return createHash("sha256").update(sourceData).update(JSON.stringify(options)).digest("hex");
|
|
241
|
+
}
|
|
242
|
+
async function readCache(cacheRoot, key) {
|
|
243
|
+
const directory = join3(cacheRoot, "hls", key);
|
|
244
|
+
try {
|
|
245
|
+
await stat(directory);
|
|
246
|
+
} catch {
|
|
247
|
+
return void 0;
|
|
248
|
+
}
|
|
249
|
+
const metadata = JSON.parse(
|
|
250
|
+
await readFile2(join3(directory, "manifest.json"), "utf8")
|
|
251
|
+
);
|
|
252
|
+
const files = await collectFiles(directory);
|
|
253
|
+
files.delete("manifest.json");
|
|
254
|
+
return {
|
|
255
|
+
directoryName: metadata.directoryName,
|
|
256
|
+
manifest: metadata.manifest,
|
|
257
|
+
files
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async function writeCache(cacheRoot, key, result) {
|
|
261
|
+
const temporaryDirectory = join3(cacheRoot, "hls", `${key}.tmp`);
|
|
262
|
+
const finalDirectory = join3(cacheRoot, "hls", key);
|
|
263
|
+
await rm2(temporaryDirectory, {
|
|
264
|
+
recursive: true,
|
|
265
|
+
force: true
|
|
266
|
+
});
|
|
267
|
+
await mkdir(temporaryDirectory, {
|
|
268
|
+
recursive: true
|
|
269
|
+
});
|
|
270
|
+
for (const [filename, data] of result.files) {
|
|
271
|
+
const destination = join3(temporaryDirectory, filename);
|
|
272
|
+
await mkdir(resolve(destination, ".."), {
|
|
273
|
+
recursive: true
|
|
274
|
+
});
|
|
275
|
+
await writeFile(destination, data);
|
|
276
|
+
}
|
|
277
|
+
await writeFile(
|
|
278
|
+
join3(temporaryDirectory, "manifest.json"),
|
|
279
|
+
JSON.stringify({
|
|
280
|
+
directoryName: result.directoryName,
|
|
281
|
+
manifest: result.manifest
|
|
282
|
+
}),
|
|
283
|
+
"utf8"
|
|
284
|
+
);
|
|
285
|
+
await rm2(finalDirectory, {
|
|
286
|
+
recursive: true,
|
|
287
|
+
force: true
|
|
288
|
+
});
|
|
289
|
+
await rename(temporaryDirectory, finalDirectory);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/playlist.ts
|
|
293
|
+
import { extname as extname2 } from "path";
|
|
294
|
+
function slugify(filename) {
|
|
295
|
+
return filename.replace(extname2(filename), "").replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "video";
|
|
296
|
+
}
|
|
297
|
+
function createMasterPlaylist(variants) {
|
|
298
|
+
const lines = ["#EXTM3U", "#EXT-X-VERSION:7"];
|
|
299
|
+
for (const variant of variants) {
|
|
300
|
+
const bandwidth = Math.round(bitrateToNumber(variant.bitrate) * 1.15);
|
|
301
|
+
lines.push(
|
|
302
|
+
`#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth}`,
|
|
303
|
+
`${variant.height}p/index.m3u8`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
return `${lines.join("\n")}
|
|
307
|
+
`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/hls.ts
|
|
311
|
+
async function generateHls(source, cacheRoot, options) {
|
|
312
|
+
const key = await getCacheKey(source, options);
|
|
313
|
+
const cached = await readCache(cacheRoot, key);
|
|
314
|
+
if (cached) {
|
|
315
|
+
console.log(`[vite-plugin-hls] cache hit ${source}`);
|
|
316
|
+
return cached;
|
|
317
|
+
}
|
|
318
|
+
console.log(`[vite-plugin-hls] encoding ${source}`);
|
|
319
|
+
const temporaryDirectory = await createEncodeDirectory();
|
|
320
|
+
try {
|
|
321
|
+
const videoName = slugify(basename(source, extname3(source)));
|
|
322
|
+
const directoryName = `${videoName}-${key.slice(0, 12)}`;
|
|
323
|
+
let manifest;
|
|
324
|
+
if (options.mode === "single") {
|
|
325
|
+
await encodeVariant(source, temporaryDirectory, options);
|
|
326
|
+
manifest = "index.m3u8";
|
|
327
|
+
} else {
|
|
328
|
+
for (const variant of options.variants) {
|
|
329
|
+
await encodeVariant(
|
|
330
|
+
source,
|
|
331
|
+
join4(temporaryDirectory, `${variant.height}p`),
|
|
332
|
+
options,
|
|
333
|
+
{
|
|
334
|
+
height: variant.height,
|
|
335
|
+
bitrate: variant.bitrate
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
const master = createMasterPlaylist(options.variants);
|
|
340
|
+
await writeFile2(join4(temporaryDirectory, "master.m3u8"), master, "utf8");
|
|
341
|
+
manifest = "master.m3u8";
|
|
342
|
+
}
|
|
343
|
+
const result = {
|
|
344
|
+
directoryName,
|
|
345
|
+
manifest,
|
|
346
|
+
files: await collectFiles(temporaryDirectory)
|
|
347
|
+
};
|
|
348
|
+
await writeCache(cacheRoot, key, result);
|
|
349
|
+
console.log(`[vite-plugin-hls] generated ${directoryName}/${manifest}`);
|
|
350
|
+
return result;
|
|
351
|
+
} finally {
|
|
352
|
+
await removeEncodeDirectory(temporaryDirectory);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/assets.ts
|
|
357
|
+
function emitHls(context, result, options) {
|
|
358
|
+
const prefix = `${options.outputDir}/${result.directoryName}`;
|
|
359
|
+
const manifestSource = result.files.get(result.manifest);
|
|
360
|
+
if (!manifestSource) {
|
|
361
|
+
throw new Error(`[vite-plugin-hls] Missing manifest ${result.manifest}`);
|
|
362
|
+
}
|
|
363
|
+
context.emitFile({
|
|
364
|
+
type: "asset",
|
|
365
|
+
fileName: `${prefix}/${result.manifest}`,
|
|
366
|
+
source: manifestSource
|
|
367
|
+
});
|
|
368
|
+
for (const [filename, data] of result.files) {
|
|
369
|
+
if (filename === result.manifest) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
context.emitFile({
|
|
373
|
+
type: "asset",
|
|
374
|
+
fileName: `${prefix}/${filename}`,
|
|
375
|
+
source: data
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function publicManifestExpression(result, options) {
|
|
380
|
+
const path = `${options.outputDir}/${result.directoryName}/${result.manifest}`;
|
|
381
|
+
return `import.meta.env.BASE_URL + ${JSON.stringify(path)}`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/plugin.ts
|
|
385
|
+
var VIRTUAL_PREFIX2 = "\0vite-plugin-hls:";
|
|
386
|
+
function hlsVideos(input) {
|
|
387
|
+
const options = resolveOptions(input);
|
|
388
|
+
let config;
|
|
389
|
+
const resolvedSources = /* @__PURE__ */ new Map();
|
|
390
|
+
const generated = /* @__PURE__ */ new Map();
|
|
391
|
+
return {
|
|
392
|
+
name: "vite-plugin-hls",
|
|
393
|
+
apply: "build",
|
|
394
|
+
enforce: "pre",
|
|
395
|
+
configResolved(resolved) {
|
|
396
|
+
config = resolved;
|
|
397
|
+
},
|
|
398
|
+
async resolveId(source, importer) {
|
|
399
|
+
if (!importer || !isVideoSource(source)) {
|
|
400
|
+
return void 0;
|
|
401
|
+
}
|
|
402
|
+
const resolved = await resolveVideoSource(source, importer, this);
|
|
403
|
+
if (!resolved) {
|
|
404
|
+
return void 0;
|
|
405
|
+
}
|
|
406
|
+
const id = virtualIdForSource(resolved);
|
|
407
|
+
resolvedSources.set(id, resolved);
|
|
408
|
+
return id;
|
|
409
|
+
},
|
|
410
|
+
async load(id, loadOptions) {
|
|
411
|
+
if (!id.startsWith(VIRTUAL_PREFIX2)) {
|
|
412
|
+
return void 0;
|
|
413
|
+
}
|
|
414
|
+
const source = resolvedSources.get(id) ?? sourceFromVirtualId(id);
|
|
415
|
+
let result = generated.get(source);
|
|
416
|
+
if (!result) {
|
|
417
|
+
result = await generateHls(source, config.cacheDir, options);
|
|
418
|
+
generated.set(source, result);
|
|
419
|
+
}
|
|
420
|
+
if (!loadOptions?.ssr) {
|
|
421
|
+
emitHls(this, result, options);
|
|
422
|
+
}
|
|
423
|
+
const manifest = publicManifestExpression(result, options);
|
|
424
|
+
return `
|
|
425
|
+
export default ${manifest};
|
|
426
|
+
`;
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/index.ts
|
|
432
|
+
var index_default = hlsVideos;
|
|
433
|
+
export {
|
|
434
|
+
index_default as default,
|
|
435
|
+
hlsVideos
|
|
436
|
+
};
|
|
437
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/options.ts","../src/resolve.ts","../src/hls.ts","../src/encoder.ts","../src/bitrate.ts","../src/cache.ts","../src/fs.ts","../src/playlist.ts","../src/assets.ts","../src/plugin.ts","../src/index.ts"],"sourcesContent":["import ffmpegStatic from \"ffmpeg-static\";\n\nimport type { HlsPluginOptions, ResolvedHlsOptions } from \"./types\";\n\nexport function resolveOptions(\n input: HlsPluginOptions = {},\n): ResolvedHlsOptions {\n const common = {\n ffmpegPath: input.ffmpegPath ?? ffmpegStatic ?? \"ffmpeg\",\n\n segmentDuration: input.segmentDuration ?? 4,\n\n segmentType: input.segmentType ?? \"fmp4\",\n\n outputDir: input.outputDir ?? \"assets/hls\",\n\n preset: input.preset ?? \"medium\",\n\n crf: input.crf ?? 23,\n };\n\n if (input.mode === \"adaptive\") {\n if (!input.variants || input.variants.length === 0) {\n throw new Error(\n \"[vite-plugin-hls] Adaptive mode requires at least one variant.\",\n );\n }\n\n return {\n ...common,\n mode: \"adaptive\",\n variants: input.variants,\n };\n }\n\n return {\n ...common,\n mode: \"single\",\n };\n}\n","import { extname } from \"node:path\";\n\nimport type { PluginContext } from \"rolldown\";\n\nconst VIDEO_EXTENSIONS = new Set([\n \".mp4\",\n \".mov\",\n \".m4v\",\n \".webm\",\n \".mkv\",\n \".avi\",\n]);\n\nconst VIRTUAL_PREFIX = \"\\0vite-plugin-hls:\";\n\nexport function isVideoSource(source: string): boolean {\n const pathname = source.split(\"?\")[0] ?? source;\n\n return VIDEO_EXTENSIONS.has(extname(pathname).toLowerCase());\n}\n\nexport function virtualIdForSource(source: string): string {\n return `${VIRTUAL_PREFIX}${encodeURIComponent(source)}`;\n}\n\nexport function sourceFromVirtualId(id: string): string {\n return decodeURIComponent(id.slice(VIRTUAL_PREFIX.length));\n}\n\nexport async function resolveVideoSource(\n source: string,\n importer: string,\n context: PluginContext,\n): Promise<string | undefined> {\n if (!isVideoSource(source)) {\n return undefined;\n }\n\n const resolved = await context.resolve(source, importer, {\n skipSelf: true,\n });\n\n if (!resolved || resolved.external) {\n return undefined;\n }\n\n /*\n * Queries such as ?url are irrelevant here.\n * FFmpeg needs the physical file.\n */\n return resolved.id.split(\"?\", 1)[0];\n}\n","import { basename, extname, join } from \"node:path\";\nimport { writeFile } from \"node:fs/promises\";\n\nimport {\n encodeVariant,\n createEncodeDirectory,\n removeEncodeDirectory,\n} from \"./encoder\";\nimport { readCache, getCacheKey, writeCache } from \"./cache\";\nimport { collectFiles } from \"./fs\";\nimport { slugify, createMasterPlaylist } from \"./playlist\";\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\nexport async function generateHls(\n source: string,\n cacheRoot: string,\n options: ResolvedHlsOptions,\n): Promise<CachedHls> {\n const key = await getCacheKey(source, options);\n\n const cached = await readCache(cacheRoot, key);\n\n if (cached) {\n console.log(`[vite-plugin-hls] cache hit ${source}`);\n\n return cached;\n }\n\n console.log(`[vite-plugin-hls] encoding ${source}`);\n\n const temporaryDirectory = await createEncodeDirectory();\n\n try {\n const videoName = slugify(basename(source, extname(source)));\n\n const directoryName = `${videoName}-${key.slice(0, 12)}`;\n\n let manifest: string;\n\n if (options.mode === \"single\") {\n await encodeVariant(source, temporaryDirectory, options);\n\n manifest = \"index.m3u8\";\n } else {\n for (const variant of options.variants) {\n await encodeVariant(\n source,\n join(temporaryDirectory, `${variant.height}p`),\n options,\n {\n height: variant.height,\n bitrate: variant.bitrate,\n },\n );\n }\n\n const master = createMasterPlaylist(options.variants);\n\n await writeFile(join(temporaryDirectory, \"master.m3u8\"), master, \"utf8\");\n\n manifest = \"master.m3u8\";\n }\n\n const result: CachedHls = {\n directoryName,\n manifest,\n files: await collectFiles(temporaryDirectory),\n };\n\n await writeCache(cacheRoot, key, result);\n\n console.log(`[vite-plugin-hls] generated ${directoryName}/${manifest}`);\n\n return result;\n } finally {\n await removeEncodeDirectory(temporaryDirectory);\n }\n}\n","import { mkdtemp, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { spawn } from \"node:child_process\";\n\nimport { bitrateToNumber } from \"./bitrate\";\nimport type { ResolvedHlsOptions } from \"./types\";\n\ninterface EncodeVariantOptions {\n height?: number;\n bitrate?: string;\n}\n\nfunction runFfmpeg(executable: string, args: string[]): Promise<void> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(executable, args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stderr = \"\";\n\n child.stderr.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on(\"error\", (error) => {\n if (\"code\" in error && error.code === \"ENOENT\") {\n reject(\n new Error(\n [\n \"[vite-plugin-hls] ffmpeg was not found.\",\n `Executable: ${executable}`,\n \"Set ffmpegPath or install ffmpeg.\",\n ].join(\"\\n\"),\n ),\n );\n return;\n }\n\n reject(error);\n });\n\n child.on(\"close\", (code) => {\n if (code === 0) {\n resolvePromise();\n return;\n }\n\n reject(\n new Error(\n [\n `[vite-plugin-hls] ffmpeg failed with exit code ${code ?? \"unknown\"}.`,\n stderr.trim(),\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n ),\n );\n });\n });\n}\n\nexport async function encodeVariant(\n source: string,\n outputDirectory: string,\n options: ResolvedHlsOptions,\n variant: EncodeVariantOptions = {},\n): Promise<void> {\n const extension = options.segmentType === \"fmp4\" ? \"m4s\" : \"ts\";\n\n const playlist = join(outputDirectory, \"index.m3u8\");\n\n const args: string[] = [\n \"-y\",\n \"-i\",\n source,\n\n \"-map\",\n \"0:v:0\",\n \"-map\",\n \"0:a:0?\",\n ];\n\n /*\n * SINGLE MODE:\n *\n * Do not encode the streams again.\n * FFmpeg only remuxes/segments them into HLS.\n */\n if (options.mode === \"single\") {\n args.push(\n \"-c:v\",\n \"copy\",\n\n \"-c:a\",\n \"copy\",\n );\n } else {\n /*\n * ADAPTIVE MODE:\n *\n * Each rendition needs to be encoded because\n * resolution and bitrate are changed.\n */\n args.push(\n \"-c:v\",\n \"libx264\",\n\n \"-preset\",\n options.preset,\n\n \"-crf\",\n String(options.crf),\n\n \"-pix_fmt\",\n \"yuv420p\",\n\n \"-c:a\",\n \"aac\",\n\n \"-b:a\",\n \"128k\",\n );\n\n if (variant.height !== undefined) {\n args.push(\n \"-vf\",\n `scale=-2:${variant.height}:force_original_aspect_ratio=decrease`,\n );\n }\n\n if (variant.bitrate !== undefined) {\n const bitrate = bitrateToNumber(variant.bitrate);\n\n args.push(\n \"-b:v\",\n variant.bitrate,\n\n \"-maxrate\",\n variant.bitrate,\n\n \"-bufsize\",\n String(Math.round(bitrate * 1.5)),\n );\n }\n }\n\n args.push(\n \"-f\",\n \"hls\",\n\n \"-hls_time\",\n String(options.segmentDuration),\n\n \"-hls_playlist_type\",\n \"vod\",\n\n \"-hls_segment_filename\",\n join(outputDirectory, `segment-%05d.${extension}`),\n );\n\n if (options.segmentType === \"fmp4\") {\n args.push(\n \"-hls_segment_type\",\n \"fmp4\",\n\n \"-hls_fmp4_init_filename\",\n \"init.mp4\",\n );\n }\n\n args.push(playlist);\n\n await runFfmpeg(options.ffmpegPath, args);\n}\n\nexport async function createEncodeDirectory(): Promise<string> {\n return mkdtemp(join(tmpdir(), \"vite-plugin-hls-\"));\n}\n\nexport async function removeEncodeDirectory(directory: string): Promise<void> {\n await rm(directory, {\n recursive: true,\n force: true,\n });\n}\n","/**\n * Converts a human-readable bitrate such as \"1200k\", \"2.5M\" or \"1G\"\n * into the equivalent number of bits per second.\n */\nexport function bitrateToNumber(bitrate: string): number {\n const match = /^([\\d.]+)\\s*([kKmMgG]?)$/.exec(bitrate.trim());\n\n if (!match) {\n throw new Error(`[vite-plugin-hls] Invalid bitrate: ${bitrate}`);\n }\n\n const amount = Number(match[1]);\n\n const suffix = (match[2] ?? \"\").toLowerCase();\n\n const multiplier =\n suffix === \"g\"\n ? 1_000_000_000\n : suffix === \"m\"\n ? 1_000_000\n : suffix === \"k\"\n ? 1_000\n : 1;\n\n return Math.round(amount * multiplier);\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { collectFiles } from \"./fs\";\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\ninterface CacheManifest {\n directoryName: string;\n manifest: string;\n}\n\nexport async function getCacheKey(\n source: string,\n options: ResolvedHlsOptions,\n): Promise<string> {\n const sourceData = await readFile(source);\n\n return createHash(\"sha256\")\n .update(sourceData)\n .update(JSON.stringify(options))\n .digest(\"hex\");\n}\n\nexport async function readCache(\n cacheRoot: string,\n key: string,\n): Promise<CachedHls | undefined> {\n const directory = join(cacheRoot, \"hls\", key);\n\n try {\n await stat(directory);\n } catch {\n return undefined;\n }\n\n const metadata = JSON.parse(\n await readFile(join(directory, \"manifest.json\"), \"utf8\"),\n ) as CacheManifest;\n\n const files = await collectFiles(directory);\n\n // manifest.json is internal bookkeeping, never an emitted asset.\n files.delete(\"manifest.json\");\n\n return {\n directoryName: metadata.directoryName,\n\n manifest: metadata.manifest,\n\n files,\n };\n}\n\nexport async function writeCache(\n cacheRoot: string,\n key: string,\n result: CachedHls,\n): Promise<void> {\n const temporaryDirectory = join(cacheRoot, \"hls\", `${key}.tmp`);\n\n const finalDirectory = join(cacheRoot, \"hls\", key);\n\n await rm(temporaryDirectory, {\n recursive: true,\n force: true,\n });\n\n await mkdir(temporaryDirectory, {\n recursive: true,\n });\n\n for (const [filename, data] of result.files) {\n const destination = join(temporaryDirectory, filename);\n\n await mkdir(resolve(destination, \"..\"), {\n recursive: true,\n });\n\n await writeFile(destination, data);\n }\n\n await writeFile(\n join(temporaryDirectory, \"manifest.json\"),\n JSON.stringify({\n directoryName: result.directoryName,\n manifest: result.manifest,\n } satisfies CacheManifest),\n \"utf8\",\n );\n\n await rm(finalDirectory, {\n recursive: true,\n force: true,\n });\n\n await rename(temporaryDirectory, finalDirectory);\n}\n","import { readFile, readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\n\n/**\n * Recursively collects every file under `directory` into a map of\n * directory-relative POSIX paths to their buffer contents.\n */\nexport async function collectFiles(\n directory: string,\n): Promise<Map<string, Buffer>> {\n const result = new Map<string, Buffer>();\n\n async function visit(current: string): Promise<void> {\n for (const entry of await readdir(current, { withFileTypes: true })) {\n const filename = join(current, entry.name);\n\n if (entry.isDirectory()) {\n await visit(filename);\n continue;\n }\n\n result.set(\n relative(directory, filename).replaceAll(\"\\\\\", \"/\"),\n await readFile(filename),\n );\n }\n }\n\n await visit(directory);\n\n return result;\n}\n","import { extname } from \"node:path\";\n\nimport { bitrateToNumber } from \"./bitrate\";\nimport type { HlsVariant } from \"./types\";\n\n/** Turns a filename into a filesystem-safe slug used for the output folder. */\nexport function slugify(filename: string): string {\n return (\n filename\n .replace(extname(filename), \"\")\n .replace(/[^a-zA-Z0-9_-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .toLowerCase() || \"video\"\n );\n}\n\n/** Builds the adaptive master playlist pointing at each rendition. */\nexport function createMasterPlaylist(variants: readonly HlsVariant[]): string {\n const lines = [\"#EXTM3U\", \"#EXT-X-VERSION:7\"];\n\n for (const variant of variants) {\n // BANDWIDTH is an estimate derived from the configured bitrate.\n const bandwidth = Math.round(bitrateToNumber(variant.bitrate) * 1.15);\n\n lines.push(\n `#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth}`,\n `${variant.height}p/index.m3u8`,\n );\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n","import type { PluginContext } from \"rolldown\";\n\nimport type { CachedHls, ResolvedHlsOptions } from \"./types\";\n\nexport function emitHls(\n context: PluginContext,\n result: CachedHls,\n options: ResolvedHlsOptions,\n): void {\n const prefix = `${options.outputDir}/${result.directoryName}`;\n\n const manifestSource = result.files.get(result.manifest);\n\n if (!manifestSource) {\n throw new Error(`[vite-plugin-hls] Missing manifest ${result.manifest}`);\n }\n\n context.emitFile({\n type: \"asset\",\n fileName: `${prefix}/${result.manifest}`,\n source: manifestSource,\n });\n\n for (const [filename, data] of result.files) {\n if (filename === result.manifest) {\n continue;\n }\n\n context.emitFile({\n type: \"asset\",\n fileName: `${prefix}/${filename}`,\n source: data,\n });\n }\n}\n\nexport function publicManifestExpression(\n result: CachedHls,\n options: ResolvedHlsOptions,\n): string {\n const path = `${options.outputDir}/${result.directoryName}/${result.manifest}`;\n\n /*\n * BASE_URL is replaced by Vite in both the client\n * and SSR builds.\n *\n * Examples:\n *\n * \"/\" -> /assets/hls/...\n * \"/notes/\" -> /notes/assets/hls/...\n * \"./\" -> ./assets/hls/...\n */\n return `import.meta.env.BASE_URL + ${JSON.stringify(path)}`;\n}\n","import type { Plugin, ResolvedConfig } from \"vite\";\n\nimport { resolveOptions } from \"./options\";\nimport {\n isVideoSource,\n resolveVideoSource,\n sourceFromVirtualId,\n virtualIdForSource,\n} from \"./resolve\";\nimport { generateHls } from \"./hls\";\nimport { emitHls, publicManifestExpression } from \"./assets\";\nimport type { HlsPluginOptions } from \"./types\";\n\nconst VIRTUAL_PREFIX = \"\\0vite-plugin-hls:\";\n\nexport function hlsVideos(input?: HlsPluginOptions): Plugin {\n const options = resolveOptions(input);\n\n let config: ResolvedConfig;\n\n const resolvedSources = new Map<string, string>();\n\n /*\n * This only caches inside the current Vite build.\n *\n * The persistent cache in cache.ts handles the\n * client/server build boundary.\n */\n const generated = new Map<string, Awaited<ReturnType<typeof generateHls>>>();\n\n return {\n name: \"vite-plugin-hls\",\n\n apply: \"build\",\n\n enforce: \"pre\",\n\n configResolved(resolved) {\n config = resolved;\n },\n\n async resolveId(source, importer) {\n if (!importer || !isVideoSource(source)) {\n return undefined;\n }\n\n const resolved = await resolveVideoSource(source, importer, this);\n\n if (!resolved) {\n return undefined;\n }\n\n const id = virtualIdForSource(resolved);\n\n resolvedSources.set(id, resolved);\n\n return id;\n },\n\n async load(id, loadOptions) {\n if (!id.startsWith(VIRTUAL_PREFIX)) {\n return undefined;\n }\n\n const source = resolvedSources.get(id) ?? sourceFromVirtualId(id);\n\n let result = generated.get(source);\n\n if (!result) {\n result = await generateHls(source, config.cacheDir, options);\n\n generated.set(source, result);\n }\n\n /*\n * Important:\n *\n * The SSR build must NOT use Vite's emitted-file\n * URL because that resolves to a file:// URL.\n *\n * We only emit the actual files during the client\n * build. SSR receives the public URL directly.\n */\n if (!loadOptions?.ssr) {\n emitHls(this, result, options);\n }\n\n const manifest = publicManifestExpression(result, options);\n\n return `\n export default ${manifest};\n `;\n },\n };\n}\n","import { hlsVideos } from \"./plugin\";\n\nexport { hlsVideos };\nexport default hlsVideos;\n\nexport type { HlsPluginOptions, HlsVariant } from \"./types\";\n"],"mappings":";AAAA,OAAO,kBAAkB;AAIlB,SAAS,eACd,QAA0B,CAAC,GACP;AACpB,QAAM,SAAS;AAAA,IACb,YAAY,MAAM,cAAc,gBAAgB;AAAA,IAEhD,iBAAiB,MAAM,mBAAmB;AAAA,IAE1C,aAAa,MAAM,eAAe;AAAA,IAElC,WAAW,MAAM,aAAa;AAAA,IAE9B,QAAQ,MAAM,UAAU;AAAA,IAExB,KAAK,MAAM,OAAO;AAAA,EACpB;AAEA,MAAI,MAAM,SAAS,YAAY;AAC7B,QAAI,CAAC,MAAM,YAAY,MAAM,SAAS,WAAW,GAAG;AAClD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,GAAG;AAAA,MACH,MAAM;AAAA,MACN,UAAU,MAAM;AAAA,IAClB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM;AAAA,EACR;AACF;;;ACvCA,SAAS,eAAe;AAIxB,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,iBAAiB;AAEhB,SAAS,cAAc,QAAyB;AACrD,QAAM,WAAW,OAAO,MAAM,GAAG,EAAE,CAAC,KAAK;AAEzC,SAAO,iBAAiB,IAAI,QAAQ,QAAQ,EAAE,YAAY,CAAC;AAC7D;AAEO,SAAS,mBAAmB,QAAwB;AACzD,SAAO,GAAG,cAAc,GAAG,mBAAmB,MAAM,CAAC;AACvD;AAEO,SAAS,oBAAoB,IAAoB;AACtD,SAAO,mBAAmB,GAAG,MAAM,eAAe,MAAM,CAAC;AAC3D;AAEA,eAAsB,mBACpB,QACA,UACA,SAC6B;AAC7B,MAAI,CAAC,cAAc,MAAM,GAAG;AAC1B,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,MAAM,QAAQ,QAAQ,QAAQ,UAAU;AAAA,IACvD,UAAU;AAAA,EACZ,CAAC;AAED,MAAI,CAAC,YAAY,SAAS,UAAU;AAClC,WAAO;AAAA,EACT;AAMA,SAAO,SAAS,GAAG,MAAM,KAAK,CAAC,EAAE,CAAC;AACpC;;;ACnDA,SAAS,UAAU,WAAAA,UAAS,QAAAC,aAAY;AACxC,SAAS,aAAAC,kBAAiB;;;ACD1B,SAAS,SAAS,UAAU;AAC5B,SAAS,YAAY;AACrB,SAAS,cAAc;AACvB,SAAS,aAAa;;;ACCf,SAAS,gBAAgB,SAAyB;AACvD,QAAM,QAAQ,2BAA2B,KAAK,QAAQ,KAAK,CAAC;AAE5D,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,sCAAsC,OAAO,EAAE;AAAA,EACjE;AAEA,QAAM,SAAS,OAAO,MAAM,CAAC,CAAC;AAE9B,QAAM,UAAU,MAAM,CAAC,KAAK,IAAI,YAAY;AAE5C,QAAM,aACJ,WAAW,MACP,MACA,WAAW,MACT,MACA,WAAW,MACT,MACA;AAEV,SAAO,KAAK,MAAM,SAAS,UAAU;AACvC;;;ADZA,SAAS,UAAU,YAAoB,MAA+B;AACpE,SAAO,IAAI,QAAQ,CAAC,gBAAgB,WAAW;AAC7C,UAAM,QAAQ,MAAM,YAAY,MAAM;AAAA,MACpC,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AAED,QAAI,SAAS;AAEb,UAAM,OAAO,GAAG,QAAQ,CAAC,SAAiB;AACxC,gBAAU,KAAK,SAAS;AAAA,IAC1B,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,UAAU;AAC3B,UAAI,UAAU,SAAS,MAAM,SAAS,UAAU;AAC9C;AAAA,UACE,IAAI;AAAA,YACF;AAAA,cACE;AAAA,cACA,eAAe,UAAU;AAAA,cACzB;AAAA,YACF,EAAE,KAAK,IAAI;AAAA,UACb;AAAA,QACF;AACA;AAAA,MACF;AAEA,aAAO,KAAK;AAAA,IACd,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,UAAI,SAAS,GAAG;AACd,uBAAe;AACf;AAAA,MACF;AAEA;AAAA,QACE,IAAI;AAAA,UACF;AAAA,YACE,kDAAkD,QAAQ,SAAS;AAAA,YACnE,OAAO,KAAK;AAAA,UACd,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AAAA,QACd;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,cACpB,QACA,iBACA,SACA,UAAgC,CAAC,GAClB;AACf,QAAM,YAAY,QAAQ,gBAAgB,SAAS,QAAQ;AAE3D,QAAM,WAAW,KAAK,iBAAiB,YAAY;AAEnD,QAAM,OAAiB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IAEA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAQA,MAAI,QAAQ,SAAS,UAAU;AAC7B,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MAEA;AAAA,MACA;AAAA,IACF;AAAA,EACF,OAAO;AAOL,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MAEA;AAAA,MACA,QAAQ;AAAA,MAER;AAAA,MACA,OAAO,QAAQ,GAAG;AAAA,MAElB;AAAA,MACA;AAAA,MAEA;AAAA,MACA;AAAA,MAEA;AAAA,MACA;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,QAAW;AAChC,WAAK;AAAA,QACH;AAAA,QACA,YAAY,QAAQ,MAAM;AAAA,MAC5B;AAAA,IACF;AAEA,QAAI,QAAQ,YAAY,QAAW;AACjC,YAAM,UAAU,gBAAgB,QAAQ,OAAO;AAE/C,WAAK;AAAA,QACH;AAAA,QACA,QAAQ;AAAA,QAER;AAAA,QACA,QAAQ;AAAA,QAER;AAAA,QACA,OAAO,KAAK,MAAM,UAAU,GAAG,CAAC;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AAEA,OAAK;AAAA,IACH;AAAA,IACA;AAAA,IAEA;AAAA,IACA,OAAO,QAAQ,eAAe;AAAA,IAE9B;AAAA,IACA;AAAA,IAEA;AAAA,IACA,KAAK,iBAAiB,gBAAgB,SAAS,EAAE;AAAA,EACnD;AAEA,MAAI,QAAQ,gBAAgB,QAAQ;AAClC,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MAEA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,OAAK,KAAK,QAAQ;AAElB,QAAM,UAAU,QAAQ,YAAY,IAAI;AAC1C;AAEA,eAAsB,wBAAyC;AAC7D,SAAO,QAAQ,KAAK,OAAO,GAAG,kBAAkB,CAAC;AACnD;AAEA,eAAsB,sBAAsB,WAAkC;AAC5E,QAAM,GAAG,WAAW;AAAA,IAClB,WAAW;AAAA,IACX,OAAO;AAAA,EACT,CAAC;AACH;;;AEzLA,SAAS,kBAAkB;AAC3B,SAAS,OAAO,YAAAC,WAAU,QAAQ,MAAAC,KAAI,MAAM,iBAAiB;AAC7D,SAAS,QAAAC,OAAM,eAAe;;;ACF9B,SAAS,UAAU,eAAe;AAClC,SAAS,QAAAC,OAAM,gBAAgB;AAM/B,eAAsB,aACpB,WAC8B;AAC9B,QAAM,SAAS,oBAAI,IAAoB;AAEvC,iBAAe,MAAM,SAAgC;AACnD,eAAW,SAAS,MAAM,QAAQ,SAAS,EAAE,eAAe,KAAK,CAAC,GAAG;AACnE,YAAM,WAAWA,MAAK,SAAS,MAAM,IAAI;AAEzC,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,MAAM,QAAQ;AACpB;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS,WAAW,QAAQ,EAAE,WAAW,MAAM,GAAG;AAAA,QAClD,MAAM,SAAS,QAAQ;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,SAAS;AAErB,SAAO;AACT;;;ADnBA,eAAsB,YACpB,QACA,SACiB;AACjB,QAAM,aAAa,MAAMC,UAAS,MAAM;AAExC,SAAO,WAAW,QAAQ,EACvB,OAAO,UAAU,EACjB,OAAO,KAAK,UAAU,OAAO,CAAC,EAC9B,OAAO,KAAK;AACjB;AAEA,eAAsB,UACpB,WACA,KACgC;AAChC,QAAM,YAAYC,MAAK,WAAW,OAAO,GAAG;AAE5C,MAAI;AACF,UAAM,KAAK,SAAS;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,KAAK;AAAA,IACpB,MAAMD,UAASC,MAAK,WAAW,eAAe,GAAG,MAAM;AAAA,EACzD;AAEA,QAAM,QAAQ,MAAM,aAAa,SAAS;AAG1C,QAAM,OAAO,eAAe;AAE5B,SAAO;AAAA,IACL,eAAe,SAAS;AAAA,IAExB,UAAU,SAAS;AAAA,IAEnB;AAAA,EACF;AACF;AAEA,eAAsB,WACpB,WACA,KACA,QACe;AACf,QAAM,qBAAqBA,MAAK,WAAW,OAAO,GAAG,GAAG,MAAM;AAE9D,QAAM,iBAAiBA,MAAK,WAAW,OAAO,GAAG;AAEjD,QAAMC,IAAG,oBAAoB;AAAA,IAC3B,WAAW;AAAA,IACX,OAAO;AAAA,EACT,CAAC;AAED,QAAM,MAAM,oBAAoB;AAAA,IAC9B,WAAW;AAAA,EACb,CAAC;AAED,aAAW,CAAC,UAAU,IAAI,KAAK,OAAO,OAAO;AAC3C,UAAM,cAAcD,MAAK,oBAAoB,QAAQ;AAErD,UAAM,MAAM,QAAQ,aAAa,IAAI,GAAG;AAAA,MACtC,WAAW;AAAA,IACb,CAAC;AAED,UAAM,UAAU,aAAa,IAAI;AAAA,EACnC;AAEA,QAAM;AAAA,IACJA,MAAK,oBAAoB,eAAe;AAAA,IACxC,KAAK,UAAU;AAAA,MACb,eAAe,OAAO;AAAA,MACtB,UAAU,OAAO;AAAA,IACnB,CAAyB;AAAA,IACzB;AAAA,EACF;AAEA,QAAMC,IAAG,gBAAgB;AAAA,IACvB,WAAW;AAAA,IACX,OAAO;AAAA,EACT,CAAC;AAED,QAAM,OAAO,oBAAoB,cAAc;AACjD;;;AEjGA,SAAS,WAAAC,gBAAe;AAMjB,SAAS,QAAQ,UAA0B;AAChD,SACE,SACG,QAAQC,SAAQ,QAAQ,GAAG,EAAE,EAC7B,QAAQ,oBAAoB,GAAG,EAC/B,QAAQ,YAAY,EAAE,EACtB,YAAY,KAAK;AAExB;AAGO,SAAS,qBAAqB,UAAyC;AAC5E,QAAM,QAAQ,CAAC,WAAW,kBAAkB;AAE5C,aAAW,WAAW,UAAU;AAE9B,UAAM,YAAY,KAAK,MAAM,gBAAgB,QAAQ,OAAO,IAAI,IAAI;AAEpE,UAAM;AAAA,MACJ,+BAA+B,SAAS;AAAA,MACxC,GAAG,QAAQ,MAAM;AAAA,IACnB;AAAA,EACF;AAEA,SAAO,GAAG,MAAM,KAAK,IAAI,CAAC;AAAA;AAC5B;;;ALlBA,eAAsB,YACpB,QACA,WACA,SACoB;AACpB,QAAM,MAAM,MAAM,YAAY,QAAQ,OAAO;AAE7C,QAAM,SAAS,MAAM,UAAU,WAAW,GAAG;AAE7C,MAAI,QAAQ;AACV,YAAQ,IAAI,+BAA+B,MAAM,EAAE;AAEnD,WAAO;AAAA,EACT;AAEA,UAAQ,IAAI,8BAA8B,MAAM,EAAE;AAElD,QAAM,qBAAqB,MAAM,sBAAsB;AAEvD,MAAI;AACF,UAAM,YAAY,QAAQ,SAAS,QAAQC,SAAQ,MAAM,CAAC,CAAC;AAE3D,UAAM,gBAAgB,GAAG,SAAS,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC;AAEtD,QAAI;AAEJ,QAAI,QAAQ,SAAS,UAAU;AAC7B,YAAM,cAAc,QAAQ,oBAAoB,OAAO;AAEvD,iBAAW;AAAA,IACb,OAAO;AACL,iBAAW,WAAW,QAAQ,UAAU;AACtC,cAAM;AAAA,UACJ;AAAA,UACAC,MAAK,oBAAoB,GAAG,QAAQ,MAAM,GAAG;AAAA,UAC7C;AAAA,UACA;AAAA,YACE,QAAQ,QAAQ;AAAA,YAChB,SAAS,QAAQ;AAAA,UACnB;AAAA,QACF;AAAA,MACF;AAEA,YAAM,SAAS,qBAAqB,QAAQ,QAAQ;AAEpD,YAAMC,WAAUD,MAAK,oBAAoB,aAAa,GAAG,QAAQ,MAAM;AAEvE,iBAAW;AAAA,IACb;AAEA,UAAM,SAAoB;AAAA,MACxB;AAAA,MACA;AAAA,MACA,OAAO,MAAM,aAAa,kBAAkB;AAAA,IAC9C;AAEA,UAAM,WAAW,WAAW,KAAK,MAAM;AAEvC,YAAQ,IAAI,+BAA+B,aAAa,IAAI,QAAQ,EAAE;AAEtE,WAAO;AAAA,EACT,UAAE;AACA,UAAM,sBAAsB,kBAAkB;AAAA,EAChD;AACF;;;AMzEO,SAAS,QACd,SACA,QACA,SACM;AACN,QAAM,SAAS,GAAG,QAAQ,SAAS,IAAI,OAAO,aAAa;AAE3D,QAAM,iBAAiB,OAAO,MAAM,IAAI,OAAO,QAAQ;AAEvD,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,MAAM,sCAAsC,OAAO,QAAQ,EAAE;AAAA,EACzE;AAEA,UAAQ,SAAS;AAAA,IACf,MAAM;AAAA,IACN,UAAU,GAAG,MAAM,IAAI,OAAO,QAAQ;AAAA,IACtC,QAAQ;AAAA,EACV,CAAC;AAED,aAAW,CAAC,UAAU,IAAI,KAAK,OAAO,OAAO;AAC3C,QAAI,aAAa,OAAO,UAAU;AAChC;AAAA,IACF;AAEA,YAAQ,SAAS;AAAA,MACf,MAAM;AAAA,MACN,UAAU,GAAG,MAAM,IAAI,QAAQ;AAAA,MAC/B,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AACF;AAEO,SAAS,yBACd,QACA,SACQ;AACR,QAAM,OAAO,GAAG,QAAQ,SAAS,IAAI,OAAO,aAAa,IAAI,OAAO,QAAQ;AAY5E,SAAO,8BAA8B,KAAK,UAAU,IAAI,CAAC;AAC3D;;;ACxCA,IAAME,kBAAiB;AAEhB,SAAS,UAAU,OAAkC;AAC1D,QAAM,UAAU,eAAe,KAAK;AAEpC,MAAI;AAEJ,QAAM,kBAAkB,oBAAI,IAAoB;AAQhD,QAAM,YAAY,oBAAI,IAAqD;AAE3E,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,OAAO;AAAA,IAEP,SAAS;AAAA,IAET,eAAe,UAAU;AACvB,eAAS;AAAA,IACX;AAAA,IAEA,MAAM,UAAU,QAAQ,UAAU;AAChC,UAAI,CAAC,YAAY,CAAC,cAAc,MAAM,GAAG;AACvC,eAAO;AAAA,MACT;AAEA,YAAM,WAAW,MAAM,mBAAmB,QAAQ,UAAU,IAAI;AAEhE,UAAI,CAAC,UAAU;AACb,eAAO;AAAA,MACT;AAEA,YAAM,KAAK,mBAAmB,QAAQ;AAEtC,sBAAgB,IAAI,IAAI,QAAQ;AAEhC,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,KAAK,IAAI,aAAa;AAC1B,UAAI,CAAC,GAAG,WAAWA,eAAc,GAAG;AAClC,eAAO;AAAA,MACT;AAEA,YAAM,SAAS,gBAAgB,IAAI,EAAE,KAAK,oBAAoB,EAAE;AAEhE,UAAI,SAAS,UAAU,IAAI,MAAM;AAEjC,UAAI,CAAC,QAAQ;AACX,iBAAS,MAAM,YAAY,QAAQ,OAAO,UAAU,OAAO;AAE3D,kBAAU,IAAI,QAAQ,MAAM;AAAA,MAC9B;AAWA,UAAI,CAAC,aAAa,KAAK;AACrB,gBAAQ,MAAM,QAAQ,OAAO;AAAA,MAC/B;AAEA,YAAM,WAAW,yBAAyB,QAAQ,OAAO;AAEzD,aAAO;AAAA,yBACY,QAAQ;AAAA;AAAA,IAE7B;AAAA,EACF;AACF;;;AC3FA,IAAO,gBAAQ;","names":["extname","join","writeFile","readFile","rm","join","join","readFile","join","rm","extname","extname","extname","join","writeFile","VIRTUAL_PREFIX"]}
|