v0.1.1: fix .app bundle PATH for pyenv/nvm, detect yt-dlp pot-provider plugin
- fix_path_env() now adds ~/.pyenv/shims (with PYENV_ROOT) before Homebrew paths so pyenv-managed yt-dlp (with pip-installed plugins) takes priority over Homebrew's bare yt-dlp binary - Also handles ~/.nvm, ~/.deno/bin, ~/.cargo/bin for broader coverage - check_pot_plugin uses lightweight 'yt-dlp -v' instead of network simulation for reliable plugin detection - Bump version to 0.1.1 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,11 +2,89 @@ mod commands;
|
||||
mod models;
|
||||
mod services;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use commands::{dependencies, export, media_analysis, video};
|
||||
use tauri::Manager;
|
||||
|
||||
struct MediaServerPort(u16);
|
||||
|
||||
/// Prepend well-known macOS tool directories to PATH so that Homebrew-installed
|
||||
/// binaries (ffmpeg, yt-dlp, etc.) are discoverable when the app is launched as
|
||||
/// a standalone .app bundle (which inherits only a minimal system PATH).
|
||||
///
|
||||
/// Also sets PYENV_ROOT when pyenv is present so that pyenv shims can resolve
|
||||
/// to the correct Python (and its pip-installed yt-dlp plugins).
|
||||
fn fix_path_env() {
|
||||
let home = std::env::var("HOME").unwrap_or_default();
|
||||
|
||||
let mut extra_dirs: Vec<PathBuf> = Vec::new();
|
||||
|
||||
if !home.is_empty() {
|
||||
let home_path = PathBuf::from(&home);
|
||||
|
||||
// pyenv shims — must come before /opt/homebrew/bin so the pyenv-managed
|
||||
// yt-dlp (which knows about pip-installed plugins) wins over Homebrew's.
|
||||
let pyenv_root = home_path.join(".pyenv");
|
||||
if pyenv_root.is_dir() {
|
||||
extra_dirs.push(pyenv_root.join("shims"));
|
||||
if std::env::var("PYENV_ROOT").is_err() {
|
||||
std::env::set_var("PYENV_ROOT", &pyenv_root);
|
||||
}
|
||||
}
|
||||
|
||||
// nvm — node/deno installed via nvm live under a versioned directory
|
||||
let nvm_dir = home_path.join(".nvm/versions/node");
|
||||
if nvm_dir.is_dir() {
|
||||
if let Ok(entries) = std::fs::read_dir(&nvm_dir) {
|
||||
// Pick the lexicographically last (highest) version
|
||||
let mut versions: Vec<PathBuf> = entries
|
||||
.filter_map(|e| e.ok())
|
||||
.map(|e| e.path().join("bin"))
|
||||
.filter(|p| p.is_dir())
|
||||
.collect();
|
||||
versions.sort();
|
||||
if let Some(latest) = versions.pop() {
|
||||
extra_dirs.push(latest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User-local installs
|
||||
for rel in [".local/bin", ".cargo/bin", ".deno/bin"] {
|
||||
extra_dirs.push(home_path.join(rel));
|
||||
}
|
||||
}
|
||||
|
||||
// System-wide Homebrew / MacPorts paths
|
||||
for dir in [
|
||||
"/opt/homebrew/bin",
|
||||
"/opt/homebrew/sbin",
|
||||
"/usr/local/bin",
|
||||
"/usr/local/sbin",
|
||||
] {
|
||||
extra_dirs.push(PathBuf::from(dir));
|
||||
}
|
||||
|
||||
// Filter to directories that actually exist
|
||||
let extra_dirs: Vec<PathBuf> = extra_dirs.into_iter().filter(|p| p.is_dir()).collect();
|
||||
|
||||
let current = std::env::var("PATH").unwrap_or_default();
|
||||
let current_dirs: HashSet<&str> = current.split(':').collect();
|
||||
|
||||
let mut new_path = String::new();
|
||||
for dir in &extra_dirs {
|
||||
let s = dir.to_string_lossy();
|
||||
if !current_dirs.contains(s.as_ref()) {
|
||||
new_path.push_str(&s);
|
||||
new_path.push(':');
|
||||
}
|
||||
}
|
||||
new_path.push_str(¤t);
|
||||
std::env::set_var("PATH", &new_path);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_media_server_port(state: tauri::State<'_, MediaServerPort>) -> u16 {
|
||||
state.0
|
||||
@@ -20,6 +98,8 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.setup(|app| {
|
||||
fix_path_env();
|
||||
|
||||
// Bind synchronously (no async needed), then spawn on Tauri's runtime
|
||||
let (port, listener) = services::media_server::bind_media_server()
|
||||
.expect("Failed to bind media server");
|
||||
|
||||
@@ -54,17 +54,11 @@ pub fn check_js_runtime() -> Option<String> {
|
||||
check_tool_exists("deno").or_else(|| check_tool_exists("node"))
|
||||
}
|
||||
|
||||
pub fn check_pot_plugin(cookie_source: &CookieSource) -> bool {
|
||||
let mut command = Command::new("yt-dlp");
|
||||
command
|
||||
.arg("-v")
|
||||
.arg("--simulate")
|
||||
.arg("--remote-components")
|
||||
.arg("ejs:github")
|
||||
.args(cookie_source.to_ytdlp_args())
|
||||
.arg("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
|
||||
let output = command.output();
|
||||
|
||||
pub fn check_pot_plugin(_cookie_source: &CookieSource) -> bool {
|
||||
// Use `yt-dlp -v` (no URL) which lists loaded plugins without network access.
|
||||
// The old approach ran a full YouTube simulation (~9s, network-dependent) which
|
||||
// failed silently in the .app bundle context.
|
||||
let output = Command::new("yt-dlp").arg("-v").output();
|
||||
match output {
|
||||
Ok(out) => {
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
|
||||
Reference in New Issue
Block a user