list formats and provide them for download
This commit is contained in:
120
app/ytdlp.go
120
app/ytdlp.go
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/lrstanley/go-ytdlp"
|
||||
@@ -10,6 +11,29 @@ import (
|
||||
type DownloadOptions struct {
|
||||
EmbedThumbnail bool
|
||||
IncludeSubtitles bool
|
||||
VideoFormatID string
|
||||
AudioFormatID string
|
||||
}
|
||||
|
||||
type VideoOption struct {
|
||||
Height *int `json:"height,omitempty"`
|
||||
Resolution string `json:"resolution,omitempty"`
|
||||
FormatID string `json:"format_id"`
|
||||
Ext string `json:"ext"`
|
||||
TBR *float64 `json:"tbr,omitempty"`
|
||||
}
|
||||
|
||||
type AudioOption struct {
|
||||
Format string `json:"format"`
|
||||
FormatID string `json:"format_id"`
|
||||
Ext string `json:"ext"`
|
||||
TBR *float64 `json:"tbr,omitempty"`
|
||||
Language *string `json:"language,omitempty"`
|
||||
}
|
||||
|
||||
type FormatOptions struct {
|
||||
VideoOptions []VideoOption `json:"video_options"`
|
||||
AudioOptions []AudioOption `json:"audio_options"`
|
||||
}
|
||||
|
||||
type ProgressUpdate struct {
|
||||
@@ -20,6 +44,89 @@ type ProgressUpdate struct {
|
||||
Phase string
|
||||
}
|
||||
|
||||
func GetFormats(url string) (*FormatOptions, error) {
|
||||
dl := ytdlp.New().
|
||||
SkipDownload().
|
||||
DumpJSON()
|
||||
|
||||
result, err := dl.Run(context.TODO(), url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse the JSON output
|
||||
var info struct {
|
||||
Formats []struct {
|
||||
VCodec *string `json:"vcodec"`
|
||||
ACodec *string `json:"acodec"`
|
||||
NeedsTesting *bool `json:"__needs_testing"`
|
||||
Height *int `json:"height"`
|
||||
Resolution string `json:"resolution"`
|
||||
FormatID string `json:"format_id"`
|
||||
Format string `json:"format"`
|
||||
Ext string `json:"ext"`
|
||||
TBR *float64 `json:"tbr"`
|
||||
Language *string `json:"language"`
|
||||
LanguagePref *int `json:"language_preference"`
|
||||
URL *string `json:"url"`
|
||||
Protocol *string `json:"protocol"`
|
||||
} `json:"formats"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(result.Stdout), &info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
formatOpts := &FormatOptions{
|
||||
VideoOptions: []VideoOption{},
|
||||
AudioOptions: []AudioOption{},
|
||||
}
|
||||
|
||||
for _, fmt := range info.Formats {
|
||||
// Skip formats that need testing
|
||||
if fmt.NeedsTesting != nil && *fmt.NeedsTesting {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip SABR formats (https://github.com/yt-dlp/yt-dlp/issues/12482)
|
||||
if fmt.URL == nil || *fmt.URL == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Video-only: has video codec but no audio codec
|
||||
if fmt.VCodec != nil && *fmt.VCodec != "none" &&
|
||||
fmt.ACodec != nil && *fmt.ACodec == "none" {
|
||||
formatOpts.VideoOptions = append(formatOpts.VideoOptions, VideoOption{
|
||||
Height: fmt.Height,
|
||||
Resolution: fmt.Resolution,
|
||||
FormatID: fmt.FormatID,
|
||||
Ext: fmt.Ext,
|
||||
TBR: fmt.TBR,
|
||||
})
|
||||
}
|
||||
|
||||
// Audio-only: has audio codec but no video codec
|
||||
if fmt.ACodec != nil && *fmt.ACodec != "none" &&
|
||||
fmt.VCodec != nil && *fmt.VCodec == "none" {
|
||||
audioOpt := AudioOption{
|
||||
Format: fmt.Format,
|
||||
FormatID: fmt.FormatID,
|
||||
Ext: fmt.Ext,
|
||||
TBR: fmt.TBR,
|
||||
}
|
||||
|
||||
// Use language if available, otherwise use language_preference
|
||||
if fmt.Language != nil {
|
||||
audioOpt.Language = fmt.Language
|
||||
}
|
||||
|
||||
formatOpts.AudioOptions = append(formatOpts.AudioOptions, audioOpt)
|
||||
}
|
||||
}
|
||||
|
||||
return formatOpts, nil
|
||||
}
|
||||
|
||||
func downloadVideo(out_dir, url string, opts DownloadOptions, progressChan chan<- ProgressUpdate) {
|
||||
defer close(progressChan)
|
||||
|
||||
@@ -27,7 +134,6 @@ func downloadVideo(out_dir, url string, opts DownloadOptions, progressChan chan<
|
||||
|
||||
dl := ytdlp.New().
|
||||
SetWorkDir(out_dir).
|
||||
FormatSort("res,ext:mp4:m4a").
|
||||
RecodeVideo("mp4").
|
||||
ProgressFunc(100*time.Millisecond, func(prog ytdlp.ProgressUpdate) {
|
||||
// Detect phase transition -- differentiate "downloading" as the main download
|
||||
@@ -54,6 +160,18 @@ func downloadVideo(out_dir, url string, opts DownloadOptions, progressChan chan<
|
||||
}).
|
||||
Output("%(title)s.%(ext)s")
|
||||
|
||||
// Set format if both video and audio format IDs are provided
|
||||
if opts.VideoFormatID != "" && opts.AudioFormatID != "" {
|
||||
dl = dl.Format(opts.VideoFormatID + "+" + opts.AudioFormatID)
|
||||
} else if opts.VideoFormatID != "" {
|
||||
dl = dl.Format(opts.VideoFormatID)
|
||||
} else if opts.AudioFormatID != "" {
|
||||
dl = dl.Format(opts.AudioFormatID)
|
||||
} else {
|
||||
// Default format selection if none specified
|
||||
dl = dl.FormatSort("res,ext:mp4:m4a")
|
||||
}
|
||||
|
||||
if opts.EmbedThumbnail {
|
||||
dl = dl.EmbedThumbnail()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user