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

1#!/usr/bin/env python3 

2 

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 

14 

15 # Import standard modules ... 

16 import os 

17 import re 

18 import struct 

19 

20 # Open MP4 read-only ... 

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

22 # Create short-hand ... 

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

24 

25 # Set triggers ... 

26 foundFTYP = False 

27 foundMDAT = False 

28 foundMOOV = False 

29 

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] 

35 

36 # Extract atom name ... 

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

38 off += 4 # [B] 

39 

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 

43 

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 

47 

48 # Set trigger ... 

49 foundFTYP = True 

50 

51 # Check if it is the MDAT atom ... 

52 if name == "mdat": 

53 foundMDAT = True 

54 

55 # Check if it is the MOOV atom ... 

56 if name == "moov": 

57 foundMOOV = True 

58 if foundMDAT: 

59 return False 

60 return True 

61 

62 # Check the length ... 

63 if val == 0: 

64 # NOTE: This atom runs until EOF. 

65 

66 # Stop looping ... 

67 break 

68 

69 # Check the length ... 

70 if val == 1: 

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

72 

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] 

77 

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

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

80 

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 

86 

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