Skip to content

Commit 8912782

Browse files
committed
refactor: restructure into Cargo workspace + bug fixes
- Restructure into workspace: shared (lib) + recorder (bin) - P0: fix unwrap panic on non-UTF-8 paths in converter - P0: fix division by zero when segment_duration=0 - P0: quote ExecStart paths in systemd service - P1: cleanup partial MP4 on failed conversion - P1: ensure ffmpeg tmp dir cleanup on extraction failure - P1: log unexpected background task termination - P1: validate GCS credential path - P2: improve error logging in uploader and cleanup - HEVC streams now re-encoded to H.264 for Linux compatibility
1 parent 7ca3117 commit 8912782

14 files changed

Lines changed: 640 additions & 120 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,9 @@ nohup rtsp-recorder --daemon &
243243
│ ├─ H.265 → copy + hvc1 tag
244244
│ └─ MJPEG → re-encode to H.264
245245
246-
└─ 智慧上傳到 GCS(網路閒置時)
247-
└─ 上傳後依 retention.max_hours 清理
246+
├─ 智慧上傳到 GCS(網路閒置時)
247+
248+
└─ 自動清理舊檔(依 retention.max_hours)
248249
```
249250
250251
---

rust/Cargo.lock

Lines changed: 15 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/Cargo.toml

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,15 @@
1-
[package]
2-
name = "rtsp-recorder"
3-
version = "0.1.0"
1+
[workspace]
2+
members = [
3+
"crates/shared",
4+
"crates/recorder",
5+
]
6+
resolver = "2"
7+
8+
[workspace.package]
9+
version = "0.4.0"
410
edition = "2021"
5-
description = "RTSP recorder with GCS upload support"
611
authors = ["Daniel Chen"]
712

8-
[dependencies]
9-
# CLI 參數解析
10-
clap = { version = "4", features = ["derive"] }
11-
12-
# 非同步運行時
13-
tokio = { version = "1", features = ["full"] }
14-
15-
# 設定檔解析
16-
serde = { version = "1", features = ["derive"] }
17-
serde_yaml = "0.9"
18-
serde_json = "1"
19-
20-
# 時間處理
21-
chrono = { version = "0.4", features = ["serde"] }
22-
23-
# 日誌
24-
tracing = "0.1"
25-
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
26-
27-
# GCS 上傳(使用 rustls 避免 OpenSSL 依賴)
28-
cloud-storage = { version = "0.11", default-features = false, features = ["rustls-tls"] }
29-
30-
# 錯誤處理
31-
anyhow = "1"
32-
thiserror = "1"
33-
34-
# 目錄路徑
35-
dirs = "5"
36-
3713
[profile.release]
3814
opt-level = 3
3915
lto = true

rust/crates/recorder/Cargo.toml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
name = "rtsp-recorder"
3+
version = "0.4.0"
4+
edition = "2021"
5+
description = "RTSP recorder with GCS upload support"
6+
authors = ["Daniel Chen"]
7+
8+
[dependencies]
9+
rtsp-shared = { path = "../shared" }
10+
11+
# CLI 參數解析
12+
clap = { version = "4", features = ["derive"] }
13+
14+
# 非同步運行時
15+
tokio = { version = "1", features = ["full"] }
16+
17+
# 設定檔解析
18+
serde_json = "1"
19+
20+
# 時間處理
21+
chrono = "0.4"
22+
23+
# 日誌
24+
tracing = "0.1"
25+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
26+
27+
# 錯誤處理
28+
anyhow = "1"
29+
30+
# 目錄路徑
31+
dirs = "5"
Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@ use std::path::PathBuf;
44
use std::sync::Arc;
55
use tokio::sync::Mutex;
66

7-
mod config;
8-
mod converter;
9-
mod ffmpeg;
7+
use rtsp_shared::{config, ffmpeg, stats, uploader};
8+
109
mod recorder;
1110
mod setup;
12-
mod stats;
13-
mod uploader;
1411

1512
#[derive(Parser)]
1613
#[command(name = "rtsp-recorder")]
@@ -56,21 +53,26 @@ enum Commands {
5653
Check,
5754
/// 互動式初始設定
5855
Setup,
56+
/// 解除安裝(清理服務、錄影、設定)
57+
Uninstall,
5958
}
6059

6160
#[tokio::main]
6261
async fn main() -> Result<()> {
6362
// 初始化日誌
6463
tracing_subscriber::fmt()
65-
.with_env_filter("rtsp_recorder=info")
64+
.with_env_filter("rtsp_recorder=info,rtsp_shared=info")
6665
.init();
6766

6867
let cli = Cli::parse();
6968

70-
// Setup 不需要事先存在 config
69+
// Setup / Uninstall 不需要事先存在 config
7170
if matches!(cli.command, Some(Commands::Setup)) {
7271
return setup::run_setup_wizard(&cli.config).await;
7372
}
73+
if matches!(cli.command, Some(Commands::Uninstall)) {
74+
return setup::run_uninstall(&cli.config).await;
75+
}
7476

7577
// 載入設定(如果 config 不存在,引導使用者執行 setup)
7678
let config = match config::Config::load(&cli.config) {
@@ -88,6 +90,15 @@ async fn main() -> Result<()> {
8890
};
8991
tracing::info!("已載入設定檔: {:?}", cli.config);
9092

93+
// P0: 設定 GCS 憑證環境變數(程式啟動時設定一次,避免多執行緒 data race)
94+
if config.gcs.credentials.exists() {
95+
let cred_path = config.gcs.credentials.to_string_lossy().to_string();
96+
if cred_path.is_empty() {
97+
anyhow::bail!("GCS 憑證路徑無效: {:?}", config.gcs.credentials);
98+
}
99+
std::env::set_var("SERVICE_ACCOUNT", &cred_path);
100+
}
101+
91102
// 載入或建立統計
92103
let stats = Arc::new(Mutex::new(
93104
stats::Stats::load(&config.output.dir).unwrap_or_default()
@@ -119,6 +130,7 @@ async fn main() -> Result<()> {
119130
check_environment(&config, &cli.config).await?;
120131
}
121132
Some(Commands::Setup) => unreachable!(), // 已在上方處理
133+
Some(Commands::Uninstall) => unreachable!(), // 已在上方處理
122134
None => {
123135
if cli.daemon {
124136
let ff = ffmpeg::ensure_ffmpeg().await?;
@@ -267,7 +279,7 @@ async fn cleanup_old_files(
267279
continue;
268280
}
269281

270-
if let Ok(metadata) = path.metadata() {
282+
if let Ok(metadata) = entry.metadata() {
271283
if let Ok(modified) = metadata.modified() {
272284
if modified < threshold {
273285
if let Err(e) = std::fs::remove_file(&path) {

0 commit comments

Comments
 (0)