---
title: "Get when and where a video was recorded in C#"
url: https://recursiveai.org/onemedia/cookbook/video-recording-date-location/
description: "Read a video’s capture date and GPS location, and the phone make and model, from iPhone and Android MP4 and MOV files in C#, with every candidate and where it came from."
version: "0.1.0-preview.1"
---

# Get when and where a video was recorded in C#

`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.

```shell
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`:

```csharp
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](https://recursiveai.org/onemedia/docs/tags/#fact-precedence). 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](https://recursiveai.org/onemedia/cookbook/read-mp4-metadata/)’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](https://recursiveai.org/onemedia/cookbook/get-video-rotation/).
