Getting metadata for MOV video
Asked Answered
G

6

19

I' ve a .MOV video sent by a phone messanger app. Can I retrieve the real creation data of the file and the author? I tried with ffprobe, mediainfo and similar tool but give me only the date when I download it.

Gooden answered 25/1, 2014 at 19:39 Comment(3)
Are you prepared to dig into the binary file programmatically? If so, what language? Not sure of a tool that will do it since you have already tried to usual suspects.Investiture
I' ve not tried to do it. Could you explain me how i can?Gooden
There is no need for some pythoh script, you can directly extract it using exiftools: exiftool -time:all vid.movSorkin
I
11

I wrote a quick Python 2 script that can obtain the creation and modification timestamps, since those are easy to find. Finding the author is a bit harder because it can be stored in several different ways. Example usage:

$ ./mov-timestamps.py file.mov
creation date: 2013-03-29 16:14:01
modification date: 2013-03-29 16:14:13

Sometimes you might see a date of 1/1/1904. That means the timestamp is 0. If you see a date of 1/1/1970, the file was probably generated by FFmpeg, which doesn't store this metadata for security reasons.

#!/usr/bin/python

import datetime
import struct
import sys

ATOM_HEADER_SIZE = 8
# difference between Unix epoch and QuickTime epoch, in seconds
EPOCH_ADJUSTER = 2082844800

if len(sys.argv) < 2:
    print "USAGE: mov-length.py <file.mov>"
    sys.exit(1)

# open file and search for moov item
f = open(sys.argv[1], "rb")
while 1:
    atom_header = f.read(ATOM_HEADER_SIZE)
    if atom_header[4:8] == 'moov':
        break
    else:
        atom_size = struct.unpack(">I", atom_header[0:4])[0]
        f.seek(atom_size - 8, 1)

# found 'moov', look for 'mvhd' and timestamps
atom_header = f.read(ATOM_HEADER_SIZE)
if atom_header[4:8] == 'cmov':
    print "moov atom is compressed"
elif atom_header[4:8] != 'mvhd':
    print "expected to find 'mvhd' header"
else:
    f.seek(4, 1)
    creation_date = struct.unpack(">I", f.read(4))[0]
    modification_date = struct.unpack(">I", f.read(4))[0]
    print "creation date:",
    print datetime.datetime.utcfromtimestamp(creation_date - EPOCH_ADJUSTER)
    print "modification date:",
    print datetime.datetime.utcfromtimestamp(modification_date - EPOCH_ADJUSTER)
Investiture answered 28/1, 2014 at 2:35 Comment(12)
Thanks for the script. I run it but it gives me the date in which I downloaded it on pc. So I think that the metadata are been overwritten.Gooden
If you're game, you could extend the script so that it digs a little further into the file and finds the creation/modification times of the trak atoms.Investiture
I don' t know how to do this. Can you link me something(tutorial, guide)?Gooden
The creation_date becomes 0, when running the script. I searched the binary code and found no "moov". So don't believe that this script will work. But thank you for trying to help me out.Rarotonga
@Rarotonga A QuickTime/MP4 file absolutely needs to have a 'moov' and an 'mdat' item. If either of those are missing, it's not a valid file.Investiture
@MultimediaMike I made a mistake by just opening it with notepad++ and searching for moov and then close it. Clearly not the right way to do it.Rarotonga
True. Use a program called a hex viewer or hex editor.Investiture
@MultimediaMike I tried this and it worked fine for my mov files, but for the mp4 file, it says that the creation date is in the year 1946. It should be 2012. Am I doing something wrong? My file is here: dropbox.com/s/6abygjx0dikiqf4/2012-04-16%2020.44.38.mp4?dl=0Trudeau
@MultimediaMike your script works great for videos made with GoPro, but i'm trying it with .mov videos from iPhone and looks like dates are stored in UTC timezone. It looks strange because when I'm looking on the apple's live photo (image + small video), I see date for photo stored in local time, but date for movie is stored in UTC. Thought maybe Apple is storing date in local timezone somewhere else (not in mvhd)?Radioactivate
@Multimedia Mike: thank you. This does work with Python2 but with 3 it gives an error: Traceback (most recent call last): File "mov_timestamp.py", line 22, in <module> atom_size = struct.unpack(">I", atom_header[0:4])[0] struct.error: unpack requires a buffer of 4 bytes Any idea how to change it for Python3? Thanks!Circuitry
I had to replace the line" if atom_header[4:8] == 'moov': with if atom_header[4:8].decode('utf-8') == 'moov': to make the first block match the 'moov' target in a .mp4 from Canon (Python 3.10)Edaedacious
Thanks for the update. I'm sure I wrote this against Python2 back in the day, which was looser about encodings.Investiture
J
12

