Compare commits

..

5 Commits
1.1.7 ... v1.x

6 changed files with 84 additions and 76 deletions

View File

@@ -64,6 +64,7 @@ async def _refresh_favorite_item_info(
process_nfo=process_nfo,
process_upper=process_upper,
process_subtitle=process_subtitle,
refresh_mode=True,
)
for item in items
],

View File

@@ -65,10 +65,6 @@ class FavoriteItem(Model):
class Meta:
unique_together = (("bvid", "favorite_list_id"),)
@property
def safe_name(self) -> str:
return self.name.replace("/", "_")
@property
def tmp_video_path(self) -> Path:
return Path(settings.path_mapper[self.favorite_list_id]) / f"tmp_{self.bvid}_video"

25
nfo.py
View File

@@ -15,6 +15,11 @@ class Base:
def to_xml(self) -> str:
...
@staticmethod
def escape(s: str) -> str:
"""转义 xml 特殊字符"""
return s.translate(str.maketrans({"<": "&lt;", ">": "&gt;", "&": "&amp;", "'": "&apos;", '"': "&quot;"}))
async def to_file(self, path: Path) -> None:
"""把 xml 写入文件"""
async with aopen(path, "w", encoding="utf-8") as f:
@@ -39,7 +44,7 @@ class EpisodeInfo(Base):
<episodedetails>
<plot />
<outline />
<title>{self.title}</title>
<title>{self.escape(self.title)}</title>
<season>{self.season}</season>
<episode>{self.episode}</episode>
</episodedetails>
@@ -59,7 +64,7 @@ class Actor(Base):
return f"""
<actor>
<name>{self.name}</name>
<role>{self.role}</role>
<role>{self.escape(self.role)}</role>
</actor>
""".strip()
@@ -88,13 +93,15 @@ class MovieInfo(Base):
def to_xml(self) -> str:
actor = "\n".join(_.to_xml() for _ in self.actor)
tags = "\n".join(f" <genre>{_}</genre>" for _ in self.tags) if isinstance(self.tags, list) else ""
tags = (
"\n".join(f" <genre>{self.escape(_)}</genre>" for _ in self.tags) if isinstance(self.tags, list) else ""
)
return f"""
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<movie>
<plot><![CDATA[{self.plot}]]></plot>
<plot><![CDATA[{self.escape(self.plot)}]]></plot>
<outline />
<title>{self.title}</title>
<title>{self.escape(self.title)}</title>
{actor}
<year>{self.aired.year}</year>
{tags}
@@ -126,13 +133,15 @@ class TVShowInfo(Base):
def to_xml(self) -> str:
actor = "\n".join(_.to_xml() for _ in self.actor)
tags = "\n".join(f" <genre>{_}</genre>" for _ in self.tags) if isinstance(self.tags, list) else ""
tags = (
"\n".join(f" <genre>{self.escape(_)}</genre>" for _ in self.tags) if isinstance(self.tags, list) else ""
)
return f"""
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<tvshow>
<plot><![CDATA[{self.plot}]]></plot>
<plot><![CDATA[{self.escape(self.plot)}]]></plot>
<outline />
<title>{self.title}</title>
<title>{self.escape(self.title)}</title>
{actor}
<year>{self.aired.year}</year>
{tags}

View File

