chore: CRLF to LR

This commit is contained in:
yzqzss 2024-06-15 15:42:49 +08:00
parent 729f0353bf
commit ca0c726d29
9 changed files with 380 additions and 379 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -1,7 +1,7 @@
{
"recommendations": [
"charliermarsh.ruff",
"tamasfe.even-better-toml",
"esbenp.prettier-vscode"
]
}
{
"recommendations": [
"charliermarsh.ruff",
"tamasfe.even-better-toml",
"esbenp.prettier-vscode"
]
}

View File

@ -1,88 +1,88 @@
from io import TextIOWrapper
import click
import os
from biliarchiver.cli_tools.utils import read_bvids
from biliarchiver.i18n import _
DEFAULT_COLLECTION = "opensource_movies"
"""
开放 collection 任何人均可上传
通过 biliarchiver 上传的 item 会在24小时内被自动转移到 bilibili_videos collection
"""
"""
An open collection. Anyone can upload.
Items uploaded by biliarchiver will be automatically moved to bilibili_videos collection within 24 hours.
"""
BILIBILI_VIDEOS_COLLECTION = "bilibili_videos"
""" 由 arkiver 管理。bilibili_videos 属于 social-media-video 的子集 """
""" Managed by arkiver. bilibili_videos is a subset of social-media-video """
BILIBILI_VIDEOS_SUB_1_COLLECTION = "bilibili_videos_sub_1"
""" 由 yzqzss 管理。属于 bilibili_videos 的子集 """
""" Managed by yzqzss. A subset of bilibili_videos """
@click.command(help=click.style(_("上传至互联网档案馆"), fg="cyan"))
@click.option("--bvids", "-i", type=click.STRING, default=None, help=_("bvids 列表的文件路径"))
@click.option(
"--by-storage-home-dir",
"-a",
is_flag=True,
default=False,
help=_("使用 `$storage_home_dir/videos` 目录下的所有视频"),
)
@click.option(
"--update-existing", "-u", is_flag=True, default=False, help=_("更新已存在的 item")
)
@click.option(
"--collection",
"-c",
default=DEFAULT_COLLECTION,
type=click.Choice(
[
DEFAULT_COLLECTION,
BILIBILI_VIDEOS_COLLECTION,
BILIBILI_VIDEOS_SUB_1_COLLECTION,
]
),
help=_("欲上传至的 collection. (非默认值仅限 collection 管理员使用)")
+ f" [default: {DEFAULT_COLLECTION}]",
)
@click.option(
"--delete-after-upload",
"-d",
is_flag=True,
default=False,
help=_("上传后删除视频文件"),
)
def up(
bvids: TextIOWrapper,
by_storage_home_dir: bool,
update_existing: bool,
collection: str,
delete_after_upload: bool,
):
from biliarchiver._biliarchiver_upload_bvid import upload_bvid
from biliarchiver.config import config
ids = []
if by_storage_home_dir:
for bvid_with_upper_part in os.listdir(config.storage_home_dir / "videos"):
bvid = bvid_with_upper_part
if "-" in bvid_with_upper_part:
bvid = bvid_with_upper_part.split("-")[0]
ids.append(bvid)
elif bvids:
ids = read_bvids(bvids)
for id in ids:
upload_bvid(
id,
update_existing=update_existing,
collection=collection,
delete_after_upload=delete_after_upload,
)
from io import TextIOWrapper
import click
import os
from biliarchiver.cli_tools.utils import read_bvids
from biliarchiver.i18n import _
DEFAULT_COLLECTION = "opensource_movies"
"""
开放 collection 任何人均可上传
通过 biliarchiver 上传的 item 会在24小时内被自动转移到 bilibili_videos collection
"""
"""
An open collection. Anyone can upload.
Items uploaded by biliarchiver will be automatically moved to bilibili_videos collection within 24 hours.
"""
BILIBILI_VIDEOS_COLLECTION = "bilibili_videos"
""" 由 arkiver 管理。bilibili_videos 属于 social-media-video 的子集 """
""" Managed by arkiver. bilibili_videos is a subset of social-media-video """
BILIBILI_VIDEOS_SUB_1_COLLECTION = "bilibili_videos_sub_1"
""" 由 yzqzss 管理。属于 bilibili_videos 的子集 """
""" Managed by yzqzss. A subset of bilibili_videos """
@click.command(help=click.style(_("上传至互联网档案馆"), fg="cyan"))
@click.option("--bvids", "-i", type=click.STRING, default=None, help=_("bvids 列表的文件路径"))
@click.option(
"--by-storage-home-dir",
"-a",
is_flag=True,
default=False,
help=_("使用 `$storage_home_dir/videos` 目录下的所有视频"),
)
@click.option(
"--update-existing", "-u", is_flag=True, default=False, help=_("更新已存在的 item")
)
@click.option(
"--collection",
"-c",
default=DEFAULT_COLLECTION,
type=click.Choice(
[
DEFAULT_COLLECTION,
BILIBILI_VIDEOS_COLLECTION,
BILIBILI_VIDEOS_SUB_1_COLLECTION,
]
),
help=_("欲上传至的 collection. (非默认值仅限 collection 管理员使用)")
+ f" [default: {DEFAULT_COLLECTION}]",
)
@click.option(
"--delete-after-upload",
"-d",
is_flag=True,
default=False,
help=_("上传后删除视频文件"),
)
def up(
bvids: TextIOWrapper,
by_storage_home_dir: bool,
update_existing: bool,
collection: str,
delete_after_upload: bool,
):
from biliarchiver._biliarchiver_upload_bvid import upload_bvid
from biliarchiver.config import config
ids = []
if by_storage_home_dir:
for bvid_with_upper_part in os.listdir(config.storage_home_dir / "videos"):
bvid = bvid_with_upper_part
if "-" in bvid_with_upper_part:
bvid = bvid_with_upper_part.split("-")[0]
ids.append(bvid)
elif bvids:
ids = read_bvids(bvids)
for id in ids:
upload_bvid(
id,
update_existing=update_existing,
collection=collection,
delete_after_upload=delete_after_upload,
)

