Merge pull request #13 from marcopeocchi/feat_download_path_selection

Feat download path selection
This commit is contained in:
Marco
2022-09-24 13:39:34 +02:00
committed by GitHub
8 changed files with 151 additions and 32 deletions

View File

@@ -9,13 +9,10 @@ RUN apk add curl wget psmisc python3 ffmpeg
COPY . .
RUN chmod +x ./fetch-yt-dlp.sh
# install node dependencies
RUN npm install
RUN npm run build
RUN npm run build-server
RUN npm run fetch
# cleanup
RUN npm remove parcel
RUN rm -rf .parcel-cache
RUN yarn
RUN yarn build
RUN yarn build-server
RUN yarn run fetch
# expose and run
EXPOSE 3022
CMD [ "node" , "./dist/main.js" ]

View File

@@ -5,10 +5,13 @@ import {
ButtonGroup,
CircularProgress,
Container,
FormControl,
Grid,
IconButton,
InputAdornment,
MenuItem,
Paper,
Select,
Snackbar,
styled,
TextField,
@@ -44,6 +47,9 @@ export default function Home({ socket }: Props) {
const [pickedAudioFormat, setPickedAudioFormat] = useState('');
const [pickedBestFormat, setPickedBestFormat] = useState('');
const [downloadPath, setDownloadPath] = useState<number>(0);
const [availableDownloadPaths, setAvailableDownloadPaths] = useState<string[]>([]);
const [url, setUrl] = useState('');
const [workingUrl, setWorkingUrl] = useState('');
const [showBackdrop, setShowBackdrop] = useState(false);
@@ -106,6 +112,14 @@ export default function Home({ socket }: Props) {
})
}, [])
useEffect(() => {
fetch(`${window.location.protocol}//${settings.serverAddr}:${settings.serverPort}/tree`)
.then(res => res.json())
.then(data => {
setAvailableDownloadPaths(data.flat)
})
}, [])
/* -------------------- component functions -------------------- */
/**
@@ -119,6 +133,7 @@ export default function Home({ socket }: Props) {
socket.emit('send-url', {
url: immediate || url || workingUrl,
path: availableDownloadPaths[downloadPath],
params: settings.cliArgs.toString() + toFormatArgs(codes),
})
setUrl('')
@@ -211,25 +226,44 @@ export default function Home({ socket }: Props) {
flexDirection: 'column',
}}
>
<TextField
id="urlInput"
label={settings.i18n.t('urlInput')}
variant="outlined"
onChange={handleUrlChange}
disabled={!status.connected || (settings.formatSelection && downloadFormats != null)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<label htmlFor="icon-button-file">
<Input id="icon-button-file" type="file" accept=".txt" onChange={parseUrlListFile} />
<IconButton color="primary" aria-label="upload file" component="span">
<FileUpload />
</IconButton>
</label>
</InputAdornment>
),
}}
/>
<Grid container spacing={1}>
<Grid item xs={10}>
<TextField
fullWidth
id="urlInput"
label={settings.i18n.t('urlInput')}
variant="outlined"
onChange={handleUrlChange}
disabled={!status.connected || (settings.formatSelection && downloadFormats != null)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<label htmlFor="icon-button-file">
<Input id="icon-button-file" type="file" accept=".txt" onChange={parseUrlListFile} />
<IconButton color="primary" aria-label="upload file" component="span">
<FileUpload />
</IconButton>
</label>
</InputAdornment>
),
}}
/>
</Grid>
<Grid item xs={2}>
<FormControl fullWidth>
<Select
defaultValue={0}
value={availableDownloadPaths[downloadPath]}
onChange={(e) => setDownloadPath(e.target.value)}
>
{availableDownloadPaths.map((val: string, idx: number) => (
<MenuItem key={idx} value={idx}>{val}</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid>
<Grid container spacing={1} pt={2}>
<Grid item>
<Button
@@ -365,7 +399,7 @@ export default function Home({ socket }: Props) {
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}>
{ /*Super big brain flatMap moment*/
Array
.from(messageMap)
.from<any>(messageMap)
.filter(flattened => [...flattened][0])
.filter(flattened => [...flattened][1].toString() !== serverStates.PROG_DONE)
.flatMap(message => (

View File

@@ -41,6 +41,12 @@ class Process {
public async start(callback?: Function): Promise<this> {
const sanitizedParams = this.params.filter((param: string) => availableParams.includes(param));
if (this.settings?.download_path) {
if (this.settings.download_path.charAt(this.settings.download_path.length - 1) !== '/') {
this.settings.download_path = `${this.settings.download_path}/`
}
}
const ytldp = spawn(this.exePath,
['-o', `${this.settings?.download_path || 'downloads/'}%(title)s.%(ext)s`]
.concat(sanitizedParams)

View File

@@ -61,7 +61,12 @@ export async function download(socket: Socket, payload: IPayload) {
payload.params.split(' ') :
payload.params;
let p = new Process(url, params, settings);
const scopedSettings: ISettings = {
...settings,
download_path: payload.path
}
let p = new Process(url, params, scopedSettings);
p.start().then(downloader => {
mem_db.add(downloader)
@@ -111,7 +116,10 @@ function streamProcess(process: Process, socket: Socket) {
map(stdout => formatter(String(stdout), process.getPid()))
)
.subscribe({
next: (stdout) => socket.emit('progress', stdout),
next: (stdout) => {
socket.emit('progress', stdout)
log.info(`proc-${stdout.pid}`, `${stdout.progress}\t${stdout.dlSpeed}`)
},
complete: () => {
process.kill().then(() => {
emitAbort();

View File

@@ -5,6 +5,7 @@
export interface IPayload {
url: string
params: Array<string> | string,
path: string,
title?: string,
thumbnail?: string,
size?: string,

View File

@@ -2,7 +2,13 @@ import { splash } from './utils/logger';
import { join } from 'path';
import { Server } from 'socket.io';
import { ytdlpUpdater } from './utils/updater';
import { download, abortDownload, retrieveDownload, abortAllDownloads, getFormatsAndMetadata } from './core/downloader';
import {
download,
abortDownload,
retrieveDownload,
abortAllDownloads,
getFormatsAndMetadata
} from './core/downloader';
import { getFreeDiskSpace } from './utils/procUtils';
import { listDownloaded } from './core/downloadArchive';
import { createServer } from 'http';
@@ -13,6 +19,7 @@ import * as serve from 'koa-static';
import * as cors from '@koa/cors';
import Logger from './utils/BetterLogger';
import { ISettings } from './interfaces/ISettings';
import { directoryTree } from './utils/directoryUtils';
const app = new Koa();
const server = createServer(app.callback());
@@ -56,6 +63,10 @@ router.get('/archive', (ctx, next) => {
router.get('/stream/:filepath', (ctx, next) => {
streamer(ctx, next)
})
router.get('/tree', (ctx, next) => {
ctx.body = directoryTree()
next()
})
// WebSocket listeners
io.on('connection', socket => {

View File

@@ -0,0 +1,62 @@
import { readdirSync, statSync } from "fs";
import { ISettings } from "../interfaces/ISettings";
let settings: ISettings;
class Node {
public path: string
public children: Node[]
constructor(path: string) {
this.path = path
this.children = []
}
}
function buildTreeDFS(rootPath: string, directoryOnly: boolean) {
const root = new Node(rootPath)
const stack: Node[] = []
const flattened: string[] = []
stack.push(root)
flattened.push(rootPath)
while (stack.length) {
const current = stack.pop()
if (current) {
const children = readdirSync(current.path)
for (const it of children) {
const childPath = `${current.path}/${it}`
const childNode = new Node(childPath)
if (directoryOnly) {
if (statSync(childPath).isDirectory()) {
current.children.push(childNode)
stack.push(childNode)
flattened.push(childNode.path)
}
} else {
current.children.push(childNode)
if (statSync(childPath).isDirectory()) {
stack.push(childNode)
flattened.push(childNode.path)
}
}
}
}
}
return {
tree: root,
flat: flattened
}
}
try {
settings = require('../../settings.json');
} catch (e) { }
export function directoryTree() {
const tree = buildTreeDFS(settings.download_path || 'downloads', true)
return tree
}

View File

@@ -17,8 +17,8 @@ export const splash = () => {
const reset = "\u001b[0m"
console.log(`${fg} __ ____ __ __ ______`)
console.log(" __ __/ /________/ / /__ _ _____ / / / / / / _/")
console.log(" / // / __/___/ _ / / _ \ | |/|/ / -_) _ \/ /_/ // / ")
console.log(" \_, /\__/ \_,_/_/ .__/ |__,__/\__/_.__/\____/___/ ")
console.log(" / // / __/___/ _ / / _ \\ | |/|/ / -_) _ \\/ /_/ // / ")
console.log(" \\_, /\\__/ \\_,_/_/ .__/ |__,__/\\__/_.__/\\____/___/ ")
console.log(`/___/ /_/ \n${reset}`)
console.log(" yt-dlp-webUI - A web-ui for yt-dlp, simply enough")
console.log("---------------------------------------------------\n")