Skip to content

Commit

Permalink
Rewrite format filters and allow filtering by langauge
Browse files Browse the repository at this point in the history
  • Loading branch information
corny committed Dec 24, 2023
1 parent bcf9cdb commit 5fdc86a
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 78 deletions.
24 changes: 24 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,30 @@ func TestGetVideoWithManifestURL(t *testing.T) {
assert.NotZero(size)
}

func TestGetVideo_MultiLanguage(t *testing.T) {
assert, require := assert.New(t), require.New(t)
video, err := testClient.GetVideo("https://www.youtube.com/watch?v=pU9sHwNKc2c")
require.NoError(err)
require.NotNil(video)

// collect languages
var languageNames, lanaguageIDs []string
for _, format := range video.Formats {
if format.AudioTrack != nil {
languageNames = append(languageNames, format.LanguageDisplayName())
lanaguageIDs = append(lanaguageIDs, format.AudioTrack.ID)
}
}

assert.Contains(languageNames, "English original")
assert.Contains(languageNames, "Portuguese (Brazil)")
assert.Contains(lanaguageIDs, "en.4")
assert.Contains(lanaguageIDs, "pt-BR.3")

assert.Empty(video.Formats.Language("Does not exist"))
assert.NotEmpty(video.Formats.Language("English original"))
}

