Code refactoring

This commit is contained in:
2022-08-29 22:44:06 +02:00
parent 32bc925fb7
commit d99899eb16
12 changed files with 62 additions and 50 deletions

View File

@@ -9,6 +9,12 @@ Developed to be as lightweight as possible (because my server is basically an in
The bottleneck remains yt-dlp startup time (until yt-dlp will provide a rpc interface). The bottleneck remains yt-dlp startup time (until yt-dlp will provide a rpc interface).
**I strongly recomend the ghrc build instead of docker hub one.**
```shell
docker pull ghcr.io/marcopeocchi/yt-dlp-web-ui:master
```
--- ---
Changelog: Changelog:
@@ -86,11 +92,17 @@ Future releases will have:
## Docker installation ## Docker installation
```shell ```shell
docker pull marcobaobao/yt-dlp-webui:latest #x86 only # recomended for ARM and x86 devices
# or alternatively for ARM and x86 devices docker pull ghcr.io/marcopeocchi/yt-dlp-web-ui:master docker pull ghcr.io/marcopeocchi/yt-dlp-web-ui:master
docker run -d -p 3022:3022 -v <your dir>:/usr/src/yt-dlp-webui/downloads marcobaobao/yt-dlp-webui docker run -d -p 3022:3022 -v <your dir>:/usr/src/yt-dlp-webui/downloads ghcr.io/marcopeocchi/yt-dlp-web-ui:master
# or even
docker pull ghcr.io/marcopeocchi/yt-dlp-web-ui:master
docker create --name yt-dlp-webui -p 8082:3022 -v <your dir>:/usr/src/yt-dlp-webui/ ghcr.io/marcopeocchi/yt-dlp-web-ui:master
``` ```
or or
```shell ```shell
docker build -t yt-dlp-webui . docker build -t yt-dlp-webui .
docker run -d -p 3022:3022 -v <your dir>:/usr/src/yt-dlp-webui/downloads yt-dlp-webui docker run -d -p 3022:3022 -v <your dir>:/usr/src/yt-dlp-webui/downloads yt-dlp-webui
@@ -103,7 +115,7 @@ docker run -d -p 3022:3022 -v <your dir>:/usr/src/yt-dlp-webui/downloads yt-dlp-
npm i npm i
npm run build-all npm run build-all
# edit the settings.json specifying the download path or # edit the settings.json specifying port and download path or
# it will default to the following created folder # it will default to the following created folder
mkdir downloads mkdir downloads

View File

@@ -18,7 +18,7 @@ import { useDispatch, useSelector } from "react-redux";
import { Socket } from "socket.io-client"; import { Socket } from "socket.io-client";
import { StackableResult } from "./components/StackableResult"; import { StackableResult } from "./components/StackableResult";
import { connected, downloading, finished } from "./features/status/statusSlice"; import { connected, downloading, finished } from "./features/status/statusSlice";
import { IDLInfo, IDLInfoBase, IDownloadInfo, IMessage } from "./interfaces"; import { IDLMetadata, IDLMetadataAndPID, IMessage } from "./interfaces";
import { RootState } from "./stores/store"; import { RootState } from "./stores/store";
import { isValidURL, toFormatArgs, updateInStateMap, } from "./utils"; import { isValidURL, toFormatArgs, updateInStateMap, } from "./utils";
import { FileUpload } from "@mui/icons-material"; import { FileUpload } from "@mui/icons-material";
@@ -37,8 +37,8 @@ export default function Home({ socket }: Props) {
// ephemeral state // ephemeral state
const [progressMap, setProgressMap] = useState(new Map<number, number>()); const [progressMap, setProgressMap] = useState(new Map<number, number>());
const [messageMap, setMessageMap] = useState(new Map<number, IMessage>()); const [messageMap, setMessageMap] = useState(new Map<number, IMessage>());
const [downloadInfoMap, setDownloadInfoMap] = useState(new Map<number, IDLInfoBase>()); const [downloadInfoMap, setDownloadInfoMap] = useState(new Map<number, IDLMetadata>());
const [downloadFormats, setDownloadFormats] = useState<IDownloadInfo>(); const [downloadFormats, setDownloadFormats] = useState<IDLMetadata>();
const [pickedVideoFormat, setPickedVideoFormat] = useState(''); const [pickedVideoFormat, setPickedVideoFormat] = useState('');
const [pickedAudioFormat, setPickedAudioFormat] = useState(''); const [pickedAudioFormat, setPickedAudioFormat] = useState('');
const [pickedBestFormat, setPickedBestFormat] = useState(''); const [pickedBestFormat, setPickedBestFormat] = useState('');
@@ -68,7 +68,7 @@ export default function Home({ socket }: Props) {
/* Handle download information sent by server */ /* Handle download information sent by server */
useEffect(() => { useEffect(() => {
socket.on('available-formats', (data: IDownloadInfo) => { socket.on('available-formats', (data: IDLMetadata) => {
setShowBackdrop(false) setShowBackdrop(false)
setDownloadFormats(data); setDownloadFormats(data);
}) })
@@ -76,10 +76,10 @@ export default function Home({ socket }: Props) {
/* Handle download information sent by server */ /* Handle download information sent by server */
useEffect(() => { useEffect(() => {
socket.on('info', (data: IDLInfo) => { socket.on('metadata', (data: IDLMetadataAndPID) => {
setShowBackdrop(false) setShowBackdrop(false)
dispatch(downloading()) dispatch(downloading())
updateInStateMap<number, IDLInfoBase>(data.pid, data.info, downloadInfoMap, setDownloadInfoMap); updateInStateMap<number, IDLMetadata>(data.pid, data.metadata, downloadInfoMap, setDownloadInfoMap);
}) })
}, []) }, [])
@@ -374,9 +374,9 @@ export default function Home({ socket }: Props) {
formattedLog={message[1]} formattedLog={message[1]}
title={downloadInfoMap.get(message[0])?.title ?? ''} title={downloadInfoMap.get(message[0])?.title ?? ''}
thumbnail={downloadInfoMap.get(message[0])?.thumbnail ?? ''} thumbnail={downloadInfoMap.get(message[0])?.thumbnail ?? ''}
resolution={downloadInfoMap.get(message[0])?.resolution ?? '...'}
progress={progressMap.get(message[0]) ?? 0} progress={progressMap.get(message[0]) ?? 0}
stopCallback={() => abort(message[0])} stopCallback={() => abort(message[0])}
resolution={''}
/> />
</Fragment> </Fragment>
</Grid> </Grid>

View File

@@ -6,22 +6,14 @@ export interface IMessage {
pid: number pid: number
} }
export interface IDLInfoBase { export interface IDLMetadata {
title: string, formats: Array<IDLFormat>,
thumbnail: string, best: IDLFormat,
upload_date?: string | Date,
duration?: number
resolution?: string
}
export interface IDownloadInfo {
formats: Array<IDownloadInfoSection>,
best: IDownloadInfoSection,
thumbnail: string, thumbnail: string,
title: string, title: string,
} }
export interface IDownloadInfoSection { export interface IDLFormat {
format_id: string, format_id: string,
format_note: string, format_note: string,
fps: number, fps: number,
@@ -30,9 +22,9 @@ export interface IDownloadInfoSection {
acodec: string, acodec: string,
} }
export interface IDLInfo { export interface IDLMetadataAndPID {
pid: number, pid: number,
info: IDLInfoBase metadata: IDLMetadata
} }
export interface IDLSpeed { export interface IDLSpeed {

View File

@@ -4,6 +4,7 @@ import { Readable } from 'stream';
import { ISettings } from '../interfaces/ISettings'; import { ISettings } from '../interfaces/ISettings';
import { availableParams } from '../utils/params'; import { availableParams } from '../utils/params';
import Logger from '../utils/BetterLogger'; import Logger from '../utils/BetterLogger';
import { IDownloadFormat, IDownloadMetadata } from '../interfaces/IDownloadMetadata';
const log = Logger.instance; const log = Logger.instance;
@@ -20,7 +21,7 @@ class Process {
private settings: ISettings; private settings: ISettings;
private stdout: Readable; private stdout: Readable;
private pid: number; private pid: number;
private metadata?: IDownloadInfo; private metadata?: IDownloadMetadata;
private exePath = join(__dirname, 'yt-dlp'); private exePath = join(__dirname, 'yt-dlp');
constructor(url: string, params: Array<string>, settings: any) { constructor(url: string, params: Array<string>, settings: any) {
@@ -59,7 +60,7 @@ class Process {
* function used internally by the download process to fetch information, usually thumbnail and title * function used internally by the download process to fetch information, usually thumbnail and title
* @returns Promise to the lock * @returns Promise to the lock
*/ */
public getInfo(): Promise<IDownloadInfo> { public getMetadata(): Promise<IDownloadMetadata> {
if (!this.metadata) { if (!this.metadata) {
let stdoutChunks = []; let stdoutChunks = [];
const ytdlpInfo = spawn(this.exePath, ['-j', this.url]); const ytdlpInfo = spawn(this.exePath, ['-j', this.url]);
@@ -74,7 +75,7 @@ class Process {
const buffer = Buffer.concat(stdoutChunks); const buffer = Buffer.concat(stdoutChunks);
const json = JSON.parse(buffer.toString()); const json = JSON.parse(buffer.toString());
const info = { const info = {
formats: json.formats.map((format: IDownloadInfoSection) => { formats: json.formats.map((format: IDownloadFormat) => {
return { return {
format_id: format.format_id ?? '', format_id: format.format_id ?? '',
format_note: format.format_note ?? '', format_note: format.format_note ?? '',
@@ -83,7 +84,7 @@ class Process {
vcodec: format.vcodec ?? '', vcodec: format.vcodec ?? '',
acodec: format.acodec ?? '', acodec: format.acodec ?? '',
} }
}).filter((format: IDownloadInfoSection) => format.format_note !== 'storyboard'), }).filter((format: IDownloadFormat) => format.format_note !== 'storyboard'),
best: { best: {
format_id: json.format_id ?? '', format_id: json.format_id ?? '',
format_note: json.format_note ?? '', format_note: json.format_note ?? '',

View File

@@ -27,9 +27,9 @@ catch (e) {
* @param socket * @param socket
* @param url * @param url
*/ */
export async function getFormatsAndInfo(socket: Socket, url: string) { export async function getFormatsAndMetadata(socket: Socket, url: string) {
let p = new Process(url, [], settings); let p = new Process(url, [], settings);
const formats = await p.getInfo(); const formats = await p.getMetadata();
socket.emit('available-formats', formats) socket.emit('available-formats', formats)
p = null; p = null;
} }
@@ -56,7 +56,7 @@ export async function download(socket: Socket, payload: IPayload) {
p.start().then(downloader => { p.start().then(downloader => {
mem_db.add(downloader) mem_db.add(downloader)
displayDownloadInfo(downloader, socket); displayDownloadMetadata(downloader, socket);
streamProcess(downloader, socket); streamProcess(downloader, socket);
}); });
} }
@@ -66,11 +66,11 @@ export async function download(socket: Socket, payload: IPayload) {
* @param process * @param process
* @param socket * @param socket
*/ */
function displayDownloadInfo(process: Process, socket: Socket) { function displayDownloadMetadata(process: Process, socket: Socket) {
process.getInfo().then(info => { process.getMetadata().then(metadata => {
socket.emit('info', { socket.emit('metadata', {
pid: process.getPid(), pid: process.getPid(),
info: info metadata: metadata,
}); });
}); });
} }
@@ -87,11 +87,8 @@ function streamProcess(process: Process, socket: Socket) {
pid: process.getPid(), pid: process.getPid(),
}); });
} }
const stdout = process.getStdout()
stdout.removeAllListeners() from(process.getStdout().removeAllListeners()) // stdout as observable
from(stdout) // stdout as observable
.pipe( .pipe(
throttle(() => interval(500)), // discard events closer than 500ms throttle(() => interval(500)), // discard events closer than 500ms
map(stdout => formatter(String(stdout), process.getPid())) map(stdout => formatter(String(stdout), process.getPid()))
@@ -148,7 +145,7 @@ export async function retrieveDownload(socket: Socket) {
// resume the jobs // resume the jobs
for (const entry of it) { for (const entry of it) {
const [, process] = entry const [, process] = entry
displayDownloadInfo(process, socket); displayDownloadMetadata(process, socket);
streamProcess(process, socket); streamProcess(process, socket);
} }
} }

View File

@@ -1,11 +1,11 @@
interface IDownloadInfo { export interface IDownloadMetadata {
formats: Array<IDownloadInfoSection>, formats: Array<IDownloadFormat>,
best: IDownloadInfoSection, best: IDownloadFormat,
thumbnail: string, thumbnail: string,
title: string, title: string,
} }
interface IDownloadInfoSection { export interface IDownloadFormat {
format_id: string, format_id: string,
format_note: string, format_note: string,
fps: number, fps: number,

View File

@@ -1,4 +1,5 @@
export interface ISettings { export interface ISettings {
download_path: string, download_path: string,
cliArgs?: string[], cliArgs?: string[],
port?: number,
} }

View File

@@ -2,7 +2,7 @@ import { splash } from './utils/logger';
import { join } from 'path'; import { join } from 'path';
import { Server } from 'socket.io'; import { Server } from 'socket.io';
import { ytdlpUpdater } from './utils/updater'; import { ytdlpUpdater } from './utils/updater';
import { download, abortDownload, retrieveDownload, abortAllDownloads, getFormatsAndInfo } from './core/downloader'; import { download, abortDownload, retrieveDownload, abortAllDownloads, getFormatsAndMetadata } from './core/downloader';
import { getFreeDiskSpace } from './utils/procUtils'; import { getFreeDiskSpace } from './utils/procUtils';
import { listDownloaded } from './core/downloadArchive'; import { listDownloaded } from './core/downloadArchive';
import { createServer } from 'http'; import { createServer } from 'http';
@@ -12,6 +12,7 @@ import * as Router from 'koa-router';
import * as serve from 'koa-static'; import * as serve from 'koa-static';
import * as cors from '@koa/cors'; import * as cors from '@koa/cors';
import Logger from './utils/BetterLogger'; import Logger from './utils/BetterLogger';
import { ISettings } from './interfaces/ISettings';
const app = new Koa(); const app = new Koa();
const server = createServer(app.callback()); const server = createServer(app.callback());
@@ -24,6 +25,14 @@ const io = new Server(server, {
} }
}); });
let settings: ISettings;
try {
settings = require('../settings.json');
} catch (e) {
log.warn('settings', 'file not found, ignore if using Docker');
}
// Koa routing // Koa routing
router.get('/settings', (ctx, next) => { router.get('/settings', (ctx, next) => {
ctx.redirect('/') ctx.redirect('/')
@@ -49,7 +58,6 @@ router.get('/stream/:filepath', (ctx, next) => {
}) })
// WebSocket listeners // WebSocket listeners
io.on('connection', socket => { io.on('connection', socket => {
log.info('ws', `${socket.handshake.address} connected!`) log.info('ws', `${socket.handshake.address} connected!`)
@@ -59,7 +67,7 @@ io.on('connection', socket => {
}) })
socket.on('send-url-format-selection', (args) => { socket.on('send-url-format-selection', (args) => {
log.info('ws', `Formats ${args?.url}`) log.info('ws', `Formats ${args?.url}`)
if (args.url) getFormatsAndInfo(socket, args?.url) if (args.url) getFormatsAndMetadata(socket, args?.url)
}) })
socket.on('abort', (args) => { socket.on('abort', (args) => {
abortDownload(socket, args) abortDownload(socket, args)
@@ -86,10 +94,10 @@ app.use(serve(join(__dirname, 'frontend')))
app.use(cors()) app.use(cors())
app.use(router.routes()) app.use(router.routes())
server.listen(process.env.PORT || 3022) server.listen(process.env.PORT || settings.port || 3022)
splash() splash()
log.info('http', `Server started on port ${process.env.PORT || 3022}`) log.info('http', `Server started on port ${process.env.PORT || settings.port || 3022}`)
/** /**
* Cleanup handler * Cleanup handler

View File

@@ -1,4 +1,5 @@
{ {
"port": 0,
"download_path": "", "download_path": "",
"cliArgs": [] "cliArgs": []
} }