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_nfo=process_nfo,
process_upper=process_upper, process_upper=process_upper,
process_subtitle=process_subtitle, process_subtitle=process_subtitle,
refresh_mode=True,
) )
for item in items for item in items
], ],

View File

@@ -65,10 +65,6 @@ class FavoriteItem(Model):
class Meta: class Meta:
unique_together = (("bvid", "favorite_list_id"),) unique_together = (("bvid", "favorite_list_id"),)
@property
def safe_name(self) -> str:
return self.name.replace("/", "_")
@property @property
def tmp_video_path(self) -> Path: def tmp_video_path(self) -> Path:
return Path(settings.path_mapper[self.favorite_list_id]) / f"tmp_{self.bvid}_video" 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: 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: async def to_file(self, path: Path) -> None:
"""把 xml 写入文件""" """把 xml 写入文件"""
async with aopen(path, "w", encoding="utf-8") as f: async with aopen(path, "w", encoding="utf-8") as f:
@@ -39,7 +44,7 @@ class EpisodeInfo(Base):
<episodedetails> <episodedetails>
<plot /> <plot />
<outline /> <outline />
<title>{self.title}</title> <title>{self.escape(self.title)}</title>
<season>{self.season}</season> <season>{self.season}</season>
<episode>{self.episode}</episode> <episode>{self.episode}</episode>
</episodedetails> </episodedetails>
@@ -59,7 +64,7 @@ class Actor(Base):
return f""" return f"""
<actor> <actor>
<name>{self.name}</name> <name>{self.name}</name>
<role>{self.role}</role> <role>{self.escape(self.role)}</role>
</actor> </actor>
""".strip() """.strip()
@@ -88,13 +93,15 @@ class MovieInfo(Base):
def to_xml(self) -> str: def to_xml(self) -> str:
actor = "\n".join(_.to_xml() for _ in self.actor) 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""" return f"""
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <?xml version="1.0" encoding="utf-8" standalone="yes"?>
<movie> <movie>
<plot><![CDATA[{self.plot}]]></plot> <plot><![CDATA[{self.escape(self.plot)}]]></plot>
<outline /> <outline />
<title>{self.title}</title> <title>{self.escape(self.title)}</title>
{actor} {actor}
<year>{self.aired.year}</year> <year>{self.aired.year}</year>
{tags} {tags}
@@ -126,13 +133,15 @@ class TVShowInfo(Base):
def to_xml(self) -> str: def to_xml(self) -> str:
actor = "\n".join(_.to_xml() for _ in self.actor) 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""" return f"""
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <?xml version="1.0" encoding="utf-8" standalone="yes"?>
<tvshow> <tvshow>
<plot><![CDATA[{self.plot}]]></plot> <plot><![CDATA[{self.escape(self.plot)}]]></plot>
<outline /> <outline />
<title>{self.title}</title> <title>{self.escape(self.title)}</title>
{actor} {actor}
<year>{self.aired.year}</year> <year>{self.aired.year}</year>
{tags} {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: async def process() -> None:
global anchor global anchor
if (today := datetime.date.today()) > anchor: if (today := datetime.date.today()) > anchor:
@@ -84,7 +96,10 @@ async def process() -> None:
logger.exception("Failed to refresh credential.") logger.exception("Failed to refresh credential.")
return return
for favorite_id in settings.path_mapper: 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: async def process_favorite(favorite_id: int) -> None:
@@ -131,6 +146,7 @@ async def process_favorite_item(
process_nfo=True, process_nfo=True,
process_upper=True, process_upper=True,
process_subtitle=True, process_subtitle=True,
refresh_mode=False,
) -> None: ) -> None:
logger.info("Start to process video {} {}.", fav_item.bvid, fav_item.name) logger.info("Start to process video {} {}.", fav_item.bvid, fav_item.name)
if fav_item.type != MediaType.VIDEO: if fav_item.type != MediaType.VIDEO:
@@ -155,66 +171,50 @@ async def process_favorite_item(
single_page = False single_page = False
if settings.paginated_video: if settings.paginated_video:
pages = None pages = None
try: if not refresh_mode:
pages = await v.get_pages() # 非手动触发的情况下,会刷新一下 pages
pages = [ try:
FavoriteItemPage( tmp_pages = await v.get_pages()
favorite_item=fav_item, if len(tmp_pages) <= 1:
cid=page["cid"], single_page = True
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
else: 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 single_page or not settings.paginated_video:
if process_nfo: if process_nfo:
try: try:
@@ -291,7 +291,7 @@ async def process_favorite_item_page(
) )
if process_poster: if process_poster:
try: 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: except FileExistsError:
logger.info( logger.info(
"Poster of {} {} page {} already exists, skipped.", "Poster of {} {} page {} already exists, skipped.",

View File

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

View File

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