Recursive AI

Get when and where a video was recorded in C#

All onemedia recipes

media.CaptureTime, media.Location and media.Device give the recording date, the GPS position and the camera or phone that made a video. Each is a list of candidates, best first, with where it was read from, because a file often holds several dates that disagree. The two inputs are small test files from the OneMedia repository (MIT OR Apache-2.0): one carries Apple-style QuickTime keys (com.apple.quicktime.*), the other Android-style keys (com.android.*). Their pictures are synthetic and their tags are invented: Contoso, Northwind and the San Francisco position are not real devices or recordings.

Terminal
dotnet new console -o WhenWhere && cd WhenWhere
dotnet add package RecursiveAI.OneMedia --version 0.1.0-preview.1
t=https://raw.githubusercontent.com/recursive-ai-oss/onemedia-csharp/v0.1.0-preview.1/testdata/metadata
curl -L -o iphone-style.mov $t/tags_keys.mov
curl -L -o android-style.mp4 $t/tags_keys_android.mp4

Put this in Program.cs and run dotnet run:

C#
using RecursiveAI.OneMedia;
using RecursiveAI.OneMedia.Metadata;
 
foreach (string path in new[] { "iphone-style.mov", "android-style.mp4" })
{
    using MediaFile media = Media.Open(path);
    Console.WriteLine(path);
 
    // The value is the first candidate; every candidate says where it came from.
    foreach (FactCandidate<MediaDateTime> c in media.CaptureTime.Candidates)
        Console.WriteLine($"  recorded {c.Value} ({c.Certainty}) from {c.Source}");
 
    // Test Best, not Value: Value is a struct and reads 0, 0 when there is no location.
    if (media.Location.Best is { } loc)
        Console.WriteLine($"  location {loc.Value.Latitude}, {loc.Value.Longitude}" +
                          $" altitude {loc.Value.Altitude?.ToString() ?? "-"} m" +
                          $" from {loc.Source}");
 
    DeviceFacts device = media.Device;
    Console.WriteLine($"  device   {device.Make.Value} {device.Model.Value}," +
                      $" software {device.Software.Value}");
 
    // As a DateTimeOffset, when the file states the zone.
    MediaDateTime when = media.CaptureTime.Value;
    if (media.CaptureTime.HasValue && when.OffsetMinutes is int offset)
        Console.WriteLine("  as DateTimeOffset " +
            new DateTimeOffset(when.Value, TimeSpan.FromMinutes(offset)).ToString("o"));
}
Text
iphone-style.mov
  recorded 2023-04-05T10:11:12+02:00 (Offset) from mdta:com.apple.quicktime.creationdate
  location 37.7749, -122.4194 altitude 12.5 m from mdta:com.apple.quicktime.location.ISO6709
  device   Contoso Camera_X1, software 17.1
  as DateTimeOffset 2023-04-05T10:11:12.0000000+02:00
android-style.mp4
  recorded 2023-06-01T09:30:00Z (Utc) from mdta:creation_time
  recorded 2023-06-01T09:30:00Z (Utc) from mvhd
  device   Northwind Droid_7, software 14

The iPhone-style file has com.apple.quicktime.creationdate, the local time with its UTC offset, so the zone is known (Offset). The Android-style file has a UTC creation_time key and the same instant in the movie header (mvhd), so both candidates appear, the key first. Location comes from the ISO 6709 text (+37.7749-122.4194+012.500/) as decimal degrees and meters.

Which candidate wins

The order is fixed and documented in Tags and capture facts. For capture time: the Apple creationdate key first, then XMP dates with a zone, the mdta creation_time, ©day, XMP dates without a zone, Matroska DATE_RECORDED, Matroska DateUTC, and the movie header last. For location: QuickTime keys, then ©xyz user data, then the 3GPP loci box, then XMP GPS.

The movie header is a weak source. Some cameras store local time in it as if it were UTC, and some muxers write a placeholder: the Sintel trailer’s says 1970-01-01T00:00:00Z, which OneMedia reports as it is, as the only candidate. c.Interpretation says what each source means, and c.RawValue is the text or number as stored.

Limits

  • Dates as stored. OneMedia doesn’t guess a zone or correct a wrong clock. Certainty says what the file states: Utc, Offset, Local or Unknown.
  • Sources read. QuickTime keys and user data, iTunes ©day, 3GPP boxes, XMP (uuid box and XMP_ atom), the movie header, and Matroska tags and DateUTC. A GPS log stored as its own timed metadata track, as some action cameras and dashcams write, is not among them.
  • Matroska dates. DateUTC is when the file was muxed, which for a converted or remuxed file is not when it was recorded.
  • No value. Many converters and upload sites drop these tags. When a file has none, HasValue is false, Best is null and Value is the type’s default: for Location that is 0, 0, a real place, so test Best or HasValue first.

The same facts include rotation: Get video rotation.