Skip to content

Commit

Permalink
extractors/bilibili: Support playlist
Browse files Browse the repository at this point in the history
  • Loading branch information
iawia002 committed Mar 6, 2018
1 parent 8a64435 commit b7fb8a1
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 22 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,27 @@ Title: 1f5a87801a0711e898b12b640777720f
1.00 MiB / 1.00 MiB [===================================] 100.00% 3.35 MiB/s 0s
```

### Download playlist

You can use the `-p` option to tell Annie to download the whole playlist rather than a single video.

```console
$ annie -i -p https://www.bilibili.com/bangumi/play/ep198061

Site: 哔哩哔哩 bilibili.com
Title: Doctor X 第四季:第一集
Type: video
Size: 845.66 MiB (886738354 Bytes)


Site: 哔哩哔哩 bilibili.com
Title: Doctor X 第四季:第二集
Type: video
Size: 930.71 MiB (975919195 Bytes)

...
```

### Resume a download

You may use <kbd>Ctrl</kbd>+<kbd>C</kbd> to interrupt a download.
Expand Down Expand Up @@ -125,6 +146,7 @@ Usage of annie:
Cookie
-d Debug mode
-i Information only
-p Download playlist
-v Show version
```

Expand Down
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ var (
InfoOnly bool
// Cookie http cookies
Cookie string
// Playlist download playlist
Playlist bool
)

// FakeHeaders fake http headers
Expand Down
86 changes: 64 additions & 22 deletions extractors/bilibili.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ type token struct {
Data tokenData `json:"data"`
}

type bangumiEpData struct {
EpID int `json:"ep_id"`
}

type bangumiData struct {
EpList []bangumiEpData `json:"epList"`
}

func getSign(params string) string {
sign := md5.New()
sign.Write([]byte(params + secKey))
Expand All @@ -57,13 +65,26 @@ func genAPI(aid, cid string, bangumi bool) string {
baseAPIURL string
params string
)
utoken := ""
if config.Cookie != "" {
utoken = request.Get(fmt.Sprintf(
"%said=%s&cid=%s", config.BILIBILI_TOKEN_API, aid, cid,
))
var t token
json.Unmarshal([]byte(utoken), &t)
if t.Code != 0 {
log.Println(config.Cookie)
log.Fatal("Cookie error: ", t.Message)
}
utoken = t.Data.Token
}
if bangumi {
// The parameters need to be sorted by name
// qn=0 flag makes the CDN address different every time
// quality=116(1080P 60) is the highest quality so far
params = fmt.Sprintf(
"appkey=%s&cid=%s&module=bangumi&otype=json&qn=116&quality=116&season_type=4&type=",
appKey, cid,
"appkey=%s&cid=%s&module=bangumi&otype=json&qn=116&quality=116&season_type=4&type=&utoken=%s",
appKey, cid, utoken,
)
baseAPIURL = config.BILIBILI_BANGUMI_API
} else {
Expand All @@ -73,22 +94,13 @@ func genAPI(aid, cid string, bangumi bool) string {
)
baseAPIURL = config.BILIBILI_API
}
utoken := ""
if config.Cookie != "" {
utoken = request.Get(fmt.Sprintf(
"%said=%s&cid=%s", config.BILIBILI_TOKEN_API, aid, cid,
))
var t token
json.Unmarshal([]byte(utoken), &t)
if t.Code != 0 {
log.Println(config.Cookie)
log.Fatal("Cookie error: ", t.Message)
}
utoken = t.Data.Token
}
// bangumi utoken also need to put in params to sign, but the ordinary video doesn't need
api := fmt.Sprintf(
"%s%s&sign=%s&utoken=%s", baseAPIURL, params, getSign(params), utoken,
"%s%s&sign=%s", baseAPIURL, params, getSign(params),
)
if !bangumi && utoken != "" {
api = fmt.Sprintf("%s&utoken=%s", api, utoken)
}
return api
}

Expand All @@ -110,20 +122,50 @@ func genURL(durl []dURLData) ([]downloader.URLData, int64) {
}

// Bilibili download function
func Bilibili(url string) downloader.VideoData {
var (
bangumi bool
cid string
)
func Bilibili(url string) {
var bangumi bool
if strings.Contains(url, "bangumi") {
bangumi = true
}
aid := utils.Match1(`av(\d+)`, url)[1]
if !config.Playlist {
download(url, bangumi)
return
}
html := request.Get(url)
if bangumi {
dataString := utils.Match1(`window.__INITIAL_STATE__=(.+?);`, html)[1]
var data bangumiData
json.Unmarshal([]byte(dataString), &data)
for _, u := range data.EpList {
download(
fmt.Sprintf("https://www.bilibili.com/bangumi/play/ep%d", u.EpID), bangumi,
)
}
} else {
urls := utils.MatchAll(`<option value='(.+?)'`, html)
if len(urls) == 0 {
// this page has no playlist
download(url, bangumi)
return
}
// /video/av16907446/index_1.html
for _, u := range urls {
download("https://www.bilibili.com"+u[1], bangumi)
}
}
}

func download(url string, bangumi bool) downloader.VideoData {
var (
aid, cid string
)
html := request.Get(url)
if bangumi {
cid = utils.Match1(`"cid":(\d+)`, html)[1]
aid = utils.Match1(`"aid":(\d+)`, html)[1]
} else {
cid = utils.Match1(`cid=(\d+)`, html)[1]
aid = utils.Match1(`av(\d+)`, url)[1]
}
api := genAPI(aid, cid, bangumi)
apiData := request.Get(api)
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func init() {
flag.BoolVar(&config.Version, "v", false, "Show version")
flag.BoolVar(&config.InfoOnly, "i", false, "Information only")
flag.StringVar(&config.Cookie, "c", "", "Cookie")
flag.BoolVar(&config.Playlist, "p", false, "Download playlist")
}

func main() {
Expand Down
7 changes: 7 additions & 0 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ func Match1(pattern, text string) []string {
return value
}

// MatchAll return all matching results
func MatchAll(pattern, text string) [][]string {
re := regexp.MustCompile(pattern)
value := re.FindAllStringSubmatch(text, -1)
return value
}

// FileSize return the file size of the specified path file
func FileSize(filePath string) int64 {
file, err := os.Stat(filePath)
Expand Down

0 comments on commit b7fb8a1

Please sign in to comment.