Coverage for pyguymer3/media/is_moov_at_beginning_of_MP4.py: 68%
37 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
1#!/usr/bin/env python3
3# Define function ...
4def is_moov_at_beginning_of_MP4(
5 fname,
6 /,
7):
8 # NOTE: The following websites have some very useful information on how to
9 # parse MP4 files - the first just forgot to say that integers are
10 # big-endian.
11 # * http://atomicparsley.sourceforge.net/mpeg-4files.html
12 # * https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html
13 # * https://wiki.multimedia.cx/index.php/QuickTime_container
15 # Import standard modules ...
16 import os
17 import re
18 import struct
20 # Open MP4 read-only ...
21 with open(fname, "rb") as fObj:
22 # Create short-hand ...
23 fsize = os.path.getsize(fname) # [B]
25 # Set triggers ...
26 foundFTYP = False
27 foundMDAT = False
28 foundMOOV = False
30 # Loop over entire contents of MP4 ...
31 while fObj.tell() < fsize:
32 # Attempt to read 4 bytes as a big-endian un-signed 32-bit integer ...
33 val, = struct.unpack(">I", fObj.read(4)) # [B]
34 off = 4 # [B]
36 # Extract atom name ...
37 name = fObj.read(4).decode("utf-8")
38 off += 4 # [B]
40 # Check that it matches the pattern ...
41 if re.match(r"[a-z][a-z][a-z][a-z]", name) is None:
42 raise Exception(f"\"{name}\" is not an atom name in \"{fname}\"") from None
44 # Check that it is a MP4 file ...
45 if not foundFTYP and name != "ftyp":
46 raise Exception(f"\"{fname}\" is not a MP4") from None
48 # Set trigger ...
49 foundFTYP = True
51 # Check if it is the MDAT atom ...
52 if name == "mdat":
53 foundMDAT = True
55 # Check if it is the MOOV atom ...
56 if name == "moov":
57 foundMOOV = True
58 if foundMDAT:
59 return False
60 return True
62 # Check the length ...
63 if val == 0:
64 # NOTE: This atom runs until EOF.
66 # Stop looping ...
67 break
69 # Check the length ...
70 if val == 1:
71 # NOTE: This atom has 64-bit sizes.
73 # Attempt to read 8 bytes as a big-endian un-signed 64-bit
74 # integer ...
75 val, = struct.unpack(">Q", fObj.read(8)) # [B]
76 off += 8 # [B]
78 # Skip to the end of the atom ...
79 fObj.seek(val - off, os.SEEK_CUR)
81 # Catch possible errors ...
82 if not foundMDAT:
83 raise Exception(f"did not find mdat atom in \"{fname}\"") from None
84 if not foundMOOV:
85 raise Exception(f"did not find moov atom in \"{fname}\"") from None
87 # Return answer ...
88 # NOTE: This cannot happen, but PyLint can't figure that out, so I have
89 # added a return statement to shut PyLint up.
90 return False