optimized and future-proofed stdout parser
This commit is contained in:
@@ -1,21 +1,21 @@
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Container,
|
Container,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Grid,
|
Grid,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Paper,
|
Paper,
|
||||||
Select,
|
Select,
|
||||||
SelectChangeEvent,
|
SelectChangeEvent,
|
||||||
Snackbar,
|
Snackbar,
|
||||||
Stack,
|
Stack,
|
||||||
Switch,
|
Switch,
|
||||||
TextField,
|
TextField,
|
||||||
Typography
|
Typography
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
@@ -23,14 +23,14 @@ import { debounceTime, distinctUntilChanged, map, of, takeWhile } from "rxjs";
|
|||||||
import { Socket } from "socket.io-client";
|
import { Socket } from "socket.io-client";
|
||||||
import { CliArguments } from "./classes";
|
import { CliArguments } from "./classes";
|
||||||
import {
|
import {
|
||||||
LanguageUnion,
|
LanguageUnion,
|
||||||
setCliArgs,
|
setCliArgs,
|
||||||
setFormatSelection,
|
setFormatSelection,
|
||||||
setLanguage,
|
setLanguage,
|
||||||
setServerAddr,
|
setServerAddr,
|
||||||
setServerPort,
|
setServerPort,
|
||||||
setTheme,
|
setTheme,
|
||||||
ThemeUnion
|
ThemeUnion
|
||||||
} from "./features/settings/settingsSlice";
|
} from "./features/settings/settingsSlice";
|
||||||
import { alreadyUpdated, updated } from "./features/status/statusSlice";
|
import { alreadyUpdated, updated } from "./features/status/statusSlice";
|
||||||
import { I18nBuilder } from "./i18n";
|
import { I18nBuilder } from "./i18n";
|
||||||
@@ -38,220 +38,220 @@ import { RootState } from "./stores/store";
|
|||||||
import { validateDomain, validateIP } from "./utils";
|
import { validateDomain, validateIP } from "./utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
socket: Socket
|
socket: Socket
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Settings({ socket }: Props) {
|
export default function Settings({ socket }: Props) {
|
||||||
const settings = useSelector((state: RootState) => state.settings)
|
const settings = useSelector((state: RootState) => state.settings)
|
||||||
const status = useSelector((state: RootState) => state.status)
|
const status = useSelector((state: RootState) => state.status)
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
const [invalidIP, setInvalidIP] = useState(false);
|
const [invalidIP, setInvalidIP] = useState(false);
|
||||||
|
|
||||||
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language])
|
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language])
|
||||||
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [settings.cliArgs])
|
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [settings.cliArgs])
|
||||||
/**
|
/**
|
||||||
* Update the server ip address state and localstorage whenever the input value changes.
|
* Update the server ip address state and localstorage whenever the input value changes.
|
||||||
* Validate the ip-addr then set.s
|
* Validate the ip-addr then set.s
|
||||||
* @param event Input change event
|
* @param event Input change event
|
||||||
*/
|
*/
|
||||||
const handleAddrChange = (event: any) => {
|
const handleAddrChange = (event: any) => {
|
||||||
const $serverAddr = of(event)
|
const $serverAddr = of(event)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(event => event.target.value),
|
map(event => event.target.value),
|
||||||
debounceTime(500),
|
debounceTime(500),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
)
|
)
|
||||||
.subscribe(addr => {
|
.subscribe(addr => {
|
||||||
if (validateIP(addr)) {
|
if (validateIP(addr)) {
|
||||||
setInvalidIP(false)
|
setInvalidIP(false)
|
||||||
dispatch(setServerAddr(addr))
|
dispatch(setServerAddr(addr))
|
||||||
} else if (validateDomain(addr)) {
|
} else if (validateDomain(addr)) {
|
||||||
setInvalidIP(false)
|
setInvalidIP(false)
|
||||||
dispatch(setServerAddr(addr))
|
dispatch(setServerAddr(addr))
|
||||||
} else {
|
} else {
|
||||||
setInvalidIP(true)
|
setInvalidIP(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return $serverAddr.unsubscribe()
|
return $serverAddr.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set server port
|
* Set server port
|
||||||
*/
|
*/
|
||||||
const handlePortChange = (event: any) => {
|
const handlePortChange = (event: any) => {
|
||||||
const $port = of(event)
|
const $port = of(event)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(event => event.target.value),
|
map(event => event.target.value),
|
||||||
map(val => Number(val)),
|
map(val => Number(val)),
|
||||||
takeWhile(val => isFinite(val) && val <= 65535),
|
takeWhile(val => isFinite(val) && val <= 65535),
|
||||||
)
|
)
|
||||||
.subscribe(port => {
|
.subscribe(port => {
|
||||||
dispatch(setServerPort(port.toString()))
|
dispatch(setServerPort(port.toString()))
|
||||||
})
|
})
|
||||||
return $port.unsubscribe()
|
return $port.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Language toggler handler
|
* Language toggler handler
|
||||||
*/
|
*/
|
||||||
const handleLanguageChange = (event: SelectChangeEvent<LanguageUnion>) => {
|
const handleLanguageChange = (event: SelectChangeEvent<LanguageUnion>) => {
|
||||||
dispatch(setLanguage(event.target.value as LanguageUnion));
|
dispatch(setLanguage(event.target.value as LanguageUnion));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theme toggler handler
|
* Theme toggler handler
|
||||||
*/
|
*/
|
||||||
const handleThemeChange = (event: SelectChangeEvent<ThemeUnion>) => {
|
const handleThemeChange = (event: SelectChangeEvent<ThemeUnion>) => {
|
||||||
dispatch(setTheme(event.target.value as ThemeUnion));
|
dispatch(setTheme(event.target.value as ThemeUnion));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send via WebSocket a message in order to update the yt-dlp binary from server
|
* Send via WebSocket a message in order to update the yt-dlp binary from server
|
||||||
*/
|
*/
|
||||||
const updateBinary = () => {
|
const updateBinary = () => {
|
||||||
socket.emit('update-bin')
|
socket.emit('update-bin')
|
||||||
dispatch(alreadyUpdated())
|
dispatch(alreadyUpdated())
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
|
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={12} md={12} lg={12}>
|
<Grid item xs={12} md={12} lg={12}>
|
||||||
<Paper
|
<Paper
|
||||||
sx={{
|
sx={{
|
||||||
p: 2,
|
p: 2,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
minHeight: 240,
|
minHeight: 240,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography pb={2} variant="h6" color="primary">
|
<Typography pb={2} variant="h6" color="primary">
|
||||||
{i18n.t('settingsAnchor')}
|
{i18n.t('settingsAnchor')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12} md={11}>
|
<Grid item xs={12} md={11}>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label={i18n.t('serverAddressTitle')}
|
label={i18n.t('serverAddressTitle')}
|
||||||
defaultValue={settings.serverAddr}
|
defaultValue={settings.serverAddr}
|
||||||
error={invalidIP}
|
error={invalidIP}
|
||||||
onChange={handleAddrChange}
|
onChange={handleAddrChange}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: <InputAdornment position="start">ws://</InputAdornment>,
|
startAdornment: <InputAdornment position="start">ws://</InputAdornment>,
|
||||||
}}
|
}}
|
||||||
sx={{ mb: 2 }}
|
sx={{ mb: 2 }}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} md={1}>
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
label={i18n.t('serverPortTitle')}
|
|
||||||
defaultValue={settings.serverPort}
|
|
||||||
onChange={handlePortChange}
|
|
||||||
error={isNaN(Number(settings.serverPort)) || Number(settings.serverPort) > 65535}
|
|
||||||
sx={{ mb: 2 }}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<Grid container spacing={2}>
|
|
||||||
<Grid item xs={12} md={6}>
|
|
||||||
<FormControl fullWidth>
|
|
||||||
<InputLabel id="demo-simple-select-label">Language</InputLabel>
|
|
||||||
<Select
|
|
||||||
defaultValue={settings.language}
|
|
||||||
label="Language"
|
|
||||||
onChange={handleLanguageChange}
|
|
||||||
>
|
|
||||||
<MenuItem value="english">English</MenuItem>
|
|
||||||
<MenuItem value="spanish">Spanish</MenuItem>
|
|
||||||
<MenuItem value="italian">Italian</MenuItem>
|
|
||||||
<MenuItem value="chinese">Chinese</MenuItem>
|
|
||||||
<MenuItem value="russian">Russian</MenuItem>
|
|
||||||
<MenuItem value="korean">Korean</MenuItem>
|
|
||||||
<MenuItem value="japanese">Japanese</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} md={6}>
|
|
||||||
<FormControl fullWidth>
|
|
||||||
<InputLabel>Theme</InputLabel>
|
|
||||||
<Select
|
|
||||||
defaultValue={settings.theme}
|
|
||||||
label="Theme"
|
|
||||||
onChange={handleThemeChange}
|
|
||||||
>
|
|
||||||
<MenuItem value="light">Light</MenuItem>
|
|
||||||
<MenuItem value="dark">Dark</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} md={6}>
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
label={'Max download speed' || i18n.t('serverPortTitle')}
|
|
||||||
defaultValue={settings.serverPort}
|
|
||||||
onChange={handlePortChange}
|
|
||||||
error={isNaN(Number(settings.serverPort)) || Number(settings.serverPort) > 65535}
|
|
||||||
sx={{ mb: 2 }}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Switch
|
|
||||||
defaultChecked={cliArgs.noMTime}
|
|
||||||
onChange={() => dispatch(setCliArgs(cliArgs.toggleNoMTime().toString()))}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={i18n.t('noMTimeCheckbox')}
|
|
||||||
sx={{ mt: 3 }}
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Switch
|
|
||||||
defaultChecked={cliArgs.extractAudio}
|
|
||||||
onChange={() => dispatch(setCliArgs(cliArgs.toggleExtractAudio().toString()))}
|
|
||||||
disabled={settings.formatSelection}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={i18n.t('extractAudioCheckbox')}
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Switch
|
|
||||||
defaultChecked={settings.formatSelection}
|
|
||||||
onChange={() => {
|
|
||||||
dispatch(setCliArgs(cliArgs.disableExtractAudio().toString()))
|
|
||||||
dispatch(setFormatSelection(!settings.formatSelection))
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={i18n.t('formatSelectionEnabler')}
|
|
||||||
/>
|
|
||||||
<Grid>
|
|
||||||
<Stack direction="row">
|
|
||||||
<Button
|
|
||||||
sx={{ mr: 1, mt: 3 }}
|
|
||||||
variant="contained"
|
|
||||||
onClick={() => dispatch(updated())}
|
|
||||||
>
|
|
||||||
{i18n.t('updateBinButton')}
|
|
||||||
</Button>
|
|
||||||
{/* <Button sx={{ mr: 1, mt: 1 }} variant="outlined">Primary</Button> */}
|
|
||||||
</Stack>
|
|
||||||
</Grid>
|
|
||||||
</FormGroup>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
<Grid item xs={12} md={1}>
|
||||||
<Snackbar
|
<TextField
|
||||||
open={status.updated}
|
fullWidth
|
||||||
autoHideDuration={1500}
|
label={i18n.t('serverPortTitle')}
|
||||||
message={i18n.t('toastUpdated')}
|
defaultValue={settings.serverPort}
|
||||||
onClose={updateBinary}
|
onChange={handlePortChange}
|
||||||
/>
|
error={isNaN(Number(settings.serverPort)) || Number(settings.serverPort) > 65535}
|
||||||
</Container>
|
sx={{ mb: 2 }}
|
||||||
);
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel id="demo-simple-select-label">Language</InputLabel>
|
||||||
|
<Select
|
||||||
|
defaultValue={settings.language}
|
||||||
|
label="Language"
|
||||||
|
onChange={handleLanguageChange}
|
||||||
|
>
|
||||||
|
<MenuItem value="english">English</MenuItem>
|
||||||
|
<MenuItem value="spanish">Spanish</MenuItem>
|
||||||
|
<MenuItem value="italian">Italian</MenuItem>
|
||||||
|
<MenuItem value="chinese">Chinese</MenuItem>
|
||||||
|
<MenuItem value="russian">Russian</MenuItem>
|
||||||
|
<MenuItem value="korean">Korean</MenuItem>
|
||||||
|
<MenuItem value="japanese">Japanese</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel>Theme</InputLabel>
|
||||||
|
<Select
|
||||||
|
defaultValue={settings.theme}
|
||||||
|
label="Theme"
|
||||||
|
onChange={handleThemeChange}
|
||||||
|
>
|
||||||
|
<MenuItem value="light">Light</MenuItem>
|
||||||
|
<MenuItem value="dark">Dark</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
{/* <Grid item xs={12} md={6}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label={'Max download speed' || i18n.t('serverPortTitle')}
|
||||||
|
defaultValue={settings.serverPort}
|
||||||
|
onChange={handlePortChange}
|
||||||
|
error={isNaN(Number(settings.serverPort)) || Number(settings.serverPort) > 65535}
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
/>
|
||||||
|
</Grid> */}
|
||||||
|
</Grid>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
defaultChecked={cliArgs.noMTime}
|
||||||
|
onChange={() => dispatch(setCliArgs(cliArgs.toggleNoMTime().toString()))}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n.t('noMTimeCheckbox')}
|
||||||
|
sx={{ mt: 3 }}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
defaultChecked={cliArgs.extractAudio}
|
||||||
|
onChange={() => dispatch(setCliArgs(cliArgs.toggleExtractAudio().toString()))}
|
||||||
|
disabled={settings.formatSelection}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n.t('extractAudioCheckbox')}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.formatSelection}
|
||||||
|
onChange={() => {
|
||||||
|
dispatch(setCliArgs(cliArgs.disableExtractAudio().toString()))
|
||||||
|
dispatch(setFormatSelection(!settings.formatSelection))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n.t('formatSelectionEnabler')}
|
||||||
|
/>
|
||||||
|
<Grid>
|
||||||
|
<Stack direction="row">
|
||||||
|
<Button
|
||||||
|
sx={{ mr: 1, mt: 3 }}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => dispatch(updated())}
|
||||||
|
>
|
||||||
|
{i18n.t('updateBinButton')}
|
||||||
|
</Button>
|
||||||
|
{/* <Button sx={{ mr: 1, mt: 1 }} variant="outlined">Primary</Button> */}
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
</FormGroup>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Snackbar
|
||||||
|
open={status.updated}
|
||||||
|
autoHideDuration={1500}
|
||||||
|
message={i18n.t('toastUpdated')}
|
||||||
|
onClose={updateBinary}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
export class CliArguments {
|
export class CliArguments {
|
||||||
private _extractAudio: boolean;
|
private _extractAudio: boolean;
|
||||||
private _noMTime: boolean;
|
private _noMTime: boolean;
|
||||||
|
private _proxy: string;
|
||||||
|
|
||||||
constructor(extractAudio = false, noMTime = false) {
|
constructor(extractAudio = false, noMTime = false) {
|
||||||
this._extractAudio = extractAudio;
|
this._extractAudio = extractAudio;
|
||||||
this._noMTime = noMTime;
|
this._noMTime = noMTime;
|
||||||
|
this._proxy = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
public get extractAudio(): boolean {
|
public get extractAudio(): boolean {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ export function StackableResult({ formattedLog, title, thumbnail, resolution, pr
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const roundMB = (bytes: number) => `${(bytes / 1_000_000).toFixed(2)}MB`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardActionArea>
|
<CardActionArea>
|
||||||
@@ -45,7 +47,7 @@ export function StackableResult({ formattedLog, title, thumbnail, resolution, pr
|
|||||||
<Chip label={formattedLog.status} color="primary" />
|
<Chip label={formattedLog.status} color="primary" />
|
||||||
<Typography>{formattedLog.progress}</Typography>
|
<Typography>{formattedLog.progress}</Typography>
|
||||||
<Typography>{formattedLog.dlSpeed}</Typography>
|
<Typography>{formattedLog.dlSpeed}</Typography>
|
||||||
<Typography>{formattedLog.size}</Typography>
|
<Typography>{roundMB(formattedLog.size ?? 0)}</Typography>
|
||||||
{guessResolution(resolution)}
|
{guessResolution(resolution)}
|
||||||
</Stack>
|
</Stack>
|
||||||
{progress ?
|
{progress ?
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export interface IMessage {
|
export interface IMessage {
|
||||||
status: string,
|
status: string,
|
||||||
progress?: string,
|
progress?: string,
|
||||||
size?: string,
|
size?: number,
|
||||||
dlSpeed?: string
|
dlSpeed?: string
|
||||||
pid: number
|
pid: number
|
||||||
}
|
}
|
||||||
|
|||||||
742
pnpm-lock.yaml
generated
742
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,16 @@ class Process {
|
|||||||
private metadata?: IDownloadMetadata;
|
private metadata?: IDownloadMetadata;
|
||||||
private exePath = join(__dirname, 'yt-dlp');
|
private exePath = join(__dirname, 'yt-dlp');
|
||||||
|
|
||||||
|
private readonly template = `download:
|
||||||
|
{
|
||||||
|
"eta":%(progress.eta)s,
|
||||||
|
"percentage":"%(progress._percent_str)s",
|
||||||
|
"speed":"%(progress._speed_str)s",
|
||||||
|
"size":%(info.filesize_approx)s
|
||||||
|
}`
|
||||||
|
.replace(/\s\s+/g, ' ')
|
||||||
|
.replace('\n', '');
|
||||||
|
|
||||||
constructor(url: string, params: Array<string>, settings: any) {
|
constructor(url: string, params: Array<string>, settings: any) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.params = params || [];
|
this.params = params || [];
|
||||||
@@ -48,7 +58,11 @@ class Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ytldp = spawn(this.exePath,
|
const ytldp = spawn(this.exePath,
|
||||||
['-o', `${this.settings?.download_path || 'downloads/'}%(title)s.%(ext)s`]
|
[
|
||||||
|
'-o', `${this.settings?.download_path || 'downloads/'}%(title)s.%(ext)s`,
|
||||||
|
'--progress-template', this.template,
|
||||||
|
'--no-colors',
|
||||||
|
]
|
||||||
.concat(sanitizedParams)
|
.concat(sanitizedParams)
|
||||||
.concat((this.settings?.cliArgs ?? []).map(arg => arg.split(' ')).flat())
|
.concat((this.settings?.cliArgs ?? []).map(arg => arg.split(' ')).flat())
|
||||||
.concat([this.url])
|
.concat([this.url])
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Socket } from 'socket.io';
|
|||||||
import MemoryDB from '../db/memoryDB';
|
import MemoryDB from '../db/memoryDB';
|
||||||
import { IPayload } from '../interfaces/IPayload';
|
import { IPayload } from '../interfaces/IPayload';
|
||||||
import { ISettings } from '../interfaces/ISettings';
|
import { ISettings } from '../interfaces/ISettings';
|
||||||
|
import { CLIProgress } from '../types';
|
||||||
import Logger from '../utils/BetterLogger';
|
import Logger from '../utils/BetterLogger';
|
||||||
import Process from './Process';
|
import Process from './Process';
|
||||||
import { states } from './states';
|
import { states } from './states';
|
||||||
@@ -118,7 +119,6 @@ function streamProcess(process: Process, socket: Socket) {
|
|||||||
.subscribe({
|
.subscribe({
|
||||||
next: (stdout) => {
|
next: (stdout) => {
|
||||||
socket.emit('progress', stdout)
|
socket.emit('progress', stdout)
|
||||||
log.info(`proc-${stdout.pid}`, `${stdout.progress}\t${stdout.dlSpeed}`)
|
|
||||||
},
|
},
|
||||||
complete: () => {
|
complete: () => {
|
||||||
process.kill().then(() => {
|
process.kill().then(() => {
|
||||||
@@ -227,27 +227,20 @@ export function getQueueSize(): number {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const formatter = (stdout: string, pid: number) => {
|
const formatter = (stdout: string, pid: number) => {
|
||||||
const cleanStdout = stdout
|
try {
|
||||||
.replace(/\s\s+/g, ' ')
|
const p: CLIProgress = JSON.parse(stdout);
|
||||||
.split(' ');
|
if (p) {
|
||||||
const status = cleanStdout[0].replace(/\[|\]|\r/g, '');
|
|
||||||
switch (status) {
|
|
||||||
case 'download':
|
|
||||||
return {
|
return {
|
||||||
status: states.PROC_DOWNLOAD,
|
status: states.PROC_DOWNLOAD,
|
||||||
progress: cleanStdout[1],
|
progress: p.percentage,
|
||||||
size: cleanStdout[3],
|
size: p.size,
|
||||||
dlSpeed: cleanStdout[5],
|
dlSpeed: p.speed,
|
||||||
pid: pid,
|
pid: pid,
|
||||||
}
|
}
|
||||||
case 'merge':
|
}
|
||||||
return {
|
} catch (e) {
|
||||||
status: states.PROC_MERGING,
|
return {
|
||||||
progress: '100',
|
progress: 0,
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return {
|
|
||||||
progress: '0'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
server/src/types/index.d.ts
vendored
Normal file
6
server/src/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export type CLIProgress = {
|
||||||
|
percentage: string
|
||||||
|
speed: string
|
||||||
|
size: number
|
||||||
|
eta: number
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user