So, I updated MMM's code to Python3 and improved a few things.

def get_mov_timestamps(filename):
    ''' Get the creation and modification date-time from .mov metadata.

        Returns None if a value is not available.
    '''
    from datetime import datetime as DateTime
    import struct

    ATOM_HEADER_SIZE = 8
    # difference between Unix epoch and QuickTime epoch, in seconds
    EPOCH_ADJUSTER = 2082844800

    creation_time = modification_time = None

    # search for moov item
    with open(filename, "rb") as f:
        while True:
            atom_header = f.read(ATOM_HEADER_SIZE)
            #~ print('atom header:', atom_header)  # debug purposes
            if atom_header[4:8] == b'moov':
                break  # found
            else:
                atom_size = struct.unpack('>I', atom_header[0:4])[0]
                f.seek(atom_size - 8, 1)

        # found 'moov', look for 'mvhd' and timestamps
        atom_header = f.read(ATOM_HEADER_SIZE)
        if atom_header[4:8] == b'cmov':
            raise RuntimeError('moov atom is compressed')
        elif atom_header[4:8] != b'mvhd':
            raise RuntimeError('expected to find "mvhd" header.')
        else:
            f.seek(4, 1)
            creation_time = struct.unpack('>I', f.read(4))[0] - EPOCH_ADJUSTER
            creation_time = DateTime.fromtimestamp(creation_time)
            if creation_time.year < 1990:  # invalid or censored data
                creation_time = None

            modification_time = struct.unpack('>I', f.read(4))[0] - EPOCH_ADJUSTER
            modification_time = DateTime.fromtimestamp(modification_time)
            if modification_time.year < 1990:  # invalid or censored data
                modification_time = None

    return creation_time, modification_time

and...

Wouldn't you know it, just as I finished I found how to do it with exiftool, which I use for similar tasks with .jpg files. :-/

⏵ exiftool -time:all img_3904.mov
Jerald answered 14/2, 2019 at 4:36 Comment(0)
I
11

I wrote a quick Python 2 script that can obtain the creation and modification timestamps, since those are easy to find. Finding the author is a bit harder because it can be stored in several different ways. Example usage:

$ ./mov-timestamps.py file.mov
creation date: 2013-03-29 16:14:01
modification date: 2013-03-29 16:14:13

Sometimes you might see a date of 1/1/1904. That means the timestamp is 0. If you see a date of 1/1/1970, the file was probably generated by FFmpeg, which doesn't store this metadata for security reasons.

#!/usr/bin/python

import datetime
import struct
import sys

ATOM_HEADER_SIZE = 8
# difference between Unix epoch and QuickTime epoch, in seconds
EPOCH_ADJUSTER = 2082844800

if len(sys.argv) < 2:
    print "USAGE: mov-length.py <file.mov>"
    sys.exit(1)

# open file and search for moov item
f = open(sys.argv[1], "rb")
while 1:
    atom_header = f.read(ATOM_HEADER_SIZE)
    if atom_header[4:8] == 'moov':
        break
    else:
        atom_size = struct.unpack(">I", atom_header[0:4])[0]
        f.seek(atom_size - 8, 1)

# found 'moov', look for 'mvhd' and timestamps
atom_header = f.read(ATOM_HEADER_SIZE)
if atom_header[4:8] == 'cmov':
    print "moov atom is compressed"
elif atom_header[4:8] != 'mvhd':
    print "expected to find 'mvhd' header"
else:
    f.seek(4, 1)
    creation_date = struct.unpack(">I", f.read(4))[0]
    modification_date = struct.unpack(">I", f.read(4))[0]
    print "creation date:",
    print datetime.datetime.utcfromtimestamp(creation_date - EPOCH_ADJUSTER)
    print "modification date:",
    print datetime.datetime.utcfromtimestamp(modification_date - EPOCH_ADJUSTER)
