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
Dockerfile Normal file
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" ]

View File

@@ -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"

View File

@@ -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")

View File

@@ -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()

View File

@@ -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)