Derived clips: link MP4 versions, list them apart, video Enhance output
Groundwork for Enhance "Animate" (#208): a video made from a photo. - Version linking reads the XMP link from MP4 / MOV too (top-level uuid box with the XMP UUID, mdat seeked over), at ingest and on Enhance approve. - New kind `animated`, a derived clip (CLIP_KINDS): hidden behind the photo like a version, but not counted in photo_version_counts / version_count, never a display choice (400 clip_not_displayable), and listed as `clips` in GET /image/versions. Approve returns the clip entry. - Templates whose imageapi:save is ImageApiSaveVideoVersion report output "video" in /enhance/templates (no enhance.toml change). Previews already relay ComfyUI's content type (video/mp4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
31c1b14d61
commit
deb3d051ac
8 files changed
+308
-31
No files matched your search
@@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::data::Claims;
|
||||
use crate::database::ExifDao;
|
||||
use crate::database::version_dao::VersionDao;
|
||||
use crate::database::version_dao::{VersionDao, is_clip_kind};
|
||||
use crate::libraries;
|
||||
use crate::state::AppState;
|
||||
use crate::utils::normalize_path;
|
||||
@@ -66,6 +66,9 @@ pub struct VersionsResponse {
|
||||
/// standalone photos).
|
||||
pub original: Option<FileRef>,
|
||||
pub versions: Vec<VersionEntry>,
|
||||
/// Derived clips: videos made from the photo (kind `animated`). Same
|
||||
/// shape as `versions`, but never a display choice.
|
||||
pub clips: Vec<VersionEntry>,
|
||||
/// The globally displayed version; `null` = the original.
|
||||
pub display_hash: Option<String>,
|
||||
}
|
||||
@@ -137,6 +140,7 @@ pub fn build_response(
|
||||
|
||||
let files = dao.list_versions(original_hash)?;
|
||||
let mut versions = Vec::with_capacity(files.len());
|
||||
let mut clips = Vec::new();
|
||||
for f in files {
|
||||
let reference_hashes = dao.list_references(&f.version_hash)?;
|
||||
let located = dao.files_for_hashes(&reference_hashes, Some(f.library_id))?;
|
||||
@@ -151,7 +155,12 @@ pub fn build_response(
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
versions.push(VersionEntry {
|
||||
let list = if is_clip_kind(&f.kind) {
|
||||
&mut clips
|
||||
} else {
|
||||
&mut versions
|
||||
};
|
||||
list.push(VersionEntry {
|
||||
path: f.rel_path,
|
||||
library_id: f.library_id,
|
||||
hash: f.version_hash,
|
||||
@@ -177,6 +186,7 @@ pub fn build_response(
|
||||
Ok(VersionsResponse {
|
||||
original,
|
||||
versions,
|
||||
clips,
|
||||
display_hash,
|
||||
})
|
||||
}
|
||||
@@ -241,6 +251,13 @@ pub async fn put_display(
|
||||
return ApiError::internal("internal error").into_response();
|
||||
}
|
||||
};
|
||||
if current.clips.iter().any(|c| c.hash == hash) {
|
||||
return ApiError::bad_request(
|
||||
"clip_not_displayable",
|
||||
"A clip made from this photo can't be shown in its place",
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if !current.versions.iter().any(|v| v.hash == hash) {
|
||||
return ApiError::bad_request(
|
||||
"not_a_version",
|
||||
|
||||
Reference in new issue
Block a user