Media.SetCaptureTime(input, output, when) sets the time a recording was made in every date field the file carries, each in that field’s own notation and zone, and returns a report of what it wrote, what it left and which dates still disagree. The audio and video bytes are copied unchanged. A preview in 0.4.0-preview.1. The inputs are MDN’s CC0 flower clip and the iPhone-style test file from the OneMedia repository (MIT OR Apache-2.0; synthetic picture, invented tags).
dotnet new console -o Redate && cd Redate dotnet add package RecursiveAI.OneMedia --version 0.4.0-preview.1 curl -L -o flower.mp4 https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4 curl -L -o iphone-style.mov https://raw.githubusercontent.com/recursive-ai-oss/onemedia-csharp/v0.4.0-preview.1/testdata/metadata/tags_keys.mov
Put this in Program.cs and run dotnet run:
using RecursiveAI.OneMedia; using RecursiveAI.OneMedia.Tags; // 00:30 on 1 May 2024 in a UTC+2 zone, which is 22:30 UTC on 30 April. var when = new DateTimeOffset(2024, 5, 1, 0, 30, 0, TimeSpan.FromHours(2)); foreach ((string input, string output) in new[] { ("flower.mp4", "flower-redated.mp4"), ("iphone-style.mov", "iphone-redated.mov") }) { using (MediaFile before = Media.Open(input)) Console.WriteLine($"{input}: recorded {before.CaptureTime}"); // Writes a new file; the input is not touched. CaptureTimeReport report = Media.SetCaptureTime(input, output, when); Console.WriteLine($" header times written as {(report.HeaderTimesLocal ? "local" : "UTC")} ({report.HeaderTimesBasis})"); foreach (DateFieldWrite w in report.Written) Console.WriteLine($" wrote {w.System}:{w.Field}: {w.OldValue ?? "(none)"} -> {w.NewValue}"); foreach (DateFieldLeft left in report.Left) Console.WriteLine($" left {left.System}:{left.Field}: {left.Reason}"); foreach (DateDisagreement d in report.Disagreements) Console.WriteLine($" still differs: {d.Source} = {d.RawValue} ({d.Note})"); using (MediaFile after = Media.Open(output)) Console.WriteLine($"{output}: recorded {after.CaptureTime}"); }
flower.mp4: recorded 2018-03-07T15:21:21Z header times written as UTC (specification: header times are UTC) wrote mvhd:creation_time: 2018-03-07T15:21:21 -> 2024-04-30T22:30:00 wrote mvhd:modification_time: 2018-03-07T15:21:21 -> 2024-04-30T22:30:00 wrote tkhd:creation_time (track 1): 2018-03-07T15:21:21 -> 2024-04-30T22:30:00 wrote tkhd:modification_time (track 1): 2018-03-07T15:21:21 -> 2024-04-30T22:30:00 wrote mdhd:creation_time (track 1): 2018-03-07T15:21:21 -> 2024-04-30T22:30:00 wrote mdhd:modification_time (track 1): 2018-03-07T15:21:21 -> 2024-04-30T22:30:00 wrote tkhd:creation_time (track 2): 2018-03-07T15:21:21 -> 2024-04-30T22:30:00 wrote tkhd:modification_time (track 2): 2018-03-07T15:21:21 -> 2024-04-30T22:30:00 wrote mdhd:creation_time (track 2): 2018-03-07T15:21:21 -> 2024-04-30T22:30:00 wrote mdhd:modification_time (track 2): 2018-03-07T15:21:21 -> 2024-04-30T22:30:00 flower-redated.mp4: recorded 2024-04-30T22:30:00Z iphone-style.mov: recorded 2023-04-05T10:11:12+02:00 header times written as UTC (specification: header times are UTC) wrote mvhd:creation_time: 1904-01-01T00:00:00 -> 2024-04-30T22:30:00 wrote mvhd:modification_time: 1904-01-01T00:00:00 -> 2024-04-30T22:30:00 wrote mdta:com.apple.quicktime.creationdate: 2023-04-05T10:11:12+0200 -> 2024-05-01T00:30:00+0200 left tkhd:creation_time (track 1): unset (0); left unset left mdhd:creation_time (track 1): unset (0); left unset left tkhd:creation_time (track 2): unset (0); left unset left mdhd:creation_time (track 2): unset (0); left unset iphone-redated.mov: recorded 2024-05-01T00:30:00+02:00
- The movie and track headers (
mvhd,tkhd,mdhd) hold UTC seconds, as the MP4 specification says, so 00:30 at UTC+2 is written as 22:30 on 30 April. A device that stores local time there can be matched withCaptureTimeOptions.HeaderTimesAreLocalor a device profile; the report says which rule it used. - Apple’s
creationdatekey keeps the offset,+0200, as Apple cameras write it. It ranks first when OneMedia reads the date back, so the iPhone-style file reads back with its zone, and the flower clip, which has only header times, reads back in UTC. - Header times that were never set. Every header time in the iPhone-style file was 0. Its track headers are left and listed with the reason; its movie header was written, as the report says. (The date-writes page says a header whose times are both 0 is left unset; we have asked the OneMedia team which is intended.) Fields that are absent are not added in MP4 and MOV: no
©dayor XMP is created.
Read it back with ExifTool and ffprobe
exiftool -a -G1 -time:all --File:all flower-redated.mp4 iphone-redated.mov======== flower-redated.mp4 [QuickTime] Create Date : 2024:04:30 22:30:00 [QuickTime] Modify Date : 2024:04:30 22:30:00 [Track1] Track Create Date : 2024:04:30 22:30:00 [Track1] Track Modify Date : 2024:04:30 22:30:00 [Track1] Media Create Date : 2024:04:30 22:30:00 [Track1] Media Modify Date : 2024:04:30 22:30:00 [Track2] Track Create Date : 2024:04:30 22:30:00 [Track2] Track Modify Date : 2024:04:30 22:30:00 [Track2] Media Create Date : 2024:04:30 22:30:00 [Track2] Media Modify Date : 2024:04:30 22:30:00 ======== iphone-redated.mov [QuickTime] Create Date : 2024:04:30 22:30:00 [QuickTime] Modify Date : 2024:04:30 22:30:00 [Track1] Track Create Date : 0000:00:00 00:00:00 [Track1] Track Modify Date : 0000:00:00 00:00:00 [Track1] Media Create Date : 0000:00:00 00:00:00 [Track1] Media Modify Date : 0000:00:00 00:00:00 [Track2] Track Create Date : 0000:00:00 00:00:00 [Track2] Track Modify Date : 0000:00:00 00:00:00 [Track2] Media Create Date : 0000:00:00 00:00:00 [Track2] Media Modify Date : 0000:00:00 00:00:00 [Keys] Creation Date : 2024:05:01 00:30:00+02:00 2 image files read
for f in flower-redated.mp4 iphone-redated.mov; do echo "$f" ffprobe -v error -show_entries format_tags=creation_time,com.apple.quicktime.creationdate:stream_tags=creation_time -of default=nw=1 $f done
flower-redated.mp4 TAG:creation_time=2024-04-30T22:30:00.000000Z TAG:creation_time=2024-04-30T22:30:00.000000Z TAG:creation_time=2024-04-30T22:30:00.000000Z iphone-redated.mov TAG:creation_time=2024-04-30T22:30:00.000000Z TAG:com.apple.quicktime.creationdate=2024-05-01T00:30:00+0200
ExifTool shows the header times in UTC and the Apple key with its offset; ffprobe reads the headers as creation_time in UTC and the key as written. It lists creation_time for the file and for each track whose header holds one, so the flower clip shows three and the iPhone-style file one.
What file managers and photo apps show
From the README: written dates were “Read back in ExifTool 13.60, MediaInfo 26.10 and ffprobe 7.0.2. Windows Explorer and Photos (Windows 11) and macOS AVFoundation were checked: they show the mvhd date, not ©day or Apple’s creationdate. Apple Photos and Google Photos display checks are pending (they need a GUI session and an account)”.
Those display checks were run by the OneMedia project, not by us for this page; the table is on its date-writes page. The practical upshot: Windows and macOS show the mvhd time, read as UTC and displayed in the viewer’s own zone.
From the command line
onemedia tags clip.mp4 --capture-time 2024-05-01T00:30:00+02:00 --out redated.mp4 does the same and prints the report; --replace or --in-place writes to the file itself.
Limits
- A preview. The README: “Checked against ExifTool 13.60 and MediaInfo 26.10 on published samples and on files we generate, not on a corpus of device recordings.” Its device-profile caveat applies to the local-time rule: “Device-profile rows for phones and cameras rest on ExifTool’s tag documentation and the few vendor samples available; treat the zone rules for devices not listed there as unverified”.
- Formats. MP4, MOV and 3GP; Matroska and WebM (
DateUTC,DATE_RECORDED); WAV/BWF/RF64; MP3; FLAC, Ogg Vorbis and Opus; ASF. Not AVI, MPEG-TS, PS, FLV, DV, CAF or AIFF. - Not rewritten: GPS times in telemetry tracks (the report lists them as disagreements when they differ),
tmcdtimecode, sidecar files, and the file system’s own dates. - Range. 1904 to 2262; a version 0 header box holds dates to 2040 and is left, with a note, for a later date.
- Saving.
Media.SetCaptureTimewrites a new file;CaptureTimeWriter.Savereplaces the file through a temporary file andSaveInPlacewrites within existing padding only. To change other tags, see Edit MP4, MKV and MP3 tags.