fix: resolve media across libraries for video, metadata, and insights
The /video/generate and /image/metadata handlers assumed files live under the resolved library only, which broke when a mobile client passed no library (union mode) but the file lived in a non-primary library. Both now fall back to scanning every configured library for an existing file. InsightGenerator held a single base_path, so vision-model loads and filename-date fallbacks failed for non-primary libraries. It now takes Vec<Library> and probes each root in resolve_full_path. /image/metadata responses now carry library_id/library_name so the mobile viewer can surface which library a file belongs to. Thumbnail generation at startup is now spawned on a background thread so the HTTP server can accept traffic while large libraries backfill. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
54
src/main.rs
54
src/main.rs
@@ -319,24 +319,32 @@ async fn get_file_metadata(
|
||||
|
||||
// Fall back to other libraries if the file isn't under the resolved one,
|
||||
// matching the `/image` handler so union-mode search results resolve.
|
||||
let full_path = is_valid_full_path(&library.root_path, &path.path, false)
|
||||
let resolved = is_valid_full_path(&library.root_path, &path.path, false)
|
||||
.filter(|p| p.exists())
|
||||
.map(|p| (library, p))
|
||||
.or_else(|| {
|
||||
app_state.libraries.iter().find_map(|lib| {
|
||||
if lib.id == library.id {
|
||||
return None;
|
||||
}
|
||||
is_valid_full_path(&lib.root_path, &path.path, false).filter(|p| p.exists())
|
||||
is_valid_full_path(&lib.root_path, &path.path, false)
|
||||
.filter(|p| p.exists())
|
||||
.map(|p| (lib, p))
|
||||
})
|
||||
});
|
||||
|
||||
match full_path
|
||||
match resolved
|
||||
.ok_or_else(|| ErrorKind::InvalidData.into())
|
||||
.and_then(File::open)
|
||||
.and_then(|file| file.metadata())
|
||||
.and_then(|(lib, full_path)| {
|
||||
File::open(&full_path)
|
||||
.and_then(|file| file.metadata())
|
||||
.map(|metadata| (lib, metadata))
|
||||
})
|
||||
{
|
||||
Ok(metadata) => {
|
||||
Ok((resolved_library, metadata)) => {
|
||||
let mut response: MetadataResponse = metadata.into();
|
||||
response.library_id = Some(resolved_library.id);
|
||||
response.library_name = Some(resolved_library.name.clone());
|
||||
|
||||
// Extract date from filename if possible
|
||||
response.filename_date =
|
||||
@@ -573,7 +581,28 @@ async fn generate_video(
|
||||
if let Some(name) = filename.file_name() {
|
||||
let filename = name.to_str().expect("Filename should convert to string");
|
||||
let playlist = format!("{}/{}.m3u8", app_state.video_path, filename);
|
||||
if let Some(path) = is_valid_full_path(&app_state.base_path, &body.path, false) {
|
||||
|
||||
let library = libraries::resolve_library_param(&app_state, body.library.as_deref())
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| app_state.primary_library());
|
||||
|
||||
// Try the resolved library first, then fall back to any other library
|
||||
// that actually contains the file — handles union-mode requests where
|
||||
// the mobile client passes no library but the file lives in a
|
||||
// non-primary library.
|
||||
let resolved = is_valid_full_path(&library.root_path, &body.path, false)
|
||||
.filter(|p| p.exists())
|
||||
.or_else(|| {
|
||||
app_state.libraries.iter().find_map(|lib| {
|
||||
if lib.id == library.id {
|
||||
return None;
|
||||
}
|
||||
is_valid_full_path(&lib.root_path, &body.path, false).filter(|p| p.exists())
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(path) = resolved {
|
||||
if let Ok(child) = create_playlist(path.to_str().unwrap(), &playlist).await {
|
||||
span.add_event(
|
||||
"playlist_created".to_string(),
|
||||
@@ -1161,7 +1190,16 @@ fn main() -> std::io::Result<()> {
|
||||
// table; we use that list to drive the initial thumbnail sweep.
|
||||
let app_data = Data::new(AppState::default());
|
||||
|
||||
create_thumbnails(&app_data.libraries);
|
||||
// Kick thumbnail generation onto a background thread so the HTTP
|
||||
// server can accept traffic while large libraries are backfilling.
|
||||
// Existing thumbs are re-used (exists() check inside the walk),
|
||||
// so missed files are filled in over successive scans.
|
||||
{
|
||||
let libs = app_data.libraries.clone();
|
||||
std::thread::spawn(move || {
|
||||
create_thumbnails(&libs);
|
||||
});
|
||||
}
|
||||
// generate_video_gifs().await;
|
||||
|
||||
let labels = HashMap::new();
|
||||
|
||||
Reference in New Issue
Block a user