youtube-likes-downloader
Version:
Download all liked videos from youtube
112 lines (88 loc) • 3.63 kB
text/typescript
import { randomUUID } from 'crypto';
import fs from 'fs';
import os from 'os';
import path from 'path';
import { open } from 'out-url';
import { z } from 'zod';
import type { dataSchema } from './types/schema';
import { durationSchema, epochSchema, fileSchema, inputDataSchema, resolutionSchema, stringSchema, youtubeIdSchema } from './types/schema';
import '@anmiles/prototypes';
import { Cli } from './utils/cli';
import { formatJSON } from './utils/formatJSON';
import { formatTitle } from './utils/formatTitle';
import { getDownloadArchive, getOutputDir } from './utils/paths';
type InputData = z.infer<typeof inputDataSchema>;
type Data = z.infer<typeof dataSchema>;
export async function addVideo(profile: string): Promise<void> {
const cli = new Cli();
const {
id,
title,
channel,
duration,
resolution,
epoch,
videoFile,
imageFile,
descriptionFile,
} = await getData(cli);
cli.close();
const ext = path.extname(videoFile).slice(1);
const outputDir = getOutputDir(profile);
fs.ensureDir(outputDir, { create: true });
const filename = path.join(outputDir, `${formatTitle({ id, title, channel }).toFilename()}`);
const json = formatJSON({ id, title, channel, duration, resolution, epoch, ext });
await fs.promises.writeFile(`${filename}.info.json`, JSON.stringify(json));
await fs.promises.copyFile(path.resolve(videoFile), filename + path.extname(videoFile));
await fs.promises.copyFile(path.resolve(imageFile), filename + path.extname(imageFile));
await fs.promises.copyFile(path.resolve(descriptionFile), `${filename}.description`);
await fs.promises.appendFile(getDownloadArchive(profile), `youtube ${id}\n`);
}
async function getData(cli: Cli): Promise<Data> {
const data = await getJSONData(cli);
if (data) {
return data;
}
return {
id : await cli.getAnswer('Youtube link', youtubeIdSchema),
title : await cli.getAnswer('Title', stringSchema),
channel : await cli.getAnswer('Channel', stringSchema),
duration : await cli.getAnswer('Duration', durationSchema),
resolution : await cli.getAnswer('Resolution', resolutionSchema),
epoch : await cli.getAnswer('Date', epochSchema),
videoFile : await cli.getAnswer('Video file', fileSchema),
imageFile : await cli.getAnswer('Image file', fileSchema),
descriptionFile: await cli.getAnswer('Description file', fileSchema),
};
}
async function getJSONData(cli: Cli): Promise<Data | undefined> {
const inputData = await cli.getAnswer('Input JSON (optional)', inputDataSchema.or(z.undefined()), JSON.parse);
if (!inputData) {
return undefined;
}
const videoFile = await cli.getAnswer('Video file', fileSchema);
cli.say('Opening thumbnail; please download it as an image file');
await open(inputData.thumbnail.split('?')[0]!);
const imageFile = await cli.getAnswer('Image file', fileSchema);
const descriptionFile = createTempFilePath('txt');
await fs.promises.writeFile(descriptionFile, inputData.description);
return transformInputData(inputData, videoFile, imageFile, descriptionFile);
}
function createTempFilePath(ext: string): string {
const tmp = os.tmpdir();
const filename = randomUUID();
return path.join(tmp, `${filename}.${ext}`);
}
function transformInputData(inputData: InputData, videoFile: string, imageFile: string, descriptionFile: string): Data {
return {
id : inputData.id,
title : inputData.title,
channel : inputData.channel,
duration : inputData.duration,
resolution: inputData.resolution,
epoch : inputData.epoch,
videoFile,
imageFile,
descriptionFile,
};
}