Coverage for pyguymer3/media/does_MP4_have_free.py: 77%
26 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 does_MP4_have_free(
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 trigger ...
26 foundFTYP = False
28 # Loop over entire contents of MP4 ...
29 while fObj.tell() < fsize:
30 # Attempt to read 4 bytes as a big-endian un-signed 32-bit integer ...
31 val, = struct.unpack(">I", fObj.read(4)) # [B]
32 off = 4 # [B]
34 # Extract atom name ...
35 name = fObj.read(4).decode("utf-8")
36 off += 4 # [B]
38 # Check that it matches the pattern ...
39 if re.match(r"[a-z][a-z][a-z][a-z]", name) is None:
40 raise Exception(f"\"{name}\" is not an atom name in \"{fname}\"") from None
42 # Check that it is a MP4 file ...
43 if not foundFTYP and name != "ftyp":
44 raise Exception(f"\"{fname}\" is not a MP4") from None
46 # Set trigger ...
47 foundFTYP = True
49 # Check if it is a FREE atom ...
50 if name == "free":
51 return True
53 # Check the length ...
54 if val == 0:
55 # NOTE: This atom runs until EOF.
57 # Stop looping ...
58 break
60 # Check the length ...
61 if val == 1:
62 # NOTE: This atom has 64-bit sizes.
64 # Attempt to read 8 bytes as a big-endian un-signed 64-bit
65 # integer ...
66 val, = struct.unpack(">Q", fObj.read(8)) # [B]
67 off += 8 # [B]
69 # Skip to the end of the atom ...
70 fObj.seek(val - off, os.SEEK_CUR)
72 # Return answer ...
73 return False