Fix tests, move helpers to media package

This commit is contained in:
Rob Watson 2021-10-28 03:05:20 +02:00
parent 3ce3736770
commit 8c30a2581a
3 changed files with 60 additions and 36 deletions

View File

@ -79,7 +79,7 @@ func (s *FetchMediaSetService) Fetch(ctx context.Context, id string) (*MediaSet,
// grab an audio stream from youtube // grab an audio stream from youtube
// TODO: avoid possible panic // TODO: avoid possible panic
format := sortAudio(video.Formats)[0] format := SortYoutubeAudio(video.Formats)[0]
sampleRate, err := strconv.Atoi(format.AudioSampleRate) sampleRate, err := strconv.Atoi(format.AudioSampleRate)
if err != nil { if err != nil {
@ -109,10 +109,7 @@ func (s *FetchMediaSetService) Fetch(ctx context.Context, id string) (*MediaSet,
return &mediaSet, nil return &mediaSet, nil
} }
// FetchAudio fetches the audio stream from Youtube, pipes it through FFMPEG to // FetchAudio fetches the audio part of a MediaSet.
// extract the raw audio samples, and uploads them to S3. It
// returns a FetchAudioProgressReader. This reader must be read until
// completion - it will return any error which occurs during the fetch process.
func (s *FetchMediaSetService) FetchAudio(ctx context.Context, id string) (FetchAudioProgressReader, error) { func (s *FetchMediaSetService) FetchAudio(ctx context.Context, id string) (FetchAudioProgressReader, error) {
mediaSet := NewMediaSet(id) mediaSet := NewMediaSet(id)
if !mediaSet.Exists() { if !mediaSet.Exists() {
@ -134,7 +131,7 @@ func (s *FetchMediaSetService) FetchAudio(ctx context.Context, id string) (Fetch
} }
// TODO: avoid possible panic // TODO: avoid possible panic
format := sortAudio(video.Formats)[0] format := SortYoutubeAudio(video.Formats)[0]
stream, _, err := s.youtube.GetStreamContext(ctx, video, &format) stream, _, err := s.youtube.GetStreamContext(ctx, video, &format)
if err != nil { if err != nil {
@ -184,8 +181,6 @@ type fetchAudioState struct {
uploader *multipartUploadWriter uploader *multipartUploadWriter
} }
// run copies the audio data from ffmpeg, waits for termination and then cleans
// up appropriately.
func (s *fetchAudioState) run(ctx context.Context) { func (s *fetchAudioState) run(ctx context.Context) {
mw := io.MultiWriter(s, s.uploader) mw := io.MultiWriter(s, s.uploader)
done := make(chan error) done := make(chan error)

View File

@ -7,10 +7,10 @@ import (
youtubev2 "github.com/kkdai/youtube/v2" youtubev2 "github.com/kkdai/youtube/v2"
) )
// sortAudio returns the provided formats ordered in descending preferred // SortYoutubeAudio returns the provided formats ordered in descending preferred
// order. The ideal candidate is opus-encoded stereo audio in a webm container, // order. The ideal candidate is opus-encoded stereo audio in a webm container,
// with the lowest available bitrate. // with the lowest available bitrate.
func sortAudio(inFormats youtubev2.FormatList) youtubev2.FormatList { func SortYoutubeAudio(inFormats youtubev2.FormatList) youtubev2.FormatList {
var formats youtubev2.FormatList var formats youtubev2.FormatList
for _, format := range inFormats { for _, format := range inFormats {
if format.FPS == 0 && format.AudioChannels > 0 { if format.FPS == 0 && format.AudioChannels > 0 {
@ -32,3 +32,25 @@ func sortAudio(inFormats youtubev2.FormatList) youtubev2.FormatList {
}) })
return formats return formats
} }
// SortYoutubeVideo returns the provided formats ordered in descending preferred
// order. The ideal candidate is video in an mp4 container with a low
// bitrate, with audio channels (needed to allow synced playback on the
// website).
func SortYoutubeVideo(inFormats youtubev2.FormatList) youtubev2.FormatList {
var formats youtubev2.FormatList
for _, format := range inFormats {
if format.FPS > 0 && format.ContentLength > 0 {
formats = append(formats, format)
}
}
sort.SliceStable(formats, func(i, j int) bool {
isMP4I := strings.Contains(formats[i].MimeType, "mp4")
isMP4J := strings.Contains(formats[j].MimeType, "mp4")
if isMP4I && isMP4J {
return formats[i].ContentLength < formats[j].ContentLength
}
return isMP4I
})
return formats
}

View File

@ -1,9 +1,9 @@
package youtube_test package media_test
import ( import (
"testing" "testing"
"git.netflux.io/rob/clipper/youtube" "git.netflux.io/rob/clipper/media"
youtubev2 "github.com/kkdai/youtube/v2" youtubev2 "github.com/kkdai/youtube/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -12,70 +12,77 @@ func TestSortAudio(t *testing.T) {
formats := []youtubev2.Format{ formats := []youtubev2.Format{
{ {
MimeType: `audio/webm; codecs="opus"`, MimeType: `audio/webm; codecs="opus"`,
Bitrate: 350_000, ContentLength: 38573,
AudioChannels: 2, AudioChannels: 1,
AudioSampleRate: "16000", AudioSampleRate: "16000",
}, },
{ {
MimeType: `audio/webm; codecs="opus"`, MimeType: `audio/webm; codecs="opus"`,
Bitrate: 350_000, ContentLength: 39458,
AudioChannels: 2, AudioChannels: 2,
AudioSampleRate: "48000", AudioSampleRate: "16000",
}, },
{ {
MimeType: `audio/mp4; codecs="mp4a.40.2"`, MimeType: `audio/mp4; codecs="mp4a.40.2"`,
Bitrate: 250_000, ContentLength: 118394,
AudioChannels: 1,
AudioSampleRate: "48000",
},
{
MimeType: `audio/webm; codecs="opus"`,
ContentLength: 127393,
AudioChannels: 2, AudioChannels: 2,
AudioSampleRate: "48000", AudioSampleRate: "48000",
}, },
{ {
MimeType: `audio/webm; codecs="opus"`, MimeType: `audio/webm; codecs="opus"`,
Bitrate: 125_000, ContentLength: 123245,
AudioChannels: 2, AudioChannels: 2,
AudioSampleRate: "48000", AudioSampleRate: "48000",
}, },
} }
sortedFormats := youtube.SortAudio(formats) sortedFormats := media.SortYoutubeAudio(formats)
assert.Equal(t, formats[1], sortedFormats[0]) assert.Equal(t, formats[1], sortedFormats[0])
assert.Equal(t, formats[3], sortedFormats[1]) assert.Equal(t, formats[4], sortedFormats[1])
assert.Equal(t, formats[0], sortedFormats[2]) assert.Equal(t, formats[3], sortedFormats[2])
assert.Equal(t, formats[2], sortedFormats[3]) assert.Equal(t, formats[0], sortedFormats[3])
assert.Equal(t, formats[2], sortedFormats[4])
} }
func TestSortVideo(t *testing.T) { func TestSortVideo(t *testing.T) {
formats := []youtubev2.Format{ formats := []youtubev2.Format{
{
MimeType: `audio/webm; codecs="opus"`,
QualityLabel: "120p",
FPS: 30,
ContentLength: 39402,
},
{ {
MimeType: `video/mp4; codecs="avc1.42001E, mp4a.40.2"`, MimeType: `video/mp4; codecs="avc1.42001E, mp4a.40.2"`,
QualityLabel: "240p", QualityLabel: "240p",
FPS: 30, FPS: 30,
AudioChannels: 2, ContentLength: 40353,
},
{
MimeType: `audio/webm; codecs="opus"`,
QualityLabel: "",
FPS: 0,
AudioChannels: 2,
}, },
{ {
MimeType: `video/mp4; codecs="avc1.42001E, mp4a.40.2"`, MimeType: `video/mp4; codecs="avc1.42001E, mp4a.40.2"`,
QualityLabel: "720p", QualityLabel: "720p",
FPS: 30, FPS: 30,
AudioChannels: 2, ContentLength: 393103,
}, },
{ {
MimeType: `video/mp4; codecs="avc1.42001E, mp4a.40.2"`, MimeType: `video/mp4; codecs="avc1.42001E, mp4a.40.2"`,
QualityLabel: "360p", QualityLabel: "360p",
FPS: 30, FPS: 0,
AudioChannels: 2, ContentLength: 20403,
}, },
} }
sortedFormats := youtube.SortVideo(formats) sortedFormats := media.SortYoutubeVideo(formats)
assert.Len(t, sortedFormats, 3) assert.Len(t, sortedFormats, 3)
assert.Equal(t, formats[3], sortedFormats[0]) assert.Equal(t, formats[1], sortedFormats[0])
assert.Equal(t, formats[0], sortedFormats[1]) assert.Equal(t, formats[2], sortedFormats[1])
assert.Equal(t, formats[2], sortedFormats[2]) assert.Equal(t, formats[0], sortedFormats[2])
} }