Skip to content

Commit

Permalink
prevent downloading playlist with format selection
Browse files Browse the repository at this point in the history
  • Loading branch information
marcopiovanello committed Nov 10, 2024
1 parent 846fb29 commit 4a87ea5
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 94 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ fe:
cd frontend && pnpm install && pnpm build

dev:
( cd frontend && pnpm install && pnpm dev )
cd frontend && pnpm install && pnpm dev

all: fe
CGO_ENABLED=0 go build -o yt-dlp-webui main.go
Expand Down
8 changes: 4 additions & 4 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 22 additions & 4 deletions frontend/src/components/DownloadDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ 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'

const Transition = forwardRef(function Transition(
props: TransitionProps & {
Expand Down Expand Up @@ -67,6 +69,7 @@ const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => {
const [pickedVideoFormat, setPickedVideoFormat] = useState('')
const [pickedAudioFormat, setPickedAudioFormat] = useState('')
const [pickedBestFormat, setPickedBestFormat] = useState('')
const [isFormatsLoading, setIsFormatsLoading] = useState(false)

const [customArgs, setCustomArgs] = useRecoilState(customArgsState)

Expand All @@ -82,6 +85,7 @@ const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => {

const { i18n } = useI18n()
const { client } = useRPC()
const { pushMessage } = useToast()

const urlInputRef = useRef<HTMLInputElement>(null)
const customFilenameInputRef = useRef<HTMLInputElement>(null)
Expand Down Expand Up @@ -129,11 +133,28 @@ const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => {
setPickedVideoFormat('')
setPickedBestFormat('')


if (isPlaylist) {
pushMessage('Format selection on playlist is not supported', 'warning')
resetInput()
onClose()
return
}

setIsFormatsLoading(true)

client.formats(url)
?.then(formats => {
if (formats.result._type === 'playlist') {
pushMessage('Format selection on playlist is not supported. Downloading as playlist.', 'info')
resetInput()
onClose()
return
}
setDownloadFormats(formats.result)
resetInput()
})
.then(() => setIsFormatsLoading(false))
}

const handleUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -175,10 +196,7 @@ const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => {
onClose={onClose}
TransitionComponent={Transition}
>
<Backdrop
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }}
open={isPending}
/>
<LoadingBackdrop isLoading={isPending || isFormatsLoading} />
<AppBar sx={{ position: 'relative' }}>
<Toolbar>
<IconButton
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ export type RPCParams = {

export type DLMetadata = {
formats: Array<DLFormat>
_type: string
best: DLFormat
thumbnail: string
title: string
entries: Array<DLMetadata>
}

export type DLFormat = {
Expand Down
56 changes: 56 additions & 0 deletions server/formats/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package formats

import (
"encoding/json"
"log/slog"
"os/exec"
"sync"

"github.com/marcopeocchi/yt-dlp-web-ui/v3/server/config"
)

func ParseURL(url string) (*Metadata, error) {
cmd := exec.Command(config.Instance().DownloaderPath, url, "-J")

stdout, err := cmd.Output()
if err != nil {
slog.Error("failed to retrieve metadata", slog.String("err", err.Error()))
return nil, err
}

slog.Info(
"retrieving metadata",
slog.String("caller", "getFormats"),
slog.String("url", url),
)

info := &Metadata{URL: url}
best := &Format{}

var (
wg sync.WaitGroup
decodingError error
)

wg.Add(2)

go func() {
decodingError = json.Unmarshal(stdout, &info)
wg.Done()
}()

go func() {
decodingError = json.Unmarshal(stdout, &best)
wg.Done()
}()

wg.Wait()

if decodingError != nil {
return nil, err
}

info.Best = *best

return info, nil
}
28 changes: 28 additions & 0 deletions server/formats/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package formats

// Used to deser the formats in the -J output
type Metadata struct {
Type string `json:"_type"`
Formats []Format `json:"formats"`
Best Format `json:"best"`
Thumbnail string `json:"thumbnail"`
Title string `json:"title"`
URL string `json:"url"`
Entries []Metadata `json:"entries"` // populated if url is playlist
}

func (m *Metadata) IsPlaylist() bool {
return m.Type == "playlist"
}

// A skimmed yt-dlp format node
type Format struct {
Format_id string `json:"format_id"`
Format_note string `json:"format_note"`
FPS float32 `json:"fps"`
Resolution string `json:"resolution"`
VCodec string `json:"vcodec"`
ACodec string `json:"acodec"`
Size float32 `json:"filesize_approx"`
Language string `json:"language"`
}
22 changes: 0 additions & 22 deletions server/internal/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ type ProgressTemplate struct {
Eta float32 `json:"eta"`
}


type PostprocessTemplate struct {
FilePath string `json:"filepath"`
}
Expand Down Expand Up @@ -45,27 +44,6 @@ type DownloadInfo struct {
CreatedAt time.Time `json:"created_at"`
}

// Used to deser the formats in the -J output
type DownloadFormats struct {
Formats []Format `json:"formats"`
Best Format `json:"best"`
Thumbnail string `json:"thumbnail"`
Title string `json:"title"`
URL string `json:"url"`
}

// A skimmed yt-dlp format node
type Format struct {
Format_id string `json:"format_id"`
Format_note string `json:"format_note"`
FPS float32 `json:"fps"`
Resolution string `json:"resolution"`
VCodec string `json:"vcodec"`
ACodec string `json:"acodec"`
Size float32 `json:"filesize_approx"`
Language string `json:"language"`
}

// struct representing the response sent to the client
// as JSON-RPC result field
type ProcessResponse struct {
Expand Down
49 changes: 0 additions & 49 deletions server/internal/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"log/slog"
"regexp"
"slices"
"sync"
"syscall"

"os"
Expand Down Expand Up @@ -261,54 +260,6 @@ func (p *Process) Kill() error {
return nil
}

// Returns the available format for this URL
//
// TODO: Move out from process.go
func (p *Process) GetFormats() (DownloadFormats, error) {
cmd := exec.Command(config.Instance().DownloaderPath, p.Url, "-J")

stdout, err := cmd.Output()
if err != nil {
slog.Error("failed to retrieve metadata", slog.String("err", err.Error()))
return DownloadFormats{}, err
}

slog.Info(
"retrieving metadata",
slog.String("caller", "getFormats"),
slog.String("url", p.Url),
)

info := DownloadFormats{URL: p.Url}
best := Format{}

var (
wg sync.WaitGroup
decodingError error
)

wg.Add(2)

go func() {
decodingError = json.Unmarshal(stdout, &info)
wg.Done()
}()
go func() {
decodingError = json.Unmarshal(stdout, &best)
wg.Done()
}()

wg.Wait()

if decodingError != nil {
return DownloadFormats{}, err
}

info.Best = best

return info, nil
}

func (p *Process) GetFileName(o *DownloadOutput) error {
cmd := exec.Command(
config.Instance().DownloaderPath,
Expand Down
30 changes: 16 additions & 14 deletions server/rpc/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"log/slog"

"github.com/marcopeocchi/yt-dlp-web-ui/v3/server/formats"
"github.com/marcopeocchi/yt-dlp-web-ui/v3/server/internal"
"github.com/marcopeocchi/yt-dlp-web-ui/v3/server/internal/livestream"
"github.com/marcopeocchi/yt-dlp-web-ui/v3/server/sys"
Expand All @@ -21,12 +22,6 @@ type Pending []string

type NoArgs struct{}

type Args struct {
Id string
URL string
Params []string
}

// Exec spawns a Process.
// The result of the execution is the newly spawned process Id.
func (s *Service) Exec(args internal.DownloadRequest, result *string) error {
Expand Down Expand Up @@ -91,7 +86,7 @@ func (s *Service) KillAllLivestream(args NoArgs, result *struct{}) error {
}

// Progess retrieves the Progress of a specific Process given its Id
func (s *Service) Progess(args Args, progress *internal.DownloadProgress) error {
func (s *Service) Progess(args internal.DownloadRequest, progress *internal.DownloadProgress) error {
proc, err := s.db.Get(args.Id)
if err != nil {
return err
Expand All @@ -102,13 +97,20 @@ func (s *Service) Progess(args Args, progress *internal.DownloadProgress) error
}

// Progess retrieves available format for a given resource
func (s *Service) Formats(args Args, meta *internal.DownloadFormats) error {
var (
err error
p = internal.Process{Url: args.URL}
)
*meta, err = p.GetFormats()
return err
func (s *Service) Formats(args internal.DownloadRequest, meta *formats.Metadata) error {
var err error

metadata, err := formats.ParseURL(args.URL)
if err != nil && metadata == nil {
return err
}

if metadata.IsPlaylist() {
go internal.PlaylistDetect(args, s.mq, s.db)
}

*meta = *metadata
return nil
}

// Pending retrieves a slice of all Pending/Running processes ids
Expand Down

0 comments on commit 4a87ea5

Please sign in to comment.