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

1#!/usr/bin/env python3 

2 

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 

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 trigger ... 

26 foundFTYP = False 

27 

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] 

33 

34 # Extract atom name ... 

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

36 off += 4 # [B] 

37 

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 

41 

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 

45 

46 # Set trigger ... 

47 foundFTYP = True 

48 

49 # Check if it is a FREE atom ... 

50 if name == "free": 

51 return True 

52 

53 # Check the length ... 

54 if val == 0: 

55 # NOTE: This atom runs until EOF. 

56 

57 # Stop looping ... 

58 break 

59 

60 # Check the length ... 

61 if val == 1: 

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

63 

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] 

68 

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

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

71 

72 # Return answer ... 

73 return False