Compare commits

..

2 Commits

Author SHA1 Message Date
d1184bf939 removed unused import 2024-11-15 14:22:59 +01:00
a39b526d64 editable templates 2024-11-15 11:29:13 +01:00
18 changed files with 1131 additions and 819 deletions

View File

@@ -1,7 +1,3 @@
> [!NOTE]
> A poll is up to decide the future of yt-dlp-web-ui frontend! If you're interested you can take part.
> https://github.com/marcopeocchi/yt-dlp-web-ui/discussions/223
# yt-dlp Web UI
A not so terrible web ui for yt-dlp.
@@ -155,8 +151,6 @@ Usage yt-dlp-webui:
session file path (default ".")
-user string
Username required for auth
-web string
frontend web resources path
```
### Config file
@@ -186,7 +180,7 @@ password: my_random_secret
queue_size: 4 # min. 2
# [optional] Full path to the yt-dlp (default: "yt-dlp")
#downloaderPath: /usr/local/bin/yt-dlp
downloaderPath: /usr/local/bin/yt-dlp
# [optional] Enable file based logging with rotation (default: false)
#enable_file_logging: false
@@ -199,9 +193,6 @@ queue_size: 4 # min. 2
# [optional] Path where the sqlite database will be created/opened (default: "./local.db")
#local_database_path
# [optional] Path where a custom frontend will be loaded (instead of the embedded one)
#frontend_path: ./web/solid-frontend
```
### Systemd integration
@@ -267,22 +258,6 @@ It is **planned** to also expose a **gRPC** server.
For more information open an issue on GitHub and I will provide more info ASAP.
## Custom frontend
To load a custom frontend you need to specify its path either in the config file ([see config file](#config-file)) or via flags.
The frontend needs to follow this structure:
```
path/to/my/frontend
├── assets
│ ├── js-chunk-1.js (example)
│ ├── js-chunk-2.js (example)
│ ├── style.css (example)
└── index.html
```
`assets` is where the resources will be loaded.
`index.html` is the entrypoint.
## Nix
This repo adds support for Nix(OS) in various ways through a `flake-parts` flake.
For more info, please refer to the [official documentation](https://nixos.org/learn/).

View File

@@ -1,27 +0,0 @@
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://app:3033;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
client_max_body_size 20000m;
proxy_connect_timeout 5000;
proxy_send_timeout 5000;
proxy_read_timeout 5000;
send_timeout 5000;
}
}

View File

@@ -1,15 +0,0 @@
services:
app:
image: marcobaobao/yt-dlp-webui
volumes:
- ./downloads:/downloads
restart: unless-stopped
nginx:
image: nginx:alpine
restart: unless-stopped
volumes:
- ./app.conf:/etc/nginx/conf.d/app.conf
depends_on:
- app
ports:
- 80:80

View File

@@ -1,6 +1,6 @@
{
"name": "yt-dlp-webui",
"version": "3.2.3",
"version": "3.2.2",
"description": "Frontend compontent of yt-dlp-webui",
"scripts": {
"dev": "vite",
@@ -10,29 +10,30 @@
"license": "GPL-3.0-only",
"private": true,
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@fontsource/roboto": "^5.0.13",
"@fontsource/roboto-mono": "^5.0.18",
"@mui/icons-material": "^6.2.0",
"@mui/material": "^6.2.0",
"@mui/icons-material": "^5.15.16",
"@mui/material": "^5.15.16",
"fp-ts": "^2.16.5",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1",
"react-virtuoso": "^4.7.11",
"jotai": "^2.10.3",
"jotai": "^2.10.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@modyfi/vite-plugin-yaml": "^1.1.0",
"@types/node": "^20.14.2",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.2.18",
"@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react-swc": "^3.7.2",
"typescript": "^5.7.2",
"vite": "^6.0.3"
"@vitejs/plugin-react-swc": "^3.7.0",
"million": "^3.1.11",
"typescript": "^5.4.5",
"vite": "^5.2.11"
}
}

1666
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ import ListItemIcon from '@mui/material/ListItemIcon'
import ListItemText from '@mui/material/ListItemText'
import Toolbar from '@mui/material/Toolbar'
import Typography from '@mui/material/Typography'
import { grey, red } from '@mui/material/colors'
import { grey } from '@mui/material/colors'
import { useMemo, useState } from 'react'
import { Link, Outlet } from 'react-router-dom'
import { settingsState } from './atoms/settings'
@@ -29,7 +29,6 @@ import ThemeToggler from './components/ThemeToggler'
import { useI18n } from './hooks/useI18n'
import Toaster from './providers/ToasterProvider'
import { useAtomValue } from 'jotai'
import { getAccentValue } from './utils'
export default function Layout() {
const [open, setOpen] = useState(false)
@@ -41,14 +40,11 @@ export default function Layout() {
createTheme({
palette: {
mode: settings.theme,
primary: {
main: getAccentValue(settings.accent)
},
background: {
default: settings.theme === 'light' ? grey[50] : '#121212'
},
},
}), [settings.theme, settings.accent]
}), [settings.theme]
)
const toggleDrawer = () => setOpen(state => !state)

