This commit is contained in:
Julian Freeman
2026-04-19 10:26:07 -04:00
parent e86bc86793
commit bcadf36b71
15 changed files with 1236 additions and 411 deletions

View File

@@ -20,6 +20,27 @@ const QJS_REPO_URL: &str = "https://bellard.org/quickjs/binary_releases";
const FFMPEG_GITHUB_API: &str = "https://api.github.com/repos/BtbN/FFmpeg-Builds/releases/latest";
const FFMPEG_EVERMEET_BASE: &str = "https://evermeet.cx/ffmpeg";
#[derive(serde::Serialize, Clone, Debug)]
pub struct RuntimeStatus {
pub ffmpeg_source: String,
pub ffmpeg_version: String,
pub js_runtime_name: String,
pub js_runtime_source: String,
}
#[derive(Clone, Debug)]
pub enum FfmpegLocation {
System,
Managed(PathBuf),
}
#[derive(Clone, Debug)]
pub enum JsRuntime {
Deno,
Node,
ManagedQuickJs(PathBuf),
}
pub fn get_ytdlp_binary_name() -> &'static str {
if cfg!(target_os = "windows") {
"yt-dlp.exe"
@@ -79,10 +100,7 @@ pub fn get_ffmpeg_path(app: &AppHandle) -> Result<PathBuf> {
}
pub fn check_binaries(app: &AppHandle) -> bool {
let ytdlp = get_ytdlp_path(app).map(|p| p.exists()).unwrap_or(false);
let qjs = get_qjs_path(app).map(|p| p.exists()).unwrap_or(false);
let ffmpeg = get_ffmpeg_path(app).map(|p| p.exists()).unwrap_or(false);
ytdlp && qjs && ffmpeg
get_ytdlp_path(app).map(|p| p.exists()).unwrap_or(false)
}
// --- yt-dlp Logic ---
@@ -436,6 +454,10 @@ pub async fn update_ffmpeg(app: &AppHandle) -> Result<String> {
}
pub fn get_ffmpeg_version(app: &AppHandle) -> Result<String> {
if let Some(version) = run_version_command("ffmpeg", "-version") {
return Ok(version);
}
let path = get_ffmpeg_path(app)?;
if !path.exists() {
return Ok("未安装".to_string());
@@ -505,37 +527,138 @@ pub async fn ensure_binaries(app: &AppHandle) -> Result<()> {
}
}
let qjs = get_qjs_path(app)?;
if !qjs.exists() {
download_qjs(app).await?;
} else {
#[cfg(target_os = "macos")]
{
std::process::Command::new("xattr")
.arg("-d")
.arg("com.apple.quarantine")
.arg(&qjs)
.output()
.ok();
Ok(())
}
fn run_version_command(command: &str, arg: &str) -> Option<String> {
let mut cmd = std::process::Command::new(command);
cmd.arg(arg);
#[cfg(target_os = "windows")]
cmd.creation_flags(0x08000000);
cmd.output()
.ok()
.filter(|output| output.status.success())
.and_then(|output| first_non_empty_line(&output))
}
pub fn resolve_ffmpeg(app: &AppHandle, allow_download: bool) -> Result<Option<FfmpegLocation>> {
if run_version_command("ffmpeg", "-version").is_some() {
return Ok(Some(FfmpegLocation::System));
}
let managed = get_ffmpeg_path(app)?;
if managed.exists() {
return Ok(Some(FfmpegLocation::Managed(managed)));
}
if allow_download {
return Ok(Some(FfmpegLocation::Managed(managed)));
}
Ok(None)
}
pub async fn ensure_ffmpeg_available(app: &AppHandle) -> Result<Option<FfmpegLocation>> {
if let Some(location) = resolve_ffmpeg(app, false)? {
return Ok(Some(location));
}
let path = download_ffmpeg(app).await?;
Ok(Some(FfmpegLocation::Managed(path)))
}
pub fn resolve_js_runtime(app: &AppHandle, allow_download: bool) -> Result<Option<JsRuntime>> {
if run_version_command("deno", "--version").is_some() {
return Ok(Some(JsRuntime::Deno));
}
if run_version_command("node", "--version").is_some() {
return Ok(Some(JsRuntime::Node));
}
let managed = get_qjs_path(app)?;
if managed.exists() {
return Ok(Some(JsRuntime::ManagedQuickJs(managed)));
}
if allow_download {
return Ok(Some(JsRuntime::ManagedQuickJs(managed)));
}
Ok(None)
}
pub async fn ensure_js_runtime_available(app: &AppHandle) -> Result<Option<JsRuntime>> {
if let Some(runtime) = resolve_js_runtime(app, false)? {
return Ok(Some(runtime));
}
let path = download_qjs(app).await?;
Ok(Some(JsRuntime::ManagedQuickJs(path)))
}
impl FfmpegLocation {
pub fn source_label(&self) -> &'static str {
match self {
FfmpegLocation::System => "system",
FfmpegLocation::Managed(_) => "managed",
}
}
let ffmpeg = get_ffmpeg_path(app)?;
if !ffmpeg.exists() {
download_ffmpeg(app).await?;
} else {
#[cfg(target_os = "macos")]
{
std::process::Command::new("xattr")
.arg("-d")
.arg("com.apple.quarantine")
.arg(&ffmpeg)
.output()
.ok();
pub fn version(&self, app: &AppHandle) -> Result<String> {
match self {
FfmpegLocation::System => Ok(run_version_command("ffmpeg", "-version").unwrap_or_else(|| "未知".to_string())),
FfmpegLocation::Managed(_) => get_ffmpeg_version(app),
}
}
Ok(())
}
impl JsRuntime {
pub fn source_label(&self) -> &'static str {
match self {
JsRuntime::Deno | JsRuntime::Node => "system",
JsRuntime::ManagedQuickJs(_) => "managed",
}
}
pub fn display_name(&self, app: &AppHandle) -> Result<String> {
match self {
JsRuntime::Deno => Ok(run_version_command("deno", "--version").unwrap_or_else(|| "deno".to_string())),
JsRuntime::Node => Ok(run_version_command("node", "--version").unwrap_or_else(|| "node".to_string())),
JsRuntime::ManagedQuickJs(_) => get_qjs_version(app),
}
}
pub fn yt_dlp_argument(&self) -> String {
match self {
JsRuntime::Deno => "deno".to_string(),
JsRuntime::Node => "node".to_string(),
JsRuntime::ManagedQuickJs(path) => format!("quickjs:{}", path.to_string_lossy()),
}
}
}
pub async fn get_runtime_status(app: &AppHandle) -> Result<RuntimeStatus> {
let ffmpeg = resolve_ffmpeg(app, false)?;
let js_runtime = resolve_js_runtime(app, false)?;
let (ffmpeg_source, ffmpeg_version) = match ffmpeg {
Some(location) => (location.source_label().to_string(), location.version(app)?),
None => ("unavailable".to_string(), "未安装".to_string()),
};
let (js_runtime_name, js_runtime_source) = match js_runtime {
Some(runtime) => (runtime.display_name(app)?, runtime.source_label().to_string()),
None => ("未安装".to_string(), "unavailable".to_string()),
};
Ok(RuntimeStatus {
ffmpeg_source,
ffmpeg_version,
js_runtime_name,
js_runtime_source,
})
}
#[cfg(test)]