@@ -49,6 +49,7 @@ languages:
|
|||||||
templatesEditorContentLabel: Template content
|
templatesEditorContentLabel: Template content
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
german:
|
german:
|
||||||
urlInput: Video URL
|
urlInput: Video URL
|
||||||
statusTitle: Status
|
statusTitle: Status
|
||||||
@@ -98,6 +99,7 @@ languages:
|
|||||||
templatesEditorContentLabel: Vorlagen Inhalt
|
templatesEditorContentLabel: Vorlagen Inhalt
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
french:
|
french:
|
||||||
urlInput: URL vidéo de YouTube ou d'un autre service pris en charge
|
urlInput: URL vidéo de YouTube ou d'un autre service pris en charge
|
||||||
statusTitle: Statut
|
statusTitle: Statut
|
||||||
@@ -149,6 +151,7 @@ languages:
|
|||||||
templatesEditorContentLabel: Template content
|
templatesEditorContentLabel: Template content
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
italian:
|
italian:
|
||||||
urlInput: URL Video (uno per linea)
|
urlInput: URL Video (uno per linea)
|
||||||
statusTitle: Stato
|
statusTitle: Stato
|
||||||
@@ -197,6 +200,7 @@ languages:
|
|||||||
templatesEditorContentLabel: Contentunto template
|
templatesEditorContentLabel: Contentunto template
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
chinese:
|
chinese:
|
||||||
urlInput: 视频 URL
|
urlInput: 视频 URL
|
||||||
statusTitle: 状态
|
statusTitle: 状态
|
||||||
@@ -246,6 +250,7 @@ languages:
|
|||||||
templatesEditorContentLabel: 模板内容
|
templatesEditorContentLabel: 模板内容
|
||||||
logsTitle: '日志'
|
logsTitle: '日志'
|
||||||
awaitingLogs: '正在等待日志…'
|
awaitingLogs: '正在等待日志…'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
spanish:
|
spanish:
|
||||||
urlInput: URL de YouTube u otro servicio compatible
|
urlInput: URL de YouTube u otro servicio compatible
|
||||||
statusTitle: Estado
|
statusTitle: Estado
|
||||||
@@ -293,6 +298,7 @@ languages:
|
|||||||
templatesEditorContentLabel: Template content
|
templatesEditorContentLabel: Template content
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
russian:
|
russian:
|
||||||
urlInput: URL-адрес YouTube или любого другого поддерживаемого сервиса
|
urlInput: URL-адрес YouTube или любого другого поддерживаемого сервиса
|
||||||
statusTitle: Статус
|
statusTitle: Статус
|
||||||
@@ -340,6 +346,7 @@ languages:
|
|||||||
templatesEditorContentLabel: Template content
|
templatesEditorContentLabel: Template content
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
korean:
|
korean:
|
||||||
urlInput: YouTube나 다른 지원되는 사이트의 URL
|
urlInput: YouTube나 다른 지원되는 사이트의 URL
|
||||||
statusTitle: 상태
|
statusTitle: 상태
|
||||||
@@ -387,6 +394,7 @@ languages:
|
|||||||
templatesEditorContentLabel: Template content
|
templatesEditorContentLabel: Template content
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
japanese:
|
japanese:
|
||||||
urlInput: YouTubeまたはサポート済み動画のURL
|
urlInput: YouTubeまたはサポート済み動画のURL
|
||||||
statusTitle: 状態
|
statusTitle: 状態
|
||||||
@@ -435,6 +443,7 @@ languages:
|
|||||||
templatesEditorContentLabel: Template content
|
templatesEditorContentLabel: Template content
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
catalan:
|
catalan:
|
||||||
urlInput: URL de YouTube o d'un altre servei compatible
|
urlInput: URL de YouTube o d'un altre servei compatible
|
||||||
statusTitle: Estat
|
statusTitle: Estat
|
||||||
@@ -482,6 +491,7 @@ languages:
|
|||||||
templatesEditorContentLabel: Template content
|
templatesEditorContentLabel: Template content
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
ukrainian:
|
ukrainian:
|
||||||
urlInput: URL-адреса YouTube або будь-якого іншого підтримуваного сервісу
|
urlInput: URL-адреса YouTube або будь-якого іншого підтримуваного сервісу
|
||||||
statusTitle: Статус
|
statusTitle: Статус
|
||||||
@@ -529,6 +539,7 @@ languages:
|
|||||||
templatesEditorContentLabel: Template content
|
templatesEditorContentLabel: Template content
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
polish:
|
polish:
|
||||||
urlInput: Adres URL YouTube lub innej obsługiwanej usługi
|
urlInput: Adres URL YouTube lub innej obsługiwanej usługi
|
||||||
statusTitle: Status
|
statusTitle: Status
|
||||||
@@ -576,3 +587,4 @@ languages:
|
|||||||
templatesEditorContentLabel: Template content
|
templatesEditorContentLabel: Template content
|
||||||
logsTitle: 'Logs'
|
logsTitle: 'Logs'
|
||||||
awaitingLogs: 'Awaiting logs...'
|
awaitingLogs: 'Awaiting logs...'
|
||||||
|
bulkDownload: 'Download files in a zip archive'
|
||||||
@@ -16,8 +16,10 @@ import {
|
|||||||
Typography
|
Typography
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
import { useRecoilValue } from 'recoil'
|
||||||
|
import { serverURL } from '../atoms/settings'
|
||||||
import { RPCResult } from '../types'
|
import { RPCResult } from '../types'
|
||||||
import { ellipsis, formatSpeedMiB, mapProcessStatus, formatSize } from '../utils'
|
import { base64URLEncode, ellipsis, formatSize, formatSpeedMiB, mapProcessStatus } from '../utils'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
download: RPCResult
|
download: RPCResult
|
||||||
@@ -35,6 +37,8 @@ const Resolution: React.FC<{ resolution?: string }> = ({ resolution }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DownloadCard: React.FC<Props> = ({ download, onStop, onCopy }) => {
|
const DownloadCard: React.FC<Props> = ({ download, onStop, onCopy }) => {
|
||||||
|
const serverAddr = useRecoilValue(serverURL)
|
||||||
|
|
||||||
const isCompleted = useCallback(
|
const isCompleted = useCallback(
|
||||||
() => download.progress.percentage === '-1',
|
() => download.progress.percentage === '-1',
|
||||||
[download.progress.percentage]
|
[download.progress.percentage]
|
||||||
@@ -47,6 +51,16 @@ const DownloadCard: React.FC<Props> = ({ download, onStop, onCopy }) => {
|
|||||||
[download.progress.percentage, isCompleted]
|
[download.progress.percentage, isCompleted]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const viewFile = (path: string) => {
|
||||||
|
const encoded = base64URLEncode(path)
|
||||||
|
window.open(`${serverAddr}/archive/v/${encoded}?token=${localStorage.getItem('token')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadFile = (path: string) => {
|
||||||
|
const encoded = base64URLEncode(path)
|
||||||
|
window.open(`${serverAddr}/archive/d/${encoded}?token=${localStorage.getItem('token')}`)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardActionArea onClick={() => {
|
<CardActionArea onClick={() => {
|
||||||
@@ -109,6 +123,26 @@ const DownloadCard: React.FC<Props> = ({ download, onStop, onCopy }) => {
|
|||||||
>
|
>
|
||||||
{isCompleted() ? "Clear" : "Stop"}
|
{isCompleted() ? "Clear" : "Stop"}
|
||||||
</Button>
|
</Button>
|
||||||
|
{isCompleted() &&
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => downloadFile(download.output.savedFilePath)}
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => viewFile(download.output.savedFilePath)}
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import DeleteIcon from '@mui/icons-material/Delete'
|
import DeleteIcon from '@mui/icons-material/Delete'
|
||||||
import DownloadIcon from '@mui/icons-material/Download'
|
import DownloadIcon from '@mui/icons-material/Download'
|
||||||
import DownloadDoneIcon from '@mui/icons-material/DownloadDone'
|
import DownloadDoneIcon from '@mui/icons-material/DownloadDone'
|
||||||
|
import FileDownloadIcon from '@mui/icons-material/FileDownload'
|
||||||
|
import SmartDisplayIcon from '@mui/icons-material/SmartDisplay'
|
||||||
import StopCircleIcon from '@mui/icons-material/StopCircle'
|
import StopCircleIcon from '@mui/icons-material/StopCircle'
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Grid,
|
ButtonGroup,
|
||||||
IconButton,
|
IconButton,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
LinearProgressProps,
|
LinearProgressProps,
|
||||||
Paper,
|
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
@@ -19,8 +20,9 @@ import {
|
|||||||
} from "@mui/material"
|
} from "@mui/material"
|
||||||
import { useRecoilValue } from 'recoil'
|
import { useRecoilValue } from 'recoil'
|
||||||
import { activeDownloadsState } from '../atoms/downloads'
|
import { activeDownloadsState } from '../atoms/downloads'
|
||||||
|
import { serverURL } from '../atoms/settings'
|
||||||
import { useRPC } from '../hooks/useRPC'
|
import { useRPC } from '../hooks/useRPC'
|
||||||
import { formatSize, formatSpeedMiB } from "../utils"
|
import { base64URLEncode, formatSize, formatSpeedMiB } from "../utils"
|
||||||
|
|
||||||
function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) {
|
function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) {
|
||||||
return (
|
return (
|
||||||
@@ -38,12 +40,23 @@ function LinearProgressWithLabel(props: LinearProgressProps & { value: number })
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DownloadsTableView: React.FC = () => {
|
const DownloadsTableView: React.FC = () => {
|
||||||
|
const serverAddr = useRecoilValue(serverURL)
|
||||||
const downloads = useRecoilValue(activeDownloadsState)
|
const downloads = useRecoilValue(activeDownloadsState)
|
||||||
|
|
||||||
const { client } = useRPC()
|
const { client } = useRPC()
|
||||||
|
|
||||||
const abort = (id: string) => client.kill(id)
|
const abort = (id: string) => client.kill(id)
|
||||||
|
|
||||||
|
const viewFile = (path: string) => {
|
||||||
|
const encoded = base64URLEncode(path)
|
||||||
|
window.open(`${serverAddr}/archive/v/${encoded}?token=${localStorage.getItem('token')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadFile = (path: string) => {
|
||||||
|
const encoded = base64URLEncode(path)
|
||||||
|
window.open(`${serverAddr}/archive/d/${encoded}?token=${localStorage.getItem('token')}`)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer
|
<TableContainer
|
||||||
sx={{ minHeight: '80vh', mt: 4 }}
|
sx={{ minHeight: '80vh', mt: 4 }}
|
||||||
@@ -108,13 +121,31 @@ const DownloadsTableView: React.FC = () => {
|
|||||||
{new Date(download.info.created_at).toLocaleString()}
|
{new Date(download.info.created_at).toLocaleString()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="right">
|
<TableCell align="right">
|
||||||
<IconButton
|
<ButtonGroup>
|
||||||
size="small"
|
<IconButton
|
||||||
onClick={() => abort(download.id)}
|
size="small"
|
||||||
>
|
onClick={() => abort(download.id)}
|
||||||
{download.progress.percentage === '-1' ? <DeleteIcon /> : <StopCircleIcon />}
|
>
|
||||||
|
{download.progress.percentage === '-1' ? <DeleteIcon /> : <StopCircleIcon />}
|
||||||
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
{download.progress.percentage === '-1' &&
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => viewFile(download.output.savedFilePath)}
|
||||||
|
>
|
||||||
|
<SmartDisplayIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => downloadFile(download.output.savedFilePath)}
|
||||||
|
>
|
||||||
|
<FileDownloadIcon />
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</ButtonGroup>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import BuildCircleIcon from '@mui/icons-material/BuildCircle'
|
|||||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever'
|
import DeleteForeverIcon from '@mui/icons-material/DeleteForever'
|
||||||
import FormatListBulleted from '@mui/icons-material/FormatListBulleted'
|
import FormatListBulleted from '@mui/icons-material/FormatListBulleted'
|
||||||
import ViewAgendaIcon from '@mui/icons-material/ViewAgenda'
|
import ViewAgendaIcon from '@mui/icons-material/ViewAgenda'
|
||||||
|
import FolderZipIcon from '@mui/icons-material/FolderZip'
|
||||||
import {
|
import {
|
||||||
SpeedDial,
|
SpeedDial,
|
||||||
SpeedDialAction,
|
SpeedDialAction,
|
||||||
SpeedDialIcon
|
SpeedDialIcon
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useRecoilState } from 'recoil'
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||||
import { listViewState } from '../atoms/settings'
|
import { listViewState, serverURL } from '../atoms/settings'
|
||||||
import { useI18n } from '../hooks/useI18n'
|
import { useI18n } from '../hooks/useI18n'
|
||||||
import { useRPC } from '../hooks/useRPC'
|
import { useRPC } from '../hooks/useRPC'
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HomeSpeedDial: React.FC<Props> = ({ onDownloadOpen, onEditorOpen }) => {
|
const HomeSpeedDial: React.FC<Props> = ({ onDownloadOpen, onEditorOpen }) => {
|
||||||
|
const serverAddr = useRecoilValue(serverURL)
|
||||||
const [listView, setListView] = useRecoilState(listViewState)
|
const [listView, setListView] = useRecoilState(listViewState)
|
||||||
|
|
||||||
const { i18n } = useI18n()
|
const { i18n } = useI18n()
|
||||||
@@ -37,6 +39,11 @@ const HomeSpeedDial: React.FC<Props> = ({ onDownloadOpen, onEditorOpen }) => {
|
|||||||
tooltipTitle={listView ? 'Card view' : 'Table view'}
|
tooltipTitle={listView ? 'Card view' : 'Table view'}
|
||||||
onClick={() => setListView(state => !state)}
|
onClick={() => setListView(state => !state)}
|
||||||
/>
|
/>
|
||||||
|
<SpeedDialAction
|
||||||
|
icon={<FolderZipIcon />}
|
||||||
|
tooltipTitle={i18n.t('bulkDownload')}
|
||||||
|
onClick={() => window.open(`${serverAddr}/archive/bulk`)}
|
||||||
|
/>
|
||||||
<SpeedDialAction
|
<SpeedDialAction
|
||||||
icon={<DeleteForeverIcon />}
|
icon={<DeleteForeverIcon />}
|
||||||
tooltipTitle={i18n.t('abortAllButton')}
|
tooltipTitle={i18n.t('abortAllButton')}
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ export type RPCResult = Readonly<{
|
|||||||
id: string
|
id: string
|
||||||
progress: DownloadProgress
|
progress: DownloadProgress
|
||||||
info: DownloadInfo
|
info: DownloadInfo
|
||||||
|
output: {
|
||||||
|
savedFilePath: string
|
||||||
|
}
|
||||||
}>
|
}>
|
||||||
|
|
||||||
export type RPCParams = {
|
export type RPCParams = {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
@@ -8,12 +10,14 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
|
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
|
||||||
|
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
||||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/utils"
|
"github.com/marcopeocchi/yt-dlp-web-ui/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -194,3 +198,50 @@ func DownloadFile(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BulkDownload(mdb *internal.MemoryDB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
procs := slices.DeleteFunc(*mdb.All(), func(e internal.ProcessResponse) bool {
|
||||||
|
return e.Progress.Status != 2 // status completed
|
||||||
|
})
|
||||||
|
|
||||||
|
var (
|
||||||
|
buff bytes.Buffer
|
||||||
|
zipWriter = zip.NewWriter(&buff)
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, p := range procs {
|
||||||
|
wr, err := zipWriter.Create(filepath.Base(p.Output.SavedFilePath))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := os.Open(p.Output.SavedFilePath)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(wr, fd)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := zipWriter.Close()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Add(
|
||||||
|
"Content-Disposition",
|
||||||
|
"inline; filename=download-archive-"+time.Now().Format(time.RFC3339)+".zip",
|
||||||
|
)
|
||||||
|
w.Header().Set("Content-Type", "application/zip")
|
||||||
|
|
||||||
|
io.Copy(w, &buff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,8 +55,9 @@ type Process struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DownloadOutput struct {
|
type DownloadOutput struct {
|
||||||
Path string
|
Path string
|
||||||
Filename string
|
Filename string
|
||||||
|
SavedFilePath string `json:"savedFilePath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts spawns/forks a new yt-dlp process and parse its stdout.
|
// Starts spawns/forks a new yt-dlp process and parse its stdout.
|
||||||
@@ -91,6 +92,8 @@ func (p *Process) Start() {
|
|||||||
|
|
||||||
buildFilename(&p.Output)
|
buildFilename(&p.Output)
|
||||||
|
|
||||||
|
go p.GetFileName(&out)
|
||||||
|
|
||||||
params := []string{
|
params := []string{
|
||||||
strings.Split(p.Url, "?list")[0], //no playlist
|
strings.Split(p.Url, "?list")[0], //no playlist
|
||||||
"--newline",
|
"--newline",
|
||||||
@@ -269,6 +272,24 @@ func (p *Process) GetFormatsSync() (DownloadFormats, error) {
|
|||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Process) GetFileName(o *DownloadOutput) error {
|
||||||
|
cmd := exec.Command(
|
||||||
|
config.Instance().DownloaderPath,
|
||||||
|
"--print", "filename",
|
||||||
|
"-o", fmt.Sprintf("%s/%s", o.Path, o.Filename),
|
||||||
|
p.Url,
|
||||||
|
)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Output.SavedFilePath = strings.Trim(string(out), "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Process) SetPending() {
|
func (p *Process) SetPending() {
|
||||||
// Since video's title isn't available yet, fill in with the URL.
|
// Since video's title isn't available yet, fill in with the URL.
|
||||||
p.Info = DownloadInfo{
|
p.Info = DownloadInfo{
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ func newServer(c serverConfig) *http.Server {
|
|||||||
r.Post("/delete", handlers.DeleteFile)
|
r.Post("/delete", handlers.DeleteFile)
|
||||||
r.Get("/d/{id}", handlers.DownloadFile)
|
r.Get("/d/{id}", handlers.DownloadFile)
|
||||||
r.Get("/v/{id}", handlers.SendFile)
|
r.Get("/v/{id}", handlers.SendFile)
|
||||||
|
r.Get("/bulk", handlers.BulkDownload(c.mdb))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Authentication routes
|
// Authentication routes
|
||||||
|
|||||||
Reference in New Issue
Block a user