Skip to content

Commit

Permalink
Show which tracks are in an mp4 file (#7474)
Browse files Browse the repository at this point in the history
### What
We currently only support showing the first video track of an `mp4`, and
we ignore all other tracks.

However, it is nice to at least tell the user that they are there:


![image](https://github.com/user-attachments/assets/78c3e8f8-3318-4509-8a60-2fc0fa6c6f10)


### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested the web demo (if applicable):
* Using examples from latest `main` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/7474?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/7474?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json)
* [x] The PR title and labels are set such as to maximize their
usefulness for the next release's CHANGELOG
* [x] If applicable, add a new check to the [release
checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)!
* [x] If have noted any breaking changes to the log API in
`CHANGELOG.md` and the migration guide

- [PR Build Summary](https://build.rerun.io/pr/7474)
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)

To run all checks from `main`, comment on the PR with `@rerun-bot
full-check`.
  • Loading branch information
emilk authored Sep 23, 2024
1 parent de49fce commit ac12c0c
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4990,6 +4990,7 @@ dependencies = [
"re_types_blueprint",
"re_types_core",
"re_ui",
"re_video",
"re_viewer_context",
"unindent",
]
Expand Down
41 changes: 40 additions & 1 deletion crates/store/re_video/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
mod mp4;

use std::ops::Range;
use std::{collections::BTreeMap, ops::Range};

use itertools::Itertools;

pub use re_mp4::TrackKind;

pub type TrackId = u64;

/// Decoded video data.
#[derive(Clone)]
pub struct VideoData {
Expand All @@ -34,6 +38,11 @@ pub struct VideoData {

/// This array stores all data used by samples.
pub data: Vec<u8>,

/// All the tracks in the mp4; not just the video track.
///
/// Can be nice to show in a UI.
pub mp4_tracks: BTreeMap<TrackId, Option<TrackKind>>,
}

impl VideoData {
Expand Down Expand Up @@ -61,6 +70,36 @@ impl VideoData {
}
}

/// Duration of the video, in milliseconds.
#[inline]
pub fn duration_ms(&self) -> f64 {
self.duration.into_millis(self.timescale)
}

/// Natural width of the video.
#[inline]
pub fn width(&self) -> u32 {
self.config.coded_width as u32
}

/// Natural height of the video.
#[inline]
pub fn height(&self) -> u32 {
self.config.coded_height as u32
}

/// The codec used to encode the video.
#[inline]
pub fn codec(&self) -> &str {
&self.config.codec
}

/// The number of samples in the video.
#[inline]
pub fn num_samples(&self) -> usize {
self.samples.len()
}

/// Determines the presentation timestamps of all frames inside a video, returning raw time values.
///
/// Returned timestamps are in nanoseconds since start and are guaranteed to be monotonically increasing.
Expand Down
3 changes: 3 additions & 0 deletions crates/store/re_video/src/mp4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use super::{Config, Sample, Segment, Time, Timescale, VideoData, VideoLoadError}
pub fn load_mp4(bytes: &[u8]) -> Result<VideoData, VideoLoadError> {
let mp4 = re_mp4::read(bytes)?;

let mp4_tracks = mp4.tracks().iter().map(|(k, t)| (*k, t.kind)).collect();

let track = mp4
.tracks()
.values()
Expand Down Expand Up @@ -78,6 +80,7 @@ pub fn load_mp4(bytes: &[u8]) -> Result<VideoData, VideoLoadError> {
segments,
samples,
data,
mp4_tracks,
})
}

Expand Down
1 change: 1 addition & 0 deletions crates/viewer/re_data_ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ re_types = { workspace = true, features = [
re_types_blueprint.workspace = true
re_types_core.workspace = true
re_ui.workspace = true
re_video.workspace = true
re_viewer_context.workspace = true

ahash.workspace = true
Expand Down
27 changes: 22 additions & 5 deletions crates/viewer/re_data_ui/src/blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,21 +169,23 @@ fn show_video_blob_info(
return;
}

let data = video.data();

re_ui::list_item::list_item_scope(ui, "video_blob_info", |ui| {
ui.list_item_flat_noninteractive(re_ui::list_item::LabelContent::new(
"Video properties",
));
ui.list_item_flat_noninteractive(
PropertyContent::new("Dimensions").value_text(format!(
"{}x{}",
video.width(),
video.height()
data.width(),
data.height()
)),
);
ui.list_item_flat_noninteractive(PropertyContent::new("Duration").value_text(
format!(
"{}",
re_log_types::Duration::from_millis(video.duration_ms() as i64)
re_log_types::Duration::from_millis(data.duration_ms() as i64)
),
));
// Some people may think that num_frames / duration = fps, but that's not true, videos may have variable frame rate.
Expand All @@ -192,12 +194,27 @@ fn show_video_blob_info(
// So the compromise is that we truthfully show the number of *samples* here and don't talk about frames.
ui.list_item_flat_noninteractive(
PropertyContent::new("Sample count")
.value_text(format!("{}", video.num_samples())),
.value_text(format!("{}", data.num_samples())),
);
ui.list_item_flat_noninteractive(
PropertyContent::new("Codec").value_text(video.codec()),
PropertyContent::new("Codec").value_text(data.codec()),
);

ui.list_item_collapsible_noninteractive_label("MP4 tracks", true, |ui| {
for (track_id, track_kind) in &data.mp4_tracks {
let track_kind_string = match track_kind {
Some(re_video::TrackKind::Audio) => "audio",
Some(re_video::TrackKind::Subtitle) => "subtitle",
Some(re_video::TrackKind::Video) => "video",
None => "unknown",
};
ui.list_item_flat_noninteractive(
PropertyContent::new(format!("Track {track_id}"))
.value_text(track_kind_string),
);
}
});

// TODO(andreas): A mini video player at this point would be awesome!
});
}
Expand Down
4 changes: 2 additions & 2 deletions crates/viewer/re_renderer/src/video/decoder/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ impl VideoDecoder {
let Some(chunk) = EncodedVideoChunk::new(&chunk)
.inspect_err(|err| {
// TODO(#7373): return this error once the decoder tries to return a frame for this sample. how exactly?
re_log::error!("failed to create video chunk: {}", js_error_to_string(err));
re_log::error_once!("failed to create video chunk: {}", js_error_to_string(err));
})
.ok()
else {
Expand All @@ -281,7 +281,7 @@ impl VideoDecoder {

if let Err(err) = self.decoder.decode(&chunk) {
// TODO(#7373): return this error once the decoder tries to return a frame for this sample. how exactly?
re_log::error!("Failed to decode video chunk: {}", js_error_to_string(&err));
re_log::error_once!("Failed to decode video chunk: {}", js_error_to_string(&err));
}
}

Expand Down
23 changes: 8 additions & 15 deletions crates/viewer/re_renderer/src/video/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,29 +83,22 @@ impl Video {
Ok(Self { data, decoder })
}

/// Duration of the video, in milliseconds.
pub fn duration_ms(&self) -> f64 {
self.data.duration.into_millis(self.data.timescale)
/// The video data
#[inline]
pub fn data(&self) -> &Arc<re_video::VideoData> {
&self.data
}

/// Natural width of the video.
#[inline]
pub fn width(&self) -> u32 {
self.data.config.coded_width as u32
self.data.width()
}

/// Natural height of the video.
#[inline]
pub fn height(&self) -> u32 {
self.data.config.coded_height as u32
}

/// The codec used to encode the video.
pub fn codec(&self) -> &str {
&self.data.config.codec
}

/// The number of samples in the video.
pub fn num_samples(&self) -> usize {
self.data.samples.len()
self.data.height()
}

/// Returns a texture with the latest frame at the given timestamp.
Expand Down

0 comments on commit ac12c0c

Please sign in to comment.