View File

@@ -65,7 +65,6 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
german:
urlInput: Video URL
statusTitle: Status
@@ -127,7 +126,6 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
french:
urlInput: URL vidéo de YouTube ou d'un autre service pris en charge
statusTitle: Statut
@@ -191,7 +189,6 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
italian:
urlInput: URL Video (uno per linea)
statusTitle: Stato
@@ -252,7 +249,6 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
chinese:
urlInput: 视频 URL
statusTitle: 状态
@@ -314,7 +310,6 @@ languages:
如果直播已经开始,那么依然可以下载,但是不会记录下载进度。
直播开始后,将会转移到下载页面
livestreamExperimentalWarning: 实验性功能可能存在未知Bug请谨慎使用
accentSelect: 'Accent'
spanish:
urlInput: URL de YouTube u otro servicio compatible
statusTitle: Estado
@@ -374,7 +369,6 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
russian:
urlInput: URL-адрес YouTube или любого другого поддерживаемого сервиса
statusTitle: Статус
@@ -434,7 +428,6 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
korean:
urlInput: YouTube나 다른 지원되는 사이트의 URL
statusTitle: 상태
@@ -494,7 +487,6 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
japanese:
urlInput: YouTubeまたはサポート済み動画のURL
statusTitle: 状態
@@ -555,7 +547,6 @@ languages:
すでに開始されているライブストリームが提供された場合、ダウンロードは継続されますが進行状況は追跡されません。
ライブストリームが開始されると、ダウンロードページに移動されます。
livestreamExperimentalWarning: この機能は実験的なものです。何かが壊れるかもしれません!
accentSelect: 'Accent'
catalan:
urlInput: URL de YouTube o d'un altre servei compatible
statusTitle: Estat
@@ -615,7 +606,6 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
ukrainian:
urlInput: URL-адреса YouTube або будь-якого іншого підтримуваного сервісу
statusTitle: Статус
@@ -675,7 +665,6 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
polish:
urlInput: Adres URL YouTube lub innej obsługiwanej usługi
statusTitle: Status
@@ -735,7 +724,6 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
swedish:
urlInput: Videolänk (en per rad)
statusTitle: Status
@@ -801,4 +789,3 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'

View File

