@pagepocket/plugin-yt-dlp 0.8.4 → 0.8.6

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/dist/index.d.ts CHANGED
@@ -1 +1,3 @@
1
+ export { default } from "./yt-dlp-plugin.js";
1
2
  export * from "./yt-dlp-plugin.js";
3
+ export { setup } from "./setup.js";
package/dist/index.js CHANGED
@@ -1 +1,3 @@
1
+ export { default } from "./yt-dlp-plugin.js";
1
2
  export * from "./yt-dlp-plugin.js";
3
+ export { setup } from "./setup.js";
@@ -0,0 +1,6 @@
1
+ export type BinaryPathJson = {
2
+ "yt-dlp": string;
3
+ ffmpeg: string;
4
+ };
5
+ export declare const resolveBinaryPathJsonFilePath: () => string;
6
+ export declare const setup: () => Promise<void>;
package/dist/setup.js ADDED
@@ -0,0 +1,53 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { helpers } from "ytdlp-nodejs";
5
+ import { findExecutable } from "./utils/find-executable.js";
6
+ const findNearestDirWithPackageJson = (startDir) => {
7
+ let dir = startDir;
8
+ for (;;) {
9
+ const pkgJsonPath = path.join(dir, "package.json");
10
+ if (fs.existsSync(pkgJsonPath)) {
11
+ return dir;
12
+ }
13
+ const parent = path.dirname(dir);
14
+ if (parent === dir) {
15
+ return startDir;
16
+ }
17
+ dir = parent;
18
+ }
19
+ };
20
+ const writeJsonAtomic = async (filePath, value) => {
21
+ const dir = path.dirname(filePath);
22
+ await fs.promises.mkdir(dir, { recursive: true });
23
+ const tmpPath = `${filePath}.tmp`;
24
+ await fs.promises.writeFile(tmpPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
25
+ await fs.promises.rename(tmpPath, filePath);
26
+ };
27
+ export const resolveBinaryPathJsonFilePath = () => {
28
+ const thisDir = path.dirname(fileURLToPath(import.meta.url));
29
+ const packageRoot = findNearestDirWithPackageJson(thisDir);
30
+ return path.join(packageRoot, "binary-path.json");
31
+ };
32
+ export const setup = async () => {
33
+ console.info("Checking yt-dlp/ffmpeg on PATH...");
34
+ const ytdlpPath = await findExecutable("yt-dlp");
35
+ const ffmpegPath = await findExecutable("ffmpeg");
36
+ const outPath = resolveBinaryPathJsonFilePath();
37
+ if (ytdlpPath && ffmpegPath) {
38
+ console.info("Found yt-dlp/ffmpeg; writing binary-path.json");
39
+ const json = { "yt-dlp": ytdlpPath, ffmpeg: ffmpegPath };
40
+ await writeJsonAtomic(outPath, json);
41
+ return;
42
+ }
43
+ console.info("yt-dlp/ffmpeg not found; downloading via ytdlp-nodejs...");
44
+ const downloadedYtdlpPath = await helpers.downloadYtDlp();
45
+ const downloadedFfmpegPath = await helpers.downloadFFmpeg();
46
+ const resolvedFfmpegPath = downloadedFfmpegPath ?? helpers.findFFmpegBinary();
47
+ if (!resolvedFfmpegPath) {
48
+ throw new Error("Failed to download or locate ffmpeg via ytdlp-nodejs helpers.");
49
+ }
50
+ const json = { "yt-dlp": downloadedYtdlpPath, ffmpeg: resolvedFfmpegPath };
51
+ console.info("Download complete; writing binary-path.json");
52
+ await writeJsonAtomic(outPath, json);
53
+ };
@@ -0,0 +1,7 @@
1
+ type FindExecutableOptions = {
2
+ pathEnv?: string;
3
+ pathExtEnv?: string;
4
+ cwd?: string;
5
+ };
6
+ export declare const findExecutable: (command: string, options?: FindExecutableOptions) => Promise<string | null>;
7
+ export {};
@@ -0,0 +1,68 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ const hasPathSeparator = (value) => value.includes("/") || value.includes("\\");
4
+ const getWindowsPathExts = (pathExtEnv) => {
5
+ const raw = (pathExtEnv ?? process.env.PATHEXT ?? "").trim();
6
+ const defaults = [".COM", ".EXE", ".BAT", ".CMD"];
7
+ if (!raw) {
8
+ return defaults;
9
+ }
10
+ const extList = raw
11
+ .split(";")
12
+ .map((s) => s.trim())
13
+ .filter(Boolean)
14
+ .map((s) => (s.startsWith(".") ? s : `.${s}`));
15
+ return extList.length > 0 ? extList : defaults;
16
+ };
17
+ const isExecutableFile = async (absPath) => {
18
+ try {
19
+ const stat = await fs.stat(absPath);
20
+ if (!stat.isFile()) {
21
+ return false;
22
+ }
23
+ if (process.platform === "win32") {
24
+ return true;
25
+ }
26
+ await fs.access(absPath, (await import("node:fs")).constants.X_OK);
27
+ return true;
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ };
33
+ const buildCandidateNames = (command, options) => {
34
+ if (process.platform !== "win32") {
35
+ return [command];
36
+ }
37
+ if (path.extname(command)) {
38
+ return [command];
39
+ }
40
+ const exts = getWindowsPathExts(options.pathExtEnv);
41
+ return exts.map((ext) => `${command}${ext.toLowerCase()}`);
42
+ };
43
+ export const findExecutable = async (command, options = {}) => {
44
+ const trimmed = command.trim();
45
+ if (!trimmed) {
46
+ return null;
47
+ }
48
+ const cwd = options.cwd ?? process.cwd();
49
+ if (hasPathSeparator(trimmed)) {
50
+ const abs = path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);
51
+ return (await isExecutableFile(abs)) ? abs : null;
52
+ }
53
+ const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
54
+ const dirs = pathEnv
55
+ .split(path.delimiter)
56
+ .map((s) => s.trim())
57
+ .filter(Boolean);
58
+ const candidateNames = buildCandidateNames(trimmed, options);
59
+ for (const dir of dirs) {
60
+ for (const name of candidateNames) {
61
+ const abs = path.join(dir, name);
62
+ if (await isExecutableFile(abs)) {
63
+ return abs;
64
+ }
65
+ }
66
+ }
67
+ return null;
68
+ };
@@ -1,11 +1,51 @@
1
+ import { YtDlp, helpers } from "ytdlp-nodejs";
2
+ import { resolveBinaryPathJsonFilePath } from "../setup.js";
3
+ const isBinaryPathJson = (value) => {
4
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5
+ return false;
6
+ }
7
+ const v = value;
8
+ return typeof v["yt-dlp"] === "string" && typeof v.ffmpeg === "string";
9
+ };
10
+ const readBinaryPathJson = async () => {
11
+ try {
12
+ const fs = await import("node:fs/promises");
13
+ const filePath = resolveBinaryPathJsonFilePath();
14
+ const text = await fs.readFile(filePath, "utf8");
15
+ const parsed = JSON.parse(text);
16
+ return isBinaryPathJson(parsed) ? parsed : null;
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ };
1
22
  export const downloadVideosAsBytes = async (jobs) => {
2
- const { YtDlp, helpers } = await import("ytdlp-nodejs");
3
23
  const fs = await import("node:fs/promises");
4
24
  const pathMod = await import("node:path");
5
25
  const os = await import("node:os");
6
- const ytdlp = new YtDlp();
7
- await helpers.downloadYtDlp();
8
- await helpers.downloadFFmpeg();
26
+ const binaryPathJson = await readBinaryPathJson();
27
+ if (!binaryPathJson) {
28
+ console.info("binary-path.json missing/invalid; downloading yt-dlp/ffmpeg via ytdlp-nodejs...");
29
+ await helpers.downloadYtDlp();
30
+ await helpers.downloadFFmpeg();
31
+ const resolvedYtdlpPath = helpers.findYtdlpBinary();
32
+ const resolvedFfmpegPath = helpers.findFFmpegBinary();
33
+ if (!resolvedYtdlpPath || !resolvedFfmpegPath) {
34
+ throw new Error("Failed to locate yt-dlp/ffmpeg after download.");
35
+ }
36
+ console.info("Binaries ready; starting yt-dlp downloads");
37
+ const ytdlp = new YtDlp({ binaryPath: resolvedYtdlpPath, ffmpegPath: resolvedFfmpegPath });
38
+ return await downloadWithYtdlp({ jobs, ytdlp, fs, pathMod, os });
39
+ }
40
+ const ytdlp = new YtDlp({
41
+ binaryPath: binaryPathJson["yt-dlp"],
42
+ ffmpegPath: binaryPathJson.ffmpeg
43
+ });
44
+ console.info("Using binary-path.json; starting yt-dlp downloads");
45
+ return await downloadWithYtdlp({ jobs, ytdlp, fs, pathMod, os });
46
+ };
47
+ const downloadWithYtdlp = async (input) => {
48
+ const { jobs, ytdlp, fs, pathMod, os } = input;
9
49
  const bytesById = new Map();
10
50
  await Promise.all(jobs.map(async (job) => {
11
51
  const tmpDir = await fs.mkdtemp(pathMod.join(os.tmpdir(), "pagepocket-ytdlp-"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagepocket/plugin-yt-dlp",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "PagePocket plugin: download YouTube embeds and replace with <video>",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,9 +11,9 @@
11
11
  "license": "ISC",
12
12
  "dependencies": {
13
13
  "ytdlp-nodejs": "^3.4.2",
14
- "@pagepocket/lib": "0.8.4",
15
- "@pagepocket/contracts": "0.8.4",
16
- "@pagepocket/shared": "0.8.4"
14
+ "@pagepocket/lib": "0.8.6",
15
+ "@pagepocket/contracts": "0.8.6",
16
+ "@pagepocket/shared": "0.8.6"
17
17
  },
18
18
  "devDependencies": {
19
19
  "typescript": "^5.4.5"