diff --git a/crates/interchange/src/otio.rs b/crates/interchange/src/otio.rs
--- a/crates/interchange/src/otio.rs
+++ b/crates/interchange/src/otio.rs
@@ -11,7 +11,7 @@
 //! state, sequence settings, media info, generators) round-trip through `metadata.filmcraft`.
 //! Nested sequences are `Stack` items inside tracks.
 
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
 
 use filmcraft_media::{Generator, MediaInfo};
 use filmcraft_project::{
@@ -22,8 +22,8 @@ use filmcraft_time::{FrameRate, TICKS_PER_SECOND, Tick};
 use serde_json::{Value, json};
 
 use crate::common::{
-    Builder, MediaSpec, base_item, empty_sequence, file_name, generator_of, item_media, path_to_file_url, rate_from_f64, relative_path, resolve_path,
-    settings_for, transition_effect, transition_name,
+    Builder, MediaSpec, base_item, empty_sequence, file_name, generator_of, item_media, media_info, path_to_file_url, rate_from_f64, relative_path,
+    resolve_path, settings_for, transition_effect, transition_name,
 };
 use crate::{Error, ExportOptions, Format, ImportOptions, Imported, Report, Result};
 
@@ -369,8 +369,10 @@ struct Imp<'a, 'r> {
     report: &'r mut Report,
     links: HashMap<u64, u64>,
     groups: HashMap<u64, u64>,
+    explicit_dimensions: HashSet<ItemId>,
 }
 
+/// Imports OTIO timelines into one project while preserving supported source metadata.
 pub(crate) fn import(text: &str, opts: &ImportOptions, report: &mut Report) -> Result<Imported> {
     let v: Value = serde_json::from_str(text).map_err(|e| Error::parse(Format::Otio, e.to_string()))?;
     let mut timelines = Vec::new();
@@ -378,7 +380,7 @@ pub(crate) fn import(text: &str, opts: &ImportOptions, report: &mut Report) -> R
     if timelines.is_empty() {
         return Err(Error::parse(Format::Otio, format!("no Timeline in a {} document", schema(&v))));
     }
-    let mut imp = Imp { b: Builder::new(&name_of(&v)), opts, report, links: HashMap::new(), groups: HashMap::new() };
+    let mut imp = Imp { b: Builder::new(&name_of(&v)), opts, report, links: HashMap::new(), groups: HashMap::new(), explicit_dimensions: HashSet::new() };
     if imp.b.p.name.is_empty() {
         imp.b.p.name = "Imported OTIO".into();
     }
@@ -389,6 +391,7 @@ pub(crate) fn import(text: &str, opts: &ImportOptions, report: &mut Report) -> R
     Ok(imp.b.finish())
 }
 
+/// Collects importable timelines, stacks and standalone tracks from an OTIO document.
 fn collect_timelines<'v>(v: &'v Value, out: &mut Vec<&'v Value>) {
     match schema_name(v) {
         "Timeline" | "Stack" => out.push(v),
@@ -402,6 +405,7 @@ fn collect_timelines<'v>(v: &'v Value, out: &mut Vec<&'v Value>) {
     }
 }
 
