Skip to content

Commit

Permalink
✨ feat: support custom time format in path template (#147)
Browse files Browse the repository at this point in the history
  • Loading branch information
SigureMo authored Jun 10, 2023
1 parent 110436e commit 9bb5476
Show file tree
Hide file tree
Showing 9 changed files with 42 additions and 12 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ yutto <url> -c "d8bc7493%2C2843925707%2C08c3e*81"

默认情况是由 yutto 自动控制存放位置的。比如下载单个视频时默认就是直接存放在设定的根目录,不会创建一层容器目录,此时自动选择了 `{name}` 作为模板;而批量下载时则会根据视频层级生成多级目录,比如番剧会是 `{title}/{name}`,首先会在设定根目录里生成一个番剧名的目录,其内才会存放各个番剧剧集视频,这样方便了多个不同番剧的管理。当然,如果你仍希望将番剧直接存放在设定根目录下的话,可以修改该参数值为 `{name}`即可。

另外,该功能语法由 Python format 函数模板语法提供,所以也支持一些高级的用法,比如 `{id:0>3}{name}`
另外,该功能语法由 Python format 函数模板语法提供,所以也支持一些高级的用法,比如 `{id:0>3}{name}`,此外还专门为时间变量 🕛 增加了自定义时间模板的语法 `{pubdate@%Y-%m-%d %H:%M:%S}`,默认时间模板为 `%Y-%m-%d`

值得注意的是,并不是所有变量在各种场合下都会提供,比如 `username`, `owner_uid` 变量当前仅在 UP 主全部投稿视频/收藏夹/稍后再看才提供,在其它情况下不应使用它。各变量详细作用域描述见下表:

Expand All @@ -327,7 +327,7 @@ yutto <url> -c "d8bc7493%2C2843925707%2C08c3e*81"
|name|系列视频单 p 标题|全部|
|username|UP 主用户名|个人空间、收藏夹、稍后再看、合集、视频列表下载|
|series_title|合集标题|收藏夹、视频合集、视频列表下载|
|pubdate|投稿日期|仅投稿视频|
|pubdate🕛|投稿日期|仅投稿视频|
|download_date|下载日期|全部|
|owner_uid|UP 主UID|个人空间、收藏夹、稍后再看、合集、视频列表下载|

Expand Down
4 changes: 2 additions & 2 deletions yutto/api/ugc_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class UgcVideoListItem(TypedDict):

class UgcVideoList(TypedDict):
title: str
pubdate: str
pubdate: int
avid: AvId
pages: list[UgcVideoListItem]

Expand Down Expand Up @@ -131,7 +131,7 @@ async def get_ugc_video_list(session: ClientSession, avid: AvId) -> UgcVideoList
result: UgcVideoList = {
"title": video_title,
"avid": avid,
"pubdate": get_time_str_by_stamp(video_info["pubdate"], "%Y-%m-%d"), # TODO: 可自由定制
"pubdate": video_info["pubdate"],
"pages": [],
}
list_api = "https://api.bilibili.com/x/player/pagelist?aid={aid}&bvid={bvid}&jsonp=jsonp"
Expand Down
2 changes: 1 addition & 1 deletion yutto/extractor/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ async def extract(
collection_title = collection_details["title"]
Logger.custom(collection_title, Badge("视频合集", fore="black", back="cyan"))

ugc_video_info_list: list[tuple[UgcVideoListItem, str, str]] = []
ugc_video_info_list: list[tuple[UgcVideoListItem, str, int]] = []

# 选集过滤
episodes = parse_episodes_selection(args.episodes, len(collection_details["pages"]))
Expand Down
2 changes: 1 addition & 1 deletion yutto/extractor/favourites.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async def extract(
)
Logger.custom(favourite_info["title"], Badge("收藏夹", fore="black", back="cyan"))

ugc_video_info_list: list[tuple[UgcVideoListItem, str, str]] = []
ugc_video_info_list: list[tuple[UgcVideoListItem, str, int]] = []

for avid in await get_favourite_avids(session, self.fid):
try:
Expand Down
2 changes: 1 addition & 1 deletion yutto/extractor/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async def extract(
)
Logger.custom(series_title, Badge("视频列表", fore="black", back="cyan"))

ugc_video_info_list: list[tuple[UgcVideoListItem, str, str]] = []
ugc_video_info_list: list[tuple[UgcVideoListItem, str, int]] = []
for avid in await get_medialist_avids(session, self.series_id, self.mid):
try:
ugc_video_list = await get_ugc_video_list(session, avid)
Expand Down
2 changes: 1 addition & 1 deletion yutto/extractor/user_all_favourites.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async def extract(
username = await get_user_name(session, self.mid)
Logger.custom(username, Badge("用户收藏夹", fore="black", back="cyan"))

ugc_video_info_list: list[tuple[UgcVideoListItem, str, str, str]] = []
ugc_video_info_list: list[tuple[UgcVideoListItem, str, int, str]] = []

for fav in await get_all_favourites(session, self.mid):
series_title = fav["title"]
Expand Down
2 changes: 1 addition & 1 deletion yutto/extractor/user_all_ugc_videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async def extract(
username = await get_user_name(session, self.mid)
Logger.custom(username, Badge("UP 主投稿视频", fore="black", back="cyan"))

ugc_video_info_list: list[tuple[UgcVideoListItem, str, str]] = []
ugc_video_info_list: list[tuple[UgcVideoListItem, str, int]] = []
for avid in await get_user_space_all_videos_avids(session, self.mid):
try:
ugc_video_list = await get_ugc_video_list(session, avid)
Expand Down
2 changes: 1 addition & 1 deletion yutto/extractor/user_watch_later.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async def extract(
) -> list[CoroutineWrapper[EpisodeData | None] | None]:
Logger.custom("当前用户", Badge("稍后再看", fore="black", back="cyan"))

ugc_video_info_list: list[tuple[UgcVideoListItem, str, str, str]] = []
ugc_video_info_list: list[tuple[UgcVideoListItem, str, int, str]] = []

try:
avid_list = await get_watch_later_avids(session)
Expand Down
34 changes: 32 additions & 2 deletions yutto/processor/path_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Literal, Union

from yutto.utils.console.logger import Logger
from yutto.utils.time import get_time_str_by_stamp

PathTemplateVariable = Literal[
"title", "id", "name", "username", "series_title", "pubdate", "download_date", "owner_uid"
Expand Down Expand Up @@ -50,18 +51,47 @@ def to_full_width_chr(matchobj: re.Match[str]) -> str:
return filename


def create_time_formatter(name: str, value: int):
regex = re.compile(rf"{{{name}(@(?P<timefmt>.+))?}}")
DEFAULT_TIMEFMT = "%Y-%m-%d"

def convert_pubdate(matchobj: re.Match[str]):
timefmt = matchobj.group("timefmt")
if timefmt is None:
timefmt = DEFAULT_TIMEFMT
formatted_time = repair_filename(get_time_str_by_stamp(value, timefmt))
return formatted_time

def formatter(text: str):
return regex.sub(convert_pubdate, text)

return formatter


def resolve_path_template(
path_template: str, auto_path_template: str, subpath_variables: PathTemplateVariableDict
) -> str:
# fav_title 已经被 series_title 取代
if "{fav_title}" in path_template:
Logger.deprecated_warning("路径变量 fav_title 已经被废除,已自动使用 series_title 替代(2.0.0 后将会彻底废除 fav_title)")
path_template = path_template.replace("{fav_title}", "{series_title}")

# 保证所有传进来的值都满足路径要求
for key, value in subpath_variables.items():
# 未知变量警告
if f"{{{key}}}" in path_template and value == UNKNOWN:
Logger.warning("使用了未知的变量,可能导致产生错误的下载路径")
# 只对字符串值修改,int 型不修改以适配高级模板
if isinstance(value, str):
if f"{{{key}}}" in path_template and value == UNKNOWN:
Logger.warning("使用了未知的变量,可能导致产生错误的下载路径")
subpath_variables[key] = repair_filename(value)

# 将时间变量转换为对应的时间格式
time_vars: list[PathTemplateVariable] = ["pubdate"] # TODO: add download_date
for var in time_vars:
value = subpath_variables.pop(var)
if value == UNKNOWN:
continue
assert isinstance(value, int), f"变量 {var} 的值必须为 int 型,但是传入了 {value}"
time_formatter = create_time_formatter(var, value)
path_template = time_formatter(path_template)
return path_template.format(auto=auto_path_template.format(**subpath_variables), **subpath_variables)

0 comments on commit 9bb5476

Please sign in to comment.