feat: 下载视频出错时打印日志,支持 docker 构建

This commit is contained in:
amtoaer
2023-11-25 12:38:09 +08:00
parent f10f9829d2
commit be6b8eaa89
5 changed files with 117 additions and 85 deletions
+22
View 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" ]
+14 -15
View File
@@ -2,23 +2,22 @@ import os
from enum import IntEnum from enum import IntEnum
from pathlib import Path 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 = ( def get_base(dir_name: str) -> Path:
Path(__file__).parent / "database.db" path = (
if not os.getenv("TESTING") Path(base)
else Path(__file__).parent / "database.test.db" 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" DEFAULT_CONFIG_PATH = get_base("config") / "config.json"
if not os.getenv("TESTING")
else Path(__file__).parent / "thumbs.test" DEFAULT_DATABASE_PATH = get_base("data") / "data.db"
)
DEFAULT_THUMB_PATH = get_base("thumb")
FFMPEG_COMMAND = "ffmpeg" FFMPEG_COMMAND = "ffmpeg"
@@ -27,6 +27,8 @@ CREATE TABLE IF NOT EXISTS "favoriteitem" (
"pubtime" TIMESTAMP NOT NULL, "pubtime" TIMESTAMP NOT NULL,
"fav_time" TIMESTAMP NOT NULL, "fav_time" TIMESTAMP NOT NULL,
"downloaded" INT NOT NULL DEFAULT 0, "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, "favorite_list_id" INT NOT NULL REFERENCES "favoritelist" ("id") ON DELETE CASCADE,
"upper_id" INT NOT NULL REFERENCES "upper" ("mid") ON DELETE CASCADE, "upper_id" INT NOT NULL REFERENCES "upper" ("mid") ON DELETE CASCADE,
CONSTRAINT "uid_favoriteite_bvid_d7b8ea" UNIQUE ("bvid", "favorite_list_id") CONSTRAINT "uid_favoriteite_bvid_d7b8ea" UNIQUE ("bvid", "favorite_list_id")
+8 -5
View File
@@ -1,3 +1,4 @@
import os
from asyncio import create_subprocess_exec from asyncio import create_subprocess_exec
from pathlib import Path from pathlib import Path
@@ -57,6 +58,8 @@ class FavoriteItem(Model):
pubtime = fields.DatetimeField() pubtime = fields.DatetimeField()
fav_time = fields.DatetimeField() fav_time = fields.DatetimeField()
downloaded = fields.BooleanField(default=False) downloaded = fields.BooleanField(default=False)
created_at = fields.DatetimeField(auto_now_add=True)
updated_at = fields.DatetimeField(auto_now=True)
class Meta: class Meta:
unique_together = (("bvid", "favorite_list_id"),) unique_together = (("bvid", "favorite_list_id"),)
@@ -103,10 +106,10 @@ class FavoriteItem(Model):
async def init_model() -> None: async def init_model() -> None:
await Tortoise.init(config=TORTOISE_ORM) await Tortoise.init(config=TORTOISE_ORM)
process = await create_subprocess_exec( migrate_commands = (
"poetry", [MIGRATE_COMMAND, "upgrade"]
"run", if os.getenv("BILI_IN_DOCKER")
MIGRATE_COMMAND, else ["poetry", "run", MIGRATE_COMMAND, "upgrade"]
"upgrade",
) )
process = await create_subprocess_exec(*migrate_commands)
await process.communicate() await process.communicate()
+71 -65
View File
@@ -10,7 +10,7 @@ from bilibili_api import HEADERS, favorite_list, video
from loguru import logger from loguru import logger
from tortoise import Tortoise from tortoise import Tortoise
from constants import DEFAULT_THUMB_PATH, FFMPEG_COMMAND, MediaType from constants import FFMPEG_COMMAND, MediaType
from credential import credential 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
@@ -126,7 +126,6 @@ async def process_favorite(favorite_id: int) -> None:
id=favorite_id, defaults={"name": favorite_video_list["info"]["title"]} id=favorite_id, defaults={"name": favorite_video_list["info"]["title"]}
) )
fav_list.video_list_path.mkdir(parents=True, exist_ok=True) fav_list.video_list_path.mkdir(parents=True, exist_ok=True)
DEFAULT_THUMB_PATH.mkdir(parents=True, exist_ok=True)
page = 0 page = 0
while True: while True:
page += 1 page += 1
@@ -169,70 +168,77 @@ async def process_video(fav_item: FavoriteItem) -> None:
if fav_item.type != MediaType.VIDEO: if fav_item.type != MediaType.VIDEO:
logger.warning("Media {} is not a video, skipped.", fav_item.name) logger.warning("Media {} is not a video, skipped.", fav_item.name)
return 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 fav_item.downloaded = True
await fav_item.save() await fav_item.save()
logger.info( logger.info(
"{} {} already exists, skipped.", fav_item.bvid, fav_item.name "{} {} processed successfully.", fav_item.bvid, fav_item.name
) )
return except Exception:
# 写入 up 主头像 logger.exception("Failed to process video {}", fav_item.name)
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)