@yemreak/yt-dlp
Version:
Downloading videos and subtitles using yt-dlp, with utilities for extracting text from subtitles
130 lines • 4.88 kB
JavaScript
import fs from "fs";
import os from "os";
import { downloadLatestRelease, execYtDlp, extractTextFromJson3Subtitle, extractTextFromSrtSubtitle, extractTextFromVttSubtitle, parseFilenamesFromOutput, } from "./helpers.js";
import { downloadFile } from "./utils.js";
export class YtDlp {
config;
filename = os.platform() === "win32" ? "yt-dlp.exe" : "yt-dlp";
binaryPath;
constructor(config) {
this.config = config;
this.binaryPath = `${config.workdir}/${this.filename}`;
fs.mkdirSync(config.workdir, { recursive: true });
}
/**
* Downloads the latest release of yt-dlp from GitHub
*/
async downloadLatestReleaseIfNotExists() {
if (fs.existsSync(this.binaryPath))
return;
await downloadLatestRelease(this.config.workdir);
}
/**
* Returns multiple {@link MediaInfo} objects if contains multiple videos
*/
async retrieveMediaInfoList(url) {
const { stdout } = await this.exec({ dumpJson: true, url });
return stdout
.trim()
.split("\n")
.map(line => JSON.parse(line));
}
async exec(optionals) {
return await execYtDlp({
binaryPath: this.binaryPath,
cookies: this.config.cookies,
...optionals,
});
}
async downloadAudio(params) {
return await this.download({ format: "ba", ...params });
}
async download(params) {
const { url, ...optionals } = params;
const pattern = "%(id)s.%(ext)s";
const symPath = `${this.config.workdir}/${pattern}`;
const { stdout, stderr } = await this.exec({
url,
outputPath: symPath,
...optionals,
});
if (stderr && stderr.includes("ERROR"))
throw new Error(stderr);
const mediaPaths = parseFilenamesFromOutput(stdout);
return mediaPaths;
}
async retrieveMediaInfoFromSource(source) {
if (typeof source === "string")
return await this.retrieveMediaInfoList(source);
return source;
}
/**
* Downloads the subtitle file of a video
*/
async downloadSubtitle(params) {
const { info, lang } = params;
const { stdout } = await this.exec({
url: info.original_url,
subtitle: { auto: true, lang },
outputPath: `${this.config.workdir}/${info.id}.%(lang)s.%(ext)s`,
});
const subtitlePath = parseFilenamesFromOutput(stdout)[0].replace(".vtt", ".srt");
return subtitlePath;
}
/**
* Downloads the subtitle file of a video without using yt-dlp
*/
async downloadSubtitleWithoutYtDlp(params) {
const { info, lang = "en" } = params;
const keys = Object.keys(info.subtitles);
if (keys.length === 0) {
const { stdout } = await this.exec({
url: info.original_url,
subtitle: { lang, auto: true },
outputPath: `${this.config.workdir}/${info.id}`,
});
const subtitlePath = parseFilenamesFromOutput(stdout)[0].replace(".vtt", ".srt");
return subtitlePath;
}
const key = keys.find(key => key.includes(lang));
if (!key)
throw new Error(`No subtitles found for ${lang}`);
const subtitle = info.subtitles[key]?.find(sub => sub.ext === "json3");
if (subtitle) {
const subtitlePath = `${this.config.workdir}/${info.id}.${subtitle.ext}`;
if (fs.existsSync(subtitlePath))
return subtitlePath;
await downloadFile(subtitle.url, subtitlePath);
return subtitlePath;
}
}
/**
* Extracts the text from a `json3`, `vtt` or `srt` subtitle file
*/
extractTextFromSubtitles(subtitlePath) {
const subtitleFile = fs.readFileSync(subtitlePath, "utf-8");
if (subtitlePath.endsWith(".json3")) {
const subtitleData = JSON.parse(subtitleFile);
return extractTextFromJson3Subtitle(subtitleData);
}
else if (subtitlePath.endsWith(".vtt")) {
return extractTextFromVttSubtitle(subtitleFile);
}
else if (subtitlePath.endsWith(".srt")) {
return extractTextFromSrtSubtitle(subtitleFile);
}
throw new Error(`Unsupported subtitle format: "${subtitlePath}"`);
}
/**
* Downloads and extracts the text from a subtitle file
* - If `source` is a video URL, retrieves the {@link MediaInfo} from the video URL
*/
async downloadSubtitleText(params) {
const subtitlePath = await this.downloadSubtitle(params);
if (!subtitlePath)
return;
const subtitleText = this.extractTextFromSubtitles(subtitlePath);
return subtitleText;
}
}
//# sourceMappingURL=index.js.map