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
@@ -14,7 +14,7 @@ use diesel::sql_query;
|
||||
use diesel::sql_types::{BigInt, Text};
|
||||
use diesel::sqlite::SqliteConnection;
|
||||
|
||||
use super::xmp::test_png::{producer_xmp, write_png};
|
||||
use super::xmp::test_png::{mp4_bytes, producer_xmp, write_png};
|
||||
use super::*;
|
||||
use crate::data::Claims;
|
||||
use crate::database::test::in_memory_db_connection;
|
||||
@@ -782,6 +782,49 @@ async fn display_choice_set_from_a_version_path_cleared_and_validated() {
|
||||
assert_eq!(awtest::call_service(&app, req).await.status(), 200);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn an_animated_clip_links_hides_and_is_listed_apart_from_versions() {
|
||||
let fx = fx();
|
||||
let original = fx.put("2014/IMG_1.JPG", b"photo bytes");
|
||||
let rel = "2014/aiedits/IMG_1.JPG.animated.mp4";
|
||||
let clip = fx.put(
|
||||
rel,
|
||||
&mp4_bytes(Some(&producer_xmp(&original, "animated", &[])), 3),
|
||||
);
|
||||
let files = vec![(fx.root.join(rel), rel.to_string())];
|
||||
assert_eq!(
|
||||
link_new_files(&fx.library, &files, &fx.exif_arc(), &fx.state.version_dao),
|
||||
1
|
||||
);
|
||||
let app = photos_app!(fx);
|
||||
|
||||
// Hidden behind the photo, like a version, and not counted as one.
|
||||
let (_, body) = get_json!(app, "/photos?path=&recursive=true&sort=nameasc");
|
||||
assert_eq!(body["photos"], serde_json::json!(["2014/IMG_1.JPG"]));
|
||||
assert!(body.get("photo_version_counts").is_none(), "{body}");
|
||||
|
||||
// Listed under `clips`, from the photo's path or the clip's own.
|
||||
for path in ["2014/IMG_1.JPG", rel] {
|
||||
let (status, body) = get_json!(app, &format!("/image/versions?path={path}"));
|
||||
assert_eq!(status, 200);
|
||||
assert_eq!(body["versions"], serde_json::json!([]));
|
||||
assert_eq!(body["clips"][0]["hash"], clip);
|
||||
assert_eq!(body["clips"][0]["kind"], "animated");
|
||||
assert_eq!(body["clips"][0]["path"], rel);
|
||||
}
|
||||
|
||||
// Never the photo's display version.
|
||||
let req = awtest::TestRequest::put()
|
||||
.uri("/image/versions/display")
|
||||
.insert_header(("Authorization", format!("Bearer {}", Fx::token())))
|
||||
.set_json(serde_json::json!({"path": "2014/IMG_1.JPG", "display_hash": clip}))
|
||||
.to_request();
|
||||
let resp = awtest::call_service(&app, req).await;
|
||||
assert_eq!(resp.status(), 400);
|
||||
let body: serde_json::Value = awtest::read_body_json(resp).await;
|
||||
assert_eq!(body["code"], "clip_not_displayable");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn deleting_the_displayed_version_falls_back_to_the_original() {
|
||||
let (fx, _o, v1, _v2) = seeded();
|
||||
|
||||
Reference in new issue
Block a user