View File

@ -1,23 +1,23 @@
from pathlib import Path
from biliarchiver.utils.identifier import is_bvid
from biliarchiver.i18n import _
def read_bvids(bvids: str) -> list[str]:
bvids_list = None
file = Path(bvids)
if file.exists() and file.is_file():
with open(file, "r", encoding="utf-8") as f:
bvids_list = f.read().split()
else:
bvids_list = bvids.split()
del bvids
for bvid in bvids_list:
assert is_bvid(bvid), _("bvid {} 不合法").format(bvid)
assert bvids_list is not None and len(bvids_list) > 0, _("bvids 为空")
return bvids_list
from pathlib import Path
from biliarchiver.utils.identifier import is_bvid
from biliarchiver.i18n import _
def read_bvids(bvids: str) -> list[str]:
bvids_list = None
file = Path(bvids)
if file.exists() and file.is_file():
with open(file, "r", encoding="utf-8") as f:
bvids_list = f.read().split()
else:
bvids_list = bvids.split()
del bvids
for bvid in bvids_list:
assert is_bvid(bvid), _("bvid {} 不合法").format(bvid)
assert bvids_list is not None and len(bvids_list) > 0, _("bvids 为空")
return bvids_list

View File

@ -1,20 +1,20 @@
import gettext
import locale
from pathlib import Path
import warnings
default_lang, default_enc = locale.getdefaultlocale()
default_lang = default_lang or "en"
languages = ["en"] if not default_lang.lower().startswith("zh") else ["zh_CN"]
appname = "biliarchiver"
localedir = Path(__file__).parent / "locales"
if not localedir.exists():
warnings.warn("Locales directory not found, i18n will not work.", RuntimeWarning)
i18n = gettext.translation(
appname, localedir=localedir, fallback=True, languages=languages
)
_ = i18n.gettext
ngettext = i18n.ngettext
import gettext
import locale
from pathlib import Path
import warnings
default_lang, default_enc = locale.getdefaultlocale()
default_lang = default_lang or "en"
languages = ["en"] if not default_lang.lower().startswith("zh") else ["zh_CN"]
appname = "biliarchiver"
localedir = Path(__file__).parent / "locales"
if not localedir.exists():
warnings.warn("Locales directory not found, i18n will not work.", RuntimeWarning)
i18n = gettext.translation(
appname, localedir=localedir, fallback=True, languages=languages
)
_ = i18n.gettext
ngettext = i18n.ngettext

