Skip to content

Commit

Permalink
Refactor Format/Stream structs
Browse files Browse the repository at this point in the history
  • Loading branch information
corny committed Aug 13, 2020
1 parent ade0884 commit d5e4a77
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 156 deletions.
20 changes: 10 additions & 10 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,27 +50,27 @@ func (c *Client) GetVideoContext(ctx context.Context, url string) (*Video, error
return v, v.parseVideoInfo(string(body))
}

// GetStream returns the HTTP response for a specific stream
func (c *Client) GetStream(video *Video, stream *Stream) (*http.Response, error) {
return c.GetStreamContext(context.Background(), video, stream)
// GetStream returns the HTTP response for a specific format
func (c *Client) GetStream(video *Video, format *Format) (*http.Response, error) {
return c.GetStreamContext(context.Background(), video, format)
}

// GetStreamContext returns the HTTP response for a specific stream with a context
func (c *Client) GetStreamContext(ctx context.Context, video *Video, stream *Stream) (*http.Response, error) {
url, err := c.getStreamURL(ctx, video, stream)
// GetStreamContext returns the HTTP response for a specific format with a context
func (c *Client) GetStreamContext(ctx context.Context, video *Video, format *Format) (*http.Response, error) {
url, err := c.getStreamURL(ctx, video, format)
if err != nil {
return nil, err
}

return c.httpGet(ctx, url)
}

func (c *Client) getStreamURL(ctx context.Context, video *Video, stream *Stream) (string, error) {
if stream.URL != "" {
return stream.URL, nil
func (c *Client) getStreamURL(ctx context.Context, video *Video, format *Format) (string, error) {
if format.URL != "" {
return format.URL, nil
}

cipher := stream.Cipher
cipher := format.Cipher
if cipher == "" {
return "", ErrCipherNotFound
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/youtubedr/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func run() error {
table.SetAutoWrapText(false)
table.SetHeader([]string{"itag", "quality", "MimeType"})

for _, itag := range video.Streams {
for _, itag := range video.Formats {
table.Append([]string{strconv.Itoa(itag.ItagNo), itag.Quality, itag.MimeType})
}
table.Render()
Expand All @@ -137,5 +137,5 @@ func run() error {
return dl.DownloadWithHighQuality(context.Background(), outputFile, video, outputQuality)
}

return dl.Download(context.Background(), video, &video.Streams[0], outputFile)
return dl.Download(context.Background(), video, &video.Formats[0], outputFile)
}
34 changes: 17 additions & 17 deletions downloader/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ type Downloader struct {
OutputDir string // optional directory to store the files
}

func (dl *Downloader) getOutputFile(v *youtube.Video, stream *youtube.Stream, outputFile string) (string, error) {
func (dl *Downloader) getOutputFile(v *youtube.Video, format *youtube.Format, outputFile string) (string, error) {

if outputFile == "" {
outputFile = SanitizeFilename(v.Title)
outputFile += pickIdealFileExtension(stream.MimeType)
outputFile += pickIdealFileExtension(format.MimeType)
}

if dl.OutputDir != "" {
Expand All @@ -39,8 +39,8 @@ func (dl *Downloader) getOutputFile(v *youtube.Video, stream *youtube.Stream, ou
}

//Download : Starting download video by arguments.
func (dl *Downloader) Download(ctx context.Context, v *youtube.Video, stream *youtube.Stream, outputFile string) error {
destFile, err := dl.getOutputFile(v, stream, outputFile)
func (dl *Downloader) Download(ctx context.Context, v *youtube.Video, format *youtube.Format, outputFile string) error {
destFile, err := dl.getOutputFile(v, format, outputFile)
if err != nil {
return err
}
Expand All @@ -53,29 +53,29 @@ func (dl *Downloader) Download(ctx context.Context, v *youtube.Video, stream *yo
defer out.Close()

dl.logf("Download to file=%s", destFile)
return dl.videoDLWorker(ctx, out, v, stream)
return dl.videoDLWorker(ctx, out, v, format)
}

//DownloadWithHighQuality : Starting downloading video with high quality (>720p).
func (dl *Downloader) DownloadWithHighQuality(ctx context.Context, outputFile string, v *youtube.Video, quality string) error {
var videoStream, audioStream *youtube.Stream
var videoFormat, audioFormat *youtube.Format

switch quality {
case "hd1080":
videoStream = v.FindStreamByItag(137)
audioStream = v.FindStreamByItag(140)
videoFormat = v.Formats.FindByItag(137)
audioFormat = v.Formats.FindByItag(140)
default:
return fmt.Errorf("unknown quality: %s", quality)
}

if videoStream == nil {
return fmt.Errorf("no Stream video/mp4 for %s found", quality)
if videoFormat == nil {
return fmt.Errorf("no format video/mp4 for %s found", quality)
}
if audioStream == nil {
return fmt.Errorf("no Stream audio/mp4 for %s found", quality)
if audioFormat == nil {
return fmt.Errorf("no format audio/mp4 for %s found", quality)
}

destFile, err := dl.getOutputFile(v, videoStream, outputFile)
destFile, err := dl.getOutputFile(v, videoFormat, outputFile)
if err != nil {
return err
}
Expand All @@ -96,13 +96,13 @@ func (dl *Downloader) DownloadWithHighQuality(ctx context.Context, outputFile st
defer os.Remove(audioFile.Name())

dl.logf("Downloading video file...")
err = dl.videoDLWorker(ctx, videoFile, v, videoStream)
err = dl.videoDLWorker(ctx, videoFile, v, videoFormat)
if err != nil {
return err
}

dl.logf("Downloading audio file...")
err = dl.videoDLWorker(ctx, audioFile, v, audioStream)
err = dl.videoDLWorker(ctx, audioFile, v, audioFormat)
if err != nil {
return err
}
Expand All @@ -123,8 +123,8 @@ func (dl *Downloader) DownloadWithHighQuality(ctx context.Context, outputFile st
return ffmpegVersionCmd.Run()
}

func (dl *Downloader) videoDLWorker(ctx context.Context, out *os.File, video *youtube.Video, stream *youtube.Stream) error {
resp, err := dl.GetStreamContext(ctx, video, stream)
func (dl *Downloader) videoDLWorker(ctx context.Context, out *os.File, video *youtube.Video, format *youtube.Format) error {
resp, err := dl.GetStreamContext(ctx, video, format)
if err != nil {
return err
}
Expand Down
14 changes: 7 additions & 7 deletions downloader/downloader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,34 +40,34 @@ func TestDownload_FirstStream(t *testing.T) {
assert.Equal(`youtube-dl test video "'/\ä↭𝕐`, video.Title)
assert.Equal(`Philipp Hagemeister`, video.Author)
assert.Equal(10*time.Second, video.Duration)
assert.Len(video.Streams, 18)
assert.Len(video.Formats, 18)

if assert.Greater(len(video.Streams), 0) {
assert.NoError(testDownloader.Download(ctx, video, &video.Streams[0], ""))
if assert.Greater(len(video.Formats), 0) {
assert.NoError(testDownloader.Download(ctx, video, &video.Formats[0], ""))
}
}

func TestYoutube_DownloadWithHighQualityFails(t *testing.T) {
tests := []struct {
name string
streams []youtube.Stream
formats []youtube.Format
message string
}{
{
name: "video Stream not found",
streams: []youtube.Stream{{ItagNo: 140}},
formats: []youtube.Format{{ItagNo: 140}},
message: "no Stream video/mp4 for hd1080 found",
},
{
name: "audio Stream not found",
streams: []youtube.Stream{{ItagNo: 137}},
formats: []youtube.Format{{ItagNo: 137}},
message: "no Stream audio/mp4 for hd1080 found",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
video := &youtube.Video{
Streams: tt.streams,
Formats: tt.formats,
}

err := testDownloader.DownloadWithHighQuality(context.Background(), "", video, "hd1080")
Expand Down
2 changes: 1 addition & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func ExampleClient() {
panic(err)
}

resp, err := client.GetStream(video, &video.Streams[0])
resp, err := client.GetStream(video, &video.Formats[0])
if err != nil {
panic(err)
}
Expand Down
21 changes: 21 additions & 0 deletions format_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package youtube

type FormatList []Format

func (list FormatList) FindByQuality(quality string) *Format {
for i := range list {
if list[i].Quality == quality {
return &list[i]
}
}
return nil
}

func (list FormatList) FindByItag(itagNo int) *Format {
for i := range list {
if list[i].ItagNo == itagNo {
return &list[i]
}
}
return nil
}
2 changes: 1 addition & 1 deletion itag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ func TestYoutube_GetItagInfo(t *testing.T) {
url := "https://www.youtube.com/watch?v=rFejpH_tAHM"
video, err := client.GetVideo(url)
require.NoError(err)
require.Len(video.Streams, 18)
require.Len(video.Formats, 18)
}
39 changes: 36 additions & 3 deletions response_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ type PlayerResponseData struct {
ContextParams string `json:"contextParams"`
} `json:"playabilityStatus"`
StreamingData struct {
ExpiresInSeconds string `json:"expiresInSeconds"`
Formats []Format `json:"formats"`
AdaptiveFormats []AdaptiveFormat `json:"adaptiveFormats"`
ExpiresInSeconds string `json:"expiresInSeconds"`
Formats []Format `json:"formats"`
AdaptiveFormats []Format `json:"adaptiveFormats"`
} `json:"streamingData"`
Captions struct {
PlayerCaptionsRenderer struct {
Expand Down Expand Up @@ -121,3 +121,36 @@ type PlayerResponseData struct {
} `json:"playerMicroformatRenderer"`
} `json:"microformat"`
}

type Format struct {
ItagNo int `json:"itag"`
URL string `json:"url"`
MimeType string `json:"mimeType"`
Quality string `json:"quality"`
Cipher string `json:"signatureCipher"`
Bitrate int `json:"bitrate"`
FPS int `json:"fps"`
Width int `json:"width"`
Height int `json:"height"`
LastModified string `json:"lastModified"`
ContentLength string `json:"contentLength"`
QualityLabel string `json:"qualityLabel"`
ProjectionType string `json:"projectionType"`
AverageBitrate int `json:"averageBitrate"`
AudioQuality string `json:"audioQuality"`
ApproxDurationMs string `json:"approxDurationMs"`
AudioSampleRate string `json:"audioSampleRate"`
AudioChannels int `json:"audioChannels"`

// InitRange is only available for adaptive formats
InitRange *struct {
Start string `json:"start"`
End string `json:"end"`
} `json:"initRange"`

// IndexRange is only available for adaptive formats
IndexRange *struct {
Start string `json:"start"`
End string `json:"end"`
} `json:"indexRange"`
}
Loading

0 comments on commit d5e4a77

Please sign in to comment.