mirror of
https://github.com/amtoaer/bili-sync.git
synced 2026-09-04 23:18:02 +08:00
feat: 下载视频出错时打印日志,支持 docker 构建
This commit is contained in:
+22
@@ -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
@@ -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"
|
||||||
|
|
||||||
|
|||||||
+2
@@ -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")
|
||||||
@@ -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
@@ -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)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user