Merge pull request #23 from marcopeocchi/feat-file-renaming
filename override, addressing #22
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
|
InputLabel,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Paper,
|
Paper,
|
||||||
Select,
|
Select,
|
||||||
@@ -49,9 +50,11 @@ export default function Home({ socket }: Props) {
|
|||||||
const [pickedAudioFormat, setPickedAudioFormat] = useState('');
|
const [pickedAudioFormat, setPickedAudioFormat] = useState('');
|
||||||
const [pickedBestFormat, setPickedBestFormat] = useState('');
|
const [pickedBestFormat, setPickedBestFormat] = useState('');
|
||||||
|
|
||||||
const [downloadPath, setDownloadPath] = useState<number>(0);
|
const [downloadPath, setDownloadPath] = useState(0);
|
||||||
const [availableDownloadPaths, setAvailableDownloadPaths] = useState<string[]>([]);
|
const [availableDownloadPaths, setAvailableDownloadPaths] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [fileNameOverride, setFilenameOverride] = useState('');
|
||||||
|
|
||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
const [workingUrl, setWorkingUrl] = useState('');
|
const [workingUrl, setWorkingUrl] = useState('');
|
||||||
const [showBackdrop, setShowBackdrop] = useState(false);
|
const [showBackdrop, setShowBackdrop] = useState(false);
|
||||||
@@ -141,14 +144,17 @@ export default function Home({ socket }: Props) {
|
|||||||
url: immediate || url || workingUrl,
|
url: immediate || url || workingUrl,
|
||||||
path: availableDownloadPaths[downloadPath],
|
path: availableDownloadPaths[downloadPath],
|
||||||
params: cliArgs.toString() + toFormatArgs(codes),
|
params: cliArgs.toString() + toFormatArgs(codes),
|
||||||
|
renameTo: fileNameOverride,
|
||||||
})
|
})
|
||||||
|
|
||||||
setUrl('')
|
setUrl('')
|
||||||
setWorkingUrl('')
|
setWorkingUrl('')
|
||||||
|
setFilenameOverride('')
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const input = document.getElementById('urlInput') as HTMLInputElement;
|
resetInput()
|
||||||
input.value = '';
|
setShowBackdrop(true)
|
||||||
setShowBackdrop(true);
|
setDownloadFormats(undefined)
|
||||||
setDownloadFormats(undefined);
|
|
||||||
}, 250);
|
}, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,16 +165,17 @@ export default function Home({ socket }: Props) {
|
|||||||
socket.emit('send-url-format-selection', {
|
socket.emit('send-url-format-selection', {
|
||||||
url: url,
|
url: url,
|
||||||
})
|
})
|
||||||
|
|
||||||
setWorkingUrl(url)
|
setWorkingUrl(url)
|
||||||
setUrl('')
|
setUrl('')
|
||||||
setPickedAudioFormat('');
|
setPickedAudioFormat('')
|
||||||
setPickedVideoFormat('');
|
setPickedVideoFormat('')
|
||||||
setPickedBestFormat('');
|
setPickedBestFormat('')
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const input = document.getElementById('urlInput') as HTMLInputElement;
|
resetInput()
|
||||||
input.value = '';
|
|
||||||
setShowBackdrop(true)
|
setShowBackdrop(true)
|
||||||
}, 250);
|
}, 250)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -179,6 +186,14 @@ export default function Home({ socket }: Props) {
|
|||||||
setUrl(e.target.value)
|
setUrl(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the filename override state whenever the input value changes
|
||||||
|
* @param e Input change event
|
||||||
|
*/
|
||||||
|
const handleFilenameOverrideChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFilenameOverride(e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abort a specific download if id's provided, other wise abort all running ones.
|
* Abort a specific download if id's provided, other wise abort all running ones.
|
||||||
* @param id The download id / pid
|
* @param id The download id / pid
|
||||||
@@ -209,6 +224,16 @@ export default function Home({ socket }: Props) {
|
|||||||
reader.readAsDataURL(urlList[0])
|
reader.readAsDataURL(urlList[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetInput = () => {
|
||||||
|
const input = document.getElementById('urlInput') as HTMLInputElement;
|
||||||
|
input.value = '';
|
||||||
|
|
||||||
|
const filename = document.getElementById('customFilenameInput') as HTMLInputElement;
|
||||||
|
if (filename) {
|
||||||
|
filename.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------- styled components -------------------- */
|
/* -------------------- styled components -------------------- */
|
||||||
|
|
||||||
const Input = styled('input')({
|
const Input = styled('input')({
|
||||||
@@ -232,43 +257,63 @@ export default function Home({ socket }: Props) {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Grid container spacing={1}>
|
<Grid container>
|
||||||
<Grid item xs={10}>
|
<TextField
|
||||||
<TextField
|
fullWidth
|
||||||
fullWidth
|
id="urlInput"
|
||||||
id="urlInput"
|
label={i18n.t('urlInput')}
|
||||||
label={i18n.t('urlInput')}
|
variant="outlined"
|
||||||
variant="outlined"
|
onChange={handleUrlChange}
|
||||||
onChange={handleUrlChange}
|
disabled={!status.connected || (settings.formatSelection && downloadFormats != null)}
|
||||||
disabled={!status.connected || (settings.formatSelection && downloadFormats != null)}
|
InputProps={{
|
||||||
InputProps={{
|
endAdornment: (
|
||||||
endAdornment: (
|
<InputAdornment position="end">
|
||||||
<InputAdornment position="end">
|
<label htmlFor="icon-button-file">
|
||||||
<label htmlFor="icon-button-file">
|
<Input id="icon-button-file" type="file" accept=".txt" onChange={parseUrlListFile} />
|
||||||
<Input id="icon-button-file" type="file" accept=".txt" onChange={parseUrlListFile} />
|
<IconButton color="primary" aria-label="upload file" component="span">
|
||||||
<IconButton color="primary" aria-label="upload file" component="span">
|
<FileUpload />
|
||||||
<FileUpload />
|
</IconButton>
|
||||||
</IconButton>
|
</label>
|
||||||
</label>
|
</InputAdornment>
|
||||||
</InputAdornment>
|
),
|
||||||
),
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</Grid>
|
||||||
</Grid>
|
<Grid container spacing={1} sx={{ mt: 1 }}>
|
||||||
<Grid item xs={2}>
|
{
|
||||||
<FormControl fullWidth>
|
settings.fileRenaming ?
|
||||||
<Select
|
<Grid item xs={8}>
|
||||||
defaultValue={0}
|
<TextField
|
||||||
value={downloadPath}
|
id="customFilenameInput"
|
||||||
onChange={(e) => setDownloadPath(Number(e.target.value))}
|
fullWidth
|
||||||
>
|
label={i18n.t('customFilename')}
|
||||||
|
variant="outlined"
|
||||||
{availableDownloadPaths.map((val: string, idx: number) => (
|
onChange={handleFilenameOverrideChange}
|
||||||
<MenuItem key={idx} value={idx}>{val}</MenuItem>
|
disabled={!status.connected || (settings.formatSelection && downloadFormats != null)}
|
||||||
))}
|
/>
|
||||||
</Select>
|
</Grid> :
|
||||||
</FormControl>
|
null
|
||||||
</Grid>
|
}
|
||||||
|
{
|
||||||
|
settings.pathOverriding ?
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel>{i18n.t('customPath')}</InputLabel>
|
||||||
|
<Select
|
||||||
|
label={i18n.t('customPath')}
|
||||||
|
defaultValue={0}
|
||||||
|
variant={'outlined'}
|
||||||
|
value={downloadPath}
|
||||||
|
onChange={(e) => setDownloadPath(Number(e.target.value))}
|
||||||
|
>
|
||||||
|
{availableDownloadPaths.map((val: string, idx: number) => (
|
||||||
|
<MenuItem key={idx} value={idx}>{val}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid> :
|
||||||
|
null
|
||||||
|
}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid container spacing={1} pt={2}>
|
<Grid container spacing={1} pt={2}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
@@ -291,119 +336,121 @@ export default function Home({ socket }: Props) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid >
|
||||||
{/* Format Selection grid */}
|
{/* Format Selection grid */}
|
||||||
{downloadFormats ? <Grid container spacing={2} mt={2}>
|
{
|
||||||
<Grid item xs={12}>
|
downloadFormats ? <Grid container spacing={2} mt={2}>
|
||||||
<Paper
|
<Grid item xs={12}>
|
||||||
sx={{
|
<Paper
|
||||||
p: 2,
|
sx={{
|
||||||
display: 'flex',
|
p: 2,
|
||||||
flexDirection: 'column',
|
display: 'flex',
|
||||||
}}
|
flexDirection: 'column',
|
||||||
>
|
}}
|
||||||
<Grid container>
|
>
|
||||||
<Grid item xs={12}>
|
<Grid container>
|
||||||
<Typography variant="h6" component="div" pb={1}>
|
<Grid item xs={12}>
|
||||||
{downloadFormats.title}
|
<Typography variant="h6" component="div" pb={1}>
|
||||||
</Typography>
|
{downloadFormats.title}
|
||||||
{/* <Skeleton variant="rectangular" height={180} /> */}
|
</Typography>
|
||||||
</Grid>
|
{/* <Skeleton variant="rectangular" height={180} /> */}
|
||||||
<Grid item xs={12} pb={1}>
|
</Grid>
|
||||||
<img src={downloadFormats.thumbnail} height={260} width="100%" style={{ objectFit: 'cover' }} />
|
<Grid item xs={12} pb={1}>
|
||||||
</Grid>
|
<img src={downloadFormats.thumbnail} height={260} width="100%" style={{ objectFit: 'cover' }} />
|
||||||
{/* video only */}
|
</Grid>
|
||||||
<Grid item xs={12}>
|
{/* video only */}
|
||||||
<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 */}
|
|
||||||
{downloadFormats.formats.filter(format => format.acodec === 'none' && format.vcodec !== 'none').length ?
|
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="body1" component="div">
|
<Typography variant="body1" component="div">
|
||||||
Video data {downloadFormats.formats[1].acodec}
|
Best quality
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
: null
|
<Grid item pr={2} py={1}>
|
||||||
}
|
|
||||||
{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>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
{downloadFormats.formats.filter(format => format.acodec === 'none' && format.vcodec !== 'none').length ?
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Typography variant="body1" component="div">
|
|
||||||
Audio data
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
{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
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={pickedBestFormat !== ''}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPickedAudioFormat('');
|
setPickedBestFormat(downloadFormats.best.format_id)
|
||||||
setPickedVideoFormat('');
|
setPickedVideoFormat('')
|
||||||
setPickedBestFormat('');
|
setPickedAudioFormat('')
|
||||||
}}
|
}}>
|
||||||
> Clear
|
{downloadFormats.best.format_note || downloadFormats.best.format_id} - {downloadFormats.best.vcodec}+{downloadFormats.best.acodec}
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</Grid>
|
||||||
|
{/* video only */}
|
||||||
|
{downloadFormats.formats.filter(format => format.acodec === 'none' && format.vcodec !== 'none').length ?
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="body1" component="div">
|
||||||
|
Video data {downloadFormats.formats[1].acodec}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
{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>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
{downloadFormats.formats.filter(format => format.acodec === 'none' && format.vcodec !== 'none').length ?
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="body1" component="div">
|
||||||
|
Audio data
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
{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>
|
</Grid>
|
||||||
</Grid>
|
</Paper>
|
||||||
</Paper>
|
</Grid>
|
||||||
</Grid>
|
</Grid> : null
|
||||||
</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*/
|
{
|
||||||
Array
|
Array
|
||||||
.from<any>(messageMap)
|
.from<any>(messageMap)
|
||||||
.filter(flattened => [...flattened][0])
|
.filter(flattened => [...flattened][0])
|
||||||
@@ -440,6 +487,6 @@ export default function Home({ socket }: Props) {
|
|||||||
message="Connected"
|
message="Connected"
|
||||||
onClose={() => setShowToast(false)}
|
onClose={() => setShowToast(false)}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -25,8 +25,10 @@ import { CliArguments } from "./classes";
|
|||||||
import {
|
import {
|
||||||
LanguageUnion,
|
LanguageUnion,
|
||||||
setCliArgs,
|
setCliArgs,
|
||||||
|
setFileRenaming,
|
||||||
setFormatSelection,
|
setFormatSelection,
|
||||||
setLanguage,
|
setLanguage,
|
||||||
|
setPathOverriding,
|
||||||
setServerAddr,
|
setServerAddr,
|
||||||
setServerPort,
|
setServerPort,
|
||||||
setTheme,
|
setTheme,
|
||||||
@@ -158,10 +160,10 @@ export default function Settings({ socket }: Props) {
|
|||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel id="demo-simple-select-label">Language</InputLabel>
|
<InputLabel>{i18n.t('languageSelect')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
defaultValue={settings.language}
|
defaultValue={settings.language}
|
||||||
label="Language"
|
label={i18n.t('languageSelect')}
|
||||||
onChange={handleLanguageChange}
|
onChange={handleLanguageChange}
|
||||||
>
|
>
|
||||||
<MenuItem value="english">English</MenuItem>
|
<MenuItem value="english">English</MenuItem>
|
||||||
@@ -176,10 +178,10 @@ export default function Settings({ socket }: Props) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Theme</InputLabel>
|
<InputLabel>{i18n.t('themeSelect')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
defaultValue={settings.theme}
|
defaultValue={settings.theme}
|
||||||
label="Theme"
|
label={i18n.t('themeSelect')}
|
||||||
onChange={handleThemeChange}
|
onChange={handleThemeChange}
|
||||||
>
|
>
|
||||||
<MenuItem value="light">Light</MenuItem>
|
<MenuItem value="light">Light</MenuItem>
|
||||||
@@ -230,6 +232,35 @@ export default function Settings({ socket }: Props) {
|
|||||||
}
|
}
|
||||||
label={i18n.t('formatSelectionEnabler')}
|
label={i18n.t('formatSelectionEnabler')}
|
||||||
/>
|
/>
|
||||||
|
<Grid>
|
||||||
|
<Typography variant="h6" color="primary" sx={{ mt: 2, mb: 0.5 }}>
|
||||||
|
{i18n.t('overridesAnchor')}
|
||||||
|
</Typography>
|
||||||
|
<Stack direction="column">
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.pathOverriding}
|
||||||
|
onChange={() => {
|
||||||
|
dispatch(setPathOverriding(!settings.pathOverriding))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n.t('pathOverrideOption')}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.fileRenaming}
|
||||||
|
onChange={() => {
|
||||||
|
dispatch(setFileRenaming(!settings.fileRenaming))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n.t('filenameOverrideOption')}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Stack direction="row">
|
<Stack direction="row">
|
||||||
<Button
|
<Button
|
||||||
@@ -239,7 +270,6 @@ export default function Settings({ socket }: Props) {
|
|||||||
>
|
>
|
||||||
{i18n.t('updateBinButton')}
|
{i18n.t('updateBinButton')}
|
||||||
</Button>
|
</Button>
|
||||||
{/* <Button sx={{ mr: 1, mt: 1 }} variant="outlined">Primary</Button> */}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid>
|
</Grid>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ languages:
|
|||||||
toastConnected: 'Connected to '
|
toastConnected: 'Connected to '
|
||||||
toastUpdated: Updated yt-dlp binary!
|
toastUpdated: Updated yt-dlp binary!
|
||||||
formatSelectionEnabler: Enable video/audio formats selection
|
formatSelectionEnabler: Enable video/audio formats selection
|
||||||
|
themeSelect: 'Theme'
|
||||||
|
languageSelect: 'Language'
|
||||||
|
overridesAnchor: Overrides
|
||||||
|
pathOverrideOption: Enable output path overriding
|
||||||
|
filenameOverrideOption: Enable output file name overriding
|
||||||
|
customFilename: Custom filemame (leave blank to use default)
|
||||||
|
customPath: Custom path
|
||||||
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
|
||||||
@@ -36,6 +43,13 @@ languages:
|
|||||||
toastConnected: 'Connesso a '
|
toastConnected: 'Connesso a '
|
||||||
toastUpdated: yt-dlp aggiornato con successo!
|
toastUpdated: yt-dlp aggiornato con successo!
|
||||||
formatSelectionEnabler: Abilita la selezione dei formati audio/video
|
formatSelectionEnabler: Abilita la selezione dei formati audio/video
|
||||||
|
themeSelect: 'Tema'
|
||||||
|
languageSelect: 'Lingua'
|
||||||
|
overridesAnchor: Sovrascritture
|
||||||
|
pathOverrideOption: Abilita sovrascrittura percorso di output
|
||||||
|
filenameOverrideOption: Abilita sovrascrittura del nome del file di output
|
||||||
|
customFilename: Custom filemame (leave blank to use default)
|
||||||
|
customPath: Custom path
|
||||||
chinese:
|
chinese:
|
||||||
urlInput: YouTube 或其他受支持服务的视频网址
|
urlInput: YouTube 或其他受支持服务的视频网址
|
||||||
statusTitle: 状态
|
statusTitle: 状态
|
||||||
@@ -54,6 +68,13 @@ languages:
|
|||||||
toastConnected: '已连接到 '
|
toastConnected: '已连接到 '
|
||||||
toastUpdated: 已更新 yt-dlp 可执行文件!
|
toastUpdated: 已更新 yt-dlp 可执行文件!
|
||||||
formatSelectionEnabler: 启用视频/音频格式选择
|
formatSelectionEnabler: 启用视频/音频格式选择
|
||||||
|
themeSelect: 'Theme'
|
||||||
|
languageSelect: 'Language'
|
||||||
|
overridesAnchor: Overrides
|
||||||
|
pathOverrideOption: Enable output path overriding
|
||||||
|
filenameOverrideOption: Enable output file name overriding
|
||||||
|
customFilename: Custom filemame (leave blank to use default)
|
||||||
|
customPath: Custom path
|
||||||
spanish:
|
spanish:
|
||||||
urlInput: YouTube or other supported service video url
|
urlInput: YouTube or other supported service video url
|
||||||
statusTitle: Status
|
statusTitle: Status
|
||||||
@@ -72,6 +93,13 @@ languages:
|
|||||||
toastConnected: 'Connected to '
|
toastConnected: 'Connected to '
|
||||||
toastUpdated: Updated yt-dlp binary!
|
toastUpdated: Updated yt-dlp binary!
|
||||||
formatSelectionEnabler: Enable video/audio formats selection
|
formatSelectionEnabler: Enable video/audio formats selection
|
||||||
|
themeSelect: 'Theme'
|
||||||
|
languageSelect: 'Language'
|
||||||
|
overridesAnchor: Overrides
|
||||||
|
pathOverrideOption: Enable output path overriding
|
||||||
|
filenameOverrideOption: Enable output file name overriding
|
||||||
|
customFilename: Custom filemame (leave blank to use default)
|
||||||
|
customPath: Custom path
|
||||||
russian:
|
russian:
|
||||||
urlInput: YouTube or other supported service video url
|
urlInput: YouTube or other supported service video url
|
||||||
statusTitle: Status
|
statusTitle: Status
|
||||||
@@ -90,6 +118,13 @@ languages:
|
|||||||
toastConnected: 'Connected to '
|
toastConnected: 'Connected to '
|
||||||
toastUpdated: Updated yt-dlp binary!
|
toastUpdated: Updated yt-dlp binary!
|
||||||
formatSelectionEnabler: Enable video/audio formats selection
|
formatSelectionEnabler: Enable video/audio formats selection
|
||||||
|
themeSelect: 'Theme'
|
||||||
|
languageSelect: 'Language'
|
||||||
|
overridesAnchor: Overrides
|
||||||
|
pathOverrideOption: Enable output path overriding
|
||||||
|
filenameOverrideOption: Enable output file name overriding
|
||||||
|
customFilename: Custom filemame (leave blank to use default)
|
||||||
|
customPath: Custom path
|
||||||
korean:
|
korean:
|
||||||
urlInput: YouTube나 다른 지원되는 사이트의 URL
|
urlInput: YouTube나 다른 지원되는 사이트의 URL
|
||||||
statusTitle: 상태
|
statusTitle: 상태
|
||||||
@@ -108,6 +143,13 @@ languages:
|
|||||||
toastConnected: '다음으로 연결됨 '
|
toastConnected: '다음으로 연결됨 '
|
||||||
toastUpdated: yt-dlp 바이너리를 업데이트 했습니다
|
toastUpdated: yt-dlp 바이너리를 업데이트 했습니다
|
||||||
formatSelectionEnabler: 비디오/오디오 포멧 옵션 표시
|
formatSelectionEnabler: 비디오/오디오 포멧 옵션 표시
|
||||||
|
themeSelect: 'Theme'
|
||||||
|
languageSelect: 'Language'
|
||||||
|
overridesAnchor: Overrides
|
||||||
|
pathOverrideOption: Enable output path overriding
|
||||||
|
filenameOverrideOption: Enable output file name overriding
|
||||||
|
customFilename: Custom filemame (leave blank to use default)
|
||||||
|
customPath: Custom path
|
||||||
japanese:
|
japanese:
|
||||||
urlInput: YouTubeまたはサポート済み動画のURL
|
urlInput: YouTubeまたはサポート済み動画のURL
|
||||||
statusTitle: 状態
|
statusTitle: 状態
|
||||||
@@ -126,3 +168,10 @@ languages:
|
|||||||
toastConnected: '接続中 '
|
toastConnected: '接続中 '
|
||||||
toastUpdated: yt-dlpを更新しました!
|
toastUpdated: yt-dlpを更新しました!
|
||||||
formatSelectionEnabler: 選択可能な動画/音源
|
formatSelectionEnabler: 選択可能な動画/音源
|
||||||
|
themeSelect: 'Theme'
|
||||||
|
languageSelect: 'Language'
|
||||||
|
overridesAnchor: Overrides
|
||||||
|
pathOverrideOption: Enable output path overriding
|
||||||
|
filenameOverrideOption: Enable output file name overriding
|
||||||
|
customFilename: Custom filemame (leave blank to use default)
|
||||||
|
customPath: Custom path
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
|
||||||
import { CliArguments } from "../../classes"
|
|
||||||
import { I18nBuilder } from "../../i18n"
|
|
||||||
|
|
||||||
export type LanguageUnion = "english" | "chinese" | "russian" | "italian" | "spanish" | "korean" | "japanese"
|
export type LanguageUnion = "english" | "chinese" | "russian" | "italian" | "spanish" | "korean" | "japanese"
|
||||||
export type ThemeUnion = "light" | "dark"
|
export type ThemeUnion = "light" | "dark"
|
||||||
@@ -13,6 +11,8 @@ export interface SettingsState {
|
|||||||
cliArgs: string,
|
cliArgs: string,
|
||||||
formatSelection: boolean,
|
formatSelection: boolean,
|
||||||
ratelimit: string,
|
ratelimit: string,
|
||||||
|
fileRenaming: boolean,
|
||||||
|
pathOverriding: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: SettingsState = {
|
const initialState: SettingsState = {
|
||||||
@@ -23,6 +23,8 @@ const initialState: SettingsState = {
|
|||||||
cliArgs: localStorage.getItem("cli-args") ?? "",
|
cliArgs: localStorage.getItem("cli-args") ?? "",
|
||||||
formatSelection: localStorage.getItem("format-selection") === "true",
|
formatSelection: localStorage.getItem("format-selection") === "true",
|
||||||
ratelimit: localStorage.getItem("rate-limit") ?? "",
|
ratelimit: localStorage.getItem("rate-limit") ?? "",
|
||||||
|
fileRenaming: localStorage.getItem("file-renaming") === "true",
|
||||||
|
pathOverriding: localStorage.getItem("path-overriding") === "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const settingsSlice = createSlice({
|
export const settingsSlice = createSlice({
|
||||||
@@ -57,9 +59,27 @@ export const settingsSlice = createSlice({
|
|||||||
state.ratelimit = action.payload
|
state.ratelimit = action.payload
|
||||||
localStorage.setItem("rate-limit", action.payload)
|
localStorage.setItem("rate-limit", action.payload)
|
||||||
},
|
},
|
||||||
|
setPathOverriding: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.pathOverriding = action.payload
|
||||||
|
localStorage.setItem("path-overriding", action.payload.toString())
|
||||||
|
},
|
||||||
|
setFileRenaming: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.fileRenaming = action.payload
|
||||||
|
localStorage.setItem("file-renaming", action.payload.toString())
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const { setLanguage, setCliArgs, setTheme, setServerAddr, setServerPort, setFormatSelection, setRateLimit } = settingsSlice.actions
|
export const {
|
||||||
|
setLanguage,
|
||||||
|
setCliArgs,
|
||||||
|
setTheme,
|
||||||
|
setServerAddr,
|
||||||
|
setServerPort,
|
||||||
|
setFormatSelection,
|
||||||
|
setRateLimit,
|
||||||
|
setFileRenaming,
|
||||||
|
setPathOverriding,
|
||||||
|
} = settingsSlice.actions
|
||||||
|
|
||||||
export default settingsSlice.reducer
|
export default settingsSlice.reducer
|
||||||
@@ -23,6 +23,7 @@ class Process {
|
|||||||
private pid: number;
|
private pid: number;
|
||||||
private metadata?: IDownloadMetadata;
|
private metadata?: IDownloadMetadata;
|
||||||
private exePath = join(__dirname, 'yt-dlp');
|
private exePath = join(__dirname, 'yt-dlp');
|
||||||
|
private customFileName?: string;
|
||||||
|
|
||||||
private readonly template = `download:
|
private readonly template = `download:
|
||||||
{
|
{
|
||||||
@@ -34,13 +35,14 @@ class Process {
|
|||||||
.replace(/\s\s+/g, ' ')
|
.replace(/\s\s+/g, ' ')
|
||||||
.replace('\n', '');
|
.replace('\n', '');
|
||||||
|
|
||||||
constructor(url: string, params: Array<string>, settings: any) {
|
constructor(url: string, params: Array<string>, settings: any, customFileName?: string) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.params = params || [];
|
this.params = params || [];
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.stdout = undefined;
|
this.stdout = undefined;
|
||||||
this.pid = undefined;
|
this.pid = undefined;
|
||||||
this.metadata = undefined;
|
this.metadata = undefined;
|
||||||
|
this.customFileName = customFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,7 +61,7 @@ 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/'}${this.customFileName || '%(title)s'}.%(ext)s`,
|
||||||
'--progress-template', this.template,
|
'--progress-template', this.template,
|
||||||
'--no-colors',
|
'--no-colors',
|
||||||
]
|
]
|
||||||
@@ -73,6 +75,10 @@ class Process {
|
|||||||
|
|
||||||
log.info('proc', `Spawned a new process, pid: ${this.pid}`)
|
log.info('proc', `Spawned a new process, pid: ${this.pid}`)
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,12 +62,14 @@ export async function download(socket: Socket, payload: IPayload) {
|
|||||||
payload.params.split(' ') :
|
payload.params.split(' ') :
|
||||||
payload.params;
|
payload.params;
|
||||||
|
|
||||||
|
const renameTo = payload.renameTo
|
||||||
|
|
||||||
const scopedSettings: ISettings = {
|
const scopedSettings: ISettings = {
|
||||||
...settings,
|
...settings,
|
||||||
download_path: payload.path
|
download_path: payload.path
|
||||||
}
|
}
|
||||||
|
|
||||||
let p = new Process(url, params, scopedSettings);
|
let p = new Process(url, params, scopedSettings, renameTo);
|
||||||
|
|
||||||
p.start().then(downloader => {
|
p.start().then(downloader => {
|
||||||
mem_db.add(downloader)
|
mem_db.add(downloader)
|
||||||
@@ -114,7 +116,7 @@ function streamProcess(process: Process, socket: Socket) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
from(process.getStdout().removeAllListeners()) // stdout as observable
|
from(process.getStdout().removeAllListeners()) // 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()))
|
||||||
|
|||||||
11
server/src/interfaces/IPayload.d.ts
vendored
11
server/src/interfaces/IPayload.d.ts
vendored
@@ -4,9 +4,10 @@
|
|||||||
|
|
||||||
export interface IPayload {
|
export interface IPayload {
|
||||||
url: string
|
url: string
|
||||||
params: Array<string> | string,
|
params: Array<string> | string
|
||||||
path: string,
|
path: string
|
||||||
title?: string,
|
title?: string
|
||||||
thumbnail?: string,
|
thumbnail?: string
|
||||||
size?: string,
|
size?: string
|
||||||
|
renameTo?: string
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user