func TestGetStream(t *testing.T) {
assert, require := assert.New(t), require.New(t)

Expand Down
5 changes: 2 additions & 3 deletions cmd/youtubedr/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ func init() {

downloadCmd.Flags().StringVarP(&outputFile, "filename", "o", "", "The output file, the default is genated by the video title.")
downloadCmd.Flags().StringVarP(&outputDir, "directory", "d", ".", "The output directory.")
addQualityFlag(downloadCmd.Flags())
addMimeTypeFlag(downloadCmd.Flags())
addVideoSelectionFlags(downloadCmd.Flags())
}

func download(id string) error {
Expand All @@ -48,7 +47,7 @@ func download(id string) error {
if err := checkFFMPEG(); err != nil {
return err
}
return downloader.DownloadComposite(context.Background(), outputFile, video, outputQuality, mimetype)
return downloader.DownloadComposite(context.Background(), outputFile, video, outputQuality, mimetype, language)
}

return downloader.Download(context.Background(), video, format, outputFile)
Expand Down
57 changes: 24 additions & 33 deletions cmd/youtubedr/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
Expand All @@ -20,16 +19,15 @@ import (
var (
insecureSkipVerify bool // skip TLS server validation
outputQuality string // itag number or quality string
mimetype string // mimetype
mimetype string
language string
downloader *ytdl.Downloader
)

func addQualityFlag(flagSet *pflag.FlagSet) {
func addVideoSelectionFlags(flagSet *pflag.FlagSet) {
flagSet.StringVarP(&outputQuality, "quality", "q", "medium", "The itag number or quality label (hd720, medium)")
}

func addMimeTypeFlag(flagSet *pflag.FlagSet) {
flagSet.StringVarP(&mimetype, "mimetype", "m", "mp4", "Mime-Type to filter (mp4, webm, av01, avc1) - applicable if --quality used is quality label")
flagSet.StringVarP(&mimetype, "mimetype", "m", "", "Mime-Type to filter (mp4, webm, av01, avc1) - applicable if --quality used is quality label")
flagSet.StringVarP(&language, "language", "l", "", "Language to filter")
}

func getDownloader() *ytdl.Downloader {
Expand Down Expand Up @@ -70,41 +68,34 @@ func getDownloader() *ytdl.Downloader {
return downloader
}

func getVideoWithFormat(id string) (*youtube.Video, *youtube.Format, error) {
func getVideoWithFormat(videoID string) (*youtube.Video, *youtube.Format, error) {
dl := getDownloader()
video, err := dl.GetVideo(id)
video, err := dl.GetVideo(videoID)
if err != nil {
return nil, nil, err
}

itag, _ := strconv.Atoi(outputQuality)
formats := video.Formats

if language != "" {
formats = formats.Language(language)
}
if mimetype != "" {
formats = formats.Type(mimetype)
}
if len(formats) == 0 {
return nil, nil, errors.New("no formats found")
if outputQuality != "" {
formats = formats.Quality(outputQuality)
}

var format *youtube.Format
itag, _ := strconv.Atoi(outputQuality)
switch {
case itag > 0:
// When an itag is specified, do not filter format with mime-type
format = video.Formats.FindByItag(itag)
if format == nil {
return nil, nil, fmt.Errorf("unable to find format with itag %d", itag)
}

case outputQuality != "":
format = formats.FindByQuality(outputQuality)
if format == nil {
return nil, nil, fmt.Errorf("unable to find format with quality %s", outputQuality)
}

default:
// select the first format
formats.Sort()
format = &formats[0]
if itag > 0 {
formats = formats.Itag(itag)
}
if formats == nil {
return nil, nil, fmt.Errorf("unable to find the specified format")
}

return video, format, nil
formats.Sort()

// select the first format
return video, &formats[0], nil
}
4 changes: 4 additions & 0 deletions cmd/youtubedr/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type VideoFormat struct {
VideoQuality string
AudioQuality string
AudioChannels int
Language string
Size int64
Bitrate int
MimeType string
Expand Down Expand Up @@ -73,6 +74,7 @@ var infoCmd = &cobra.Command{
Size: size,
Bitrate: bitrate,
MimeType: format.MimeType,
Language: format.LanguageDisplayName(),
})
}

Expand Down Expand Up @@ -102,6 +104,7 @@ func writeInfoOutput(w io.Writer, info *VideoInfo) {
"size [MB]",
"bitrate",
"MimeType",
"language",
})

for _, format := range info.Formats {
Expand All @@ -114,6 +117,7 @@ func writeInfoOutput(w io.Writer, info *VideoInfo) {
fmt.Sprintf("%0.1f", float64(format.Size)/1024/1024),
strconv.Itoa(format.Bitrate),
format.MimeType,
format.Language,
})
}

Expand Down
3 changes: 1 addition & 2 deletions cmd/youtubedr/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ var urlCmd = &cobra.Command{
}

func init() {
addQualityFlag(urlCmd.Flags())
addMimeTypeFlag(urlCmd.Flags())
addVideoSelectionFlags(urlCmd.Flags())
rootCmd.AddCommand(urlCmd)
}
10 changes: 7 additions & 3 deletions downloader/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ func (dl *Downloader) Download(ctx context.Context, v *youtube.Video, format *yo
}

// DownloadComposite : Downloads audio and video streams separately and merges them via ffmpeg.
func (dl *Downloader) DownloadComposite(ctx context.Context, outputFile string, v *youtube.Video, quality string, mimetype string) error {
videoFormat, audioFormat, err1 := getVideoAudioFormats(v, quality, mimetype)
func (dl *Downloader) DownloadComposite(ctx context.Context, outputFile string, v *youtube.Video, quality string, mimetype, language string) error {
videoFormat, audioFormat, err1 := getVideoAudioFormats(v, quality, mimetype, language)
if err1 != nil {
return err1
}
Expand Down Expand Up @@ -122,7 +122,7 @@ func (dl *Downloader) DownloadComposite(ctx context.Context, outputFile string,
return ffmpegVersionCmd.Run()
}

func getVideoAudioFormats(v *youtube.Video, quality string, mimetype string) (*youtube.Format, *youtube.Format, error) {
func getVideoAudioFormats(v *youtube.Video, quality string, mimetype, language string) (*youtube.Format, *youtube.Format, error) {
var videoFormat, audioFormat *youtube.Format
var videoFormats, audioFormats youtube.FormatList

Expand All @@ -138,6 +138,10 @@ func getVideoAudioFormats(v *youtube.Video, quality string, mimetype string) (*y
videoFormats = videoFormats.Quality(quality)
}

if language != "" {
audioFormats = audioFormats.Language(language)
}

if len(videoFormats) > 0 {
videoFormats.Sort()
videoFormat = &videoFormats[0]
Expand Down
3 changes: 1 addition & 2 deletions downloader/downloader_hq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,5 @@ func TestDownload_HighQuality(t *testing.T) {

video, err := testDownloader.Client.GetVideoContext(ctx, "BaW_jenozKc")
require.NoError(err)

require.NoError(testDownloader.DownloadComposite(ctx, "", video, "hd1080", "mp4"))
require.NoError(testDownloader.DownloadComposite(ctx, "", video, "hd1080", "mp4", ""))
}
6 changes: 3 additions & 3 deletions downloader/downloader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestYoutube_DownloadWithHighQualityFails(t *testing.T) {
Formats: tt.formats,
}

err := testDownloader.DownloadComposite(context.Background(), "", video, "hd1080", "")
err := testDownloader.DownloadComposite(context.Background(), "", video, "hd1080", "", "")
assert.EqualError(t, err, tt.message)
})
}
Expand Down Expand Up @@ -101,7 +101,7 @@ func Test_getVideoAudioFormats(t *testing.T) {
{ItagNo: 249, MimeType: "audio/webm; codecs=\"opus\"", Quality: "tiny", Bitrate: 72862, FPS: 0, Width: 0, Height: 0, LastModified: "1540474783513282", ContentLength: 24839529, QualityLabel: "", ProjectionType: "RECTANGULAR", AverageBitrate: 55914, AudioQuality: "AUDIO_QUALITY_LOW", ApproxDurationMs: "3553941", AudioSampleRate: "48000", AudioChannels: 2},
}}
{
videoFormat, audioFormat, err := getVideoAudioFormats(v, "hd720", "mp4")
videoFormat, audioFormat, err := getVideoAudioFormats(v, "hd720", "mp4", "")
require.NoError(err)
require.NotNil(videoFormat)
require.Equal(398, videoFormat.ItagNo)
Expand All @@ -110,7 +110,7 @@ func Test_getVideoAudioFormats(t *testing.T) {
}

{
videoFormat, audioFormat, err := getVideoAudioFormats(v, "large", "webm")
videoFormat, audioFormat, err := getVideoAudioFormats(v, "large", "webm", "")
require.NoError(err)
require.NotNil(videoFormat)
require.Equal(244, videoFormat.ItagNo)
Expand Down
28 changes: 13 additions & 15 deletions format_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,30 @@ import (

type FormatList []Format

// FindByQuality returns the first format matching Quality or QualityLabel
//
// Examples: tiny, small, medium, large, 720p, hd720, hd1080
func (list FormatList) FindByQuality(quality string) *Format {
// Type returns a new FormatList filtered by itag
func (list FormatList) Itag(itagNo int) (result FormatList) {
for i := range list {
if list[i].Quality == quality || list[i].QualityLabel == quality {
return &list[i]
if list[i].ItagNo == itagNo {
result = append(result, list[i])
}
}
return nil
return result
}

// FindByItag returns the first format matching the itag number
func (list FormatList) FindByItag(itagNo int) *Format {
// Type returns a new FormatList filtered by mime type
func (list FormatList) Type(t string) (result FormatList) {
for i := range list {
if list[i].ItagNo == itagNo {
return &list[i]
if strings.Contains(list[i].MimeType, t) {
result = append(result, list[i])
}
}
return nil
return result
}

// Type returns a new FormatList filtered by mime type of video
func (list FormatList) Type(t string) (result FormatList) {
// Type returns a new FormatList filtered by display name
func (list FormatList) Language(displayName string) (result FormatList) {
for i := range list {
if strings.Contains(list[i].MimeType, t) {
if list[i].LanguageDisplayName() == displayName {
result = append(result, list[i])
}
}
Expand Down
14 changes: 9 additions & 5 deletions format_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestFormatList_FindByQuality(t *testing.T) {
},
},
{
name: "find nothing with quality",
name: "find nothing with quality small",
list: list,
args: args{
quality: "small",
Expand All @@ -62,8 +62,12 @@ func TestFormatList_FindByQuality(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
format := tt.list.FindByQuality(tt.args.quality)
assert.Equal(t, format, tt.want)
formats := tt.list.Quality(tt.args.quality)
if tt.want == nil {
assert.Empty(t, formats)
} else if assert.NotEmpty(t, formats) {
assert.Equal(t, &formats[0], tt.want)
}
})
}
}
Expand Down Expand Up @@ -116,8 +120,8 @@ func TestFormatList_FindByItag(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
format := tt.list.FindByItag(tt.args.itagNo)
assert.Equal(t, format, tt.want)
format := tt.list.Itag(tt.args.itagNo)
assert.Equal(t, format[0], tt.want)
})
}
}
Expand Down
7 changes: 7 additions & 0 deletions response_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ type Format struct {
}
}

func (f *Format) LanguageDisplayName() string {
if f.AudioTrack == nil {
return ""
}
return f.AudioTrack.DisplayName
}

type Thumbnails []Thumbnail

type Thumbnail struct {
Expand Down
Loading

0 comments on commit 5fdc86a

Please sign in to comment.