mirror of
https://github.com/amtoaer/bili-sync.git
synced 2026-05-11 18:11:05 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4db12b154 | ||
|
|
2ef99a20c9 | ||
|
|
67de151234 | ||
|
|
73f97f937f | ||
|
|
8fee6fb97a | ||
|
|
e5e5b07978 | ||
|
|
cd2bd9cbb3 | ||
|
|
f044b18337 | ||
|
|
d3bfca42f6 | ||
|
|
10ccb47790 |
2
.github/workflows/check.yaml
vendored
2
.github/workflows/check.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- run: rustup toolchain install nightly && rustup default nightly && rustup component add rustfmt clippy
|
- run: rustup default nightly-2024-04-30 && rustup component add rustfmt clippy
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: swatinem/rust-cache@v2
|
uses: swatinem/rust-cache@v2
|
||||||
|
|||||||
70
Cargo.lock
generated
70
Cargo.lock
generated
@@ -427,7 +427,6 @@ dependencies = [
|
|||||||
"cookie 0.18.1",
|
"cookie 0.18.1",
|
||||||
"dirs",
|
"dirs",
|
||||||
"entity",
|
"entity",
|
||||||
"env_logger",
|
|
||||||
"filenamify",
|
"filenamify",
|
||||||
"float-ord",
|
"float-ord",
|
||||||
"futures",
|
"futures",
|
||||||
@@ -450,6 +449,8 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -862,29 +863,6 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "env_filter"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"regex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "env_logger"
|
|
||||||
version = "0.11.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
|
|
||||||
dependencies = [
|
|
||||||
"anstream",
|
|
||||||
"anstyle",
|
|
||||||
"env_filter",
|
|
||||||
"humantime",
|
|
||||||
"log",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -1366,12 +1344,6 @@ version = "1.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "humantime"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -1700,6 +1672,16 @@ dependencies = [
|
|||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-ansi-term"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||||
|
dependencies = [
|
||||||
|
"overload",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
@@ -1828,6 +1810,12 @@ dependencies = [
|
|||||||
"syn 2.0.58",
|
"syn 2.0.58",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "overload"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking"
|
name = "parking"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
@@ -3446,6 +3434,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3454,13 +3454,17 @@ version = "0.3.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"matchers",
|
"matchers",
|
||||||
|
"nu-ansi-term",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex",
|
"regex",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3552,6 +3556,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "value-bag"
|
name = "value-bag"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ chrono = { version = "0.4.35", features = ["serde"] }
|
|||||||
cookie = "0.18.0"
|
cookie = "0.18.0"
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
entity = { path = "entity" }
|
entity = { path = "entity" }
|
||||||
env_logger = "0.11.3"
|
|
||||||
filenamify = "0.1.0"
|
filenamify = "0.1.0"
|
||||||
float-ord = "0.3.2"
|
float-ord = "0.3.2"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
@@ -49,6 +48,8 @@ strum = { version = "0.26", features = ["derive"] }
|
|||||||
thiserror = "1.0.58"
|
thiserror = "1.0.58"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
toml = "0.8.12"
|
toml = "0.8.12"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["chrono"] }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [".", "entity", "migration"]
|
members = [".", "entity", "migration"]
|
||||||
|
|||||||
@@ -4,15 +4,12 @@ ARG TARGETPLATFORM
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY ./*-bili-sync-rs ./targets/
|
|
||||||
|
|
||||||
RUN apk update && apk add --no-cache \
|
RUN apk update && apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
ffmpeg \
|
ffmpeg
|
||||||
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
|
||||||
&& echo "Asia/Shanghai" > /etc/timezone \
|
COPY ./*-bili-sync-rs ./targets/
|
||||||
&& apk del tzdata
|
|
||||||
|
|
||||||
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
||||||
mv ./targets/Linux-x86_64-bili-sync-rs ./bili-sync-rs; \
|
mv ./targets/Linux-x86_64-bili-sync-rs ./bili-sync-rs; \
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
network_mode: bridge
|
network_mode: bridge
|
||||||
tty: true # 该选项请仅在日志终端支持彩色输出时启用,否则日志中可能会出现乱码
|
tty: true # 该选项请仅在日志终端支持彩色输出时启用,否则日志中可能会出现乱码
|
||||||
|
# user: 1000:1000 # 非必需设置项,说明见下
|
||||||
hostname: bili-sync-rs
|
hostname: bili-sync-rs
|
||||||
container_name: bili-sync-rs
|
container_name: bili-sync-rs
|
||||||
volumes:
|
volumes:
|
||||||
@@ -153,6 +154,9 @@ services:
|
|||||||
logging:
|
logging:
|
||||||
driver: "local"
|
driver: "local"
|
||||||
```
|
```
|
||||||
|
### user 的设置
|
||||||
|
- 可设置为宿主机适当用户的 uid 及 gid (`$uid:$gid`),使项目下载的文件的所有者与该处设置的用户保持一致,不设置默认为 root
|
||||||
|
- 执行 `id ${user}` 以获得 `user` 用户的 uid 及 gid ,了解更多可参阅 [Docker文档](https://docs.docker.com/engine/reference/run/#user)
|
||||||
|
|
||||||
## 路线图
|
## 路线图
|
||||||
|
|
||||||
@@ -177,4 +181,4 @@ services:
|
|||||||
|
|
||||||
+ [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect) B 站的第三方接口文档
|
+ [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect) B 站的第三方接口文档
|
||||||
+ [bilibili-api](https://github.com/Nemo2011/bilibili-api) 使用 Python 调用接口的参考实现
|
+ [bilibili-api](https://github.com/Nemo2011/bilibili-api) 使用 Python 调用接口的参考实现
|
||||||
+ [danmu2ass](https://github.com/gwy15/danmu2ass) 本项目弹幕下载功能的缝合来源
|
+ [danmu2ass](https://github.com/gwy15/danmu2ass) 本项目弹幕下载功能的缝合来源
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub struct Model {
|
|||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub video_id: i32,
|
pub video_id: i32,
|
||||||
pub cid: i32,
|
pub cid: i64,
|
||||||
pub pid: i32,
|
pub pid: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub width: Option<u32>,
|
pub width: Option<u32>,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use reqwest::{header, Method};
|
use reqwest::{header, Method};
|
||||||
|
|
||||||
use crate::bilibili::Credential;
|
use crate::bilibili::Credential;
|
||||||
@@ -29,7 +29,7 @@ impl Client {
|
|||||||
.default_headers(headers)
|
.default_headers(headers)
|
||||||
.gzip(true)
|
.gzip(true)
|
||||||
.connect_timeout(std::time::Duration::from_secs(10))
|
.connect_timeout(std::time::Duration::from_secs(10))
|
||||||
.read_timeout(std::time::Duration::from_secs(30))
|
.read_timeout(std::time::Duration::from_secs(10))
|
||||||
.build()
|
.build()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
@@ -85,4 +85,13 @@ impl BiliClient {
|
|||||||
CONFIG.credential.store(Some(Arc::new(new_credential)));
|
CONFIG.credential.store(Some(Arc::new(new_credential)));
|
||||||
CONFIG.save()
|
CONFIG.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 检查凭据是否已设置且有效
|
||||||
|
pub async fn is_login(&self) -> Result<()> {
|
||||||
|
let credential = CONFIG.credential.load();
|
||||||
|
let Some(credential) = credential.as_deref() else {
|
||||||
|
bail!("no credential found");
|
||||||
|
};
|
||||||
|
credential.is_login(&self.client).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,24 @@ impl Credential {
|
|||||||
res["data"]["refresh"].as_bool().ok_or(anyhow!("check refresh failed"))
|
res["data"]["refresh"].as_bool().ok_or(anyhow!("check refresh failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 需要使用一个需要鉴权的接口来检查是否登录
|
||||||
|
/// 此处使用查看用户状态数的接口,该接口返回内容少,请求成本低
|
||||||
|
pub async fn is_login(&self, client: &Client) -> Result<()> {
|
||||||
|
client
|
||||||
|
.request(
|
||||||
|
Method::GET,
|
||||||
|
"https://api.bilibili.com/x/web-interface/nav/stat",
|
||||||
|
Some(self),
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.json::<serde_json::Value>()
|
||||||
|
.await?
|
||||||
|
.validate()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn refresh(&self, client: &Client) -> Result<Self> {
|
pub async fn refresh(&self, client: &Client) -> Result<Self> {
|
||||||
let correspond_path = Self::get_correspond_path();
|
let correspond_path = Self::get_correspond_path();
|
||||||
let csrf = self.get_refresh_csrf(client, correspond_path).await?;
|
let csrf = self.get_refresh_csrf(client, correspond_path).await?;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ impl serde::Serialize for Tag {
|
|||||||
}
|
}
|
||||||
#[derive(Debug, serde::Deserialize, Default)]
|
#[derive(Debug, serde::Deserialize, Default)]
|
||||||
pub struct PageInfo {
|
pub struct PageInfo {
|
||||||
pub cid: i32,
|
pub cid: i64,
|
||||||
pub page: i32,
|
pub page: i32,
|
||||||
#[serde(rename = "part")]
|
#[serde(rename = "part")]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -92,7 +92,7 @@ impl<'a> Video<'a> {
|
|||||||
pub async fn get_danmaku_writer(&self, page: &'a PageInfo) -> Result<DanmakuWriter> {
|
pub async fn get_danmaku_writer(&self, page: &'a PageInfo) -> Result<DanmakuWriter> {
|
||||||
let tasks = FuturesUnordered::new();
|
let tasks = FuturesUnordered::new();
|
||||||
for i in 1..=(page.duration + 359) / 360 {
|
for i in 1..=(page.duration + 359) / 360 {
|
||||||
tasks.push(self.get_danmaku_segment(page, i as i32));
|
tasks.push(self.get_danmaku_segment(page, i as i64));
|
||||||
}
|
}
|
||||||
let result: Vec<Vec<DanmakuElem>> = tasks.try_collect().await?;
|
let result: Vec<Vec<DanmakuElem>> = tasks.try_collect().await?;
|
||||||
let mut result: Vec<DanmakuElem> = result.into_iter().flatten().collect();
|
let mut result: Vec<DanmakuElem> = result.into_iter().flatten().collect();
|
||||||
@@ -100,7 +100,7 @@ impl<'a> Video<'a> {
|
|||||||
Ok(DanmakuWriter::new(page, result.into_iter().map(|x| x.into()).collect()))
|
Ok(DanmakuWriter::new(page, result.into_iter().map(|x| x.into()).collect()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_danmaku_segment(&self, page: &PageInfo, segment_idx: i32) -> Result<Vec<DanmakuElem>> {
|
async fn get_danmaku_segment(&self, page: &PageInfo, segment_idx: i64) -> Result<Vec<DanmakuElem>> {
|
||||||
let mut res = self
|
let mut res = self
|
||||||
.client
|
.client
|
||||||
.request(Method::GET, "http://api.bilibili.com/x/v2/dm/web/seg.so")
|
.request(Method::GET, "http://api.bilibili.com/x/v2/dm/web/seg.so")
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub static CONFIG: Lazy<Config> = Lazy::new(|| {
|
|||||||
panic!("加载配置文件失败,错误为: {err}");
|
panic!("加载配置文件失败,错误为: {err}");
|
||||||
}
|
}
|
||||||
warn!("配置文件不存在,使用默认配置...");
|
warn!("配置文件不存在,使用默认配置...");
|
||||||
Config::new()
|
Config::default()
|
||||||
});
|
});
|
||||||
// 放到外面,确保新的配置项被保存
|
// 放到外面,确保新的配置项被保存
|
||||||
info!("配置加载完毕,覆盖刷新原有配置");
|
info!("配置加载完毕,覆盖刷新原有配置");
|
||||||
@@ -44,16 +44,20 @@ pub struct Config {
|
|||||||
pub page_name: Cow<'static, str>,
|
pub page_name: Cow<'static, str>,
|
||||||
pub interval: u64,
|
pub interval: u64,
|
||||||
pub upper_path: PathBuf,
|
pub upper_path: PathBuf,
|
||||||
|
#[serde(default)]
|
||||||
|
pub nfo_time_type: NFOTimeType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum NFOTimeType {
|
||||||
|
#[default]
|
||||||
|
FavTime,
|
||||||
|
PubTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
credential: ArcSwapOption::from(Some(Arc::new(Credential::default()))),
|
credential: ArcSwapOption::from(Some(Arc::new(Credential::default()))),
|
||||||
filter_option: FilterOption::default(),
|
filter_option: FilterOption::default(),
|
||||||
@@ -63,9 +67,12 @@ impl Config {
|
|||||||
page_name: Cow::Borrowed("{{bvid}}"),
|
page_name: Cow::Borrowed("{{bvid}}"),
|
||||||
interval: 1200,
|
interval: 1200,
|
||||||
upper_path: CONFIG_DIR.join("upper_face"),
|
upper_path: CONFIG_DIR.join("upper_face"),
|
||||||
|
nfo_time_type: NFOTimeType::FavTime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
/// 简单的预检查
|
/// 简单的预检查
|
||||||
pub fn check(&self) {
|
pub fn check(&self) {
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
|||||||
@@ -157,8 +157,8 @@ pub async fn download_unprocessed_videos(
|
|||||||
favorite_model.f_id, favorite_model.name
|
favorite_model.f_id, favorite_model.name
|
||||||
);
|
);
|
||||||
let unhandled_videos_pages = unhandled_videos_pages(&favorite_model, connection).await?;
|
let unhandled_videos_pages = unhandled_videos_pages(&favorite_model, connection).await?;
|
||||||
// 对于视频,允许五个同时下载(视频内还有分页、不同分页还有多种下载任务)
|
// 对于视频,允许三个同时下载(视频内还有分页、不同分页还有多种下载任务)
|
||||||
let semaphore = Semaphore::new(5);
|
let semaphore = Semaphore::new(3);
|
||||||
let downloader = Downloader::new(bili_client.client.clone());
|
let downloader = Downloader::new(bili_client.client.clone());
|
||||||
let mut uppers_mutex: HashMap<i64, (Mutex<()>, Mutex<()>)> = HashMap::new();
|
let mut uppers_mutex: HashMap<i64, (Mutex<()>, Mutex<()>)> = HashMap::new();
|
||||||
for (video_model, _) in &unhandled_videos_pages {
|
for (video_model, _) in &unhandled_videos_pages {
|
||||||
@@ -312,8 +312,8 @@ pub async fn dispatch_download_page(
|
|||||||
if !should_run {
|
if !should_run {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// 对于视频的分页,允许同时下载三个同时下载(绝大部分是单页视频)
|
// 对于视频的分页,允许两个同时下载(绝大部分是单页视频)
|
||||||
let child_semaphore = Semaphore::new(5);
|
let child_semaphore = Semaphore::new(2);
|
||||||
let mut tasks = pages
|
let mut tasks = pages
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|page_model| download_page(bili_client, video_model, page_model, &child_semaphore, downloader))
|
.map(|page_model| download_page(bili_client, video_model, page_model, &child_semaphore, downloader))
|
||||||
@@ -657,7 +657,11 @@ async fn generate_nfo(serializer: NFOSerializer<'_>, nfo_path: PathBuf) -> Resul
|
|||||||
if let Some(parent) = nfo_path.parent() {
|
if let Some(parent) = nfo_path.parent() {
|
||||||
fs::create_dir_all(parent).await?;
|
fs::create_dir_all(parent).await?;
|
||||||
}
|
}
|
||||||
fs::write(nfo_path, serializer.generate_nfo().await?.as_bytes()).await?;
|
fs::write(
|
||||||
|
nfo_path,
|
||||||
|
serializer.generate_nfo(&CONFIG.nfo_time_type).await?.as_bytes(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use serde_json::json;
|
|||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
use crate::bilibili::{FavoriteListInfo, PageInfo, VideoInfo};
|
use crate::bilibili::{FavoriteListInfo, PageInfo, VideoInfo};
|
||||||
use crate::config::CONFIG;
|
use crate::config::{NFOTimeType, CONFIG};
|
||||||
use crate::core::status::Status;
|
use crate::core::status::Status;
|
||||||
|
|
||||||
pub static TEMPLATE: Lazy<handlebars::Handlebars> = Lazy::new(|| {
|
pub static TEMPLATE: Lazy<handlebars::Handlebars> = Lazy::new(|| {
|
||||||
@@ -274,7 +274,7 @@ pub async fn update_pages_model(pages: Vec<page::ActiveModel>, connection: &Data
|
|||||||
/// serde xml 似乎不太好用,先这么裸着写
|
/// serde xml 似乎不太好用,先这么裸着写
|
||||||
/// (真是又臭又长啊
|
/// (真是又臭又长啊
|
||||||
impl<'a> NFOSerializer<'a> {
|
impl<'a> NFOSerializer<'a> {
|
||||||
pub async fn generate_nfo(self) -> Result<String> {
|
pub async fn generate_nfo(self, nfo_time_type: &NFOTimeType) -> Result<String> {
|
||||||
let mut buffer = r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
let mut buffer = r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
"#
|
"#
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
@@ -283,6 +283,10 @@ impl<'a> NFOSerializer<'a> {
|
|||||||
let mut writer = Writer::new_with_indent(&mut tokio_buffer, b' ', 4);
|
let mut writer = Writer::new_with_indent(&mut tokio_buffer, b' ', 4);
|
||||||
match self {
|
match self {
|
||||||
NFOSerializer(ModelWrapper::Video(v), NFOMode::MOVIE) => {
|
NFOSerializer(ModelWrapper::Video(v), NFOMode::MOVIE) => {
|
||||||
|
let nfo_time = match nfo_time_type {
|
||||||
|
NFOTimeType::FavTime => v.favtime,
|
||||||
|
NFOTimeType::PubTime => v.pubtime,
|
||||||
|
};
|
||||||
writer
|
writer
|
||||||
.create_element("movie")
|
.create_element("movie")
|
||||||
.write_inner_content_async::<_, _, Error>(|writer| async move {
|
.write_inner_content_async::<_, _, Error>(|writer| async move {
|
||||||
@@ -316,7 +320,7 @@ impl<'a> NFOSerializer<'a> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
writer
|
writer
|
||||||
.create_element("year")
|
.create_element("year")
|
||||||
.write_text_content_async(BytesText::new(&v.favtime.format("%Y").to_string()))
|
.write_text_content_async(BytesText::new(&nfo_time.format("%Y").to_string()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if let Some(tags) = &v.tags {
|
if let Some(tags) = &v.tags {
|
||||||
@@ -337,7 +341,7 @@ impl<'a> NFOSerializer<'a> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
writer
|
writer
|
||||||
.create_element("aired")
|
.create_element("aired")
|
||||||
.write_text_content_async(BytesText::new(&v.favtime.format("%Y-%m-%d").to_string()))
|
.write_text_content_async(BytesText::new(&nfo_time.format("%Y-%m-%d").to_string()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Ok(writer)
|
Ok(writer)
|
||||||
@@ -346,6 +350,10 @@ impl<'a> NFOSerializer<'a> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
NFOSerializer(ModelWrapper::Video(v), NFOMode::TVSHOW) => {
|
NFOSerializer(ModelWrapper::Video(v), NFOMode::TVSHOW) => {
|
||||||
|
let nfo_time = match nfo_time_type {
|
||||||
|
NFOTimeType::FavTime => v.favtime,
|
||||||
|
NFOTimeType::PubTime => v.pubtime,
|
||||||
|
};
|
||||||
writer
|
writer
|
||||||
.create_element("tvshow")
|
.create_element("tvshow")
|
||||||
.write_inner_content_async::<_, _, Error>(|writer| async move {
|
.write_inner_content_async::<_, _, Error>(|writer| async move {
|
||||||
@@ -379,7 +387,7 @@ impl<'a> NFOSerializer<'a> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
writer
|
writer
|
||||||
.create_element("year")
|
.create_element("year")
|
||||||
.write_text_content_async(BytesText::new(&v.favtime.format("%Y").to_string()))
|
.write_text_content_async(BytesText::new(&nfo_time.format("%Y").to_string()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if let Some(tags) = &v.tags {
|
if let Some(tags) = &v.tags {
|
||||||
@@ -400,7 +408,7 @@ impl<'a> NFOSerializer<'a> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
writer
|
writer
|
||||||
.create_element("aired")
|
.create_element("aired")
|
||||||
.write_text_content_async(BytesText::new(&v.favtime.format("%Y-%m-%d").to_string()))
|
.write_text_content_async(BytesText::new(&nfo_time.format("%Y-%m-%d").to_string()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Ok(writer)
|
Ok(writer)
|
||||||
@@ -490,8 +498,8 @@ mod tests {
|
|||||||
chrono::NaiveTime::from_hms_opt(2, 2, 2).unwrap(),
|
chrono::NaiveTime::from_hms_opt(2, 2, 2).unwrap(),
|
||||||
),
|
),
|
||||||
pubtime: chrono::NaiveDateTime::new(
|
pubtime: chrono::NaiveDateTime::new(
|
||||||
chrono::NaiveDate::from_ymd_opt(2022, 2, 2).unwrap(),
|
chrono::NaiveDate::from_ymd_opt(2033, 3, 3).unwrap(),
|
||||||
chrono::NaiveTime::from_hms_opt(2, 2, 2).unwrap(),
|
chrono::NaiveTime::from_hms_opt(3, 3, 3).unwrap(),
|
||||||
),
|
),
|
||||||
bvid: "bvid".to_string(),
|
bvid: "bvid".to_string(),
|
||||||
tags: Some(serde_json::json!(["tag1", "tag2"])),
|
tags: Some(serde_json::json!(["tag1", "tag2"])),
|
||||||
@@ -499,7 +507,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NFOSerializer(ModelWrapper::Video(&video), NFOMode::MOVIE)
|
NFOSerializer(ModelWrapper::Video(&video), NFOMode::MOVIE)
|
||||||
.generate_nfo()
|
.generate_nfo(&NFOTimeType::PubTime)
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
@@ -511,16 +519,16 @@ mod tests {
|
|||||||
<name>1</name>
|
<name>1</name>
|
||||||
<role>upper_name</role>
|
<role>upper_name</role>
|
||||||
</actor>
|
</actor>
|
||||||
<year>2022</year>
|
<year>2033</year>
|
||||||
<genre>tag1</genre>
|
<genre>tag1</genre>
|
||||||
<genre>tag2</genre>
|
<genre>tag2</genre>
|
||||||
<uniqueid type="bilibili">bvid</uniqueid>
|
<uniqueid type="bilibili">bvid</uniqueid>
|
||||||
<aired>2022-02-02</aired>
|
<aired>2033-03-03</aired>
|
||||||
</movie>"#,
|
</movie>"#,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NFOSerializer(ModelWrapper::Video(&video), NFOMode::TVSHOW)
|
NFOSerializer(ModelWrapper::Video(&video), NFOMode::TVSHOW)
|
||||||
.generate_nfo()
|
.generate_nfo(&NFOTimeType::FavTime)
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
@@ -541,7 +549,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NFOSerializer(ModelWrapper::Video(&video), NFOMode::UPPER)
|
NFOSerializer(ModelWrapper::Video(&video), NFOMode::UPPER)
|
||||||
.generate_nfo()
|
.generate_nfo(&NFOTimeType::FavTime)
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
@@ -549,7 +557,7 @@ mod tests {
|
|||||||
<plot/>
|
<plot/>
|
||||||
<outline/>
|
<outline/>
|
||||||
<lockdata>false</lockdata>
|
<lockdata>false</lockdata>
|
||||||
<dateadded>2022-02-02 02:02:02</dateadded>
|
<dateadded>2033-03-03 03:03:03</dateadded>
|
||||||
<title>1</title>
|
<title>1</title>
|
||||||
<sorttitle>1</sorttitle>
|
<sorttitle>1</sorttitle>
|
||||||
</person>"#,
|
</person>"#,
|
||||||
@@ -561,7 +569,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NFOSerializer(ModelWrapper::Page(&page), NFOMode::EPOSODE)
|
NFOSerializer(ModelWrapper::Page(&page), NFOMode::EPOSODE)
|
||||||
.generate_nfo()
|
.generate_nfo(&NFOTimeType::FavTime)
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ impl Downloader {
|
|||||||
audio_path.to_str().unwrap(),
|
audio_path.to_str().unwrap(),
|
||||||
"-c",
|
"-c",
|
||||||
"copy",
|
"copy",
|
||||||
|
"-y",
|
||||||
output_path.to_str().unwrap(),
|
output_path.to_str().unwrap(),
|
||||||
])
|
])
|
||||||
.output()
|
.output()
|
||||||
|
|||||||
26
src/main.rs
26
src/main.rs
@@ -1,5 +1,5 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate tracing;
|
||||||
|
|
||||||
mod bilibili;
|
mod bilibili;
|
||||||
mod config;
|
mod config;
|
||||||
@@ -8,8 +8,11 @@ mod database;
|
|||||||
mod downloader;
|
mod downloader;
|
||||||
mod error;
|
mod error;
|
||||||
|
|
||||||
use env_logger::Env;
|
use std::time::Duration;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use tokio::time;
|
||||||
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
|
||||||
use crate::bilibili::BiliClient;
|
use crate::bilibili::BiliClient;
|
||||||
use crate::config::CONFIG;
|
use crate::config::CONFIG;
|
||||||
@@ -18,7 +21,15 @@ use crate::database::{database_connection, migrate_database};
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> ! {
|
async fn main() -> ! {
|
||||||
env_logger::init_from_env(Env::default().default_filter_or("None,bili_sync=info"));
|
let default_log_level = std::env::var("RUST_LOG").unwrap_or("None,bili_sync=info".to_owned());
|
||||||
|
tracing_subscriber::fmt::Subscriber::builder()
|
||||||
|
.with_env_filter(tracing_subscriber::EnvFilter::builder().parse_lossy(default_log_level))
|
||||||
|
.with_timer(tracing_subscriber::fmt::time::ChronoLocal::new(
|
||||||
|
"%Y-%m-%d %H:%M:%S%.3f".to_owned(),
|
||||||
|
))
|
||||||
|
.finish()
|
||||||
|
.try_init()
|
||||||
|
.expect("初始化日志失败");
|
||||||
Lazy::force(&SCAN_ONLY);
|
Lazy::force(&SCAN_ONLY);
|
||||||
Lazy::force(&CONFIG);
|
Lazy::force(&CONFIG);
|
||||||
let mut anchor = chrono::Local::now().date_naive();
|
let mut anchor = chrono::Local::now().date_naive();
|
||||||
@@ -26,10 +37,15 @@ async fn main() -> ! {
|
|||||||
let connection = database_connection().await.unwrap();
|
let connection = database_connection().await.unwrap();
|
||||||
migrate_database(&connection).await.unwrap();
|
migrate_database(&connection).await.unwrap();
|
||||||
loop {
|
loop {
|
||||||
|
if let Err(e) = bili_client.is_login().await {
|
||||||
|
error!("检查登录状态时遇到错误:{e},等待下一轮执行");
|
||||||
|
time::sleep(Duration::from_secs(CONFIG.interval)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if anchor != chrono::Local::now().date_naive() {
|
if anchor != chrono::Local::now().date_naive() {
|
||||||
if let Err(e) = bili_client.check_refresh().await {
|
if let Err(e) = bili_client.check_refresh().await {
|
||||||
error!("检查刷新 Credential 遇到错误:{e},等待下一轮执行");
|
error!("检查刷新 Credential 遇到错误:{e},等待下一轮执行");
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(CONFIG.interval)).await;
|
time::sleep(Duration::from_secs(CONFIG.interval)).await;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
anchor = chrono::Local::now().date_naive();
|
anchor = chrono::Local::now().date_naive();
|
||||||
@@ -41,6 +57,6 @@ async fn main() -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("所有收藏夹处理完毕,等待下一轮执行");
|
info!("所有收藏夹处理完毕,等待下一轮执行");
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(CONFIG.interval)).await;
|
time::sleep(Duration::from_secs(CONFIG.interval)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user