format selection enabled
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,4 +11,4 @@ src/server/core/yt-dlp
|
|||||||
*.ytdl
|
*.ytdl
|
||||||
*.part
|
*.part
|
||||||
*.db
|
*.db
|
||||||
*.DS_Store
|
.DS_Store
|
||||||
24
README.md
24
README.md
@@ -33,6 +33,8 @@ Refactoring and JSDoc.
|
|||||||
04/01/22: Background jobs now are retrieved!! It's still rudimentary but it leverages on yt-dlp resume feature
|
04/01/22: Background jobs now are retrieved!! It's still rudimentary but it leverages on yt-dlp resume feature
|
||||||
|
|
||||||
05/05/22: Material UI update
|
05/05/22: Material UI update
|
||||||
|
|
||||||
|
03/06/22: The most requested feature finally implemented: Format Selection!!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -49,15 +51,30 @@ The avaible settings are currently only:
|
|||||||
- Switch theme
|
- Switch theme
|
||||||
- Extract audio
|
- Extract audio
|
||||||
- Switch language
|
- Switch language
|
||||||
|
- Optional format selection
|
||||||
|
|
||||||
<img src="https://i.imgur.com/2zPs8FH.png">
|
<img src="https://i.imgur.com/2zPs8FH.png">
|
||||||
<img src="https://i.imgur.com/b4Jhkfk.png">
|
<img src="https://i.imgur.com/b4Jhkfk.png">
|
||||||
<img src="https://i.imgur.com/knjLa8c.png">
|
<img src="https://i.imgur.com/knjLa8c.png">
|
||||||
|
|
||||||
|
## Format selection
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
This feature is disabled by default as this WebUI/Wrapper/Software/Bunch of Code is intended to be used to retrieve the best quality automatically.
|
||||||
|
|
||||||
|
To enable it go to the settings page:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
And set it :D
|
||||||
|
|
||||||
Future releases will have:
|
Future releases will have:
|
||||||
- ~~Multi download~~ *done*
|
- ~~Multi download~~ *done*
|
||||||
- ~~Exctract audio~~ *done*
|
- ~~Exctract audio~~ *done*
|
||||||
- Format selection *in-progess*
|
- ~~Format selection~~ *done*
|
||||||
|
- Download archive
|
||||||
|
- ARM Build
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
- **It says that it isn't connected/ip in the footer is not defined.**
|
- **It says that it isn't connected/ip in the footer is not defined.**
|
||||||
@@ -105,8 +122,3 @@ node dist/main.js
|
|||||||
- Well, yes (until now).
|
- Well, yes (until now).
|
||||||
- **Why is it so slow to start a download?**
|
- **Why is it so slow to start a download?**
|
||||||
- I genuinely don't know. I know that standalone yt-dlp is slow to start up even on my M1 Mac, so....
|
- I genuinely don't know. I know that standalone yt-dlp is slow to start up even on my M1 Mac, so....
|
||||||
|
|
||||||
## Todo list
|
|
||||||
- ~~retrieve background tasks~~
|
|
||||||
- format selection
|
|
||||||
- better ui/ux
|
|
||||||
|
|||||||
@@ -237,8 +237,8 @@ function AppContent() {
|
|||||||
>
|
>
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Home socket={socket}></Home>}></Route>
|
<Route path="/" element={<Home></Home>}></Route>
|
||||||
<Route path="/settings" element={<Settings socket={socket}></Settings>}></Route>
|
<Route path="/settings" element={<Settings></Settings>}></Route>
|
||||||
<Route path="/downloaded" element={<ArchivedDownloads></ArchivedDownloads>}></Route>
|
<Route path="/downloaded" element={<ArchivedDownloads></ArchivedDownloads>}></Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { Backdrop, Button, CircularProgress, Container, Grid, Paper, Snackbar, TextField, } from "@mui/material";
|
import { Backdrop, Button, ButtonGroup, CircularProgress, Container, Grid, Paper, Skeleton, Snackbar, TextField, Typography, } from "@mui/material";
|
||||||
import React, { Fragment, useEffect, useState } from "react";
|
import React, { Fragment, useEffect, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { Socket } from "socket.io-client";
|
import { io, Socket } from "socket.io-client";
|
||||||
import { StackableResult } from "./components/StackableResult";
|
import { StackableResult } from "./components/StackableResult";
|
||||||
import { connected, disconnected, downloading, finished } from "./features/status/statusSlice";
|
import { connected, disconnected, downloading, finished } from "./features/status/statusSlice";
|
||||||
import { IDLInfo, IDLInfoBase, IDownloadInfo, IMessage } from "./interfaces";
|
import { IDLInfo, IDLInfoBase, IDownloadInfo, IMessage } from "./interfaces";
|
||||||
import { RootState } from "./stores/store";
|
import { RootState } from "./stores/store";
|
||||||
import { updateInStateMap, } from "./utils";
|
import { toFormatArgs, updateInStateMap, } from "./utils";
|
||||||
|
|
||||||
type Props = {
|
let socket: Socket;
|
||||||
socket: Socket
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home({ socket }: Props) {
|
export default function Home() {
|
||||||
// redux state
|
// redux state
|
||||||
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)
|
||||||
@@ -22,11 +20,23 @@ export default function Home({ socket }: Props) {
|
|||||||
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, IDLInfoBase>());
|
||||||
|
const [downloadFormats, setDownloadFormats] = useState<IDownloadInfo>();
|
||||||
|
const [pickedVideoFormat, setPickedVideoFormat] = useState('');
|
||||||
|
const [pickedAudioFormat, setPickedAudioFormat] = useState('');
|
||||||
|
const [pickedBestFormat, setPickedBestFormat] = useState('');
|
||||||
|
|
||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
|
const [workingUrl, setWorkingUrl] = useState('');
|
||||||
const [showBackdrop, setShowBackdrop] = useState(false);
|
const [showBackdrop, setShowBackdrop] = useState(false);
|
||||||
|
|
||||||
/* -------------------- Effects -------------------- */
|
/* -------------------- Effects -------------------- */
|
||||||
|
useEffect(() => {
|
||||||
|
socket = io(`http://${localStorage.getItem('server-addr') || 'localhost'}:3022`);
|
||||||
|
return () => {
|
||||||
|
socket.disconnect()
|
||||||
|
};
|
||||||
|
}, [])
|
||||||
|
|
||||||
/* WebSocket connect event handler*/
|
/* WebSocket connect event handler*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
@@ -53,6 +63,7 @@ export default function Home({ socket }: Props) {
|
|||||||
socket.on('available-formats', (data: IDownloadInfo) => {
|
socket.on('available-formats', (data: IDownloadInfo) => {
|
||||||
setShowBackdrop(false)
|
setShowBackdrop(false)
|
||||||
console.log(data)
|
console.log(data)
|
||||||
|
setDownloadFormats(data);
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -89,11 +100,37 @@ export default function Home({ socket }: Props) {
|
|||||||
* Retrive url from input, cli-arguments from checkboxes and emits via WebSocket
|
* Retrive url from input, cli-arguments from checkboxes and emits via WebSocket
|
||||||
*/
|
*/
|
||||||
const sendUrl = () => {
|
const sendUrl = () => {
|
||||||
|
const codes = new Array<string>();
|
||||||
|
if (pickedVideoFormat !== '') codes.push(pickedVideoFormat);
|
||||||
|
if (pickedAudioFormat !== '') codes.push(pickedAudioFormat);
|
||||||
|
if (pickedBestFormat !== '') codes.push(pickedBestFormat);
|
||||||
|
|
||||||
socket.emit('send-url', {
|
socket.emit('send-url', {
|
||||||
url: url,
|
url: url || workingUrl,
|
||||||
params: settings.cliArgs.toString(),
|
params: settings.cliArgs.toString() + toFormatArgs(codes),
|
||||||
})
|
})
|
||||||
setUrl('')
|
setUrl('')
|
||||||
|
setWorkingUrl('')
|
||||||
|
setTimeout(() => {
|
||||||
|
const input = document.getElementById('urlInput') as HTMLInputElement;
|
||||||
|
input.value = '';
|
||||||
|
setShowBackdrop(true);
|
||||||
|
setDownloadFormats(null);
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrive url from input and display the formats selection view
|
||||||
|
*/
|
||||||
|
const sendUrlFormatSelection = () => {
|
||||||
|
socket.emit('send-url-format-selection', {
|
||||||
|
url: url,
|
||||||
|
})
|
||||||
|
setWorkingUrl(url)
|
||||||
|
setUrl('')
|
||||||
|
setPickedAudioFormat('');
|
||||||
|
setPickedVideoFormat('');
|
||||||
|
setPickedBestFormat('');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const input = document.getElementById('urlInput') as HTMLInputElement;
|
const input = document.getElementById('urlInput') as HTMLInputElement;
|
||||||
input.value = '';
|
input.value = '';
|
||||||
@@ -120,6 +157,7 @@ export default function Home({ socket }: Props) {
|
|||||||
socket.emit('abort', { pid: id })
|
socket.emit('abort', { pid: id })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
setDownloadFormats(null)
|
||||||
socket.emit('abort-all')
|
socket.emit('abort-all')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,13 +183,14 @@ export default function Home({ socket }: Props) {
|
|||||||
label={settings.i18n.t('urlInput')}
|
label={settings.i18n.t('urlInput')}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={handleUrlChange}
|
onChange={handleUrlChange}
|
||||||
|
disabled={settings.formatSelection && downloadFormats != null}
|
||||||
/>
|
/>
|
||||||
<Grid container spacing={1} pt={2}>
|
<Grid container spacing={1} pt={2}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={url === ''}
|
disabled={url === ''}
|
||||||
onClick={() => sendUrl()}
|
onClick={() => settings.formatSelection ? sendUrlFormatSelection() : sendUrl()}
|
||||||
>
|
>
|
||||||
{settings.i18n.t('startButton')}
|
{settings.i18n.t('startButton')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -168,6 +207,110 @@ export default function Home({ socket }: Props) {
|
|||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{/* Format Selection grid */}
|
||||||
|
{downloadFormats ? <Grid container spacing={2} mt={2}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Paper
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="h6" component="div" pb={1}>
|
||||||
|
{downloadFormats.title}
|
||||||
|
</Typography>
|
||||||
|
{/* <Skeleton variant="rectangular" height={180} /> */}
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} pb={1}>
|
||||||
|
<img src={downloadFormats.thumbnail} height={260} width="100%" style={{ objectFit: 'cover' }} />
|
||||||
|
</Grid>
|
||||||
|
{/* video only */}
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="body1" component="div">
|
||||||
|
Best quality
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item pr={2} py={1}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={pickedBestFormat !== ''}
|
||||||
|
onClick={() => {
|
||||||
|
setPickedBestFormat(downloadFormats.best.format_id)
|
||||||
|
setPickedVideoFormat('')
|
||||||
|
setPickedAudioFormat('')
|
||||||
|
}}>
|
||||||
|
{downloadFormats.best.format_note || downloadFormats.best.format_id} - {downloadFormats.best.vcodec}+{downloadFormats.best.acodec}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
{/* video only */}
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="body1" component="div">
|
||||||
|
Video data
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
{downloadFormats.formats
|
||||||
|
.filter(format => format.acodec === 'none' && format.vcodec !== 'none')
|
||||||
|
.map((format, idx) => (
|
||||||
|
<Grid item pr={2} py={1} key={idx}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
setPickedVideoFormat(format.format_id)
|
||||||
|
setPickedBestFormat('')
|
||||||
|
}}
|
||||||
|
disabled={pickedVideoFormat === format.format_id}
|
||||||
|
>
|
||||||
|
{format.format_note} - {format.vcodec === 'none' ? format.acodec : format.vcodec}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="body1" component="div">
|
||||||
|
Audio data
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
{downloadFormats.formats
|
||||||
|
.filter(format => format.acodec !== 'none' && format.vcodec === 'none')
|
||||||
|
.map((format, idx) => (
|
||||||
|
<Grid item pr={2} py={1} key={idx}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
setPickedAudioFormat(format.format_id)
|
||||||
|
setPickedBestFormat('')
|
||||||
|
}}
|
||||||
|
disabled={pickedAudioFormat === format.format_id}
|
||||||
|
>
|
||||||
|
{format.format_note} - {format.vcodec === 'none' ? format.acodec : format.vcodec}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
<Grid item xs={12} pt={2}>
|
||||||
|
<ButtonGroup disableElevation variant="contained">
|
||||||
|
<Button
|
||||||
|
onClick={() => sendUrl()}
|
||||||
|
disabled={!pickedBestFormat && !(pickedAudioFormat || pickedVideoFormat)}
|
||||||
|
> Download
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setPickedAudioFormat('');
|
||||||
|
setPickedVideoFormat('');
|
||||||
|
setPickedBestFormat('');
|
||||||
|
}}
|
||||||
|
> Clear
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
</Grid> : null}
|
||||||
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}>
|
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}>
|
||||||
{ /*Super big brain flatMap moment*/
|
{ /*Super big brain flatMap moment*/
|
||||||
Array
|
Array
|
||||||
|
|||||||
@@ -19,17 +19,15 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { Socket } from "socket.io-client";
|
import { io } from "socket.io-client";
|
||||||
import { LanguageUnion, setCliArgs, setLanguage, setServerAddr, setTheme, ThemeUnion } from "./features/settings/settingsSlice";
|
import { LanguageUnion, setCliArgs, setFormatSelection, setLanguage, setServerAddr, setTheme, ThemeUnion } from "./features/settings/settingsSlice";
|
||||||
import { alreadyUpdated, updated } from "./features/status/statusSlice";
|
import { alreadyUpdated, updated } from "./features/status/statusSlice";
|
||||||
import { RootState } from "./stores/store";
|
import { RootState } from "./stores/store";
|
||||||
import { validateDomain, validateIP } from "./utils";
|
import { validateDomain, validateIP } from "./utils";
|
||||||
|
|
||||||
type Props = {
|
const socket = io(`http://${localStorage.getItem('server-addr') || 'localhost'}:3022`)
|
||||||
socket: Socket
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Settings({ socket }: Props) {
|
export default function Settings() {
|
||||||
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()
|
||||||
@@ -155,6 +153,15 @@ export default function Settings({ socket }: Props) {
|
|||||||
}
|
}
|
||||||
label={settings.i18n.t('extractAudioCheckbox')}
|
label={settings.i18n.t('extractAudioCheckbox')}
|
||||||
/>
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.formatSelection}
|
||||||
|
onChange={() => dispatch(setFormatSelection(!settings.formatSelection))}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={settings.i18n.t('formatSelectionEnabler')}
|
||||||
|
/>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Stack direction="row">
|
<Stack direction="row">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ languages:
|
|||||||
bgReminder: Once you close this page the download will continue in the background.
|
bgReminder: Once you close this page the download will continue in the background.
|
||||||
toastConnected: 'Connected to '
|
toastConnected: 'Connected to '
|
||||||
toastUpdated: Updated yt-dlp binary!
|
toastUpdated: Updated yt-dlp binary!
|
||||||
|
formatSelectionEnabler: Enable video/audio formats selection
|
||||||
italian:
|
italian:
|
||||||
urlInput: URL di YouTube o di qualsiasi altro servizio supportato
|
urlInput: URL di YouTube o di qualsiasi altro servizio supportato
|
||||||
statusTitle: Stato
|
statusTitle: Stato
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { EightK, FourK, Hd, Sd } from "@mui/icons-material";
|
import { EightK, FourK, Hd, Sd } from "@mui/icons-material";
|
||||||
import { Button, Card, CardActionArea, CardActions, CardContent, CardMedia, Chip, Grid, LinearProgress, Skeleton, Stack, Typography } from "@mui/material";
|
import { Button, Card, CardActionArea, CardActions, CardContent, CardMedia, Chip, LinearProgress, Skeleton, Stack, Typography } from "@mui/material";
|
||||||
import { IMessage } from "../interfaces";
|
import { IMessage } from "../interfaces";
|
||||||
import { ellipsis } from "../utils";
|
import { ellipsis } from "../utils";
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ export interface SettingsState {
|
|||||||
language: LanguageUnion,
|
language: LanguageUnion,
|
||||||
theme: ThemeUnion,
|
theme: ThemeUnion,
|
||||||
cliArgs: CliArguments,
|
cliArgs: CliArguments,
|
||||||
i18n: I18nBuilder
|
i18n: I18nBuilder,
|
||||||
|
formatSelection: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: SettingsState = {
|
const initialState: SettingsState = {
|
||||||
@@ -19,6 +20,7 @@ const initialState: SettingsState = {
|
|||||||
theme: (localStorage.getItem("theme") || "light") as ThemeUnion,
|
theme: (localStorage.getItem("theme") || "light") as ThemeUnion,
|
||||||
cliArgs: localStorage.getItem("cli-args") ? new CliArguments().fromString(localStorage.getItem("cli-args")) : new CliArguments(false, true),
|
cliArgs: localStorage.getItem("cli-args") ? new CliArguments().fromString(localStorage.getItem("cli-args")) : new CliArguments(false, true),
|
||||||
i18n: new I18nBuilder((localStorage.getItem("language") || "english")),
|
i18n: new I18nBuilder((localStorage.getItem("language") || "english")),
|
||||||
|
formatSelection: localStorage.getItem("format-selection") === "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const settingsSlice = createSlice({
|
export const settingsSlice = createSlice({
|
||||||
@@ -42,9 +44,13 @@ export const settingsSlice = createSlice({
|
|||||||
state.theme = action.payload
|
state.theme = action.payload
|
||||||
localStorage.setItem("theme", action.payload)
|
localStorage.setItem("theme", action.payload)
|
||||||
},
|
},
|
||||||
|
setFormatSelection: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.formatSelection = action.payload
|
||||||
|
localStorage.setItem("format-selection", action.payload.toString())
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const { setLanguage, setCliArgs, setTheme, setServerAddr } = settingsSlice.actions
|
export const { setLanguage, setCliArgs, setTheme, setServerAddr, setFormatSelection } = settingsSlice.actions
|
||||||
|
|
||||||
export default settingsSlice.reducer
|
export default settingsSlice.reducer
|
||||||
@@ -27,6 +27,7 @@ export interface IDownloadInfoSection {
|
|||||||
fps: number,
|
fps: number,
|
||||||
resolution: string,
|
resolution: string,
|
||||||
vcodec: string,
|
vcodec: string,
|
||||||
|
acodec: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDLInfo {
|
export interface IDLInfo {
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ export const updateInStateMap = (k: number, v: any, target: Map<number, any>, ca
|
|||||||
callback(new Map(target.set(k, v)));
|
callback(new Map(target.set(k, v)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateInStateArray<T>(v: T, target: Array<T>, callback: Function) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pre like function
|
* Pre like function
|
||||||
* @param data
|
* @param data
|
||||||
@@ -75,3 +77,14 @@ export const updateInStateMap = (k: number, v: any, target: Map<number, any>, ca
|
|||||||
export function buildMessage(data: IMessage) {
|
export function buildMessage(data: IMessage) {
|
||||||
return `operation: ${data.status || '...'} \nprogress: ${data.progress || '?'} \nsize: ${data.size || '?'} \nspeed: ${data.dlSpeed || '?'}`;
|
return `operation: ${data.status || '...'} \nprogress: ${data.progress || '?'} \nsize: ${data.size || '?'} \nspeed: ${data.dlSpeed || '?'}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function toFormatArgs(codes: string[]): string {
|
||||||
|
if (codes.length > 1) {
|
||||||
|
return codes.reduce((v, a) => ` -f ${v}+${a}`)
|
||||||
|
}
|
||||||
|
if (codes.length === 1) {
|
||||||
|
return ` -f ${codes[0]}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
@@ -47,6 +47,8 @@
|
|||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"parcel": "^2.5.0",
|
"parcel": "^2.5.0",
|
||||||
|
"path-browserify": "^1.0.1",
|
||||||
|
"process": "^0.11.10",
|
||||||
"typescript": "^4.6.4"
|
"typescript": "^4.6.4"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|||||||
@@ -83,14 +83,16 @@ class Process {
|
|||||||
fps: format.fps ?? '',
|
fps: format.fps ?? '',
|
||||||
resolution: format.resolution ?? '',
|
resolution: format.resolution ?? '',
|
||||||
vcodec: format.vcodec ?? '',
|
vcodec: format.vcodec ?? '',
|
||||||
|
acodec: format.acodec ?? '',
|
||||||
}
|
}
|
||||||
}),
|
}).filter((format: IDownloadInfoSection) => 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 ?? '',
|
||||||
fps: json.fps ?? '',
|
fps: json.fps ?? '',
|
||||||
resolution: json.resolution ?? '',
|
resolution: json.resolution ?? '',
|
||||||
vcodec: json.vcodec ?? '',
|
vcodec: json.vcodec ?? '',
|
||||||
|
acodec: json.acodec ?? '',
|
||||||
},
|
},
|
||||||
thumbnail: json.thumbnail,
|
thumbnail: json.thumbnail,
|
||||||
title: json.title,
|
title: json.title,
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ catch (e) {
|
|||||||
export async function getFormatsAndInfo(socket: Socket, url: string) {
|
export async function getFormatsAndInfo(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.getInfo();
|
||||||
console.log(formats)
|
|
||||||
socket.emit('available-formats', formats)
|
socket.emit('available-formats', formats)
|
||||||
p = null;
|
p = null;
|
||||||
}
|
}
|
||||||
@@ -102,7 +101,7 @@ export async function retrieveDownload(socket: Socket) {
|
|||||||
coldRestart = false;
|
coldRestart = false;
|
||||||
let downloads = [];
|
let downloads = [];
|
||||||
// sanitize
|
// sanitize
|
||||||
downloads = [... new Set(downloads.filter(el => el !== undefined))];
|
downloads = [...new Set(downloads.filter(el => el !== undefined))];
|
||||||
log.info('dl', `Cold restart, retrieving ${downloads.length} jobs`)
|
log.info('dl', `Cold restart, retrieving ${downloads.length} jobs`)
|
||||||
for (const entry of downloads) {
|
for (const entry of downloads) {
|
||||||
if (entry) {
|
if (entry) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { stat, createReadStream } from 'fs';
|
|||||||
import { lookup } from 'mime-types';
|
import { lookup } from 'mime-types';
|
||||||
|
|
||||||
export function streamer(ctx: any, next: any) {
|
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'
|
const filepath = ''
|
||||||
stat(filepath, (err, stat) => {
|
stat(filepath, (err, stat) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
ctx.response.status = 404;
|
ctx.response.status = 404;
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ interface IDownloadInfoSection {
|
|||||||
fps: number,
|
fps: number,
|
||||||
resolution: string,
|
resolution: string,
|
||||||
vcodec: string,
|
vcodec: string,
|
||||||
|
acodec: string,
|
||||||
}
|
}
|
||||||
@@ -48,9 +48,12 @@ io.on('connection', socket => {
|
|||||||
|
|
||||||
socket.on('send-url', (args) => {
|
socket.on('send-url', (args) => {
|
||||||
logger('ws', args?.url)
|
logger('ws', args?.url)
|
||||||
//if (args.url) getFormatsAndInfo(socket, args?.url)
|
|
||||||
download(socket, args)
|
download(socket, args)
|
||||||
})
|
})
|
||||||
|
socket.on('send-url-format-selection', (args) => {
|
||||||
|
logger('ws', args?.url)
|
||||||
|
if (args.url) getFormatsAndInfo(socket, args?.url)
|
||||||
|
})
|
||||||
socket.on('abort', (args) => {
|
socket.on('abort', (args) => {
|
||||||
abortDownload(socket, args)
|
abortDownload(socket, args)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user