Skip to content

Commit

Permalink
Add support for HLS manifests [WIP]
Browse files Browse the repository at this point in the history
test video: YcNc4IBLI1A
  • Loading branch information
corny committed Dec 25, 2023
1 parent ee4db5e commit 6d292b0
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 1 deletion.
17 changes: 16 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var (
)

// DefaultClient type to use. No reason to change but you could if you wanted to.
var DefaultClient = AndroidClient
var DefaultClient = IosClient

// Client offers methods to download video metadata and video streams.
type Client struct {
Expand Down Expand Up @@ -72,6 +72,16 @@ func (c *Client) GetVideoContext(ctx context.Context, url string) (*Video, error
return c.videoFromID(ctx, id)
}

func (c *Client) fetchHLS(ctx context.Context, v *Video) error {
resp, err := c.httpGet(ctx, v.HLSManifestURL)
if err != nil {
return err
}
defer resp.Body.Close()
parseM3U8(resp.Body)

Check failure on line 81 in client.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go (ubuntu-22.04, 1.21.x)

Error return value is not checked (errcheck)

Check failure on line 81 in client.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go (ubuntu-22.04, 1.x)

Error return value is not checked (errcheck)
return nil
}

func (c *Client) videoFromID(ctx context.Context, id string) (*Video, error) {
c.assureClient()

Expand All @@ -86,6 +96,11 @@ func (c *Client) videoFromID(ctx context.Context, id string) (*Video, error) {

// return early if all good
if err = v.parseVideoInfo(body); err == nil {

if v.HLSManifestURL != "" {
c.fetchHLS(ctx, &v)

Check failure on line 101 in client.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go (ubuntu-22.04, 1.21.x)

Error return value of `c.fetchHLS` is not checked (errcheck)

Check failure on line 101 in client.go

View workflow job for this annotation

GitHub Actions / Linters (Static Analysis) for Go (ubuntu-22.04, 1.x)

Error return value of `c.fetchHLS` is not checked (errcheck)
}

return &v, nil
}

Expand Down
4 changes: 4 additions & 0 deletions cmd/youtubedr/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"io"
"log"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -52,6 +53,9 @@ var infoCmd = &cobra.Command{
Description: video.Description,
}

log.Println("DASH", video.DASHManifestURL)
log.Println("HLS", video.HLSManifestURL)

for _, format := range video.Formats {
bitrate := format.AverageBitrate
if bitrate == 0 {
Expand Down
49 changes: 49 additions & 0 deletions m3u8.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package youtube

import (
"bufio"
"io"
"strconv"
"strings"
)

func parseM3U8(r io.Reader) ([]Format, error) {
var result []Format

scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "#EXT-X-STREAM-INF:") {
continue
}

// TODO parse line
scanner.Scan()
url := scanner.Text()

itag := extractURLcomponent(url, "itag")
itagNo, _ := strconv.Atoi(itag)

result = append(result, Format{
URL: url,
ItagNo: itagNo,
})
}

return result, scanner.Err()
}

func extractURLcomponent(url, arg string) string {
i := strings.Index(url, "/"+arg+"/")
if i < 0 {
return ""
}
i += len(arg) + 2

j := strings.Index(url[i:], "/")
if j < 0 {
return ""
}

return url[i : i+j]
}
24 changes: 24 additions & 0 deletions m3u8_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package youtube

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestParseM3U8(t *testing.T) {
assert, require := assert.New(t), require.New(t)
file, err := os.Open("testdata/index.m3u8")

require.NoError(err)
assert.NotNil(file)
defer file.Close()

result, err := parseM3U8(file)
require.NoError(err)
require.Len(result, 4)

assert.Equal(229, result[0].ItagNo)
}
12 changes: 12 additions & 0 deletions testdata/index.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#EXTM3U
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MEDIA:URI="https://manifest.googlevideo.com/api/manifest/hls_playlist/expire/1703490098/ei/0t2IZaThF-y86dsP5rKOsAU/ip/2a00:c381:e005:70d0:cda6:d594:d8d2:314f/id/61c35ce0804b2350/itag/233/source/youtube/requiressl/yes/ratebypass/yes/pfa/1/goi/133/sgoap/clen%3D32217304%3Bdur%3D5283.282%3Bgir%3Dyes%3Bitag%3D139%3Blmt%3D1701065390462815/rqh/1/hls_chunk_host/rr1---sn-i5heen7z.googlevideo.com/xpc/EgVo2aDSNQ%3D%3D/mh/Id/mm/31,29/mn/sn-i5heen7z,sn-i5h7lner/ms/au,rdu/mv/m/mvi/1/pl/36/force_finished/1/initcwndbps/2698750/vprv/1/playlist_type/DVR/dover/13/txp/5532434/mt/1703468463/fvip/1/short_key/1/keepalive/yes/fexp/24007246/sparams/expire,ei,ip,id,itag,source,requiressl,ratebypass,pfa,goi,sgoap,rqh,xpc,force_finished,vprv,playlist_type/sig/AJfQdSswRAIgIaXunck2_oD6xkLrrU_PoLCVTCM3In8aNsSAMRyyiSsCIF1F_AVVSunFKh6-GNAqXzpdBLam9RC3fYe1jPQTqLK4/lsparams/hls_chunk_host,mh,mm,mn,ms,mv,mvi,pl,initcwndbps/lsig/AAO5W4owRQIgNWI34JJquq1UJiquWRQvAFcUrwxScaipZWCLD05Kdf8CIQCIrmAnf5jjZXYm7JjMRMc3hNXH07h1PZYBPfTvCeoK3A%3D%3D/playlist/index.m3u8",TYPE=AUDIO,GROUP-ID="233",NAME="Default",DEFAULT=YES,AUTOSELECT=YES
#EXT-X-MEDIA:URI="https://manifest.googlevideo.com/api/manifest/hls_playlist/expire/1703490098/ei/0t2IZaThF-y86dsP5rKOsAU/ip/2a00:c381:e005:70d0:cda6:d594:d8d2:314f/id/61c35ce0804b2350/itag/234/source/youtube/requiressl/yes/ratebypass/yes/pfa/1/goi/133/sgoap/clen%3D85504044%3Bdur%3D5283.236%3Bgir%3Dyes%3Bitag%3D140%3Blmt%3D1701065410347038/rqh/1/hls_chunk_host/rr1---sn-i5heen7z.googlevideo.com/xpc/EgVo2aDSNQ%3D%3D/mh/Id/mm/31,29/mn/sn-i5heen7z,sn-i5h7lner/ms/au,rdu/mv/m/mvi/1/pl/36/force_finished/1/initcwndbps/2698750/vprv/1/playlist_type/DVR/dover/13/txp/5532434/mt/1703468463/fvip/1/short_key/1/keepalive/yes/fexp/24007246/sparams/expire,ei,ip,id,itag,source,requiressl,ratebypass,pfa,goi,sgoap,rqh,xpc,force_finished,vprv,playlist_type/sig/AJfQdSswRAIgWnY3H7EEwncXleUiDGxk7HMDsKPRx_fge5fFiTgSIXICIG0ah2ooN1H5X1mJCFAlZ7O_Z5yu9icMl0ga6pMfx8Vp/lsparams/hls_chunk_host,mh,mm,mn,ms,mv,mvi,pl,initcwndbps/lsig/AAO5W4owRgIhANVlq74IorMvHKbDnylPtxMyRxnffDxza1Yn132TQmHPAiEA3L6LAO-T_XtyuULq_RIoZi7kWuzZmTQ9BpxUtAGJ5yg%3D/playlist/index.m3u8",TYPE=AUDIO,GROUP-ID="234",NAME="Default",DEFAULT=YES,AUTOSELECT=YES
#EXT-X-STREAM-INF:BANDWIDTH=294157,CODECS="avc1.4D4015,mp4a.40.5",RESOLUTION=426x240,FRAME-RATE=25,VIDEO-RANGE=SDR,AUDIO="233",CLOSED-CAPTIONS=NONE
https://manifest.googlevideo.com/api/manifest/hls_playlist/expire/1703490098/ei/0t2IZaThF-y86dsP5rKOsAU/ip/2a00:c381:e005:70d0:cda6:d594:d8d2:314f/id/61c35ce0804b2350/itag/229/source/youtube/requiressl/yes/ratebypass/yes/pfa/1/sgovp/clen%3D32957304%3Bdur%3D5283.160%3Bgir%3Dyes%3Bitag%3D133%3Blmt%3D1701071526311950/rqh/1/hls_chunk_host/rr1---sn-i5heen7z.googlevideo.com/xpc/EgVo2aDSNQ%3D%3D/mh/Id/mm/31,29/mn/sn-i5heen7z,sn-i5h7lner/ms/au,rdu/mv/m/mvi/1/pl/36/force_finished/1/initcwndbps/2698750/vprv/1/playlist_type/DVR/dover/13/txp/5535434/mt/1703468463/fvip/1/short_key/1/keepalive/yes/fexp/24007246/sparams/expire,ei,ip,id,itag,source,requiressl,ratebypass,pfa,sgovp,rqh,xpc,force_finished,vprv,playlist_type/sig/AJfQdSswRgIhAJZhHGpPpIrcBpogYNT44NGD7zBu9lJE8OKAPDvD_bPlAiEA2HVtye2wAy8otXj2nfwZzQtp5gdRc7Jid-XSIwx2r7g%3D/lsparams/hls_chunk_host,mh,mm,mn,ms,mv,mvi,pl,initcwndbps/lsig/AAO5W4owRQIgCC4_JiQPqoUs26SVZXU3NL6LfBTIwt8VLCE6vSrKiIECIQDy3h5XYWUlpsp6gzq05xpZEStl-nglRpxnmXqrTvvUPw%3D%3D/playlist/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1747396,CODECS="vp09.00.31.08,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=25,VIDEO-RANGE=SDR,AUDIO="234",CLOSED-CAPTIONS=NONE
https://manifest.googlevideo.com/api/manifest/hls_playlist/expire/1703490098/ei/0t2IZaThF-y86dsP5rKOsAU/ip/2a00:c381:e005:70d0:cda6:d594:d8d2:314f/id/61c35ce0804b2350/itag/609/source/youtube/requiressl/yes/ratebypass/yes/pfa/1/wft/1/sgovp/clen%3D164286710%3Bdur%3D5283.160%3Bgir%3Dyes%3Bitag%3D247%3Blmt%3D1701069221015789/rqh/1/hls_chunk_host/rr1---sn-i5heen7z.googlevideo.com/xpc/EgVo2aDSNQ%3D%3D/mh/Id/mm/31,29/mn/sn-i5heen7z,sn-i5h7lner/ms/au,rdu/mv/m/mvi/1/pl/36/force_finished/1/initcwndbps/2698750/vprv/1/playlist_type/DVR/dover/13/txp/5535434/mt/1703468463/fvip/1/short_key/1/keepalive/yes/fexp/24007246/sparams/expire,ei,ip,id,itag,source,requiressl,ratebypass,pfa,wft,sgovp,rqh,xpc,force_finished,vprv,playlist_type/sig/AJfQdSswRQIhAKTf9inutX7RxcUshYgqGRj_MO5VRoVxydZw36yh9pDCAiB7sj7YbGesIzOca7j9julETmasbWclor_9BUc_rWdlGA%3D%3D/lsparams/hls_chunk_host,mh,mm,mn,ms,mv,mvi,pl,initcwndbps/lsig/AAO5W4owRAIgSfVltd6kS_haQE23UiGZ1KibxZCT-VU1PbQi9gxHCB0CIB-a_ZGP-9pMYcgy7Na6h46SMYERy2JziSs9YrpYmv_8/playlist/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2803988,CODECS="vp09.00.40.08,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=25,VIDEO-RANGE=SDR,AUDIO="234",CLOSED-CAPTIONS=NONE
https://manifest.googlevideo.com/api/manifest/hls_playlist/expire/1703490098/ei/0t2IZaThF-y86dsP5rKOsAU/ip/2a00:c381:e005:70d0:cda6:d594:d8d2:314f/id/61c35ce0804b2350/itag/614/source/youtube/requiressl/yes/ratebypass/yes/pfa/1/wft/1/sgovp/clen%3D410468741%3Bdur%3D5283.160%3Bgir%3Dyes%3Bitag%3D248%3Blmt%3D1701068841070619/rqh/1/hls_chunk_host/rr1---sn-i5heen7z.googlevideo.com/xpc/EgVo2aDSNQ%3D%3D/mh/Id/mm/31,29/mn/sn-i5heen7z,sn-i5h7lner/ms/au,rdu/mv/m/mvi/1/pl/36/force_finished/1/initcwndbps/2698750/vprv/1/playlist_type/DVR/dover/13/txp/5535434/mt/1703468463/fvip/1/short_key/1/keepalive/yes/fexp/24007246/sparams/expire,ei,ip,id,itag,source,requiressl,ratebypass,pfa,wft,sgovp,rqh,xpc,force_finished,vprv,playlist_type/sig/AJfQdSswRAIgNXg1rfixd-GX-XmjwU6-2VKQSxz_WxW5zj-CZUy6BS0CIDbmmXIG2_mZtla0rIk1MONPM5r5TMtbkGrqkTsgd-jM/lsparams/hls_chunk_host,mh,mm,mn,ms,mv,mvi,pl,initcwndbps/lsig/AAO5W4owRgIhAPQXbJtXeax1ibFM5bJMcjxrgp3iy7qt7toTX82slXZ1AiEA-j8zoJ-CG78PrbZkvBWh8tBHimIg5xRr2tiJFDpfvmE%3D/playlist/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5777838,CODECS="vp09.00.40.08,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=25,VIDEO-RANGE=SDR,AUDIO="234",CLOSED-CAPTIONS=NONE
https://manifest.googlevideo.com/api/manifest/hls_playlist/expire/1703490098/ei/0t2IZaThF-y86dsP5rKOsAU/ip/2a00:c381:e005:70d0:cda6:d594:d8d2:314f/id/61c35ce0804b2350/itag/616/source/youtube/requiressl/yes/ratebypass/yes/pfa/1/wft/1/sgovp/clen%3D1064670006%3Bdur%3D5283.160%3Bgir%3Dyes%3Bitag%3D356%3Blmt%3D1701065764877554/rqh/1/hls_chunk_host/rr1---sn-i5heen7z.googlevideo.com/xpc/EgVo2aDSNQ%3D%3D/mh/Id/mm/31,29/mn/sn-i5heen7z,sn-i5h7lner/ms/au,rdu/mv/m/mvi/1/pl/36/force_finished/1/initcwndbps/2698750/vprv/1/playlist_type/DVR/dover/13/txp/5532434/mt/1703468463/fvip/1/short_key/1/keepalive/yes/fexp/24007246/sparams/expire,ei,ip,id,itag,source,requiressl,ratebypass,pfa,wft,sgovp,rqh,xpc,force_finished,vprv,playlist_type/sig/AJfQdSswRQIhAKm06ZrEroaCGoSx9trZEQi7G6ZoBI7-kPLEZO2TCdLzAiBqqORueVMBFRRWDu-1f9NBpS-ltCAlwbpnzl7iGkazPA%3D%3D/lsparams/hls_chunk_host,mh,mm,mn,ms,mv,mvi,pl,initcwndbps/lsig/AAO5W4owRQIgJOchZn1jYj1SOMpghtNkpLB3pvPaPWE8DZN4kl5CWqICIQD6Q-zYM1e8_FKx000B_xAj8EmJ6ZiEggAa1aj8otpz2Q%3D%3D/playlist/index.m3u8

0 comments on commit 6d292b0

Please sign in to comment.