Code refactoring and set up format selection

Set up format selection and archive download for next releases
This commit is contained in:
2022-06-02 17:01:26 +02:00
parent 413b89166b
commit 975784ed72
18 changed files with 298 additions and 101 deletions

View File

@@ -39,9 +39,7 @@ class Process {
* @param callback not yet implemented
* @returns the process instance
*/
async start(callback?: Function): Promise<this> {
await this.internalGetInfo();
public async start(callback?: Function): Promise<this> {
const sanitizedParams = this.params.filter((param: string) => availableParams.includes(param));
const ytldp = spawn(this.exePath,
@@ -59,37 +57,50 @@ class Process {
}
/**
* @private
* function used internally by the download process to fetch information, usually thumbnail and title
* @returns Promise to the lock
*/
private async internalGetInfo() {
this.lock = true;
public getInfo(): Promise<IDownloadInfo> {
let stdoutChunks = [];
const ytdlpInfo = spawn(this.exePath, ['-s', '-j', this.url]);
const ytdlpInfo = spawn(this.exePath, ['-j', this.url]);
ytdlpInfo.stdout.on('data', (data) => {
stdoutChunks.push(data);
});
ytdlpInfo.on('exit', () => {
try {
const buffer = Buffer.concat(stdoutChunks);
const json = JSON.parse(buffer.toString());
this.info = json;
this.lock = false;
return new Promise((resolve, reject) => {
ytdlpInfo.on('exit', () => {
try {
const buffer = Buffer.concat(stdoutChunks);
const json = JSON.parse(buffer.toString());
this.info = json;
this.lock = false;
resolve({
formats: json.formats.map((format: IDownloadInfoSection) => {
return {
format_id: format.format_id ?? '',
format_note: format.format_note ?? '',
fps: format.fps ?? '',
resolution: format.resolution ?? '',
vcodec: format.vcodec ?? '',
}
}),
best: {
format_id: json.format_id ?? '',
format_note: json.format_note ?? '',
fps: json.fps ?? '',
resolution: json.resolution ?? '',
vcodec: json.vcodec ?? '',
},
thumbnail: json.thumbnail,
title: json.title,
});
} catch (e) {
this.info = {
title: "",
thumbnail: "",
};
}
});
if (!this.lock) {
return true;
}
} catch (e) {
reject('failed fetching formats, downloading best available');
}
});
})
}
/**
@@ -119,14 +130,6 @@ class Process {
getStdout(): Readable {
return this.stdout
}
/**
* download info getter function
* @returns {*}
*/
getInfo(): any {
return this.info
}
}
export default Process;

View File

@@ -0,0 +1,15 @@
import { resolve } from "path";
const archived = [
{
id: 1,
title: 'AleXa (알렉사)  Voting Open in American Song Contest Grand Final!',
path: resolve('downloads/AleXa (알렉사)  Voting Open in American Song Contest Grand Final!.webm'),
img: 'https://i.ytimg.com/vi/WbBUz7pjUnM/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAi5MNtvpgnY9aRpdFlhAfhdV7Zlg',
},
]
export function listDownloaded(ctx: any, next: any) {
ctx.body = archived
next()
}

View File

@@ -23,6 +23,18 @@ catch (e) {
new Promise(resolve => setTimeout(resolve, 500))
.then(() => log.warn('dl', 'settings.json not found, ignore if using Docker'));
}
/**
* Get download info such as thumbnail, title, resolution and list all formats
* @param socket
* @param url
*/
export async function getFormatsAndInfo(socket: Socket, url: string) {
let p = new Process(url, [], settings);
const formats = await p.getInfo();
console.log(formats)
socket.emit('available-formats', formats)
p = null;
}
/**
* Invoke a new download.
@@ -42,27 +54,21 @@ export async function download(socket: Socket, payload: IPayload) {
payload.params.split(' ') :
payload.params;
const p = new Process(url, params, settings);
let p = new Process(url, params, settings);
p.start().then(downloader => {
pool.add(p)
let infoLock = true;
let pid = downloader.getPid();
p.getInfo().then(info => {
socket.emit('info', { pid: pid, info: info });
});
from(downloader.getStdout()) // stdout as observable
.pipe(throttle(() => interval(500))) // discard events closer than 500ms
.subscribe({
next: (stdout) => {
if (infoLock) {
if (downloader.getInfo() === null) {
return;
}
socket.emit('info', {
pid: pid, info: downloader.getInfo()
});
infoLock = false;
}
socket.emit('progress', formatter(String(stdout), pid)) // finally, emit
socket.emit('progress', formatter(String(stdout), pid))
},
complete: () => {
downloader.kill().then(() => {
@@ -79,11 +85,10 @@ export async function download(socket: Socket, payload: IPayload) {
});
}
});
})
});
}
/**
* @deprecated
* Retrieve all downloads.
* If the server has just been launched retrieve the ones saved to the database.
* If the server is running fetches them from the process pool.

View File

@@ -0,0 +1,39 @@
import { stat, createReadStream } from 'fs';
import { lookup } from 'mime-types';
export function streamer(ctx: any, next: any) {
const filepath = '/Users/marco/dev/homebrew/yt-dlp-web-ui/downloads/AleXa (알렉사) Voting Open in American Song Contest Grand Final!.webm'
stat(filepath, (err, stat) => {
if (err) {
ctx.response.status = 404;
ctx.body = { err: 'resource not found' };
next();
}
const fileSize = stat.size;
const range = ctx.headers.range;
if (range) {
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
const chunksize = end - start + 1;
const file = createReadStream(filepath, { start, end });
const head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': lookup(filepath)
};
ctx.res.writeHead(206, head);
file.pipe(ctx.res);
next();
} else {
const head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4'
};
ctx.res.writeHead(200, head);
createReadStream(ctx.params.filepath).pipe(ctx.res);
next();
}
});
}