Coverage for pyguymer3/media/print_MP4_atoms.py: 3%

29 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-08 18:47 +0000

1#!/usr/bin/env python3 

2 

3# Define function ... 

4def print_MP4_atoms( 

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 

14 

15 # Import standard modules ... 

16 import os 

17 import re 

18 import struct 

19 

20 # Import sub-functions ... 

21 from ..convert_bytes_to_pretty_bytes import convert_bytes_to_pretty_bytes 

22 

23 # Open MP4 read-only ... 

24 with open(fname, "rb") as fObj: 

25 # Create short-hand ... 

26 fsize = os.path.getsize(fname) # [B] 

27 

28 # Set trigger ... 

29 foundFTYP = False 

30 

31 # Loop over entire contents of MP4 ... 

32 while fObj.tell() < fsize: 

33 # Attempt to read 4 bytes as a big-endian un-signed 32-bit integer ... 

34 val, = struct.unpack(">I", fObj.read(4)) # [B] 

35 off = 4 # [B] 

36 

37 # Extract atom name ... 

38 name = fObj.read(4).decode("utf-8") 

39 off += 4 # [B] 

40 

41 # Check that it matches the pattern ... 

42 if re.match(r"[a-z][a-z][a-z][a-z]", name) is None: 

43 raise Exception(f"\"{name}\" is not an atom name in \"{fname}\"") from None 

44 

45 # Check that it is a MP4 file ... 

46 if not foundFTYP and name != "ftyp": 

47 raise Exception(f"\"{fname}\" is not a MP4") from None 

48 

49 # Set trigger ... 

50 foundFTYP = True 

51 

52 # Check the length ... 

53 if val == 0: 

54 # NOTE: This atom runs until EOF. 

55 

56 # Print summary ... 

57 print(f"{name} is the remainder of the file") 

58 

59 # Stop looping ... 

60 break 

61 

62 # Check the length ... 

63 if val == 1: 

64 # NOTE: This atom has 64-bit sizes. 

65 

66 # Attempt to read 8 bytes as a big-endian un-signed 64-bit 

67 # integer ... 

68 val, = struct.unpack(">Q", fObj.read(8)) # [B] 

69 off += 8 # [B] 

70 

71 # Print summary ... 

72 size, units = convert_bytes_to_pretty_bytes(val) 

73 print(f"{name} is {size:6.1f} {units:3s} long (as a 64-bit atom)") 

74 else: 

75 # Print summary ... 

76 size, units = convert_bytes_to_pretty_bytes(val) 

77 print(f"{name} is {size:6.1f} {units:3s} long (as a 32-bit atom)") 

78 

79 # Skip to the end of the atom ... 

80 fObj.seek(val - off, os.SEEK_CUR)