Phase 3.5b: persist image assets in the .beam container
Image asset bytes are now stored as MediaKind::ImageAsset rows in the SQLite container (chunked, kept-in-place on re-save) instead of base64-embedded in the project JSON — the pageable storage Phase 4 needs. - ImageAsset.data is `#[serde(default, skip_serializing)]`: never written to JSON, but still deserialized for old projects (base64) which then migrate to the container on the next save. - save_beam writes each asset's bytes (keyed by asset id; ext from the source path), keeping an existing row when bytes aren't resident; live_media covers them so orphan cleanup doesn't drop them. - load_beam_sqlite eager-reads the bytes back into `data` (Phase 4 makes this lazy + LRU). Old base64 projects keep their JSON-deserialized data (no container row).
This commit is contained in:
parent
aad2d5c515
commit
2f3b0d7790
|
|
@ -263,9 +263,11 @@ pub struct ImageAsset {
|
|||
/// Image height in pixels
|
||||
pub height: u32,
|
||||
|
||||
/// Embedded image data (for project portability)
|
||||
/// If None, the image will be loaded from path when needed
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// Raw image file bytes. NOT serialized to project JSON — persisted as a
|
||||
/// `MediaKind::ImageAsset` row in the `.beam` container (chunked, pageable) and
|
||||
/// read back on load. `default` so new projects (bytes in the container, not JSON)
|
||||
/// deserialize; old projects with base64-embedded `data` still load via deserialize.
|
||||
#[serde(default, skip_serializing)]
|
||||
pub data: Option<Vec<u8>>,
|
||||
|
||||
/// Folder this asset belongs to (None = root of category)
|
||||
|
|
|
|||
|
|
@ -462,6 +462,32 @@ pub fn save_beam(
|
|||
}
|
||||
}
|
||||
|
||||
// --- image assets -> media rows (original file bytes), keyed by asset id ---
|
||||
let mut image_count = 0usize;
|
||||
for (id, asset) in &document.image_assets {
|
||||
if let Some(ref data) = asset.data {
|
||||
let ext = asset
|
||||
.path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("img")
|
||||
.to_lowercase();
|
||||
txn.put_media_packed(
|
||||
*id,
|
||||
MediaKind::ImageAsset,
|
||||
&ext,
|
||||
data,
|
||||
MediaMeta { width: Some(asset.width), height: Some(asset.height), ..Default::default() },
|
||||
)?;
|
||||
live_media.insert(*id);
|
||||
image_count += 1;
|
||||
} else if txn.media_exists(*id)? {
|
||||
// Bytes not resident (paged out) but already stored — keep the row.
|
||||
live_media.insert(*id);
|
||||
}
|
||||
}
|
||||
let _ = image_count;
|
||||
|
||||
// --- orphan cleanup: drop media for removed clips/keyframes ---
|
||||
let removed = txn.retain_media(&live_media)?;
|
||||
|
||||
|
|
@ -625,6 +651,19 @@ fn load_beam_sqlite(path: &Path) -> Result<LoadedProject, String> {
|
|||
}
|
||||
let _ = proxy_load_count;
|
||||
|
||||
// Eager-read image-asset bytes from the container into `data` (Phase 4 will make
|
||||
// this lazy + LRU). Old projects keep their base64-deserialized `data` and have no
|
||||
// container row — those are skipped (`data` already Some).
|
||||
for (id, asset) in document.image_assets.iter_mut() {
|
||||
if asset.data.is_none() {
|
||||
if let Ok(Some(_)) = archive.media_info(*id) {
|
||||
if let Ok(bytes) = archive.read_media_full(*id) {
|
||||
asset.data = Some(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Missing external files (referenced entries whose file no longer exists).
|
||||
let project_dir = path.parent().unwrap_or_else(|| Path::new("."));
|
||||
let missing_files: Vec<MissingFileInfo> = restored_entries
|
||||
|
|
|
|||
Loading…
Reference in New Issue