Investiture answered 28/1, 2014 at 2:35 Comment(12)
Thanks for the script. I run it but it gives me the date in which I downloaded it on pc. So I think that the metadata are been overwritten.Gooden
If you're game, you could extend the script so that it digs a little further into the file and finds the creation/modification times of the trak atoms.Investiture
I don' t know how to do this. Can you link me something(tutorial, guide)?Gooden
The creation_date becomes 0, when running the script. I searched the binary code and found no "moov". So don't believe that this script will work. But thank you for trying to help me out.Rarotonga
@Rarotonga A QuickTime/MP4 file absolutely needs to have a 'moov' and an 'mdat' item. If either of those are missing, it's not a valid file.Investiture
@MultimediaMike I made a mistake by just opening it with notepad++ and searching for moov and then close it. Clearly not the right way to do it.Rarotonga
True. Use a program called a hex viewer or hex editor.Investiture
@MultimediaMike I tried this and it worked fine for my mov files, but for the mp4 file, it says that the creation date is in the year 1946. It should be 2012. Am I doing something wrong? My file is here: dropbox.com/s/6abygjx0dikiqf4/2012-04-16%2020.44.38.mp4?dl=0Trudeau
@MultimediaMike your script works great for videos made with GoPro, but i'm trying it with .mov videos from iPhone and looks like dates are stored in UTC timezone. It looks strange because when I'm looking on the apple's live photo (image + small video), I see date for photo stored in local time, but date for movie is stored in UTC. Thought maybe Apple is storing date in local timezone somewhere else (not in mvhd)?Radioactivate
@Multimedia Mike: thank you. This does work with Python2 but with 3 it gives an error: Traceback (most recent call last): File "mov_timestamp.py", line 22, in <module> atom_size = struct.unpack(">I", atom_header[0:4])[0] struct.error: unpack requires a buffer of 4 bytes Any idea how to change it for Python3? Thanks!Circuitry
I had to replace the line" if atom_header[4:8] == 'moov': with if atom_header[4:8].decode('utf-8') == 'moov': to make the first block match the 'moov' target in a .mp4 from Canon (Python 3.10)Edaedacious
Thanks for the update. I'm sure I wrote this against Python2 back in the day, which was looser about encodings.Investiture
H
7

Did you try hachoir? Install it with pip install hachoir, and then, at the command line:

$ hachoir-metadata IMG_9395.MOV

which returns e.g.

Metadata:
- Duration: 2 sec 220 ms
- Image width: 1440 pixels
- Image height: 1080 pixels
- Creation date: 2020-04-15 20:22:57
- Last modification: 2020-04-15 20:22:58
- Comment: Play speed: 100.0%
- Comment: User volume: 100.0%
- MIME type: video/quicktime
- Endianness: Big endian

You can also use it in Python if you prefer:

from hachoir.parser import createParser
from hachoir.metadata import extractMetadata


def creation_date(filename):
    parser = createParser(filename)
    metadata = extractMetadata(parser)
    return metadata.get('creation_date')
Halfback answered 11/5, 2020 at 20:35 Comment(0)
S
5

Here is a version that is not pure python but instead requires libmediainfo that is a part of the mediainfo tool.

import pymediainfo
import sys

media_info = pymediainfo.MediaInfo.parse(sys.argv[1])

#For the first track - otherwise iterate over each track
print(' Encoded date {}'.format(track[0].encoded_date))
print(' Tagged date {}'.format(track[0].tagged_date))
Scaphoid answered 26/4, 2020 at 16:33 Comment(0)
M
0

QuickTime stores the video creation date in the MediaCreateDate XMP field or at least this is what Exiv2 thinks it is in. That said you can use py3exiv2 to read the creation date from the MOV file metadata.

import time
import pyexiv2

from datetime import datetime

# The XMP field that QuickTime stores media creation date in
MEDIA_CREATE_KEY = 'Xmp.video.MediaCreateDate'

# HFS Epoch offset, difference between 01-01-1904 and 01-01-1970
HFS_EPOCH_OFFSET = 2082844800

def get_ctime_exiv2_mov(fpath: str) -> datetime:
    ret = None

    metadata = pyexiv2.ImageMetadata(fpath)
    metadata.read()

    if MEDIA_CREATE_KEY in metadata:
        _ts_hfs = metadata[MEDIA_CREATE_KEY].raw_value.strip()

        if _ts_hfs and _ts_hfs.isnumeric():
            ts_hfs = int(_ts_hfs)

            if ts_hfs > HFS_EPOCH_OFFSET:
                ts_unix = ts_hfs - HFS_EPOCH_OFFSET

                if ts_unix < time.time():
                    ret = datetime.fromtimestamp(ts_unix)

    return ret
Mecham answered 10/7, 2023 at 12:32 Comment(0)
B
0

from @jeanggi90's comment and the addendum to Gringo Suave's answer, posting separately so it is easier to find:

exiftool -time:all img_3904.mov

I found that it can even set the file modification time accordingly, e.g. to the "Create Date":

exiftool '-FileModifyDate<CreateDate' img_3904.mov
Billboard answered 8/10, 2023 at 16:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.