Code refactoring and bump deps
This commit is contained in:
10
Dockerfile
10
Dockerfile
@@ -1,7 +1,4 @@
|
||||
# Multi stage build Dockerfile
|
||||
|
||||
# There's no point in using the edge (development branch of alpine)
|
||||
FROM alpine:3.17 AS build
|
||||
FROM golang:1.20-alpine AS build
|
||||
# folder structure
|
||||
WORKDIR /usr/src/yt-dlp-webui
|
||||
# install core dependencies
|
||||
@@ -15,7 +12,7 @@ RUN npm install
|
||||
RUN npm run build
|
||||
# build backend + incubator
|
||||
WORKDIR /usr/src/yt-dlp-webui
|
||||
RUN go build -o yt-dlp-webui
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o yt-dlp-webui
|
||||
|
||||
# but here yes :)
|
||||
FROM alpine:edge
|
||||
@@ -28,8 +25,7 @@ WORKDIR /app
|
||||
RUN apk update && \
|
||||
apk add psmisc ffmpeg yt-dlp
|
||||
|
||||
COPY --from=build /usr/src/yt-dlp-webui /app
|
||||
RUN chmod +x /app/yt-dlp-webui
|
||||
COPY --from=build /usr/src/yt-dlp-webui/yt-dlp-webui /app
|
||||
|
||||
EXPOSE 3033
|
||||
CMD [ "./yt-dlp-webui" , "--out", "/downloads" ]
|
||||
|
||||
10
Makefile
10
Makefile
@@ -1,14 +1,14 @@
|
||||
default:
|
||||
go build -o yt-dlp-webui main.go
|
||||
CGO_ENABLED=0 go build -o yt-dlp-webui main.go
|
||||
|
||||
all:
|
||||
cd frontend && pnpm build && cd ..
|
||||
go build -o yt-dlp-webui main.go
|
||||
CGO_ENABLED=0 go build -o yt-dlp-webui main.go
|
||||
|
||||
multiarch:
|
||||
GOOS=linux GOARCH=arm go build -o yt-dlp-webui_linux-arm *.go
|
||||
GOOS=linux GOARCH=arm64 go build -o yt-dlp-webui_linux-arm64 *.go
|
||||
GOOS=linux GOARCH=amd64 go build -o yt-dlp-webui_linux-amd64 *.go
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o yt-dlp-webui_linux-arm main.go
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o yt-dlp-webui_linux-arm64 main.go
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o yt-dlp-webui_linux-amd64 main.go
|
||||
mkdir -p build
|
||||
mv yt-dlp-webui* build
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yt-dlp-webui",
|
||||
"version": "2.0.6",
|
||||
"version": "2.0.7",
|
||||
"description": "Frontend compontent of yt-dlp-webui",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -9,28 +9,28 @@
|
||||
"author": "marcopeocchi",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@mui/icons-material": "^5.11.0",
|
||||
"@mui/material": "^5.11.5",
|
||||
"@reduxjs/toolkit": "^1.9.1",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@mui/icons-material": "^5.11.16",
|
||||
"@mui/material": "^5.12.0",
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "^6.7.0",
|
||||
"react-router-dom": "^6.9.0",
|
||||
"rxjs": "^7.8.0",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modyfi/vite-plugin-yaml": "^1.0.4",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/react": "^18.0.35",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@vitejs/plugin-react": "^3.0.1",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"buffer": "^6.0.3",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.0.4"
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.2.1"
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,38 @@
|
||||
import { ThemeProvider } from "@emotion/react";
|
||||
import { ThemeProvider } from '@emotion/react'
|
||||
import {
|
||||
ChevronLeft,
|
||||
Dashboard,
|
||||
// Download,
|
||||
Menu, Settings as SettingsIcon,
|
||||
FormatListBulleted,
|
||||
Menu,
|
||||
SettingsEthernet,
|
||||
Settings as SettingsIcon,
|
||||
Storage
|
||||
} from "@mui/icons-material";
|
||||
} from '@mui/icons-material'
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
createTheme, CssBaseline,
|
||||
CssBaseline,
|
||||
Divider,
|
||||
IconButton, List,
|
||||
ListItemIcon, ListItemText, Toolbar,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { grey } from "@mui/material/colors";
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import { lazy, Suspense, useMemo, useState } from "react";
|
||||
import { Provider, useDispatch, useSelector } from "react-redux";
|
||||
Typography,
|
||||
createTheme
|
||||
} from '@mui/material'
|
||||
import ListItemButton from '@mui/material/ListItemButton'
|
||||
import { grey } from '@mui/material/colors'
|
||||
import { Suspense, lazy, useMemo, useState } from 'react'
|
||||
import { Provider, useDispatch, useSelector } from 'react-redux'
|
||||
import {
|
||||
BrowserRouter as Router, Link, Route,
|
||||
Link, Route,
|
||||
BrowserRouter as Router,
|
||||
Routes
|
||||
} from 'react-router-dom';
|
||||
import { AppBar } from "./components/AppBar";
|
||||
import { Drawer } from "./components/Drawer";
|
||||
import { toggleListView } from "./features/settings/settingsSlice";
|
||||
import Home from "./Home";
|
||||
import { RootState, store } from './stores/store';
|
||||
import { formatGiB, getWebSocketEndpoint } from "./utils";
|
||||
} from 'react-router-dom'
|
||||
import Home from './Home'
|
||||
import { AppBar } from './components/AppBar'
|
||||
import { Drawer } from './components/Drawer'
|
||||
import { toggleListView } from './features/settings/settingsSlice'
|
||||
import { RootState, store } from './stores/store'
|
||||
import { formatGiB, getWebSocketEndpoint } from './utils'
|
||||
|
||||
function AppContent() {
|
||||
const [open, setOpen] = useState(false)
|
||||
@@ -179,7 +181,7 @@ function AppContent() {
|
||||
</Box>
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function App() {
|
||||
@@ -187,5 +189,5 @@ export function App() {
|
||||
<Provider store={store}>
|
||||
<AppContent />
|
||||
</Provider>
|
||||
);
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { FileUpload } from "@mui/icons-material";
|
||||
import { FileUpload } from '@mui/icons-material'
|
||||
import {
|
||||
Alert,
|
||||
Backdrop,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
CircularProgress,
|
||||
Container,
|
||||
FormControl,
|
||||
@@ -15,21 +15,21 @@ import {
|
||||
Select,
|
||||
Snackbar,
|
||||
styled,
|
||||
TextField,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { Buffer } from 'buffer';
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { DownloadsCardView } from "./components/DownloadsCardView";
|
||||
import { DownloadsListView } from "./components/DownloadsListView";
|
||||
import { CliArguments } from "./features/core/argsParser";
|
||||
import I18nBuilder from "./features/core/intl";
|
||||
import { RPCClient } from "./features/core/rpcClient";
|
||||
import { connected, setFreeSpace } from "./features/status/statusSlice";
|
||||
import { RootState } from "./stores/store";
|
||||
import { IDLMetadata, RPCResult } from "./types";
|
||||
import { isValidURL, toFormatArgs } from "./utils";
|
||||
TextField
|
||||
} from '@mui/material'
|
||||
import { Buffer } from 'buffer'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { DownloadsCardView } from './components/DownloadsCardView'
|
||||
import { DownloadsListView } from './components/DownloadsListView'
|
||||
import FormatsGrid from './components/FormatsGrid'
|
||||
import { CliArguments } from './features/core/argsParser'
|
||||
import I18nBuilder from './features/core/intl'
|
||||
import { RPCClient } from './features/core/rpcClient'
|
||||
import { connected, setFreeSpace } from './features/status/statusSlice'
|
||||
import { RootState } from './stores/store'
|
||||
import type { DLMetadata, RPCResult } from './types'
|
||||
import { isValidURL, toFormatArgs } from './utils'
|
||||
|
||||
type Props = {
|
||||
socket: WebSocket
|
||||
@@ -42,30 +42,35 @@ export default function Home({ socket }: Props) {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
// ephemeral state
|
||||
const [activeDownloads, setActiveDownloads] = useState<Array<RPCResult>>();
|
||||
const [downloadFormats, setDownloadFormats] = useState<IDLMetadata>();
|
||||
const [pickedVideoFormat, setPickedVideoFormat] = useState('');
|
||||
const [pickedAudioFormat, setPickedAudioFormat] = useState('');
|
||||
const [pickedBestFormat, setPickedBestFormat] = useState('');
|
||||
const [activeDownloads, setActiveDownloads] = useState<Array<RPCResult>>()
|
||||
const [downloadFormats, setDownloadFormats] = useState<DLMetadata>()
|
||||
const [pickedVideoFormat, setPickedVideoFormat] = useState('')
|
||||
const [pickedAudioFormat, setPickedAudioFormat] = useState('')
|
||||
const [pickedBestFormat, setPickedBestFormat] = useState('')
|
||||
|
||||
const [customArgs, setCustomArgs] = useState('');
|
||||
const [downloadPath, setDownloadPath] = useState(0);
|
||||
const [availableDownloadPaths, setAvailableDownloadPaths] = useState<string[]>([]);
|
||||
const [customArgs, setCustomArgs] = useState('')
|
||||
const [downloadPath, setDownloadPath] = useState(0)
|
||||
const [availableDownloadPaths, setAvailableDownloadPaths] = useState<string[]>([])
|
||||
|
||||
const [fileNameOverride, setFilenameOverride] = useState('');
|
||||
const [fileNameOverride, setFilenameOverride] = useState('')
|
||||
|
||||
const [url, setUrl] = useState('');
|
||||
const [workingUrl, setWorkingUrl] = useState('');
|
||||
const [url, setUrl] = useState('')
|
||||
const [workingUrl, setWorkingUrl] = useState('')
|
||||
|
||||
const [showBackdrop, setShowBackdrop] = useState(true);
|
||||
const [showToast, setShowToast] = useState(true);
|
||||
const [showBackdrop, setShowBackdrop] = useState(true)
|
||||
const [showToast, setShowToast] = useState(true)
|
||||
|
||||
// memos
|
||||
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language])
|
||||
const client = useMemo(() => new RPCClient(socket), [settings.serverAddr, settings.serverPort])
|
||||
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [settings.cliArgs])
|
||||
|
||||
// refs
|
||||
const urlInputRef = useRef<HTMLInputElement>(null)
|
||||
const customFilenameInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
/* -------------------- Effects -------------------- */
|
||||
|
||||
/* WebSocket connect event handler*/
|
||||
useEffect(() => {
|
||||
socket.onopen = () => {
|
||||
@@ -84,8 +89,7 @@ export default function Home({ socket }: Props) {
|
||||
}, [status.connected])
|
||||
|
||||
useEffect(() => {
|
||||
client.freeSpace()
|
||||
.then(bytes => dispatch(setFreeSpace(bytes.result)))
|
||||
client.freeSpace().then(bytes => dispatch(setFreeSpace(bytes.result)))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -118,7 +122,19 @@ export default function Home({ socket }: Props) {
|
||||
})
|
||||
}, [])
|
||||
|
||||
/* -------------------- component functions -------------------- */
|
||||
const [socketHasError, setSocketHasError] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
socket.onerror = () => {
|
||||
setSocketHasError(true)
|
||||
setShowBackdrop(false)
|
||||
}
|
||||
return () => {
|
||||
socket.onerror = null
|
||||
}
|
||||
}, [socket])
|
||||
|
||||
/* -------------------- callbacks-------------------- */
|
||||
|
||||
/**
|
||||
* Retrive url from input, cli-arguments from checkboxes and emits via WebSocket
|
||||
@@ -222,12 +238,9 @@ export default function Home({ socket }: Props) {
|
||||
}
|
||||
|
||||
const resetInput = () => {
|
||||
const input = document.getElementById('urlInput') as HTMLInputElement;
|
||||
input.value = '';
|
||||
|
||||
const filename = document.getElementById('customFilenameInput') as HTMLInputElement;
|
||||
if (filename) {
|
||||
filename.value = '';
|
||||
urlInputRef.current!.value = '';
|
||||
if (customFilenameInputRef.current) {
|
||||
customFilenameInputRef.current!.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +270,7 @@ export default function Home({ socket }: Props) {
|
||||
<Grid container>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="urlInput"
|
||||
ref={urlInputRef}
|
||||
label={i18n.t('urlInput')}
|
||||
variant="outlined"
|
||||
onChange={handleUrlChange}
|
||||
@@ -278,10 +291,9 @@ export default function Home({ socket }: Props) {
|
||||
</Grid>
|
||||
<Grid container spacing={1} sx={{ mt: 1 }}>
|
||||
{
|
||||
settings.enableCustomArgs ?
|
||||
settings.enableCustomArgs &&
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="customArgsInput"
|
||||
fullWidth
|
||||
label={i18n.t('customArgsInput')}
|
||||
variant="outlined"
|
||||
@@ -289,14 +301,13 @@ export default function Home({ socket }: Props) {
|
||||
value={customArgs}
|
||||
disabled={!status.connected || (settings.formatSelection && downloadFormats != null)}
|
||||
/>
|
||||
</Grid> :
|
||||
null
|
||||
</Grid>
|
||||
}
|
||||
{
|
||||
settings.fileRenaming ?
|
||||
settings.fileRenaming &&
|
||||
<Grid item xs={8}>
|
||||
<TextField
|
||||
id="customFilenameInput"
|
||||
ref={customFilenameInputRef}
|
||||
fullWidth
|
||||
label={i18n.t('customFilename')}
|
||||
variant="outlined"
|
||||
@@ -304,11 +315,10 @@ export default function Home({ socket }: Props) {
|
||||
onChange={handleFilenameOverrideChange}
|
||||
disabled={!status.connected || (settings.formatSelection && downloadFormats != null)}
|
||||
/>
|
||||
</Grid> :
|
||||
null
|
||||
</Grid>
|
||||
}
|
||||
{
|
||||
settings.pathOverriding ?
|
||||
settings.pathOverriding &&
|
||||
<Grid item xs={4}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>{i18n.t('customPath')}</InputLabel>
|
||||
@@ -324,8 +334,7 @@ export default function Home({ socket }: Props) {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid> :
|
||||
null
|
||||
</Grid>
|
||||
}
|
||||
</Grid>
|
||||
<Grid container spacing={1} pt={2}>
|
||||
@@ -351,120 +360,31 @@ export default function Home({ socket }: Props) {
|
||||
</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)
|
||||
{downloadFormats && <FormatsGrid
|
||||
downloadFormats={downloadFormats}
|
||||
onBestQualitySelected={(id) => {
|
||||
setPickedBestFormat(id)
|
||||
setPickedVideoFormat('')
|
||||
setPickedAudioFormat('')
|
||||
}}>
|
||||
{downloadFormats.best.format_note || downloadFormats.best.format_id} - {downloadFormats.best.vcodec}+{downloadFormats.best.acodec}
|
||||
({downloadFormats.best.resolution}{(downloadFormats.best.filesize_approx>0)?", ~"+Math.round(downloadFormats.best.filesize_approx/1024/1024)+" MiB":""})
|
||||
</Button>
|
||||
</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)
|
||||
}}
|
||||
onVideoSelected={(id) => {
|
||||
setPickedVideoFormat(id)
|
||||
setPickedBestFormat('')
|
||||
}}
|
||||
disabled={pickedVideoFormat === format.format_id}
|
||||
>
|
||||
{format.format_note} - {format.vcodec === 'none' ? format.acodec : format.vcodec}
|
||||
({format.resolution}{(format.filesize_approx>0)?", ~"+Math.round(format.filesize_approx/1024/1024)+" MiB":""})
|
||||
</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)
|
||||
onAudioSelected={(id) => {
|
||||
setPickedAudioFormat(id)
|
||||
setPickedBestFormat('')
|
||||
}}
|
||||
disabled={pickedAudioFormat === format.format_id}
|
||||
>
|
||||
{format.format_note} - {format.vcodec === 'none' ? format.acodec : format.vcodec}
|
||||
{(format.filesize_approx>0)?" (~"+Math.round(format.filesize_approx/1024/1024)+" MiB)":""}
|
||||
</Button>
|
||||
</Grid>
|
||||
))
|
||||
}
|
||||
<Grid item xs={12} pt={2}>
|
||||
<ButtonGroup disableElevation variant="contained">
|
||||
<Button
|
||||
onClick={() => sendUrl()}
|
||||
disabled={!pickedBestFormat && !(pickedAudioFormat || pickedVideoFormat)}
|
||||
> Download
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onClear={() => {
|
||||
setPickedAudioFormat('');
|
||||
setPickedVideoFormat('');
|
||||
setPickedBestFormat('');
|
||||
}}
|
||||
> Clear
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid> : null
|
||||
}
|
||||
onSubmit={sendUrl}
|
||||
pickedBestFormat={pickedBestFormat}
|
||||
pickedVideoFormat={pickedVideoFormat}
|
||||
pickedAudioFormat={pickedAudioFormat}
|
||||
/>}
|
||||
{
|
||||
settings.listView ?
|
||||
<DownloadsListView downloads={activeDownloads ?? []} abortFunction={abort} /> :
|
||||
@@ -473,9 +393,17 @@ export default function Home({ socket }: Props) {
|
||||
<Snackbar
|
||||
open={showToast === status.connected}
|
||||
autoHideDuration={1500}
|
||||
message="Connected"
|
||||
onClose={() => setShowToast(false)}
|
||||
/>
|
||||
>
|
||||
<Alert variant="filled" severity="success">
|
||||
{`Connected to (${settings.serverAddr}:${settings.serverPort})`}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<Snackbar open={socketHasError}>
|
||||
<Alert variant="filled" severity="error">
|
||||
{`${i18n.t('rpcConnErr')} (${settings.serverAddr}:${settings.serverPort})`}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -16,15 +16,16 @@ import {
|
||||
Switch,
|
||||
TextField,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { debounceTime, distinctUntilChanged, map, of, takeWhile } from "rxjs";
|
||||
import { CliArguments } from "./features/core/argsParser";
|
||||
import I18nBuilder from "./features/core/intl";
|
||||
import { RPCClient } from "./features/core/rpcClient";
|
||||
} from '@mui/material'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { Subject, debounceTime, distinctUntilChanged, map, takeWhile } from 'rxjs'
|
||||
import { CliArguments } from './features/core/argsParser'
|
||||
import I18nBuilder from './features/core/intl'
|
||||
import { RPCClient } from './features/core/rpcClient'
|
||||
import {
|
||||
LanguageUnion,
|
||||
ThemeUnion,
|
||||
setCliArgs,
|
||||
setEnableCustomArgs,
|
||||
setFileRenaming,
|
||||
@@ -33,32 +34,31 @@ import {
|
||||
setPathOverriding,
|
||||
setServerAddr,
|
||||
setServerPort,
|
||||
setTheme,
|
||||
ThemeUnion
|
||||
} from "./features/settings/settingsSlice";
|
||||
import { updated } from "./features/status/statusSlice";
|
||||
import { RootState } from "./stores/store";
|
||||
import { validateDomain, validateIP } from "./utils";
|
||||
setTheme
|
||||
} from './features/settings/settingsSlice'
|
||||
import { updated } from './features/status/statusSlice'
|
||||
import { RootState } from './stores/store'
|
||||
import { validateDomain, validateIP } from './utils'
|
||||
|
||||
export default function Settings({ socket }: { socket: WebSocket }) {
|
||||
const settings = useSelector((state: RootState) => state.settings)
|
||||
const status = useSelector((state: RootState) => state.status)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const status = useSelector((state: RootState) => state.status)
|
||||
const settings = useSelector((state: RootState) => state.settings)
|
||||
|
||||
const [invalidIP, setInvalidIP] = useState(false);
|
||||
|
||||
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language])
|
||||
const client = useMemo(() => new RPCClient(socket), [settings.serverAddr, settings.serverPort])
|
||||
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [settings.cliArgs])
|
||||
/**
|
||||
* Update the server ip address state and localstorage whenever the input value changes.
|
||||
* Validate the ip-addr then set.s
|
||||
* @param event Input change event
|
||||
*/
|
||||
const handleAddrChange = (event: any) => {
|
||||
const $serverAddr = of(event)
|
||||
|
||||
const client = useMemo(() => new RPCClient(socket), [])
|
||||
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [])
|
||||
|
||||
const serverAddr$ = useMemo(() => new Subject<string>(), [])
|
||||
const serverPort$ = useMemo(() => new Subject<string>(), [])
|
||||
|
||||
useEffect(() => {
|
||||
const sub = serverAddr$
|
||||
.pipe(
|
||||
map(event => event.target.value),
|
||||
debounceTime(500),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
@@ -73,24 +73,20 @@ export default function Settings({ socket }: { socket: WebSocket }) {
|
||||
setInvalidIP(true)
|
||||
}
|
||||
})
|
||||
return $serverAddr.unsubscribe()
|
||||
}
|
||||
return () => sub.unsubscribe()
|
||||
}, [serverAddr$])
|
||||
|
||||
/**
|
||||
* Set server port
|
||||
*/
|
||||
const handlePortChange = (event: any) => {
|
||||
const $port = of(event)
|
||||
useEffect(() => {
|
||||
const sub = serverPort$
|
||||
.pipe(
|
||||
map(event => event.target.value),
|
||||
map(val => Number(val)),
|
||||
takeWhile(val => isFinite(val) && val <= 65535),
|
||||
)
|
||||
.subscribe(port => {
|
||||
dispatch(setServerPort(port.toString()))
|
||||
})
|
||||
return $port.unsubscribe()
|
||||
}
|
||||
return () => sub.unsubscribe()
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Language toggler handler
|
||||
@@ -107,7 +103,7 @@ export default function Settings({ socket }: { socket: WebSocket }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send via WebSocket a message in order to update the yt-dlp binary from server
|
||||
* Send via WebSocket a message to update yt-dlp binary
|
||||
*/
|
||||
const updateBinary = () => {
|
||||
client.updateExecutable().then(() => dispatch(updated()))
|
||||
@@ -136,7 +132,7 @@ export default function Settings({ socket }: { socket: WebSocket }) {
|
||||
label={i18n.t('serverAddressTitle')}
|
||||
defaultValue={settings.serverAddr}
|
||||
error={invalidIP}
|
||||
onChange={handleAddrChange}
|
||||
onChange={(e) => serverAddr$.next(e.currentTarget.value)}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start">ws://</InputAdornment>,
|
||||
}}
|
||||
@@ -148,7 +144,7 @@ export default function Settings({ socket }: { socket: WebSocket }) {
|
||||
fullWidth
|
||||
label={i18n.t('serverPortTitle')}
|
||||
defaultValue={settings.serverPort}
|
||||
onChange={handlePortChange}
|
||||
onChange={(e) => serverPort$.next(e.currentTarget.value)}
|
||||
error={isNaN(Number(settings.serverPort)) || Number(settings.serverPort) > 65535}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
@@ -186,16 +182,6 @@ export default function Settings({ socket }: { socket: WebSocket }) {
|
||||
</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={
|
||||
|
||||
@@ -28,6 +28,7 @@ languages:
|
||||
customPath: Custom path
|
||||
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
||||
customArgsInput: Custom yt-dlp arguments
|
||||
rpcConnErr: Error while conencting to RPC server
|
||||
italian:
|
||||
urlInput: URL di YouTube o di qualsiasi altro servizio supportato
|
||||
statusTitle: Stato
|
||||
@@ -55,6 +56,7 @@ languages:
|
||||
customPath: Custom path
|
||||
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
||||
customArgsInput: Custom yt-dlp arguments
|
||||
rpcConnErr: Error nella connessione al server RPC
|
||||
chinese:
|
||||
urlInput: YouTube 或其他受支持服务的视频网址
|
||||
statusTitle: 状态
|
||||
@@ -82,6 +84,7 @@ languages:
|
||||
customPath: 自定义路径
|
||||
customArgs: 启用自定义 yt-dlp 参数(能力越大 = 责任越大)
|
||||
customArgsInput: 自定义 yt-dlp 参数
|
||||
rpcConnErr: Error while conencting to RPC server
|
||||
spanish:
|
||||
urlInput: YouTube or other supported service video url
|
||||
statusTitle: Status
|
||||
@@ -109,6 +112,7 @@ languages:
|
||||
customPath: Custom path
|
||||
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
||||
customArgsInput: Custom yt-dlp arguments
|
||||
rpcConnErr: Error while conencting to RPC server
|
||||
russian:
|
||||
urlInput: YouTube or other supported service video url
|
||||
statusTitle: Status
|
||||
@@ -136,6 +140,7 @@ languages:
|
||||
customPath: Custom path
|
||||
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
||||
customArgsInput: Custom yt-dlp arguments
|
||||
rpcConnErr: Error while conencting to RPC server
|
||||
korean:
|
||||
urlInput: YouTube나 다른 지원되는 사이트의 URL
|
||||
statusTitle: 상태
|
||||
@@ -163,6 +168,7 @@ languages:
|
||||
customPath: Custom path
|
||||
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
||||
customArgsInput: Custom yt-dlp arguments
|
||||
rpcConnErr: Error while conencting to RPC server
|
||||
japanese:
|
||||
urlInput: YouTubeまたはサポート済み動画のURL
|
||||
statusTitle: 状態
|
||||
@@ -191,3 +197,4 @@ languages:
|
||||
customPath: 保存先
|
||||
customArgs: yt-dlpのオプションの有効化 (最適設定にする場合)
|
||||
customArgsInput: yt-dlpのオプション
|
||||
rpcConnErr: Error while conencting to RPC server
|
||||
126
frontend/src/components/FormatsGrid.tsx
Normal file
126
frontend/src/components/FormatsGrid.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { Button, ButtonGroup, Grid, Paper, Typography } from "@mui/material"
|
||||
import type { DLMetadata } from "../types"
|
||||
|
||||
type Props = {
|
||||
downloadFormats: DLMetadata
|
||||
onAudioSelected: (format: string) => void
|
||||
onVideoSelected: (format: string) => void
|
||||
onBestQualitySelected: (format: string) => void
|
||||
onSubmit: () => void
|
||||
onClear: () => void
|
||||
pickedBestFormat: string
|
||||
pickedAudioFormat: string
|
||||
pickedVideoFormat: string
|
||||
}
|
||||
|
||||
export default function FormatsGrid({
|
||||
downloadFormats,
|
||||
onAudioSelected,
|
||||
onVideoSelected,
|
||||
onBestQualitySelected,
|
||||
onSubmit,
|
||||
onClear,
|
||||
pickedBestFormat,
|
||||
pickedAudioFormat,
|
||||
pickedVideoFormat,
|
||||
}: Props) {
|
||||
return (
|
||||
<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={() => onBestQualitySelected(downloadFormats.best.format_id)}
|
||||
>
|
||||
{downloadFormats.best.format_note || downloadFormats.best.format_id} - {downloadFormats.best.vcodec}+{downloadFormats.best.acodec}
|
||||
({downloadFormats.best.resolution}{(downloadFormats.best.filesize_approx > 0) ? ", ~" + Math.round(downloadFormats.best.filesize_approx / 1024 / 1024) + " MiB" : ""})
|
||||
</Button>
|
||||
</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>
|
||||
}
|
||||
{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={() => onVideoSelected(format.format_id)}
|
||||
disabled={pickedVideoFormat === format.format_id}
|
||||
>
|
||||
{format.format_note} - {format.vcodec === 'none' ? format.acodec : format.vcodec}
|
||||
({format.resolution}{(format.filesize_approx > 0) ? ", ~" + Math.round(format.filesize_approx / 1024 / 1024) + " MiB" : ""})
|
||||
</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>
|
||||
}
|
||||
{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={() => onAudioSelected(format.format_id)}
|
||||
disabled={pickedAudioFormat === format.format_id}
|
||||
>
|
||||
{format.format_note} - {format.vcodec === 'none' ? format.acodec : format.vcodec}
|
||||
{(format.filesize_approx > 0) ? " (~" + Math.round(format.filesize_approx / 1024 / 1024) + " MiB)" : ""}
|
||||
</Button>
|
||||
</Grid>
|
||||
))
|
||||
}
|
||||
<Grid item xs={12} pt={2}>
|
||||
<ButtonGroup disableElevation variant="contained">
|
||||
<Button
|
||||
onClick={() => onSubmit()}
|
||||
disabled={!pickedBestFormat && !(pickedAudioFormat || pickedVideoFormat)}
|
||||
> Download
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => onClear()}
|
||||
> Clear
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RPCRequest, RPCResponse, IDLMetadata } from "../../types"
|
||||
import type { RPCRequest, RPCResponse, DLMetadata } from "../../types"
|
||||
|
||||
import { getHttpRPCEndpoint } from '../../utils'
|
||||
|
||||
@@ -19,18 +19,17 @@ export class RPCClient {
|
||||
this.socket.send(JSON.stringify(req))
|
||||
}
|
||||
|
||||
private sendHTTP<T>(req: RPCRequest) {
|
||||
return new Promise<RPCResponse<T>>((resolve) => {
|
||||
fetch(getHttpRPCEndpoint(), {
|
||||
private async sendHTTP<T>(req: RPCRequest) {
|
||||
const res = await fetch(getHttpRPCEndpoint(), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
...req,
|
||||
id: this.incrementSeq(),
|
||||
...req
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => resolve(data))
|
||||
})
|
||||
const data: RPCResponse<T> = await res.json()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
public download(url: string, args: string, pathOverride = '', renameTo = '') {
|
||||
@@ -50,7 +49,7 @@ export class RPCClient {
|
||||
|
||||
public formats(url: string) {
|
||||
if (url) {
|
||||
return this.sendHTTP<IDLMetadata>({
|
||||
return this.sendHTTP<DLMetadata>({
|
||||
id: this.incrementSeq(),
|
||||
method: 'Service.Formats',
|
||||
params: [{
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { App } from './App'
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root')!)
|
||||
const root = createRoot(document.getElementById('root')!)
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App></App>
|
||||
</React.StrictMode>
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
)
|
||||
|
||||
8
frontend/src/types.d.ts
vendored
8
frontend/src/types.d.ts
vendored
@@ -45,14 +45,14 @@ export type RPCParams = {
|
||||
Params?: string
|
||||
}
|
||||
|
||||
export interface IDLMetadata {
|
||||
formats: Array<IDLFormat>,
|
||||
best: IDLFormat,
|
||||
export interface DLMetadata {
|
||||
formats: Array<DLFormat>,
|
||||
best: DLFormat,
|
||||
thumbnail: string,
|
||||
title: string,
|
||||
}
|
||||
|
||||
export interface IDLFormat {
|
||||
export interface DLFormat {
|
||||
format_id: string,
|
||||
format_note: string,
|
||||
fps: number,
|
||||
|
||||
27
go.mod
27
go.mod
@@ -3,25 +3,28 @@ module github.com/marcopeocchi/yt-dlp-web-ui
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/goccy/go-json v0.10.0
|
||||
github.com/gofiber/fiber/v2 v2.41.0
|
||||
github.com/gofiber/websocket/v2 v2.1.2
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/gofiber/fiber/v2 v2.43.0
|
||||
github.com/gofiber/websocket/v2 v2.1.5
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/marcopeocchi/fazzoletti v0.0.0-20221114144444-1e802380a7db
|
||||
golang.org/x/sys v0.4.0
|
||||
github.com/marcopeocchi/fazzoletti v0.0.0-20230308161120-c545580f79fa
|
||||
golang.org/x/sys v0.7.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/fasthttp/websocket v1.5.0 // indirect
|
||||
github.com/klauspost/compress v1.15.14 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/fasthttp/websocket v1.5.2 // indirect
|
||||
github.com/klauspost/compress v1.16.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/rivo/uniseg v0.4.3 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect
|
||||
github.com/philhofer/fwd v1.1.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||
github.com/tinylib/msgp v1.1.8 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.43.0 // indirect
|
||||
github.com/valyala/fasthttp v1.45.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
)
|
||||
|
||||
69
go.sum
69
go.sum
@@ -1,61 +1,130 @@
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/fasthttp/websocket v1.5.0 h1:B4zbe3xXyvIdnqjOZrafVFklCUq5ZLo/TqCt5JA1wLE=
|
||||
github.com/fasthttp/websocket v1.5.0/go.mod h1:n0BlOQvJdPbTuBkZT0O5+jk/sp/1/VCzquR1BehI2F4=
|
||||
github.com/fasthttp/websocket v1.5.2 h1:KdCb0EpLpdJpfE3IPA5YLK/aYBO3dhZcvwxz6tXe2LQ=
|
||||
github.com/fasthttp/websocket v1.5.2/go.mod h1:S0KC1VBlx1SaXGXq7yi1wKz4jMub58qEnHQG9oHuqBw=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gofiber/fiber/v2 v2.40.1/go.mod h1:Gko04sLksnHbzLSRBFWPFdzM9Ws9pRxvvIaohJK1dsk=
|
||||
github.com/gofiber/fiber/v2 v2.41.0 h1:YhNoUS/OTjEz+/WLYuQ01xI7RXgKEFnGBKMagAu5f0M=
|
||||
github.com/gofiber/fiber/v2 v2.41.0/go.mod h1:RdebcCuCRFp4W6hr3968/XxwJVg0K+jr9/Ae0PFzZ0Q=
|
||||
github.com/gofiber/fiber/v2 v2.43.0 h1:yit3E4kHf178B60p5CQBa/3v+WVuziWMa/G2ZNyLJB0=
|
||||
github.com/gofiber/fiber/v2 v2.43.0/go.mod h1:mpS1ZNE5jU+u+BA4FbM+KKnUzJ4wzTK+FT2tG3tU+6I=
|
||||
github.com/gofiber/websocket/v2 v2.1.2 h1:EulKyLB/fJgui5+6c8irwEnYQ9FRsrLZfkrq9OfTDGc=
|
||||
github.com/gofiber/websocket/v2 v2.1.2/go.mod h1:S+sKWo0xeC7Wnz5h4/8f6D/NxsrLFIdWDYB3SyVO9pE=
|
||||
github.com/gofiber/websocket/v2 v2.1.5 h1:2weAMr0Shb2ubhZ3+P4bkeWL+uCZ/NlgjSa1siEcvFM=
|
||||
github.com/gofiber/websocket/v2 v2.1.5/go.mod h1:BZZEk+XsjjF0V6/sAw00iGcB69dFb6Hb85ER9gr/xaU=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.15.14 h1:i7WCKDToww0wA+9qrUZ1xOjp218vfFo3nTU6UHp+gOc=
|
||||
github.com/klauspost/compress v1.15.14/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU=
|
||||
github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/marcopeocchi/fazzoletti v0.0.0-20221114144444-1e802380a7db h1:SmKRgCLsImPxBTIzmUpbQyv+7FembiZaq/QTwtDqar4=
|
||||
github.com/marcopeocchi/fazzoletti v0.0.0-20221114144444-1e802380a7db/go.mod h1:RvfVo/6Sbnfra9kkvIxDW8NYOOaYsHjF0DdtMCs9cdo=
|
||||
github.com/marcopeocchi/fazzoletti v0.0.0-20230308161120-c545580f79fa h1:uaAQLGhN4SesB9inOQ1Q6EH+BwTWHQOvwhR0TIJvnYc=
|
||||
github.com/marcopeocchi/fazzoletti v0.0.0-20230308161120-c545580f79fa/go.mod h1:RvfVo/6Sbnfra9kkvIxDW8NYOOaYsHjF0DdtMCs9cdo=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4=
|
||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
|
||||
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas=
|
||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo=
|
||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
||||
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.33.0/go.mod h1:KJRK/MXx0J+yd0c5hlR+s1tIHD72sniU8ZJjl97LIw4=
|
||||
github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
||||
github.com/valyala/fasthttp v1.43.0 h1:Gy4sb32C98fbzVWZlTM1oTMdLWGyvxR03VhM6cBIU4g=
|
||||
github.com/valyala/fasthttp v1.43.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
||||
github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA=
|
||||
github.com/valyala/fasthttp v1.45.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
9
main.go
9
main.go
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"flag"
|
||||
"io/fs"
|
||||
@@ -11,8 +10,6 @@ import (
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
|
||||
)
|
||||
|
||||
type ContextKey interface{}
|
||||
|
||||
var (
|
||||
port int
|
||||
downloadPath string
|
||||
@@ -48,9 +45,5 @@ func main() {
|
||||
cfg.DownloadPath(downloadPath)
|
||||
cfg.DownloaderPath(downloaderPath)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, ContextKey("port"), port)
|
||||
ctx = context.WithValue(ctx, ContextKey("frontend"), frontend)
|
||||
|
||||
server.RunBlocking(ctx)
|
||||
server.RunBlocking(port, frontend)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
@@ -17,10 +16,7 @@ import (
|
||||
|
||||
var db MemoryDB
|
||||
|
||||
func RunBlocking(ctx context.Context) {
|
||||
fe := ctx.Value("frontend").(fs.SubFS)
|
||||
port := ctx.Value("port").(int)
|
||||
|
||||
func RunBlocking(port int, frontend fs.FS) {
|
||||
service := new(Service)
|
||||
rpc.Register(service)
|
||||
|
||||
@@ -28,9 +24,13 @@ func RunBlocking(ctx context.Context) {
|
||||
|
||||
app.Use(cors.New())
|
||||
app.Use("/", filesystem.New(filesystem.Config{
|
||||
Root: http.FS(fe),
|
||||
Root: http.FS(frontend),
|
||||
}))
|
||||
|
||||
app.Get("/settings", func(c *fiber.Ctx) error {
|
||||
return c.Redirect("/")
|
||||
})
|
||||
|
||||
// RPC handlers
|
||||
// websocket
|
||||
app.Get("/ws-rpc", websocket.New(func(c *websocket.Conn) {
|
||||
@@ -52,8 +52,10 @@ func RunBlocking(ctx context.Context) {
|
||||
app.Post("/http-rpc", func(c *fiber.Ctx) error {
|
||||
reader := c.Context().RequestBodyStream()
|
||||
writer := c.Response().BodyWriter()
|
||||
|
||||
res := NewRPCRequest(reader).Call()
|
||||
io.Copy(writer, res)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user