summaryrefslogtreecommitdiff
path: root/rust/crates/timeline/src/scenes.rs
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-07-28 19:41:16 +0200
committercashmere <cashmere@autistici.org>2026-07-28 19:41:16 +0200
commitf5a82152394fde4d8f740f664ab7737b82861618 (patch)
tree5a51b181abead1ce03d2dbfc12b77918d3be0e1b /rust/crates/timeline/src/scenes.rs
parente837e7504379e100ff3b56282577ec67bb88bec9 (diff)
downloadopencut-f5a82152394fde4d8f740f664ab7737b82861618.tar.gz
feat: GPUI desktop port foundation
- scene crate: full project/scene/element model, serde round-trip proven against the web SerializedProject shape - transfer crate: .ocp zip container, cross-compat with web fflate archives proven in both directions - storage crate: filesystem project store replacing IndexedDB, .ocp import/export - media crate: ffmpeg-next 8 decoder (frames, audio, thumbnails, waveform summaries) - playback crate: wall-clock anchored, frame-rounded playback clock - gpu crate: native texture readback; compositor renders headless offscreen on Vulkan (spike test) - flake devshell: rust toolchain, GPUI native deps, ffmpeg dev, bindgen clang args; GPUI hello-world builds - port plan in notes/gpui-port.md
Diffstat (limited to 'rust/crates/timeline/src/scenes.rs')
-rw-r--r--rust/crates/timeline/src/scenes.rs120
1 files changed, 120 insertions, 0 deletions
diff --git a/rust/crates/timeline/src/scenes.rs b/rust/crates/timeline/src/scenes.rs
new file mode 100644
index 00000000..8a7a5c4f
--- /dev/null
+++ b/rust/crates/timeline/src/scenes.rs
@@ -0,0 +1,120 @@
+use chrono::{DateTime, Utc};
+use scene::{Scene, SceneTracks};
+use time::MediaTime;
+
+use crate::placement::MAIN_TRACK_NAME;
+use crate::placement::track_factory::build_empty_track;
+use crate::types::TrackType;
+
+pub fn get_main_scene(scenes: &[Scene]) -> Option<&Scene> {
+ scenes.iter().find(|scene| scene.is_main)
+}
+
+pub fn ensure_main_scene(scenes: &[Scene], next_id: &mut dyn FnMut() -> String, now: DateTime<Utc>) -> Vec<Scene> {
+ if scenes.iter().any(|scene| scene.is_main) {
+ return scenes.to_vec();
+ }
+
+ let main_scene = build_default_scene(
+ next_id(),
+ next_id(),
+ "Main scene".to_string(),
+ true,
+ now,
+ );
+ let mut next = Vec::with_capacity(scenes.len() + 1);
+ next.push(main_scene);
+ next.extend(scenes.iter().cloned());
+ next
+}
+
+pub fn build_default_scene(
+ id: impl Into<String>,
+ main_track_id: impl Into<String>,
+ name: String,
+ is_main: bool,
+ now: DateTime<Utc>,
+) -> Scene {
+ Scene {
+ id: id.into(),
+ name,
+ is_main,
+ tracks: SceneTracks {
+ overlay: Vec::new(),
+ main: build_empty_track(
+ main_track_id,
+ TrackType::Video,
+ Some(MAIN_TRACK_NAME.to_string()),
+ ),
+ audio: Vec::new(),
+ },
+ bookmarks: Vec::new(),
+ created_at: now,
+ updated_at: now,
+ }
+}
+
+pub struct SceneDeleteCheck {
+ pub can_delete: bool,
+ pub reason: Option<String>,
+}
+
+pub fn can_delete_scene(scene: &Scene) -> SceneDeleteCheck {
+ if scene.is_main {
+ return SceneDeleteCheck {
+ can_delete: false,
+ reason: Some("Cannot delete main scene".to_string()),
+ };
+ }
+ SceneDeleteCheck {
+ can_delete: true,
+ reason: None,
+ }
+}
+
+pub fn get_fallback_scene_after_delete<'a>(
+ scenes: &'a [Scene],
+ deleted_scene_id: &str,
+ current_scene_id: Option<&str>,
+) -> Option<&'a Scene> {
+ if current_scene_id != Some(deleted_scene_id) {
+ return scenes.iter().find(|scene| Some(scene.id.as_str()) == current_scene_id);
+ }
+ get_main_scene(scenes)
+}
+
+pub fn find_current_scene<'a>(
+ scenes: &'a [Scene],
+ current_scene_id: &str,
+) -> Option<&'a Scene> {
+ scenes
+ .iter()
+ .find(|scene| scene.id == current_scene_id)
+ .or_else(|| get_main_scene(scenes))
+ .or_else(|| scenes.first())
+}
+
+pub fn get_project_duration_from_scenes(scenes: &[Scene]) -> MediaTime {
+ let Some(main_scene) = get_main_scene(scenes).or_else(|| scenes.first()) else {
+ return MediaTime::ZERO;
+ };
+
+ crate::calculate_total_duration(&main_scene.tracks)
+}
+
+pub fn update_scene_in_array(
+ scenes: &[Scene],
+ scene_id: &str,
+ update: impl Fn(&Scene) -> Scene,
+) -> Vec<Scene> {
+ scenes
+ .iter()
+ .map(|scene| {
+ if scene.id == scene_id {
+ update(scene)
+ } else {
+ scene.clone()
+ }
+ })
+ .collect()
+}