View File

@ -1 +1 @@
*.mo
*.mo

View File

@ -1,74 +1,74 @@
import asyncio
from enum import Enum
import time
from typing import Optional
class VideoStatus(str, Enum):
pending = "pending"
downloading = "downloading"
uploading = "uploading"
finished = "finished"
failed = "failed"
class BiliVideo:
def __init__(self, bvid: str, status: VideoStatus):
if not bvid.startswith("BV"):
bvid = "BV" + bvid
self.added_time = int(time.time())
self.bvid = bvid
self.status = status
def __str__(self) -> str:
return "\t".join([self.bvid, self.status])
async def down(self):
from asyncio import subprocess
from shlex import quote
cmd = ["biliarchiver", "down" ,"-i", quote(self.bvid), "-s", "--disable-version-check"]
process: Optional[subprocess.Process] = None
try:
process = await asyncio.create_subprocess_exec(*cmd)
retcode = await process.wait()
process = None
except (KeyboardInterrupt, SystemExit, Exception) as e:
if process:
process.terminate()
await process.wait()
print("download terminated:", e)
return -1
else:
return retcode
finally:
if process:
process.terminate()
await process.wait()
print("download terminated: (finally)")
async def up(self) -> int:
from asyncio import subprocess
from shlex import quote
cmd = ["biliarchiver", "up" ,"-i", quote(self.bvid), "-d"]
process: Optional[subprocess.Process] = None
try:
process = await subprocess.create_subprocess_exec(*cmd)
retcode = await process.wait()
process = None
except (KeyboardInterrupt, SystemExit, Exception) as e:
if process:
process.terminate()
await process.wait()
print("upload terminated", e)
return -1
else:
return retcode
finally:
if process:
process.terminate()
await process.wait()
print("upload terminated: (finally)")
import asyncio
from enum import Enum
import time
from typing import Optional
class VideoStatus(str, Enum):
pending = "pending"
downloading = "downloading"
uploading = "uploading"
finished = "finished"
failed = "failed"
class BiliVideo:
def __init__(self, bvid: str, status: VideoStatus):
if not bvid.startswith("BV"):
bvid = "BV" + bvid
self.added_time = int(time.time())
self.bvid = bvid
self.status = status
def __str__(self) -> str:
return "\t".join([self.bvid, self.status])
async def down(self):
from asyncio import subprocess
from shlex import quote
cmd = ["biliarchiver", "down" ,"-i", quote(self.bvid), "-s", "--disable-version-check"]
process: Optional[subprocess.Process] = None
try:
process = await asyncio.create_subprocess_exec(*cmd)
retcode = await process.wait()
process = None
except (KeyboardInterrupt, SystemExit, Exception) as e:
if process:
process.terminate()
await process.wait()
print("download terminated:", e)
return -1
else:
return retcode
finally:
if process:
process.terminate()
await process.wait()
print("download terminated: (finally)")
async def up(self) -> int:
from asyncio import subprocess
from shlex import quote
cmd = ["biliarchiver", "up" ,"-i", quote(self.bvid), "-d"]
process: Optional[subprocess.Process] = None
try:
process = await subprocess.create_subprocess_exec(*cmd)
retcode = await process.wait()
process = None
except (KeyboardInterrupt, SystemExit, Exception) as e:
if process:
process.terminate()
await process.wait()
print("upload terminated", e)
return -1
else:
return retcode
finally:
if process:
process.terminate()
await process.wait()
print("upload terminated: (finally)")

View File

