Skip to content

Commit

Permalink
feat: add download series
Browse files Browse the repository at this point in the history
  • Loading branch information
robo-dani committed Apr 30, 2024
1 parent 72ed726 commit 0bdd288
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 0 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,19 @@ bilifm season $uid $sid [OPTIONS]
- Options:
- -o, --directory 选择音频保存地址

### series 模式
```bash
bilifm season $uid $sid [OPTIONS]
```
- uid, sid 的获取:
打开用户空间中的合集和列表, 找到列表点击更多, 然后从URL中获取

https://space.bilibili.com/488978908/channel/seriesdetail?sid=888434&ctype=0

例如上面链接, uid为488978908, sid为888434. 使用下面命令
```bash
bilifm series 488978908 888434
```

## Features

Expand Down
19 changes: 19 additions & 0 deletions src/bilifm/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .audio import Audio
from .fav import Fav
from .season import Season
from .series import Series
from .user import User
from .util import Directory, Path

Expand Down Expand Up @@ -63,3 +64,21 @@ def season(uid: str, sid: str, directory: Directory = None):
audio = Audio(id)
audio.download()
typer.echo("Download complete")


@app.command()
def series(uid: str, sid: str, directory: Directory = None):
"""download bilibili video series
because the api of series lacks the series name, executing
this command will not create a folder for the series
"""
ser = Series(uid, sid)
ret = ser.get_videos()
if not ret:
typer.Exit(1)
return

for id in ser.videos:
audio = Audio(id)
audio.download()
typer.echo("Download complete")
77 changes: 77 additions & 0 deletions src/bilifm/series.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""download bilibili video series, 视频列表"""

import typer
from .util import request

headers: dict[str, str] = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"Referer": "https://www.bilibili.com",
}


class Series:
series_url: str = "https://api.bilibili.com/x/series/archives"
retry: int = 3

def __init__(self, uid: str, series_id: str, page_size=30) -> None:
self.uid = uid
self.series_id = series_id
self.page_size = page_size
self.videos = []
self.total = 0

def get_videos(self):
params = {
"mid": self.uid,
"series_id": self.series_id,
"pn": 1,
"ps": self.page_size,
"current_id": self.uid,
}

def wrapped_request():
"""wrap request with retry"""
for _ in range(self.retry):
res = request(
method="get", url=self.series_url, params=params, headers=headers
).json()
if self.__response_succeed(res):
return res
self.__handle_error_response(res)
return None

res = wrapped_request()
if res is None:
return False

self.total = res["data"]["page"]["total"]

for i in range(1, self.total // self.page_size + 2):
params["pn"] = i
res = wrapped_request()
if res:
bvids = [ar["bvid"] for ar in res["data"]["archives"]]
self.videos.extend(bvids)
else:
typer.echo(
f"skip audios from {(i-1)* self.page_size} to {i * self.page_size}"
)

return True

def __handle_error_response(self, response):
try:
archives = response["data"]["archives"]
except KeyError:
archives = 0 # something null not none
if archives is None:
typer.echo(f"Error: uid {self.uid} or sid {self.series_id} error.")
else:
typer.echo("Error: Unknown problem.")
typer.echo(f"resp: {response}")

def __response_succeed(self, response) -> bool:
try:
return response["data"]["archives"] is not None
except KeyError:
return False

0 comments on commit 0bdd288

Please sign in to comment.