mirror of
https://github.com/amtoaer/bili-sync.git
synced 2026-05-06 20:42:48 +08:00
feat: 下载视频出错时打印日志,支持 docker 构建
This commit is contained in:
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM python:3.11.6-alpine3.18 AS base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV BILI_IN_DOCKER=true
|
||||
|
||||
COPY poetry.lock pyproject.toml ./
|
||||
|
||||
RUN apk add ffmpeg \
|
||||
&& apk add --no-cache --virtual .build-deps \
|
||||
gcc \
|
||||
musl-dev \
|
||||
libffi-dev \
|
||||
openssl-dev \
|
||||
&& pip install poetry \
|
||||
&& poetry config virtualenvs.create false \
|
||||
&& poetry install --no-dev --no-interaction --no-ansi \
|
||||
&& apk del .build-deps
|
||||
|
||||
COPY . .
|
||||
|
||||
ENTRYPOINT [ "python", "entry.py" ]
|
||||
29
constants.py
29
constants.py
@@ -2,23 +2,22 @@ import os
|
||||
from enum import IntEnum
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_CONFIG_PATH = (
|
||||
Path(__file__).parent / "config.json"
|
||||
if not os.getenv("TESTING")
|
||||
else Path(__file__).parent / "config.test.json"
|
||||
)
|
||||
|
||||
DEFAULT_DATABASE_PATH = (
|
||||
Path(__file__).parent / "database.db"
|
||||
if not os.getenv("TESTING")
|
||||
else Path(__file__).parent / "database.test.db"
|
||||
)
|
||||
def get_base(dir_name: str) -> Path:
|
||||
path = (
|
||||
Path(base)
|
||||
if (base := os.getenv(f"{dir_name.upper()}_PATH"))
|
||||
else Path(__file__).parent / dir_name
|
||||
)
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
return path
|
||||
|
||||
DEFAULT_THUMB_PATH = (
|
||||
Path(__file__).parent / "thumbs"
|
||||
if not os.getenv("TESTING")
|
||||
else Path(__file__).parent / "thumbs.test"
|
||||
)
|
||||
|
||||
DEFAULT_CONFIG_PATH = get_base("config") / "config.json"
|
||||
|
||||
DEFAULT_DATABASE_PATH = get_base("data") / "data.db"
|
||||
|
||||
DEFAULT_THUMB_PATH = get_base("thumb")
|
||||
|
||||
FFMPEG_COMMAND = "ffmpeg"
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ CREATE TABLE IF NOT EXISTS "favoriteitem" (
|
||||
"pubtime" TIMESTAMP NOT NULL,
|
||||
"fav_time" TIMESTAMP NOT NULL,
|
||||
"downloaded" INT NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"favorite_list_id" INT NOT NULL REFERENCES "favoritelist" ("id") ON DELETE CASCADE,
|
||||
"upper_id" INT NOT NULL REFERENCES "upper" ("mid") ON DELETE CASCADE,
|
||||
CONSTRAINT "uid_favoriteite_bvid_d7b8ea" UNIQUE ("bvid", "favorite_list_id")
|
||||
13
models.py
13
models.py
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
from asyncio import create_subprocess_exec
|
||||
from pathlib import Path
|
||||
|
||||
@@ -57,6 +58,8 @@ class FavoriteItem(Model):
|
||||
pubtime = fields.DatetimeField()
|
||||
fav_time = fields.DatetimeField()
|
||||
downloaded = fields.BooleanField(default=False)
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
updated_at = fields.DatetimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("bvid", "favorite_list_id"),)
|
||||
@@ -103,10 +106,10 @@ class FavoriteItem(Model):
|
||||
|
||||
async def init_model() -> None:
|
||||
await Tortoise.init(config=TORTOISE_ORM)
|
||||
process = await create_subprocess_exec(
|
||||
"poetry",
|
||||
"run",
|
||||
MIGRATE_COMMAND,
|
||||
"upgrade",
|
||||
migrate_commands = (
|
||||
[MIGRATE_COMMAND, "upgrade"]
|
||||
if os.getenv("BILI_IN_DOCKER")
|
||||
else ["poetry", "run", MIGRATE_COMMAND, "upgrade"]
|
||||
)
|
||||
process = await create_subprocess_exec(*migrate_commands)
|
||||
await process.communicate()
|
||||
|
||||
136
processor.py
136
processor.py
@@ -10,7 +10,7 @@ from bilibili_api import HEADERS, favorite_list, video
|
||||
from loguru import logger
|
||||
from tortoise import Tortoise
|
||||
|
||||
from constants import DEFAULT_THUMB_PATH, FFMPEG_COMMAND, MediaType
|
||||
from constants import FFMPEG_COMMAND, MediaType
|
||||
from credential import credential
|
||||
from models import FavoriteItem, FavoriteList, Upper
|
||||
from nfo import Actor, EpisodeInfo
|
||||
@@ -126,7 +126,6 @@ async def process_favorite(favorite_id: int) -> None:
|
||||
id=favorite_id, defaults={"name": favorite_video_list["info"]["title"]}
|
||||
)
|
||||
fav_list.video_list_path.mkdir(parents=True, exist_ok=True)
|
||||
DEFAULT_THUMB_PATH.mkdir(parents=True, exist_ok=True)
|
||||
page = 0
|
||||
while True:
|
||||
page += 1
|
||||
@@ -169,70 +168,77 @@ async def process_video(fav_item: FavoriteItem) -> None:
|
||||
if fav_item.type != MediaType.VIDEO:
|
||||
logger.warning("Media {} is not a video, skipped.", fav_item.name)
|
||||
return
|
||||
if fav_item.video_path.exists():
|
||||
try:
|
||||
if fav_item.video_path.exists():
|
||||
fav_item.downloaded = True
|
||||
await fav_item.save()
|
||||
logger.info(
|
||||
"{} {} already exists, skipped.", fav_item.bvid, fav_item.name
|
||||
)
|
||||
return
|
||||
# 写入 up 主头像
|
||||
if not fav_item.upper.thumb_path.exists():
|
||||
await download_content(
|
||||
fav_item.upper.thumb, fav_item.upper.thumb_path
|
||||
)
|
||||
# 写入 nfo
|
||||
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,
|
||||
aired=fav_item.ctime,
|
||||
).write_nfo(fav_item.nfo_path)
|
||||
# 写入 poster
|
||||
await download_content(fav_item.cover, fav_item.poster_path)
|
||||
# 开始处理视频内容
|
||||
v = video.Video(fav_item.bvid, credential=credential)
|
||||
detector = video.VideoDownloadURLDataDetecter(
|
||||
await v.get_download_url(page_index=0)
|
||||
)
|
||||
streams = detector.detect_best_streams()
|
||||
if detector.check_flv_stream():
|
||||
await download_content(streams[0].url, fav_item.tmp_video_path)
|
||||
process = await create_subprocess_exec(
|
||||
FFMPEG_COMMAND,
|
||||
"-i",
|
||||
str(fav_item.tmp_video_path),
|
||||
str(fav_item.video_path),
|
||||
stdout=DEVNULL,
|
||||
stderr=DEVNULL,
|
||||
)
|
||||
await process.communicate()
|
||||
fav_item.tmp_video_path.unlink()
|
||||
else:
|
||||
await asyncio.gather(
|
||||
download_content(streams[0].url, fav_item.tmp_video_path),
|
||||
download_content(streams[1].url, fav_item.tmp_audio_path),
|
||||
)
|
||||
process = await create_subprocess_exec(
|
||||
FFMPEG_COMMAND,
|
||||
"-i",
|
||||
str(fav_item.tmp_video_path),
|
||||
"-i",
|
||||
str(fav_item.tmp_audio_path),
|
||||
"-c",
|
||||
"copy",
|
||||
str(fav_item.video_path),
|
||||
stdout=DEVNULL,
|
||||
stderr=DEVNULL,
|
||||
)
|
||||
await process.communicate()
|
||||
fav_item.tmp_video_path.unlink()
|
||||
fav_item.tmp_audio_path.unlink()
|
||||
fav_item.downloaded = True
|
||||
await fav_item.save()
|
||||
logger.info(
|
||||
"{} {} already exists, skipped.", fav_item.bvid, fav_item.name
|
||||
"{} {} processed successfully.", fav_item.bvid, fav_item.name
|
||||
)
|
||||
return
|
||||
# 写入 up 主头像
|
||||
if not fav_item.upper.thumb_path.exists():
|
||||
await download_content(fav_item.upper.thumb, fav_item.upper.thumb_path)
|
||||
# 写入 nfo
|
||||
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,
|
||||
aired=fav_item.ctime,
|
||||
).write_nfo(fav_item.nfo_path)
|
||||
# 写入 poster
|
||||
await download_content(fav_item.cover, fav_item.poster_path)
|
||||
# 开始处理视频内容
|
||||
v = video.Video(fav_item.bvid, credential=credential)
|
||||
detector = video.VideoDownloadURLDataDetecter(
|
||||
await v.get_download_url(page_index=0)
|
||||
)
|
||||
streams = detector.detect_best_streams()
|
||||
if detector.check_flv_stream():
|
||||
await download_content(streams[0].url, fav_item.tmp_video_path)
|
||||
process = await create_subprocess_exec(
|
||||
FFMPEG_COMMAND,
|
||||
"-i",
|
||||
str(fav_item.tmp_video_path),
|
||||
str(fav_item.video_path),
|
||||
stdout=DEVNULL,
|
||||
stderr=DEVNULL,
|
||||
)
|
||||
await process.communicate()
|
||||
fav_item.tmp_video_path.unlink()
|
||||
else:
|
||||
await asyncio.gather(
|
||||
download_content(streams[0].url, fav_item.tmp_video_path),
|
||||
download_content(streams[1].url, fav_item.tmp_audio_path),
|
||||
)
|
||||
process = await create_subprocess_exec(
|
||||
FFMPEG_COMMAND,
|
||||
"-i",
|
||||
str(fav_item.tmp_video_path),
|
||||
"-i",
|
||||
str(fav_item.tmp_audio_path),
|
||||
"-c",
|
||||
"copy",
|
||||
str(fav_item.video_path),
|
||||
stdout=DEVNULL,
|
||||
stderr=DEVNULL,
|
||||
)
|
||||
await process.communicate()
|
||||
fav_item.tmp_video_path.unlink()
|
||||
fav_item.tmp_audio_path.unlink()
|
||||
fav_item.downloaded = True
|
||||
await fav_item.save()
|
||||
logger.info("{} {} processed successfully.", fav_item.bvid, fav_item.name)
|
||||
except Exception:
|
||||
logger.exception("Failed to process video {}", fav_item.name)
|
||||
|
||||
Reference in New Issue
Block a user