@ -1,165 +1,165 @@
import asyncio
from asyncio import Queue
from contextlib import asynccontextmanager
from datetime import datetime
from typing import List
try:
from fastapi import FastAPI
except ImportError:
print("Please install fastapi")
print("`pip install fastapi`")
raise
from biliarchiver.rest_api.bilivid import BiliVideo, VideoStatus
from biliarchiver.version import BILI_ARCHIVER_VERSION
@asynccontextmanager
async def lifespan(app: FastAPI):
global pending_queue, other_queue
pending_queue = BiliQueue()
other_queue = BiliQueue(maxsize=250)
print("Loading queue...")
load_queue()
print("Queue loaded")
asyncio.create_task(scheduler())
yield
print("Shutting down...")
save_queue()
print("Queue saved")
class BiliQueue(Queue):
def get_all(self) -> List[BiliVideo]:
return list(self._queue) # type: ignore
async def get(self) -> BiliVideo:
return await super().get()
async def remove(self, video: BiliVideo):
try:
self._queue.remove(video) # type: ignore
return True
except ValueError:
return False
def get_nowait(self) -> BiliVideo:
return super().get_nowait()
async def change_status(self, ori_video: BiliVideo, status: VideoStatus):
await self.remove(ori_video)
ori_video.status = status
await self.put(ori_video)
pending_queue: BiliQueue = None # type: ignore
other_queue: BiliQueue = None # type: ignore
app = FastAPI(lifespan=lifespan)
def get_all_items() -> List[BiliVideo]:
l = pending_queue.get_all() + other_queue.get_all()
l.sort(key=lambda x: x.added_time)
return l
@app.get("/")
async def root():
return {
"status": "ok",
"biliarchiver": {"version": BILI_ARCHIVER_VERSION},
"api": {"version": 1},
"timestamp": int(datetime.now().timestamp()),
}
@app.put("/archive/{vid}")
@app.post("/archive/{vid}")
async def add(vid: str):
video = BiliVideo(vid, status=VideoStatus.pending)
await pending_queue.put(video)
return {"success": True, "vid": vid}
@app.get("/archive")
async def get_all():
all_item = get_all_items()
return {"success": True, "total_tasks_queued": pending_queue.qsize(), "items": all_item}
@app.get("/archive/{vid}")
async def get_one(vid: str):
all_item = get_all_items()
if vid in [v.bvid for v in all_item]:
v: BiliVideo = [v for v in all_item if v.bvid == vid][0]
return {"success": True, "vid": vid, "status": v.status, "queue_index": all_item.index(v)}
return {"success": False, "vid": vid, "status": "not_found"} # TODO
@app.delete("/archive/{vid}")
async def delete(vid: str):
queue_list = pending_queue.get_all()
if vid in [v.bvid for v in queue_list]:
v: BiliVideo = [v for v in queue_list if v.bvid == vid][0]
if await pending_queue.remove(v):
return {"success": True, "vid": vid, "result": "removed", "queue_index": queue_list.index(v)}
return {"success": False, "vid": vid, "status": "not_found"}
async def scheduler():
while True:
print("Getting a video URL... If no video URL is printed, the queue is empty.")
video = await pending_queue.get()
print(f"Start downloading {video}")
video.status = VideoStatus.downloading
await other_queue.put(video)
downloaded = False
for _ in range(2):
try:
if retcode := await video.down():
raise Exception(f"Download failed with retcode {retcode}")
downloaded = True
break
except Exception as e:
print(e)
print(f"(down) Retrying {video}...")
await asyncio.sleep(5)
if not downloaded:
await other_queue.change_status(video, VideoStatus.failed)
print(f"Failed to download {video}")
continue
print(f"Start uploading {video}")
await other_queue.change_status(video, VideoStatus.uploading)
uploaded = False
for _ in range(3):
try:
retcode = await video.up()
uploaded = True
if retcode != 0:
raise Exception(f"Upload failed with retcode {retcode}")
break
except Exception as e:
print(e)
print(f"(up) Retrying {video}...")
await asyncio.sleep(10)
if not uploaded:
await other_queue.change_status(video, VideoStatus.failed)
print(f"Failed to upload {video}")
continue
await other_queue.change_status(video, VideoStatus.finished)
print(f"Finished {video}")
def save_queue():
with open("queue.txt", "w") as f:
while not pending_queue.empty():
video = pending_queue.get_nowait()
f.write(str(video) + "\n")
def load_queue():
try:
with open("queue.txt", "r") as f:
while line := f.readline().strip():
pending_queue.put_nowait(BiliVideo(*line.split("\t")))
except FileNotFoundError:
pass
import asyncio
from asyncio import Queue
from contextlib import asynccontextmanager
from datetime import datetime
from typing import List
try:
from fastapi import FastAPI
except ImportError:
print("Please install fastapi")
print("`pip install fastapi`")
raise
from biliarchiver.rest_api.bilivid import BiliVideo, VideoStatus
from biliarchiver.version import BILI_ARCHIVER_VERSION
@asynccontextmanager
async def lifespan(app: FastAPI):
global pending_queue, other_queue
pending_queue = BiliQueue()
other_queue = BiliQueue(maxsize=250)
print("Loading queue...")
load_queue()
print("Queue loaded")
asyncio.create_task(scheduler())
yield
print("Shutting down...")
save_queue()
print("Queue saved")
class BiliQueue(Queue):
def get_all(self) -> List[BiliVideo]:
return list(self._queue) # type: ignore
async def get(self) -> BiliVideo:
return await super().get()
async def remove(self, video: BiliVideo):
try:
self._queue.remove(video) # type: ignore
return True
except ValueError:
return False
def get_nowait(self) -> BiliVideo:
return super().get_nowait()
async def change_status(self, ori_video: BiliVideo, status: VideoStatus):
await self.remove(ori_video)
ori_video.status = status
await self.put(ori_video)
pending_queue: BiliQueue = None # type: ignore
other_queue: BiliQueue = None # type: ignore
app = FastAPI(lifespan=lifespan)
def get_all_items() -> List[BiliVideo]:
l = pending_queue.get_all() + other_queue.get_all()
l.sort(key=lambda x: x.added_time)
return l
@app.get("/")
async def root():
return {
"status": "ok",
"biliarchiver": {"version": BILI_ARCHIVER_VERSION},
"api": {"version": 1},
"timestamp": int(datetime.now().timestamp()),
}
@app.put("/archive/{vid}")
@app.post("/archive/{vid}")
async def add(vid: str):
video = BiliVideo(vid, status=VideoStatus.pending)
await pending_queue.put(video)
return {"success": True, "vid": vid}
@app.get("/archive")
async def get_all():
all_item = get_all_items()
return {"success": True, "total_tasks_queued": pending_queue.qsize(), "items": all_item}
@app.get("/archive/{vid}")
async def get_one(vid: str):
all_item = get_all_items()
if vid in [v.bvid for v in all_item]:
v: BiliVideo = [v for v in all_item if v.bvid == vid][0]
return {"success": True, "vid": vid, "status": v.status, "queue_index": all_item.index(v)}
return {"success": False, "vid": vid, "status": "not_found"} # TODO
@app.delete("/archive/{vid}")
async def delete(vid: str):
queue_list = pending_queue.get_all()
if vid in [v.bvid for v in queue_list]:
v: BiliVideo = [v for v in queue_list if v.bvid == vid][0]
if await pending_queue.remove(v):
return {"success": True, "vid": vid, "result": "removed", "queue_index": queue_list.index(v)}
return {"success": False, "vid": vid, "status": "not_found"}
async def scheduler():
while True:
print("Getting a video URL... If no video URL is printed, the queue is empty.")
video = await pending_queue.get()
print(f"Start downloading {video}")
video.status = VideoStatus.downloading
await other_queue.put(video)
downloaded = False
for _ in range(2):
try:
if retcode := await video.down():
raise Exception(f"Download failed with retcode {retcode}")
downloaded = True
break
except Exception as e:
print(e)
print(f"(down) Retrying {video}...")
await asyncio.sleep(5)
if not downloaded:
await other_queue.change_status(video, VideoStatus.failed)
print(f"Failed to download {video}")
continue
print(f"Start uploading {video}")
await other_queue.change_status(video, VideoStatus.uploading)
uploaded = False
for _ in range(3):
try:
retcode = await video.up()
uploaded = True
if retcode != 0:
raise Exception(f"Upload failed with retcode {retcode}")
break
except Exception as e:
print(e)
print(f"(up) Retrying {video}...")
await asyncio.sleep(10)
if not uploaded:
await other_queue.change_status(video, VideoStatus.failed)
print(f"Failed to upload {video}")
continue
await other_queue.change_status(video, VideoStatus.finished)
print(f"Finished {video}")
def save_queue():
with open("queue.txt", "w") as f:
while not pending_queue.empty():
video = pending_queue.get_nowait()
f.write(str(video) + "\n")
def load_queue():
try:
with open("queue.txt", "r") as f:
while line := f.readline().strip():
pending_queue.put_nowait(BiliVideo(*line.split("\t")))
except FileNotFoundError:
pass

View File

@ -1 +1 @@
BILI_ARCHIVER_VERSION = "0.1.9"
BILI_ARCHIVER_VERSION = "0.1.9"