+/// Finds the first source-range frame rate while walking a timeline's children.
 fn first_rate(v: &Value) -> Option<FrameRate> {
     if let Some(sr) = v.get("source_range").filter(|s| !s.is_null()) {
         return sr.get("duration").and_then(read_rate);
@@ -409,6 +413,99 @@ fn first_rate(v: &Value) -> Option<FrameRate> {
     v.get("children").and_then(Value::as_array).into_iter().flatten().find_map(first_rate)
 }
 
+/// Converts a positive integral JSON number within the u32 dimension range.
+fn frame_dimension(value: &Value) -> Option<u32> {
+    if let Some(integer) = value.as_u64() {
+        let dimension = u32::try_from(integer).ok()?;
+        return (dimension > 0).then_some(dimension);
+    }
+    frame_dimension_number(value.as_f64()?)
+}
+
+/// Converts a finite, positive integral float within the frame-size integer range.
+fn frame_dimension_number(value: f64) -> Option<u32> {
+    if !value.is_finite() || value.fract() != 0.0 || value <= 0.0 || value > f64::from(u32::MAX) {
+        return None;
+    }
+    Some(value as u32)
+}
+
+/// Reads and validates dimensions from media metadata or an OTIO image bounds box.
+fn image_dimensions(v: &Value) -> Option<(u32, u32)> {
+    let (width, height) = if let (Some(width), Some(height)) = (v.get("width"), v.get("height")) {
+        (frame_dimension(width)?, frame_dimension(height)?)
+    } else {
+        let min = v.get("min")?;
+        let max = v.get("max")?;
+        let width = max.get("x")?.as_f64()? - min.get("x")?.as_f64()?;
+        let height = max.get("y")?.as_f64()? - min.get("y")?.as_f64()?;
+        (frame_dimension_number(width)?, frame_dimension_number(height)?)
+    };
+    filmcraft_project::validate_frame_size(width, height).ok()?;
+    Some((width, height))
+}
+
+/// Prefers FilmCraft media info, then OTIO metadata, then available image bounds.
+fn reference_dimensions(reference: &Value) -> Option<(u32, u32)> {
+    let metadata = reference.get("metadata");
+    let filmcraft = metadata.and_then(|m| m.get("filmcraft"));
+    filmcraft
+        .and_then(|m| m.get("info"))
+        .and_then(|i| i.get("video"))
+        .and_then(image_dimensions)
+        .or_else(|| metadata.and_then(image_dimensions))
+        .or_else(|| reference.get("available_image_bounds").and_then(image_dimensions))
+}
+
+/// Selects the active OTIO media reference, falling back to the first one if needed.
+fn active_media_reference(clip: &Value) -> Option<&Value> {
+    let refs = clip.get("media_references");
+    let key = clip.get("active_media_reference_key").and_then(Value::as_str).unwrap_or("DEFAULT_MEDIA");
+    match refs {
+        Some(refs) => refs.get(key).or_else(|| refs.as_object().and_then(|o| o.values().next())),
+        None => clip.get("media_reference"),
+    }
+}
+
+/// Returns dimensions only for a clip import() will retain (positive source duration).
+fn media_reference_dimensions(clip: &Value) -> Option<(u32, u32)> {
+    let reference = active_media_reference(clip)?;
+    let available = reference.get("available_range").and_then(read_tr);
+    let (_, duration) = clip.get("source_range").and_then(read_tr).or(available)?;
+    if duration <= Tick::ZERO {
+        return None;
+    }
+    reference_dimensions(reference)
+}
+
+/// Finds dimensions from the first importable video clip, respecting nested stacks.
+fn first_video_dimensions_at(stack: &Value, depth: usize) -> Option<(u32, u32)> {
+    if depth >= 64 {
+        return None;
+    }
+    for track in stack.get("children").and_then(Value::as_array).into_iter().flatten() {
+        if schema_name(track) != "Track" || track.get("kind").and_then(Value::as_str) == Some("Audio") {
+            continue;
+        }
+        for child in track.get("children").and_then(Value::as_array).into_iter().flatten() {
+            let dimensions = match schema_name(child) {
+                "Clip" => media_reference_dimensions(child),
+                "Stack" => child.get("source_range").and_then(read_tr).and_then(|_| first_video_dimensions_at(child, depth + 1)),
+                _ => None,
+            };
+            if dimensions.is_some() {
+                return dimensions;
+            }
+        }
+    }
+    None
+}
+
+/// Returns the first usable video clip dimensions in a stack.
+fn first_video_dimensions(stack: &Value) -> Option<(u32, u32)> {
+    first_video_dimensions_at(stack, 0)
+}
+
 impl Imp<'_, '_> {
     fn timeline(&mut self, tl: &Value) -> ItemId {
         let name = name_of(tl);
@@ -433,9 +530,12 @@ impl Imp<'_, '_> {
         id
     }
 
+    /// Imports a sequence, preferring explicit settings over inferred video dimensions.
     fn sequence(&mut self, stack: &Value, name: &str, meta: Option<&Value>, rate: FrameRate) -> ItemId {
-        let settings: SequenceSettings =
-            meta.and_then(|m| m.get("settings")).and_then(|s| serde_json::from_value(s.clone()).ok()).unwrap_or_else(|| settings_for(rate, 1920, 1080, false));
+        let settings: SequenceSettings = meta.and_then(|m| m.get("settings")).and_then(|s| serde_json::from_value(s.clone()).ok()).unwrap_or_else(|| {
+            let (width, height) = first_video_dimensions(stack).unwrap_or((1920, 1080));
+            settings_for(rate, width, height, false)
+        });
         let id = self.b.reserve_sequence(name, settings.clone(), None);
         if let Some(l) = meta.and_then(|m| m.get("label")).and_then(|l| serde_json::from_value::<Label>(l.clone()).ok())
             && let Some(it) = self.b.p.item_mut(id)
@@ -618,16 +718,9 @@ impl Imp<'_, '_> {
         }
     }
 
+    /// Imports a positive-duration clip using the same reference selection as dimension discovery.
     fn clip(&mut self, c: &Value, kind: TrackKind, pos: Tick) -> Option<TrackItem> {
-        let mref = match c.get("media_references") {
-            Some(refs) => {
-                let key = c.get("active_media_reference_key").and_then(Value::as_str).unwrap_or("DEFAULT_MEDIA");
-                refs.get(key).or_else(|| refs.as_object().and_then(|o| o.values().next()))
-            }
-            None => c.get("media_reference"),
-        }
-        .cloned()
-        .unwrap_or(Value::Null);
+        let mref = active_media_reference(c).cloned().unwrap_or(Value::Null);
         let name = name_of(c);
         let avail = mref.get("available_range").and_then(read_tr);
         let (src_in, dur) = c.get("source_range").and_then(read_tr).or(avail)?;
@@ -674,6 +767,7 @@ impl Imp<'_, '_> {
         Some(ti)
     }
 
+    /// Registers a referenced media item with dimensions inferred from its reference metadata.
     fn media(&mut self, r: &Value, clip_name: &str, kind: TrackKind, avail: Option<(Tick, Tick)>) -> Option<ItemId> {
         let meta = fc(r);
         let info: Option<MediaInfo> = meta.and_then(|m| m.get("info")).and_then(|i| serde_json::from_value(i.clone()).ok());
@@ -681,9 +775,13 @@ impl Imp<'_, '_> {
             let n = name_of(r);
             if n.is_empty() { clip_name.to_string() } else { n }
         };
+        let dimensions = reference_dimensions(r);
         let spec = MediaSpec {
             duration: avail.map(|a| a.1),
-            video: (kind == TrackKind::Video).then(|| (1920, 1080, FrameRate::default())),
+            video: (kind == TrackKind::Video).then(|| {
+                let (width, height) = dimensions.unwrap_or((1920, 1080));
+                (width, height, FrameRate::default())
+            }),
             audio: (kind == TrackKind::Audio).then_some((48_000, 2)),
             start_tc: None,
             kind: None,
@@ -745,12 +843,33 @@ impl Imp<'_, '_> {
                 id
             }
         };
+        if kind == TrackKind::Video
+            && let Some((width, height)) = dimensions
+            && !self.explicit_dimensions.contains(&id)
+            && let Some(media) = self.b.p.item_mut(id).and_then(|item| item.as_media_mut())
+        {
+            if let Some(video) = media.info.video.as_mut() {
+                video.width = width;
+                video.height = height;
+            } else {
+                media.info.video = media_info(&media.info.name, &spec).video;
+            }
+            if media.info.video.is_some() {
+                if media.info.kind == filmcraft_media::MediaKind::AudioOnly {
+                    media.info.kind = filmcraft_media::MediaKind::Movie;
+                }
+                self.explicit_dimensions.insert(id);
+            }
+        }
         Some(id)
     }
 
+    /// Applies FilmCraft media info and availability metadata to an imported media item.
     fn fill_media(&mut self, id: ItemId, info: Option<MediaInfo>, meta: Option<&Value>, kind: TrackKind) {
         let label = meta.and_then(|m| m.get("label")).and_then(|l| serde_json::from_value::<Label>(l.clone()).ok());
         let offline = meta.and_then(|m| m.get("offline")).and_then(Value::as_bool);
+        let explicit_video_dimensions =
+            info.as_ref().and_then(|i| i.video.as_ref()).is_some_and(|v| filmcraft_project::validate_frame_size(v.width, v.height).is_ok());
         let Some(it) = self.b.p.item_mut(id) else { return };
         if let Some(l) = label {
             it.label = l;
@@ -767,6 +886,9 @@ impl Imp<'_, '_> {
                 m.offline = o;
             }
         }
+        if explicit_video_dimensions {
+            self.explicit_dimensions.insert(id);
+        }
     }
 
     fn markers(&mut self, v: &Value) -> Vec<Marker> {
diff --git a/crates/interchange/tests/it/otio.rs b/crates/interchange/tests/it/otio.rs
--- a/crates/interchange/tests/it/otio.rs
+++ b/crates/interchange/tests/it/otio.rs
@@ -252,6 +252,326 @@ fn lossless_roundtrip_with_filmcraft_metadata() {
     assert_eq!(structure(&imp2.project, imp2.sequences[0], TrackKind::Video), structure(&p, s, TrackKind::Video));
 }
 
+/// Explicit sequence metadata is absent; the first video's FilmCraft media info supplies the size.
+#[test]
+fn sequence_dimensions_default_from_first_video_clip_media_info() {
+    let document = r#"{
+        "OTIO_SCHEMA":"Timeline.1",
+        "tracks":{"OTIO_SCHEMA":"Stack.1","children":[
+            {"OTIO_SCHEMA":"Track.1","kind":"Video","children":[
+                {"OTIO_SCHEMA":"Clip.2","name":"portrait",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"portrait.mp4",
+                    "available_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":100}},
+                    "metadata":{"filmcraft":{"info":{"video":{"width":1080,"height":1920}}}}}}
+            ]}
+        ]}
+    }"#;
+    let (imp, _) = import(document.as_bytes(), Format::Otio, None).unwrap();
+    let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+    assert_eq!((sequence.settings.width, sequence.settings.height), (1080, 1920));
+    let media_id = sequence.video_tracks[0].items[0].item;
+    let media = imp.project.item(media_id).unwrap().as_media().unwrap();
+    assert_eq!(media.info.video.as_ref().map(|v| (v.width, v.height)), Some((1080, 1920)));
+}
+
+/// Image bounds supply portrait dimensions without any FilmCraft metadata.
+#[test]
+fn sequence_dimensions_default_from_first_video_clip_image_bounds() {
+    let document = r#"{
+        "OTIO_SCHEMA":"Timeline.1",
+        "tracks":{"OTIO_SCHEMA":"Stack.1","children":[
+            {"OTIO_SCHEMA":"Track.1","kind":"Video","children":[
+                {"OTIO_SCHEMA":"Clip.2","name":"portrait",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"portrait.mp4",
+                    "available_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":100}},
+                    "available_image_bounds":{"OTIO_SCHEMA":"Box2d.1","min":{"x":0,"y":0},"max":{"x":1080,"y":1920}}}}
+            ]}
+        ]}
+    }"#;
+    let (imp, _) = import(document.as_bytes(), Format::Otio, None).unwrap();
+    let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+    assert_eq!((sequence.settings.width, sequence.settings.height), (1080, 1920));
+    let media_id = sequence.video_tracks[0].items[0].item;
+    let media = imp.project.item(media_id).unwrap().as_media().unwrap();
+    assert_eq!(media.info.video.as_ref().map(|v| (v.width, v.height)), Some((1080, 1920)));
+}
+
+/// Integral JSON floats preserve portrait dimensions for both the sequence and imported media.
+#[test]
+fn sequence_dimensions_accept_integral_float_media_metadata() {
+    let document = r#"{
+        "OTIO_SCHEMA":"Timeline.1",
+        "tracks":{"OTIO_SCHEMA":"Stack.1","children":[
+            {"OTIO_SCHEMA":"Track.1","kind":"Video","children":[
+                {"OTIO_SCHEMA":"Clip.2","name":"portrait",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"portrait.mp4",
+                    "available_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":100}},
+                    "metadata":{"width":1080.0,"height":1920.0}}}
+            ]}
+        ]}
+    }"#;
+    let (imp, _) = import(document.as_bytes(), Format::Otio, None).unwrap();
+    let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+    assert_eq!((sequence.settings.width, sequence.settings.height), (1080, 1920));
+    let media_id = sequence.video_tracks[0].items[0].item;
+    let media = imp.project.item(media_id).unwrap().as_media().unwrap();
+    assert_eq!(media.info.video.as_ref().map(|v| (v.width, v.height)), Some((1080, 1920)));
+}
+
+/// Invalid numeric dimensions retain the HD fallback rather than admitting unsafe frame sizes.
+#[test]
+fn sequence_dimensions_reject_invalid_metadata_widths() {
+    let template = r#"{
+        "OTIO_SCHEMA":"Timeline.1",
+        "tracks":{"OTIO_SCHEMA":"Stack.1","children":[
+            {"OTIO_SCHEMA":"Track.1","kind":"Video","children":[
+                {"OTIO_SCHEMA":"Clip.2","name":"portrait",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"portrait.mp4",
+                    "available_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":100}},
+                    "metadata":{"width":WIDTH,"height":1920}}}
+            ]}
+        ]}
+    }"#;
+
+    for width in ["1080.5", "-1", "0", "4294967296"] {
+        let document = template.replace("\"width\":WIDTH", &format!("\"width\":{width}"));
+        let (imp, _) = import(document.as_bytes(), Format::Otio, None).unwrap();
+        let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+        assert_eq!((sequence.settings.width, sequence.settings.height), (1920, 1080), "width={width}");
+    }
+}
+
+/// A present but empty media_references map does not fall back to the legacy field.
+#[test]
+fn empty_media_references_do_not_use_legacy_reference_dimensions() {
+    let document = r#"{
+        "OTIO_SCHEMA":"Timeline.1",
+        "tracks":{"OTIO_SCHEMA":"Stack.1","children":[
+            {"OTIO_SCHEMA":"Track.1","kind":"Video","children":[
+                {"OTIO_SCHEMA":"Clip.2","name":"clip",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_references":{},
+                 "active_media_reference_key":"DEFAULT_MEDIA",
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"legacy.mp4",
+                    "metadata":{"filmcraft":{"info":{"video":{"width":1080,"height":1920}}}}}}
+            ]}
+        ]}
+    }"#;
+    let (imp, _) = import(document.as_bytes(), Format::Otio, None).unwrap();
+    let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+    assert_eq!((sequence.settings.width, sequence.settings.height), (1920, 1080));
+}
+
+/// Updates deduplicated default media dimensions when a later reference supplies them.
+#[test]
+fn later_same_path_metadata_updates_default_media_dimensions() {
+    let document = r#"{
+        "OTIO_SCHEMA":"Timeline.1",
+        "tracks":{"OTIO_SCHEMA":"Stack.1","children":[
+            {"OTIO_SCHEMA":"Track.1","kind":"Video","children":[
+                {"OTIO_SCHEMA":"Clip.2","name":"first",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"shared.mp4",
+                    "metadata":{}}},
+                {"OTIO_SCHEMA":"Clip.2","name":"second",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"shared.mp4",
+                    "metadata":{"filmcraft":{"info":{"video":{"width":1080,"height":1920}}}}}}
+            ]}
+        ]}
+    }"#;
+    let (imp, _) = import(document.as_bytes(), Format::Otio, None).unwrap();
+    let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+    assert_eq!((sequence.settings.width, sequence.settings.height), (1080, 1920));
+    let media = imp.project.item(sequence.video_tracks[0].items[0].item).unwrap().as_media().unwrap();
+    assert_eq!(media.info.video.as_ref().map(|v| (v.width, v.height)), Some((1080, 1920)));
+}
+
+/// Adds a later video stream when an audio reference created the shared media item first.
+#[test]
+fn later_video_reference_adds_dimensions_to_audio_only_shared_media() {
+    let document = r#"{
+        "OTIO_SCHEMA":"Timeline.1",
+        "tracks":{"OTIO_SCHEMA":"Stack.1","children":[
+            {"OTIO_SCHEMA":"Track.1","kind":"Audio","children":[
+                {"OTIO_SCHEMA":"Clip.2","name":"audio first",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"shared.mp4",
+                    "metadata":{}}}
+            ]},
+            {"OTIO_SCHEMA":"Track.1","kind":"Video","children":[
+                {"OTIO_SCHEMA":"Clip.2","name":"video later",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"shared.mp4",
+                    "metadata":{"filmcraft":{"info":{"video":{"width":1080,"height":1920}}}}}}
+            ]}
+        ]}
+    }"#;
+    let (imp, _) = import(document.as_bytes(), Format::Otio, None).unwrap();
+    let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+    let audio_item = sequence.audio_tracks[0].items[0].item;
+    let video_item = sequence.video_tracks[0].items[0].item;
+    assert_eq!(audio_item, video_item);
+    let media = imp.project.item(audio_item).unwrap().as_media().unwrap();
+    assert_eq!(media.info.video.as_ref().map(|v| (v.width, v.height)), Some((1080, 1920)));
+    assert_eq!(media.info.kind, filmcraft_media::MediaKind::Movie);
+    assert_eq!(media.info.audio_streams.len(), 1);
+}
+
+/// Keeps the first explicit dimensions when later references to the same file disagree.
+#[test]
+fn first_explicit_same_path_dimensions_are_preserved() {
+    let document = r#"{
+        "OTIO_SCHEMA":"Timeline.1",
+        "tracks":{"OTIO_SCHEMA":"Stack.1","children":[
+            {"OTIO_SCHEMA":"Track.1","kind":"Video","children":[
+                {"OTIO_SCHEMA":"Clip.2","name":"first",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"shared.mp4",
+                    "metadata":{"filmcraft":{"info":{"video":{"width":1920,"height":1080}}}}}},
+                {"OTIO_SCHEMA":"Clip.2","name":"second",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"shared.mp4",
+                    "metadata":{"filmcraft":{"info":{"video":{"width":1080,"height":1920}}}}}}
+            ]}
+        ]}
+    }"#;
+    let (imp, _) = import(document.as_bytes(), Format::Otio, None).unwrap();
+    let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+    assert_eq!((sequence.settings.width, sequence.settings.height), (1920, 1080));
+    let media = imp.project.item(sequence.video_tracks[0].items[0].item).unwrap().as_media().unwrap();
+    assert_eq!(media.info.video.as_ref().map(|v| (v.width, v.height)), Some((1920, 1080)));
+}
+
+/// Preserves complete first-reference media info when it includes video on an audio track.
+#[test]
+fn first_audio_reference_complete_media_info_keeps_explicit_dimensions() {
+    let info = serde_json::to_value(media_info("shared.mp4", true, true, FrameRate::FPS_24, 60)).unwrap();
+    let document = serde_json::json!({
+        "OTIO_SCHEMA": "Timeline.1",
+        "tracks": {
+            "OTIO_SCHEMA": "Stack.1",
+            "children": [
+                {
+                    "OTIO_SCHEMA": "Track.1",
+                    "kind": "Audio",
+                    "children": [{
+                        "OTIO_SCHEMA": "Clip.2",
+                        "name": "audio first",
+                        "source_range": {
+                            "OTIO_SCHEMA": "TimeRange.1",
+                            "start_time": {"OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0},
+                            "duration": {"OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 24}
+                        },
+                        "media_reference": {
+                            "OTIO_SCHEMA": "ExternalReference.1",
+                            "target_url": "shared.mp4",
+                            "metadata": {"filmcraft": {"info": info}}
+                        }
+                    }]
+                },
+                {
+                    "OTIO_SCHEMA": "Track.1",
+                    "kind": "Video",
+                    "children": [{
+                        "OTIO_SCHEMA": "Clip.2",
+                        "name": "video later",
+                        "source_range": {
+                            "OTIO_SCHEMA": "TimeRange.1",
+                            "start_time": {"OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 0},
+                            "duration": {"OTIO_SCHEMA": "RationalTime.1", "rate": 24, "value": 24}
+                        },
+                        "media_reference": {
+                            "OTIO_SCHEMA": "ExternalReference.1",
+                            "target_url": "shared.mp4",
+                            "metadata": {"filmcraft": {"info": {"video": {"width": 1080, "height": 1920}}}}
+                        }
+                    }]
+                }
+            ]
+        }
+    });
+    let bytes = serde_json::to_vec(&document).unwrap();
+    let (imp, _) = import(&bytes, Format::Otio, None).unwrap();
+    let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+    let media = imp.project.item(sequence.video_tracks[0].items[0].item).unwrap().as_media().unwrap();
+    assert_eq!(media.info.video.as_ref().map(|v| (v.width, v.height)), Some((1920, 1080)));
+    assert_eq!(media.info.audio_streams.len(), 1);
+}
+
+/// Selects dimensions only from nested stacks that the importer can actually include.
+#[test]
+fn sequence_dimensions_skip_stacks_without_readable_source_ranges() {
+    let template = r#"{
+        "OTIO_SCHEMA":"Timeline.1",
+        "tracks":{"OTIO_SCHEMA":"Stack.1","children":[
+            {"OTIO_SCHEMA":"Track.1","kind":"Video","children":[
+                {"OTIO_SCHEMA":"Stack.1","name":"unreadable nested",NESTED_RANGE "children":[
+                    {"OTIO_SCHEMA":"Track.1","kind":"Video","children":[
+                        {"OTIO_SCHEMA":"Clip.2","name":"nested portrait",
+                         "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                         "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"portrait.mp4",
+                            "metadata":{"filmcraft":{"info":{"video":{"width":1080,"height":1920}}}}}}
+                    ]}
+                ]},
+                {"OTIO_SCHEMA":"Clip.2","name":"direct landscape",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"landscape.mp4",
+                    "metadata":{"filmcraft":{"info":{"video":{"width":1920,"height":1080}}}}}}
+            ]}
+        ]}
+    }"#;
+
+    for source_range in ["", r#""source_range":null,"#, r#""source_range":{},"#] {
+        let document = template.replace("NESTED_RANGE", source_range);
+        let (imp, _) = import(document.as_bytes(), Format::Otio, None).unwrap();
+        let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+        assert_eq!((sequence.settings.width, sequence.settings.height), (1920, 1080));
+        assert_eq!(sequence.video_tracks[0].items.len(), 1);
+    }
+
+    let nested_range = r#""source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},"#;
+    let document = template.replace("NESTED_RANGE", nested_range);
+    let (imp, _) = import(document.as_bytes(), Format::Otio, None).unwrap();
+    let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+    assert_eq!((sequence.settings.width, sequence.settings.height), (1080, 1920));
+    assert_eq!(sequence.video_tracks[0].items.len(), 2);
+    let nested = sequence.video_tracks[0].items[0].item;
+    let nested = imp.project.sequence(nested).unwrap();
+    assert_eq!((nested.settings.width, nested.settings.height), (1080, 1920));
+    assert_eq!(nested.video_tracks[0].items.len(), 1);
+}
+
+/// Empty clips are skipped when choosing the sequence's video dimensions.
+#[test]
+fn sequence_dimensions_skip_zero_duration_video_clips() {
+    let document = r#"{
+        "OTIO_SCHEMA":"Timeline.1",
+        "tracks":{"OTIO_SCHEMA":"Stack.1","children":[
+            {"OTIO_SCHEMA":"Track.1","kind":"Video","children":[
+                {"OTIO_SCHEMA":"Clip.2","name":"empty landscape",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"empty.mp4",
+                    "available_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":100}},
+                    "metadata":{"filmcraft":{"info":{"video":{"width":1920,"height":1080}}}}}},
+                {"OTIO_SCHEMA":"Clip.2","name":"portrait",
+                 "source_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":24}},
+                 "media_reference":{"OTIO_SCHEMA":"ExternalReference.1","target_url":"portrait.mp4",
+                    "available_range":{"OTIO_SCHEMA":"TimeRange.1","start_time":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":0},"duration":{"OTIO_SCHEMA":"RationalTime.1","rate":24,"value":100}},
+                    "metadata":{"filmcraft":{"info":{"video":{"width":1080,"height":1920}}}}}}
+            ]}
+        ]}
+    }"#;
+    let (imp, _) = import(document.as_bytes(), Format::Otio, None).unwrap();
+    let sequence = imp.project.sequence(imp.sequences[0]).unwrap();
+    assert_eq!((sequence.settings.width, sequence.settings.height), (1080, 1920));
+    assert_eq!(sequence.video_tracks[0].items.len(), 1);
+}
+
 #[test]
 fn collections_and_bare_tracks() {
     let one = r#"{"OTIO_SCHEMA": "SerializableCollection.1", "name": "Bundle", "children": [
