fix: resolve preview clip rel_path against all libraries
PreviewClipGenerator stripped a single base_path, so videos in a
non-primary library ended up with the absolute path as 'relative'.
On Windows, PathBuf::from(preview_clips_dir).join(absolute) replaces
with the absolute path, and .with_extension("mp4") on a .mp4 input
yields the input path — ffmpeg then errors out with 'cannot edit
existing files in place'.
The generator now holds Vec<Library> and strips whichever root
actually contains the video, with separator normalization to match
the rest of the code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use crate::database::PreviewDao;
|
||||
use crate::is_video;
|
||||
use crate::libraries::Library;
|
||||
use crate::otel::global_tracer;
|
||||
use crate::video::ffmpeg::generate_preview_clip;
|
||||
use actix::prelude::*;
|
||||
@@ -500,23 +501,40 @@ pub struct GeneratePreviewClipMessage {
|
||||
pub struct PreviewClipGenerator {
|
||||
semaphore: Arc<Semaphore>,
|
||||
preview_clips_dir: String,
|
||||
base_path: String,
|
||||
libraries: Vec<Library>,
|
||||
preview_dao: Arc<Mutex<Box<dyn PreviewDao>>>,
|
||||
}
|
||||
|
||||
impl PreviewClipGenerator {
|
||||
pub fn new(
|
||||
preview_clips_dir: String,
|
||||
base_path: String,
|
||||
libraries: Vec<Library>,
|
||||
preview_dao: Arc<Mutex<Box<dyn PreviewDao>>>,
|
||||
) -> Self {
|
||||
PreviewClipGenerator {
|
||||
semaphore: Arc::new(Semaphore::new(2)),
|
||||
preview_clips_dir,
|
||||
base_path,
|
||||
libraries,
|
||||
preview_dao,
|
||||
}
|
||||
}
|
||||
|
||||
/// Strip whichever library root actually contains `video_path`.
|
||||
/// Falls back to the first library if none match, so we never
|
||||
/// accidentally emit the absolute input path as the output path
|
||||
/// (which ffmpeg rejects as "cannot edit existing files in place").
|
||||
fn relativize(&self, video_path: &str) -> String {
|
||||
for lib in &self.libraries {
|
||||
if let Some(stripped) = video_path.strip_prefix(&lib.root_path) {
|
||||
return stripped
|
||||
.trim_start_matches(['/', '\\'])
|
||||
.replace('\\', "/");
|
||||
}
|
||||
}
|
||||
video_path
|
||||
.trim_start_matches(['/', '\\'])
|
||||
.replace('\\', "/")
|
||||
}
|
||||
}
|
||||
|
||||
impl Actor for PreviewClipGenerator {
|
||||
@@ -533,9 +551,10 @@ impl Handler<GeneratePreviewClipMessage> for PreviewClipGenerator {
|
||||
) -> Self::Result {
|
||||
let semaphore = self.semaphore.clone();
|
||||
let preview_clips_dir = self.preview_clips_dir.clone();
|
||||
let base_path = self.base_path.clone();
|
||||
let preview_dao = self.preview_dao.clone();
|
||||
let video_path = msg.video_path;
|
||||
// Resolve against whichever library actually owns this video.
|
||||
let relative_path = self.relativize(&video_path);
|
||||
|
||||
Box::pin(async move {
|
||||
let permit = semaphore
|
||||
@@ -543,13 +562,6 @@ impl Handler<GeneratePreviewClipMessage> for PreviewClipGenerator {
|
||||
.await
|
||||
.expect("Unable to acquire preview semaphore");
|
||||
|
||||
// Compute relative path (from BASE_PATH) for DB operations, consistent with EXIF convention
|
||||
let relative_path = video_path
|
||||
.strip_prefix(&base_path)
|
||||
.unwrap_or(&video_path)
|
||||
.trim_start_matches(['/', '\\'])
|
||||
.to_string();
|
||||
|
||||
// Update status to processing
|
||||
{
|
||||
let otel_ctx = opentelemetry::Context::current();
|
||||
|
||||
Reference in New Issue
Block a user