@@ -71,6 +71,18 @@ async def update_favorite_item(medias: list[dict], fav_list: FavoriteList) -> No
)
async def update_favorite_item_page(pages: list[dict], item: FavoriteItem):
pages = [
FavoriteItemPage(
favorite_item=item, cid=page["cid"], page=page["page"], name=page["part"], image=page.get("first_frame", "")
)
for page in pages
]
await FavoriteItemPage.bulk_create(
pages, on_conflict=["favorite_item_id", "page"], update_fields=["cid", "name", "image"], batch_size=300
)
async def process() -> None:
global anchor
if (today := datetime.date.today()) > anchor:
@@ -84,7 +96,10 @@ async def process() -> None:
logger.exception("Failed to refresh credential.")
return
for favorite_id in settings.path_mapper:
await process_favorite(favorite_id)
try:
await process_favorite(favorite_id)
except asyncio.TimeoutError:
logger.exception("Process favorite {} timeout.", favorite_id)
async def process_favorite(favorite_id: int) -> None:
@@ -131,6 +146,7 @@ async def process_favorite_item(
process_nfo=True,
process_upper=True,
process_subtitle=True,
refresh_mode=False,
) -> None:
logger.info("Start to process video {} {}.", fav_item.bvid, fav_item.name)
if fav_item.type != MediaType.VIDEO:
@@ -155,66 +171,50 @@ async def process_favorite_item(
single_page = False
if settings.paginated_video:
pages = None
try:
pages = await v.get_pages()
pages = [
FavoriteItemPage(
favorite_item=fav_item,
cid=page["cid"],
page=page["page"],
name=page["part"],
image=page["first_frame"],
)
for page in pages
]
except Exception:
logger.exception("Failed to get pages of video {} {}.", fav_item.bvid, fav_item.name)
if pages:
if len(pages) == 1:
single_page = True
else:
# 如果有多个分 p那么先创建记录
await FavoriteItemPage.bulk_create(
pages,
on_conflict=["favorite_item_id", "page"],
update_fields=["cid", "name", "image"],
batch_size=300,
)
# 重新拉一下数据,不能用 bulk create 的返回值,因为 bulk_create 不会填充主键
pages = await FavoriteItemPage.filter(favorite_item=fav_item).order_by("page")
for page in pages:
page.favorite_item = fav_item
if process_nfo:
try:
await get_nfo(fav_item.tvshow_nfo_path, obj=fav_item, mode=NfoMode.TVSHOW)
except FileExistsError:
logger.info("Nfo of {} {} already exists, skipped.", fav_item.bvid, fav_item.name)
except Exception:
logger.exception("Failed to process nfo of video {} {}.", fav_item.bvid, fav_item.name)
if process_poster:
try:
await get_file(fav_item.cover, fav_item.tvshow_poster_path)
except FileExistsError:
logger.info("Poster of {} {} already exists, skipped.", fav_item.bvid, fav_item.name)
except Exception:
logger.exception("Failed to process poster of video {} {}.", fav_item.bvid, fav_item.name)
await asyncio.gather(
*[
process_favorite_item_page(
page, v, process_poster, process_video, process_nfo, process_subtitle
)
for page in pages
],
return_exceptions=True,
)
fav_item.downloaded = all(page.downloaded for page in pages)
page_status = {page.status for page in pages}
if MediaStatus.INVISIBLE in page_status:
fav_item.status = MediaStatus.INVISIBLE
elif MediaStatus.DELETED in page_status:
fav_item.status = MediaStatus.DELETED
if not refresh_mode:
# 非手动触发的情况下,会刷新一下 pages
try:
tmp_pages = await v.get_pages()
if len(tmp_pages) <= 1:
single_page = True
else:
fav_item.status = MediaStatus.NORMAL
await update_favorite_item_page(tmp_pages, fav_item)
except Exception:
logger.exception("Failed to get pages of video {} {}.", fav_item.bvid, fav_item.name)
# 从表中查出 pages
pages = await FavoriteItemPage.filter(favorite_item=fav_item).order_by("page")
for page in pages:
page.favorite_item = fav_item
if pages and not single_page:
if process_nfo:
try:
await get_nfo(fav_item.tvshow_nfo_path, obj=fav_item, mode=NfoMode.TVSHOW)
except FileExistsError:
logger.info("Nfo of {} {} already exists, skipped.", fav_item.bvid, fav_item.name)
except Exception:
logger.exception("Failed to process nfo of video {} {}.", fav_item.bvid, fav_item.name)
if process_poster:
try:
await get_file(fav_item.cover, fav_item.tvshow_poster_path)
except FileExistsError:
logger.info("Poster of {} {} already exists, skipped.", fav_item.bvid, fav_item.name)
except Exception:
logger.exception("Failed to process poster of video {} {}.", fav_item.bvid, fav_item.name)
await asyncio.gather(
*[
process_favorite_item_page(page, v, process_poster, process_video, process_nfo, process_subtitle)
for page in pages
],
return_exceptions=True,
)
fav_item.downloaded = all(page.downloaded for page in pages)
page_status = {page.status for page in pages}
if MediaStatus.INVISIBLE in page_status:
fav_item.status = MediaStatus.INVISIBLE
elif MediaStatus.DELETED in page_status:
fav_item.status = MediaStatus.DELETED
else:
fav_item.status = MediaStatus.NORMAL
if single_page or not settings.paginated_video:
if process_nfo:
try:
@@ -291,7 +291,7 @@ async def process_favorite_item_page(
)
if process_poster:
try:
await get_file(fav_page.image, fav_page.poster_path)
await get_file(fav_page.image or fav_page.favorite_item.cover, fav_page.poster_path)
except FileExistsError:
logger.info(
"Poster of {} {} page {} already exists, skipped.",

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "bili-sync"
version = "1.1.7"
version = "1.1.9"
description = ""
authors = ["amtoaer <amtoaer@gmail.com>"]
license = "GPL-3.0"
@@ -68,7 +68,7 @@ message = "chore: bump version from {current_version} to {new_version}"
tag = true
tag_name = "{new_version}"
tag_message = ""
current_version = "1.1.7"
current_version = "1.1.9"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
[[tool.bumpversion.files]]
@@ -82,6 +82,8 @@ filename = "pyproject.toml"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1 +1 @@
VERSION = "1.1.7"
VERSION = "1.1.9"