mirror of
https://github.com/amtoaer/bili-sync.git
synced 2026-08-28 19:47:54 +08:00
fix: 尝试修复 emby 头像路径,异步化所有文件操作
This commit is contained in:
+26
-5
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user