1 """Utilities to help convert mp4s to fmp4s."""
3 from __future__
import annotations
5 from collections.abc
import Generator
6 from typing
import TYPE_CHECKING
10 from .core
import Orientation
13 from io
import BufferedIOBase
17 mp4_bytes: bytes, target_type: bytes, box_start: int = 0
19 """Find location of first box (or sub box if box_start provided) of given type."""
22 box_end = len(mp4_bytes)
24 box_end = box_start + int.from_bytes(
25 mp4_bytes[box_start : box_start + 4], byteorder=
"big"
29 if index > box_end - 8:
31 box_header = mp4_bytes[index : index + 8]
32 if box_header[4:8] == target_type:
34 index += int.from_bytes(box_header[0:4], byteorder=
"big")
38 """Get RFC 6381 codec string."""
42 moov_location = next(
find_box(mp4_bytes, b
"moov"))
45 for trak_location
in find_box(mp4_bytes, b
"trak", moov_location):
47 mdia_location = next(
find_box(mp4_bytes, b
"mdia", trak_location))
48 minf_location = next(
find_box(mp4_bytes, b
"minf", mdia_location))
49 stbl_location = next(
find_box(mp4_bytes, b
"stbl", minf_location))
50 stsd_location = next(
find_box(mp4_bytes, b
"stsd", stbl_location))
53 stsd_length = int.from_bytes(
54 mp4_bytes[stsd_location : stsd_location + 4], byteorder=
"big"
56 stsd_box = mp4_bytes[stsd_location : stsd_location + stsd_length]
59 codec = stsd_box[20:24].decode(
"utf-8")
63 codec
in (
"avc1",
"avc2",
"avc3",
"avc4")
65 and stsd_box[106:110] == b
"avcC"
67 profile = stsd_box[111:112].hex()
68 compatibility = stsd_box[112:113].hex()
70 level = hex(
min(stsd_box[113], 41))[2:]
71 codec +=
"." + profile + compatibility + level
75 codec
in (
"hev1",
"hvc1")
77 and stsd_box[106:110] == b
"hvcC"
79 tmp_byte = int.from_bytes(stsd_box[111:112], byteorder=
"big")
83 profile_space_map = {0:
"", 1:
"A", 2:
"B", 3:
"C"}
84 profile_space = tmp_byte >> 6
85 codec += profile_space_map[profile_space]
86 general_profile_idc = tmp_byte & 31
87 codec +=
str(general_profile_idc)
91 general_profile_compatibility = int.from_bytes(
92 stsd_box[112:116], byteorder=
"big"
96 reverse |= general_profile_compatibility & 1
100 general_profile_compatibility >>= 1
101 codec += hex(reverse)[2:]
104 if (tmp_byte & 32) >> 5 == 0:
108 codec +=
str(int.from_bytes(stsd_box[122:123], byteorder=
"big"))
112 constraint_string =
""
113 for i
in range(121, 115, -1):
114 gci = int.from_bytes(stsd_box[i : i + 1], byteorder=
"big")
116 constraint_string =
"." + hex(gci)[2:] + constraint_string
118 codec += constraint_string
121 elif codec ==
"mp4a":
126 oti_loc = stsd_box.find(b
"\x04\x80\x80\x80")
128 oti = stsd_box[oti_loc + 5 : oti_loc + 6].hex()
131 dsi_loc = stsd_box.find(b
"\x05\x80\x80\x80")
133 dsi_length = int.from_bytes(
134 stsd_box[dsi_loc + 4 : dsi_loc + 5], byteorder=
"big"
136 dsi_data = stsd_box[dsi_loc + 5 : dsi_loc + 5 + dsi_length]
137 dsi0 = int.from_bytes(dsi_data[0:1], byteorder=
"big")
138 dsi = (dsi0 & 248) >> 3
139 if dsi == 31
and len(dsi_data) >= 2:
140 dsi1 = int.from_bytes(dsi_data[1:2], byteorder=
"big")
141 dsi = 32 + ((dsi0 & 7) << 3) + ((dsi1 & 224) >> 5)
146 return ",".join(codecs)
150 """Find location of moov atom in a BufferedIOBase mp4."""
155 box_header = mp4_io.read(8)
156 if len(box_header) != 8
or box_header[0:4] == b
"\x00\x00\x00\x00":
158 if box_header[4:8] == b
"moov":
160 index += int.from_bytes(box_header[0:4], byteorder=
"big")
164 """Read the init from a mp4 file."""
166 bytes_io.seek(moov_loc)
167 moov_len = int.from_bytes(bytes_io.read(4), byteorder=
"big")
169 return bytes_io.read(moov_loc + moov_len)
172 ZERO32 = b
"\x00\x00\x00\x00"
173 ONE32 = b
"\x00\x01\x00\x00"
174 NEGONE32 = b
"\xff\xff\x00\x00"
175 XYW_ROW = ZERO32 + ZERO32 + b
"\x40\x00\x00\x00"
176 ROTATE_RIGHT = (ZERO32 + ONE32 + ZERO32) + (NEGONE32 + ZERO32 + ZERO32)
177 ROTATE_LEFT = (ZERO32 + NEGONE32 + ZERO32) + (ONE32 + ZERO32 + ZERO32)
178 ROTATE_180 = (NEGONE32 + ZERO32 + ZERO32) + (ZERO32 + NEGONE32 + ZERO32)
179 MIRROR = (NEGONE32 + ZERO32 + ZERO32) + (ZERO32 + ONE32 + ZERO32)
180 FLIP = (ONE32 + ZERO32 + ZERO32) + (ZERO32 + NEGONE32 + ZERO32)
182 ROTATE_LEFT_FLIP = (ZERO32 + NEGONE32 + ZERO32) + (NEGONE32 + ZERO32 + ZERO32)
183 ROTATE_RIGHT_FLIP = (ZERO32 + ONE32 + ZERO32) + (ONE32 + ZERO32 + ZERO32)
185 TRANSFORM_MATRIX_TOP = (
202 """Change the transformation matrix in the header."""
203 if orientation == Orientation.NO_TRANSFORM:
206 moov_location = next(
find_box(init, b
"moov"))
207 mvhd_location = next(
find_box(init, b
"trak", moov_location))
208 tkhd_location = next(
find_box(init, b
"tkhd", mvhd_location))
209 tkhd_length = int.from_bytes(
210 init[tkhd_location : tkhd_location + 4], byteorder=
"big"
213 init[: tkhd_location + tkhd_length - 44]
214 + TRANSFORM_MATRIX_TOP[orientation]
216 + init[tkhd_location + tkhd_length - 8 :]
int find_moov(BufferedIOBase mp4_io)
Generator[int] find_box(bytes mp4_bytes, bytes target_type, int box_start=0)
str get_codec_string(bytes mp4_bytes)
bytes transform_init(bytes init, Orientation orientation)
bytes read_init(BufferedIOBase bytes_io)