diff --git a/src/core/downloads.rs b/src/core/downloads.rs index d1cbcad..d3f2dd5 100644 --- a/src/core/downloads.rs +++ b/src/core/downloads.rs @@ -942,3 +942,57 @@ fn aria2_front_status(state: &Arc>) -> Option { let res = aria2_rpc_call("aria2.tellStatus", json!([gid, ["status"]])).ok()?; res.get("status").and_then(|v| v.as_str()).map(|s| s.to_string()) } + + +/// If this item_id has a local download marker, return the local media file path. +/// Marker files are written alongside the media file as: +/// . +/// where DOWNLOAD_MARKER_EXT == "cj-jftui.json" +pub fn local_media_path_for_item_id(item_id: &str) -> Option { + fn walk(dir: &Path, item_id: &str) -> Option { + if !dir.exists() { + return None; + } + + let rd = fs::read_dir(dir).ok()?; + for ent in rd { + let p = ent.ok()?.path(); + + if p.is_dir() { + if let Some(found) = walk(&p, item_id) { + return Some(found); + } + continue; + } + + let name = p.file_name().and_then(|s| s.to_str()).unwrap_or(""); + if !name.ends_with(DOWNLOAD_MARKER_EXT) { + continue; + } + + let raw = fs::read_to_string(&p).ok()?; + let v: Value = serde_json::from_str(raw.trim()).ok()?; + let id = v.get("item_id").and_then(|x| x.as_str())?; + + if id == item_id { + // Marker file is "." + // So the media file path is the same path with that suffix stripped. + let suffix = format!(".{DOWNLOAD_MARKER_EXT}"); + if !name.ends_with(&suffix) { + continue; + } + let media_name = &name[..name.len() - suffix.len()]; + let media_path = p.with_file_name(media_name); + + if media_path.exists() { + return Some(media_path); + } + } + } + + None + } + + let root = downloads_root().ok()?; + walk(&root, item_id) +} diff --git a/src/main.rs b/src/main.rs index abe4565..f6c28c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -694,7 +694,11 @@ fn handle_key(app: &mut App, code: KeyCode) { app.mpv_now_playing = Some(item_name); // If mpv running -> replace, else spawn - let url = build_jellyfin_play_url(&cfg, &item_id); + let url = if let Some(p) = crate::core::downloads::local_media_path_for_item_id(&item_id) { + p.to_string_lossy().to_string() + } else { + build_jellyfin_play_url(&cfg, &item_id) + }; if let Some(mpv) = &app.mpv { match mpv_load_url(&cfg, mpv, &url) { @@ -733,7 +737,11 @@ fn handle_key(app: &mut App, code: KeyCode) { app.autoplay_next_index = if selected + 1 < len { Some(selected + 1) } else { None }; // load/replace or spawn - let url = build_jellyfin_play_url(&cfg, &item_id); + let url = if let Some(p) = crate::core::downloads::local_media_path_for_item_id(&item_id) { + p.to_string_lossy().to_string() + } else { + build_jellyfin_play_url(&cfg, &item_id) + }; if let Some(mpv) = &app.mpv { match mpv_load_url(&cfg, mpv, &url) {