fix: 尝试修复 emby 头像路径,异步化所有文件操作

This commit is contained in:
amtoaer
2023-12-02 00:26:19 +08:00
parent bbde9d6ba6
commit 012b3f9f31
6 changed files with 113 additions and 43 deletions
+26 -5
View File
@@ -1,10 +1,11 @@
import asyncio import asyncio
from aiofiles.os import path
from loguru import logger from loguru import logger
from constants import MediaStatus, MediaType from constants import MediaStatus, MediaType
from models import FavoriteItem from models import FavoriteItem, Upper
from processor import download_content
from utils import aexists, amakedirs
async def recheck(): async def recheck():
@@ -14,9 +15,7 @@ async def recheck():
status=MediaStatus.NORMAL, status=MediaStatus.NORMAL,
downloaded=True, downloaded=True,
) )
exists = await asyncio.gather( exists = await asyncio.gather(*[aexists(item.video_path) for item in items])
*[path.exists(item.video_path) for item in items]
)
for item, exist in zip(items, exists): for item, exist in zip(items, exists):
if isinstance(exist, Exception): if isinstance(exist, Exception):
logger.error( logger.error(
@@ -36,3 +35,25 @@ async def recheck():
logger.info("Updating database...") logger.info("Updating database...")
await FavoriteItem.bulk_update(items, fields=["downloaded"]) await FavoriteItem.bulk_update(items, fields=["downloaded"])
logger.info("Database updated.") logger.info("Database updated.")
async def upper_thumb():
makedir_tasks = []
other_tasks = []
for upper in await Upper.all():
if not all(
await asyncio.gather(
aexists(upper.thumb_path), aexists(upper.meta_path)
)
):
makedir_tasks.append(
amakedirs(upper.thumb_path.parent, exist_ok=True)
)
other_tasks.extend(
[
upper.save_metadata(),
download_content(upper.thumb_url, upper.thumb_path),
]
)
await asyncio.gather(*makedir_tasks)
await asyncio.gather(*other_tasks)
+10 -11
View File
@@ -4,7 +4,7 @@ import sys
import uvloop import uvloop
from loguru import logger from loguru import logger
from commands import recheck from commands import recheck, upper_thumb
from models import init_model from models import init_model
from processor import cleanup, process from processor import cleanup, process
from settings import settings from settings import settings
@@ -14,16 +14,15 @@ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
async def entry() -> None: async def entry() -> None:
await init_model() await init_model()
if any("once" in _ for _ in sys.argv): for command, func in [
# 单次运行 ("once", process),
logger.info("Running once...") ("recheck", recheck),
await process() ("upper_thumb", upper_thumb),
return ]:
if any("recheck" in _ for _ in sys.argv): if any(command in _ for _ in sys.argv):
# 重新检查 logger.info("Running {}...", command)
logger.info("Rechecking...") await func()
await recheck() return
return
logger.info("Running daemon...") logger.info("Running daemon...")
while True: while True:
await process() await process()
+26 -1
View File
@@ -13,6 +13,7 @@ from constants import (
MediaType, MediaType,
) )
from settings import settings from settings import settings
from utils import aopen
class FavoriteList(Model): class FavoriteList(Model):
@@ -39,7 +40,31 @@ class Upper(Model):
@property @property
def thumb_path(self) -> Path: def thumb_path(self) -> Path:
return DEFAULT_THUMB_PATH / f"{self.mid}.jpg" return (
DEFAULT_THUMB_PATH / f"{self.mid[0]}" / f"{self.mid}" / "folder.jpg"
)
@property
def meta_path(self) -> Path:
return (
DEFAULT_THUMB_PATH / f"{self.mid[0]}" / f"{self.mid}" / "person.nfo"
)
async def save_metadata(self):
async with aopen(self.meta_path, "w") as f:
await f.write(
f"""
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<person>
<plot />
<outline />
<lockdata>false</lockdata>
<dateadded>{self.created_at.strftime("%Y-%m-%d %H:%M:%S")}</dateadded>
<title>{self.mid}</title>
<sorttitle>{self.mid}</sorttitle>
</person>
""".strip()
)
class FavoriteItem(Model): class FavoriteItem(Model):
+5 -5
View File
@@ -2,19 +2,19 @@ import datetime
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from utils import aopen
@dataclass @dataclass
class Actor: class Actor:
name: str name: str
role: str role: str
thumb: Path
def to_xml(self) -> str: def to_xml(self) -> str:
return f""" return f"""
<actor> <actor>
<name>{self.name}</name> <name>{self.name}</name>
<role>{self.role}</role> <role>{self.role}</role>
<thumb>{self.thumb.resolve()}</thumb>
</actor> </actor>
""".strip( """.strip(
"\n" "\n"
@@ -29,9 +29,9 @@ class EpisodeInfo:
bvid: str bvid: str
aired: datetime.datetime aired: datetime.datetime
def write_nfo(self, path: Path) -> None: async def write_nfo(self, path: Path) -> None:
with path.open("w", encoding="utf-8") as f: async with aopen(path, "w", encoding="utf-8") as f:
f.write(self.to_xml()) await f.write(self.to_xml())
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)
+13 -21
View File
@@ -2,11 +2,8 @@ import asyncio
import datetime import datetime
from asyncio import Semaphore, create_subprocess_exec from asyncio import Semaphore, create_subprocess_exec
from asyncio.subprocess import DEVNULL from asyncio.subprocess import DEVNULL
from pathlib import Path
import aiofiles from bilibili_api import favorite_list, video
import httpx
from bilibili_api import HEADERS, favorite_list, video
from bilibili_api.exceptions import ResponseCodeException from bilibili_api.exceptions import ResponseCodeException
from loguru import logger from loguru import logger
from tortoise import Tortoise from tortoise import Tortoise
@@ -16,8 +13,7 @@ from credential import credential
from models import FavoriteItem, FavoriteList, Upper from models import FavoriteItem, FavoriteList, Upper
from nfo import Actor, EpisodeInfo from nfo import Actor, EpisodeInfo
from settings import settings from settings import settings
from utils import aexists, amakedirs, client, download_content
client = httpx.AsyncClient(headers=HEADERS)
anchor = datetime.date.today() anchor = datetime.date.today()
@@ -40,16 +36,6 @@ def concurrent_decorator(concurrency: int) -> callable:
return decorator return decorator
async def download_content(url: str, path: Path) -> None:
async with client.stream("GET", url) as resp, aiofiles.open(
path, "wb"
) as f:
async for chunk in resp.aiter_bytes(40960):
if not chunk:
return
await f.write(chunk)
async def manage_model(medias: list[dict], fav_list: FavoriteList) -> None: async def manage_model(medias: list[dict], fav_list: FavoriteList) -> None:
uppers = [ uppers = [
Upper( Upper(
@@ -174,7 +160,7 @@ async def process_video(fav_item: FavoriteItem) -> None:
logger.warning("Media {} is not a video, skipped.", fav_item.name) logger.warning("Media {} is not a video, skipped.", fav_item.name)
return return
try: try:
if fav_item.video_path.exists(): if await aexists(fav_item.video_path):
fav_item.downloaded = True fav_item.downloaded = True
await fav_item.save() await fav_item.save()
logger.info( logger.info(
@@ -182,19 +168,25 @@ async def process_video(fav_item: FavoriteItem) -> None:
) )
return return
# 写入 up 主头像 # 写入 up 主头像
if not fav_item.upper.thumb_path.exists(): if not all(
await asyncio.gather(
aexists(fav_item.upper.thumb_path),
aexists(fav_item.upper.meta_path),
)
):
await amakedirs(fav_item.upper.thumb_path.parent, exist_ok=True)
await fav_item.upper.save_metadata()
await download_content( await download_content(
fav_item.upper.thumb, fav_item.upper.thumb_path fav_item.upper.thumb_url, fav_item.upper.thumb_path
) )
# 写入 nfo # 写入 nfo
EpisodeInfo( await EpisodeInfo(
title=fav_item.name, title=fav_item.name,
plot=fav_item.desc, plot=fav_item.desc,
actor=[ actor=[
Actor( Actor(
name=fav_item.upper.mid, name=fav_item.upper.mid,
role=fav_item.upper.name, role=fav_item.upper.name,
thumb=fav_item.upper.thumb_path,
) )
], ],
bvid=fav_item.bvid, bvid=fav_item.bvid,
+33
View File
@@ -0,0 +1,33 @@
from pathlib import Path
import aiofiles
import httpx
from aiofiles.base import AiofilesContextManager
from aiofiles.os import makedirs
from aiofiles.ospath import exists
from aiofiles.threadpool.text import AsyncTextIOWrapper
from bilibili_api import HEADERS
client = httpx.AsyncClient(headers=HEADERS)
async def download_content(url: str, path: Path) -> None:
async with client.stream("GET", url) as resp, aopen(path, "wb") as f:
async for chunk in resp.aiter_bytes(40960):
if not chunk:
return
await f.write(chunk)
async def aexists(path: Path) -> bool:
return await exists(path)
async def amakedirs(path: Path, exist_ok=False) -> None:
await makedirs(path, parents=True, exist_ok=exist_ok)
def aopen(
path: Path, mode: str = "r", **kwargs
) -> AiofilesContextManager[None, None, AsyncTextIOWrapper]:
return aiofiles.open(path, mode, **kwargs)