Files
yaoayanui/src-tauri/src/cover_python.rs
949036910@qq.com f9bccba872 111
2026-06-02 22:48:18 +08:00

136 lines
4.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 封面 Python 运行时定位bundled `resources/pythonruntime/python.exe`
//! 封面脚本由服务端下发,不在此目录打包。
use std::collections::HashMap;
use std::path::{Path, PathBuf};
fn exe_name() -> &'static str {
if cfg!(windows) {
"python.exe"
} else {
"python3"
}
}
fn pythonruntime_candidates() -> Vec<PathBuf> {
let mut out = Vec::new();
// 开发源码树优先target/debug/resources 可能是残缺副本(缺 PIL 等)
out.push(
PathBuf::from("src-tauri/resources/pythonruntime").join(exe_name()),
);
out.push(PathBuf::from("resources/pythonruntime").join(exe_name()));
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
out.push(
dir.join("resources")
.join("pythonruntime")
.join(exe_name()),
);
}
}
out
}
/// `python.exe` 所在目录即 PYTHONHOME含 Lib/site-packages
pub fn python_home(python_exe: &Path) -> PathBuf {
python_exe
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| PathBuf::from("."))
}
fn site_packages_dir(python_exe: &Path) -> PathBuf {
python_home(python_exe).join("Lib").join("site-packages")
}
fn runtime_has_pillow(python_exe: &Path) -> bool {
let site = site_packages_dir(python_exe);
site.join("PIL").is_dir() || site.join("Pillow").is_dir()
}
/// bundled `resources/pythonruntime/python.exe` 或 `AICLIENT_PYTHON_PATH`。
pub fn locate_python() -> Option<PathBuf> {
if let Ok(p) = std::env::var("AICLIENT_PYTHON_PATH") {
let pb = PathBuf::from(&p);
if pb.is_file() {
return Some(pb);
}
}
for path in pythonruntime_candidates() {
if path.is_file() && runtime_has_pillow(&path) {
return Some(path);
}
}
for path in pythonruntime_candidates() {
if path.is_file() {
return Some(path);
}
}
None
}
/// 为 bundled Python 子进程设置 PYTHONHOME / PYTHONPATH否则找不到 PIL/cv2
pub fn apply_python_runtime_env(
env: &mut HashMap<String, String>,
python_exe: &Path,
bundle_dir: Option<&Path>,
) {
let home = python_home(python_exe);
env.insert(
"PYTHONHOME".into(),
home.to_string_lossy().into_owned(),
);
let site = site_packages_dir(python_exe);
let sep = if cfg!(windows) { ";" } else { ":" };
let mut paths = vec![site.to_string_lossy().into_owned()];
if let Some(dir) = bundle_dir {
paths.push(dir.to_string_lossy().into_owned());
}
env.insert("PYTHONPATH".into(), paths.join(sep));
env.remove("PYTHONUSERBASE");
env.insert("PYTHONNOUSERSITE".into(), "1".into());
env.insert(
"AICLIENT_PYTHON_HOME".into(),
home.to_string_lossy().into_owned(),
);
}
/// 开发期本地脚本目录(发布构建由服务端拉取,不依赖此路径)。
pub fn locate_cover_scripts_dir() -> Option<PathBuf> {
if let Ok(p) = std::env::var("AICLIENT_COVER_SCRIPTS_DIR") {
let pb = PathBuf::from(&p);
if pb.join("advanced_cover_generator.py").is_file() {
return Some(pb);
}
}
let backend = PathBuf::from("../pythonbackend/scripts/cover");
if backend.join("advanced_cover_generator.py").is_file() {
return backend.canonicalize().ok();
}
None
}
/// 将 ffmpeg 所在目录 prepend 到 PATH供 Python 子进程调用 ffmpeg
pub fn prepend_ffmpeg_to_path(env_path: &str) -> String {
let sep = if cfg!(windows) { ";" } else { ":" };
if let Some(ffmpeg) = crate::ffmpeg::locate_ffmpeg() {
if let Some(dir) = ffmpeg.parent() {
return format!("{}{}{}", dir.display(), sep, env_path);
}
}
env_path.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pythonruntime_dev_path() {
let dev = PathBuf::from("src-tauri/resources/pythonruntime/python.exe");
if dev.is_file() {
assert!(locate_python().is_some());
}
}
}