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

View File

@@ -1,10 +1,11 @@
import asyncio
from aiofiles.os import path
from loguru import logger
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():
@@ -14,9 +15,7 @@ async def recheck():
status=MediaStatus.NORMAL,
downloaded=True,
)
exists = await asyncio.gather(
*[path.exists(item.video_path) for item in items]
)
exists = await asyncio.gather(*[aexists(item.video_path) for item in items])
for item, exist in zip(items, exists):
if isinstance(exist, Exception):
logger.error(
@@ -36,3 +35,25 @@ async def recheck():
logger.info("Updating database...")
await FavoriteItem.bulk_update(items, fields=["downloaded"])
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)

View File

@@ -4,7 +4,7 @@ import sys
import uvloop
from loguru import logger
from commands import recheck
from commands import recheck, upper_thumb
from models import init_model
from processor import cleanup, process
from settings import settings
@@ -14,16 +14,15 @@ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
async def entry() -> None:
await init_model()
if any("once" in _ for _ in sys.argv):
# 单次运行
logger.info("Running once...")
await process()
return
if any("recheck" in _ for _ in sys.argv):
# 重新检查
logger.info("Rechecking...")
await recheck()
return
for command, func in [
("once", process),
("recheck", recheck),
("upper_thumb", upper_thumb),
]:
if any(command in _ for _ in sys.argv):
logger.info("Running {}...", command)
await func()
return
logger.info("Running daemon...")
while True:
await process()

View File

@@ -13,6 +13,7 @@ from constants import (
MediaType,
)
from settings import settings
from utils import aopen
class FavoriteList(Model):
@@ -39,7 +40,31 @@ class Upper(Model):
@property
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):

10
nfo.py
View File

@@ -2,19 +2,19 @@ import datetime
from dataclasses import dataclass
from pathlib import Path
from utils import aopen
@dataclass
class Actor:
name: str
role: str
thumb: Path
def to_xml(self) -> str:
return f"""
<actor>
<name>{self.name}</name>
<role>{self.role}</role>
<thumb>{self.thumb.resolve()}</thumb>
</actor>
""".strip(
"\n"
@@ -29,9 +29,9 @@ class EpisodeInfo:
bvid: str
aired: datetime.datetime
def write_nfo(self, path: Path) -> None:
with path.open("w", encoding="utf-8") as f:
f.write(self.to_xml())
async def write_nfo(self, path: Path) -> None:
async with aopen(path, "w", encoding="utf-8") as f:
await f.write(self.to_xml())
def to_xml(self) -> str:
actor = "\n".join(_.to_xml() for _ in self.actor)

View File

@@ -2,11 +2,8 @@ import asyncio
import datetime
from asyncio import Semaphore, create_subprocess_exec
from asyncio.subprocess import DEVNULL
from pathlib import Path
import aiofiles
import httpx
from bilibili_api import HEADERS, favorite_list, video
from bilibili_api import favorite_list, video
from bilibili_api.exceptions import ResponseCodeException
from loguru import logger
from tortoise import Tortoise
@@ -16,8 +13,7 @@ from credential import credential
from models import FavoriteItem, FavoriteList, Upper
from nfo import Actor, EpisodeInfo
from settings import settings
client = httpx.AsyncClient(headers=HEADERS)
from utils import aexists, amakedirs, client, download_content
anchor = datetime.date.today()
@@ -40,16 +36,6 @@ def concurrent_decorator(concurrency: int) -> callable:
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:
uppers = [
Upper(
@@ -174,7 +160,7 @@ async def process_video(fav_item: FavoriteItem) -> None:
logger.warning("Media {} is not a video, skipped.", fav_item.name)
return
try:
if fav_item.video_path.exists():
if await aexists(fav_item.video_path):
fav_item.downloaded = True
await fav_item.save()
logger.info(
@@ -182,19 +168,25 @@ async def process_video(fav_item: FavoriteItem) -> None:
)
return
# 写入 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(
fav_item.upper.thumb, fav_item.upper.thumb_path
fav_item.upper.thumb_url, fav_item.upper.thumb_path
)
# 写入 nfo
EpisodeInfo(
await EpisodeInfo(
title=fav_item.name,
plot=fav_item.desc,
actor=[
Actor(
name=fav_item.upper.mid,
role=fav_item.upper.name,
thumb=fav_item.upper.thumb_path,
)
],
bvid=fav_item.bvid,

33
utils.py Normal file
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)