@@ -22,8 +22,8 @@ export const filenameTemplateState = atomWithStorage(
localStorage.getItem('lastFilenameTemplate') ?? ''
)
export const downloadTemplateState = atom<Promise<string>>(async (get) =>
`${get(customArgsState)} ${await get(cookiesTemplateState)}`
export const downloadTemplateState = atom<string>((get) =>
`${get(customArgsState)} ${get(cookiesTemplateState)}`
.replace(/ +/g, ' ')
.trim()
)

View File

@@ -26,15 +26,11 @@ export type Language = (typeof languages)[number]
export type Theme = 'light' | 'dark' | 'system'
export type ThemeNarrowed = 'light' | 'dark'
export const accents = ['default', 'red'] as const
export type Accent = (typeof accents)[number]
export interface SettingsState {
serverAddr: string
serverPort: number
language: Language
theme: ThemeNarrowed
accent: Accent
cliArgs: string
formatSelection: boolean
fileRenaming: boolean
@@ -150,11 +146,7 @@ const themeSelector = atom<ThemeNarrowed>((get) => {
return 'dark'
}
return 'light'
})
export const accentState = atomWithStorage<Accent>(
'accent-color',
localStorage.getItem('accent-color') as Accent ?? 'default',
}
)
export const settingsState = atom<SettingsState>((get) => ({
@@ -162,7 +154,6 @@ export const settingsState = atom<SettingsState>((get) => ({
serverPort: get(serverPortState),
language: get(languageState),
theme: get(themeSelector),
accent: get(accentState),
cliArgs: get(latestCliArgumentsState),
formatSelection: get(formatSelectionState),
fileRenaming: get(fileRenamingState),

View File

@@ -2,6 +2,7 @@ import { FileUpload } from '@mui/icons-material'
import CloseIcon from '@mui/icons-material/Close'
import {
Autocomplete,
Backdrop,
Box,
Button,
Checkbox,
@@ -20,7 +21,6 @@ import Slide from '@mui/material/Slide'
import Toolbar from '@mui/material/Toolbar'
import Typography from '@mui/material/Typography'
import { TransitionProps } from '@mui/material/transitions'
import { useAtom, useAtomValue } from 'jotai'
import {
FC,
Suspense,
@@ -30,22 +30,18 @@ import {
useState,
useTransition
} from 'react'
import {
customArgsState,
downloadTemplateState,
filenameTemplateState,
savedTemplatesState
} from '../atoms/downloadTemplate'
import { customArgsState, downloadTemplateState, filenameTemplateState, savedTemplatesState } from '../atoms/downloadTemplate'
import { settingsState } from '../atoms/settings'
import { availableDownloadPathsState, connectedState } from '../atoms/status'
import FormatsGrid from '../components/FormatsGrid'
import { useToast } from '../hooks/toast'
import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC'
import type { DLMetadata } from '../types'
import { toFormatArgs } from '../utils'
import ExtraDownloadOptions from './ExtraDownloadOptions'
import { useToast } from '../hooks/toast'
import LoadingBackdrop from './LoadingBackdrop'
import { useAtom, useAtomValue } from 'jotai'
const Transition = forwardRef(function Transition(
props: TransitionProps & {

View File

@@ -1,33 +0,0 @@
import { Button, CircularProgress } from '@mui/material'
import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC'
import { useState } from 'react'
import { useToast } from '../hooks/toast'
const UpdateBinaryButton: React.FC = () => {
const { i18n } = useI18n()
const { client } = useRPC()
const { pushMessage } = useToast()
const [isLoading, setIsLoading] = useState(false)
const updateBinary = () => {
setIsLoading(true)
client
.updateExecutable()
.then(() => pushMessage(i18n.t('toastUpdated'), 'success'))
.then(() => setIsLoading(false))
}
return (
<Button
variant="contained"
endIcon={isLoading ? <CircularProgress size={16} color='secondary' /> : <></>}
onClick={updateBinary}
>
{i18n.t('updateBinButton')}
</Button>
)
}
export default UpdateBinaryButton

View File

@@ -191,11 +191,4 @@ export class RPCClient {
params: []
})
}
public updateExecutable() {
return this.sendHTTP({
method: 'Service.UpdateExecutable',
params: []
})
}
}

View File

@@ -1,6 +1,4 @@
import { blue, red } from '@mui/material/colors'
import { pipe } from 'fp-ts/lib/function'
import { Accent } from './atoms/settings'
import type { RPCResponse } from "./types"
import { ProcessStatus } from './types'
@@ -82,14 +80,3 @@ export const base64URLEncode = (s: string) => pipe(
btoa,
encodeURIComponent
)
export const getAccentValue = (accent: Accent) => {
switch (accent) {
case 'default':
return blue[700]
case 'red':
return red[600]
default:
return blue[700]
}
}

View File

@@ -1,4 +1,5 @@
import {
Button,
Checkbox,
Container,
FormControl,
@@ -17,7 +18,6 @@ import {
Typography,
capitalize
} from '@mui/material'
import { useAtom } from 'jotai'
import { Suspense, useEffect, useMemo, useState } from 'react'
import {
Subject,
@@ -28,11 +28,8 @@ import {
} from 'rxjs'
import { rpcPollingTimeState } from '../atoms/rpc'
import {
Accent,
Language,
Theme,
accentState,
accents,
appTitleState,
enableCustomArgsState,
fileRenamingState,
@@ -47,10 +44,11 @@ import {
themeState
} from '../atoms/settings'
import CookiesTextField from '../components/CookiesTextField'
import UpdateBinaryButton from '../components/UpdateBinaryButton'
import { useToast } from '../hooks/toast'
import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC'
import { validateDomain, validateIP } from '../utils'
import { useAtom } from 'jotai'
// NEED ABSOLUTELY TO BE SPLIT IN MULTIPLE COMPONENTS
export default function Settings() {
@@ -68,13 +66,13 @@ export default function Settings() {
const [pollingTime, setPollingTime] = useAtom(rpcPollingTimeState)
const [language, setLanguage] = useAtom(languageState)
const [appTitle, setApptitle] = useAtom(appTitleState)
const [accent, setAccent] = useAtom(accentState)
const [theme, setTheme] = useAtom(themeState)
const [invalidIP, setInvalidIP] = useState(false)
const { i18n } = useI18n()
const { client } = useRPC()
const { pushMessage } = useToast()
@@ -142,6 +140,13 @@ export default function Settings() {
setTheme(event.target.value as Theme)
}
/**
* Updates yt-dlp binary via RPC
*/
const updateBinary = () => {
client.updateExecutable().then(() => pushMessage(i18n.t('toastUpdated'), 'success'))
}
return (
<Container maxWidth="xl" sx={{ mt: 4, mb: 8 }}>
<Paper
@@ -252,7 +257,7 @@ export default function Settings() {
Appearance
</Typography>
<Grid container spacing={2}>
<Grid item xs={12}>
<Grid item xs={12} md={6}>
<FormControl fullWidth>
<InputLabel>{i18n.t('languageSelect')}</InputLabel>
<Select
@@ -282,22 +287,6 @@ export default function Settings() {
</Select>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl fullWidth>
<InputLabel>{i18n.t('accentSelect')}</InputLabel>
<Select
defaultValue={accent}
label={i18n.t('accentSelect')}
onChange={(e) => setAccent(e.target.value as Accent)}
>
{accents.map((accent) => (
<MenuItem key={accent} value={accent}>
{capitalize(accent)}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid>
<Typography variant="h6" color="primary" sx={{ mt: 2, mb: 0.5 }}>
General download settings
@@ -363,8 +352,14 @@ export default function Settings() {
</Suspense>
</Grid>
<Grid>
<Stack direction="row" sx={{ pt: 2 }}>
<UpdateBinaryButton />
<Stack direction="row">
<Button
sx={{ mr: 1, mt: 3 }}
variant="contained"
onClick={() => updateBinary()}
>
{i18n.t('updateBinButton')}
</Button>
</Stack>
</Grid>
</Paper>

View File

@@ -1,10 +1,12 @@
import react from '@vitejs/plugin-react-swc'
import million from 'million/compiler'
import ViteYaml from '@modyfi/vite-plugin-yaml'
import { defineConfig } from 'vite'
export default defineConfig(() => {
return {
plugins: [
million.vite({ auto: true }),
react(),
ViteYaml(),
],

View File

@@ -23,7 +23,6 @@ var (
downloaderPath string
sessionFilePath string
localDatabasePath string
frontendPath string
requireAuth bool
username string
@@ -53,7 +52,6 @@ func init() {
flag.StringVar(&downloaderPath, "driver", "yt-dlp", "yt-dlp executable path")
flag.StringVar(&sessionFilePath, "session", ".", "session file path")
flag.StringVar(&localDatabasePath, "db", "local.db", "local database path")
flag.StringVar(&frontendPath, "web", "", "frontend web resources path")
flag.BoolVar(&enableFileLogging, "fl", false, "enable outputting logs to a file")
flag.StringVar(&logFile, "lf", "yt-dlp-webui.log", "set log file location")
@@ -71,10 +69,6 @@ func main() {
log.Fatalln(err)
}
if frontendPath != "" {
frontend = os.DirFS(frontendPath)
}
c := config.Instance()
{

View File

@@ -22,13 +22,13 @@ type Config struct {
QueueSize int `yaml:"queue_size"`
LocalDatabasePath string `yaml:"local_database_path"`
SessionFilePath string `yaml:"session_file_path"`
path string // private
path string
UseOpenId bool `yaml:"use_openid"`
OpenIdProviderURL string `yaml:"openid_provider_url"`
OpenIdClientId string `yaml:"openid_client_id"`
OpenIdClientSecret string `yaml:"openid_client_secret"`
OpenIdRedirectURL string `yaml:"openid_redirect_url"`
FrontendPath string `yaml:"frontend_path"`
}
var (

View File

@@ -164,7 +164,7 @@ func (s *Service) DeleteTemplate(ctx context.Context, id string) error {
func (s *Service) GetVersion(ctx context.Context) (string, string, error) {
//TODO: load from realease properties file, or anything else outside code
const CURRENT_RPC_VERSION = "3.2.3"
const CURRENT_RPC_VERSION = "3.2.2"
result := make(chan string, 1)