Skip to content

Commit cc9a687

Browse files
Merge pull request #322 from nbirkbeck/add-projection-bounds
Adding support for v2 equirect bounds (vr180).
2 parents 14b8642 + c86397f commit cc9a687

File tree

4 files changed

+54
-16
lines changed

4 files changed

+54
-16
lines changed

spatialmedia/__main__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ def main(main_args):
8080
" where w=CroppedAreaImageWidthPixels h=CroppedAreaImageHeightPixels "
8181
"f_w=FullPanoWidthPixels f_h=FullPanoHeightPixels "
8282
"x=CroppedAreaLeftPixels y=CroppedAreaTopPixels")
83+
video_group.add_argument(
84+
"-b",
85+
"--bounds",
86+
action="store",
87+
default=None,
88+
help=
89+
"Equirect projection bounds for VR180. Must specify 4 integers "
90+
"in the form of \"top:bottom:left:right\" where each integer is "
91+
"a 0.32 fixed point value on the amount to crop from the corresponding "
92+
"border")
8393
audio_group = parser.add_argument_group("Spatial Audio")
8494
audio_group.add_argument(
8595
"-a",
@@ -97,7 +107,7 @@ def main(main_args):
97107
console("Injecting metadata requires both an input file and output file.")
98108
return
99109

100-
metadata = metadata_utils.Metadata(args.projection, args.stereo_mode)
110+
metadata = metadata_utils.Metadata(args.projection, args.stereo_mode, args.bounds)
101111
if not args.v2:
102112
metadata.projection = None
103113
metadata.stereo_mode = None

spatialmedia/metadata_utils.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,14 @@
9595
]
9696

9797
class Metadata(object):
98-
def __init__(self, projection=None, stereo_mode=None):
98+
def __init__(self, projection=None, stereo_mode=None, bounds=None):
9999
self.projection = None if (not projection or projection == "none") else projection
100100
self.stereo_mode = None if (not stereo_mode or stereo_mode == "none") else stereo_mode
101+
if bounds:
102+
bounds = [max(0, min(int(b, 0), 0xFFFFFFFF)) for b in bounds.split(":")]
103+
if len(bounds) != 4:
104+
print("Error: input bounds is incorrectly specified")
105+
self.bounds = bounds
101106
self.video = None
102107
self.audio = None
103108

@@ -186,7 +191,7 @@ def mpeg4_add_spherical_xml_v1(mpeg4_file, in_fh, metadata):
186191
mpeg4_file.resize()
187192
return True
188193

189-
def mpeg4_add_spherical_v2(mpeg4_file, in_fh, projection, stereo_mode):
194+
def mpeg4_add_spherical_v2(mpeg4_file, in_fh, projection, stereo_mode, bounds):
190195
for element in mpeg4_file.moov_box.contents:
191196
if element.name == mpeg.constants.TAG_TRAK:
192197
for sub_element in element.contents:
@@ -199,18 +204,20 @@ def mpeg4_add_spherical_v2(mpeg4_file, in_fh, projection, stereo_mode):
199204
in_fh.seek(position)
200205
if in_fh.read(4) == mpeg.constants.TAG_VIDE:
201206
ret = inject_spatial_video_v2_atoms(
202-
in_fh, sub_element, projection, stereo_mode)
207+
in_fh, sub_element, projection, stereo_mode, bounds)
203208
mpeg4_file.resize()
204209
return ret
205210

206211

207-
def inject_spatial_video_v2_atoms(in_fh, video_media_atom, projection, stereo_mode):
212+
def inject_spatial_video_v2_atoms(in_fh, video_media_atom, projection, stereo_mode, bounds):
208213
"""Adds spherical v2 boxes to an mpeg4 file for all video tracks.
209214
210215
Args:
211-
mpeg4_file: mpeg4, Mpeg4 file structure to add metadata.
212216
in_fh: file handle, Source for uncached file contents.
213-
metadata: string, xml metadata to inject into spherical tag.
217+
video_media_atom: parent media atom.
218+
projection: the projection type.
219+
stereo_mode: stereo mode (if 3d), else none.
220+
bounds: equirect bounds.
214221
"""
215222
for atom in video_media_atom.contents:
216223
if atom.name != mpeg.constants.TAG_MINF:
@@ -240,7 +247,7 @@ def inject_spatial_video_v2_atoms(in_fh, video_media_atom, projection, stereo_mo
240247
proj_atom.name = mpeg.constants.TAG_PROJ
241248

242249
proj_atom.add(mpeg.sv3d.PRHDBox.create())
243-
proj_atom.add(mpeg.sv3d.EQUIBox.create())
250+
proj_atom.add(mpeg.sv3d.EQUIBox.create(bounds=bounds))
244251

245252
sv3d_atom = mpeg.container.Container(header_size=8)
246253
sv3d_atom.name = mpeg.constants.TAG_SV3D
@@ -465,7 +472,8 @@ def inject_mpeg4(input_file, output_file, metadata, console):
465472
console("Error failed to insert spherical data")
466473

467474
if ((metadata.projection or metadata.stereo_mode)
468-
and not mpeg4_add_spherical_v2(mpeg4_file, in_fh, metadata.projection, metadata.stereo_mode)):
475+
and not mpeg4_add_spherical_v2(mpeg4_file, in_fh, metadata.projection,
476+
metadata.stereo_mode, metadata.bounds)):
469477
console("Error failed to insert spherical data v2")
470478

471479
if metadata.audio:

spatialmedia/mpeg/sv3d.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,19 +115,19 @@ def load_content(self, in_fh):
115115

116116

117117
class EQUIBox(box.Box):
118-
def __init__(self):
118+
def __init__(self, bounds=None):
119119
box.Box.__init__(self)
120120
self.name = constants.TAG_EQUI
121121
self.header_size = 8
122-
self.bounds_top = 0
123-
self.bounds_bottom = 0
124-
self.bounds_left = 0
125-
self.bounds_right = 0
122+
self.bounds_top = bounds[0] if bounds else 0
123+
self.bounds_bottom = bounds[1] if bounds else 0
124+
self.bounds_left = bounds[2] if bounds else 0
125+
self.bounds_right = bounds[3] if bounds else 0
126126
self.content_size = 20
127127

128128
@staticmethod
129-
def create():
130-
return EQUIBox()
129+
def create(bounds=None):
130+
return EQUIBox(bounds)
131131

132132
def print_box(self, console):
133133
""" Prints the contents of this box to console."""

spatialmedia_test.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,26 @@ def test_inject_v2_equirect_mono(self):
7070
self.assertTrue(contents.find('PRHD') >= 0)
7171
self.assertTrue(contents.find('EQUI') >= 0)
7272
self.assertFalse(contents.find('ST3D') >= 0)
73+
self.assertTrue(contents.find('Bounds Top: 0') >= 0)
74+
self.assertTrue(contents.find('Bounds Bottom: 0') >= 0)
75+
self.assertTrue(contents.find('Bounds Left: 0') >= 0)
76+
self.assertTrue(contents.find('Bounds Right: 0') >= 0)
77+
78+
def test_inject_v2_equirect_mono_with_bounds(self):
79+
contents = self.inject_metadata(['-i',
80+
'--v2',
81+
'--bounds', '0x1:-2:0x7FFFFFFF:32',
82+
'--projection', 'equirectangular',
83+
'data/testsrc_320x240_h264.mp4',
84+
f'{_OUTPUT_DIR}/equirect_mono.mp4'])
85+
self.assertTrue(contents.find('SV3D') >= 0)
86+
self.assertTrue(contents.find('PRHD') >= 0)
87+
self.assertTrue(contents.find('EQUI') >= 0)
88+
self.assertFalse(contents.find('ST3D') >= 0)
89+
self.assertTrue(contents.find('Bounds Top: 1') >= 0)
90+
self.assertTrue(contents.find('Bounds Bottom: 0') >= 0)
91+
self.assertTrue(contents.find('Bounds Left: 2147483647') >= 0)
92+
self.assertTrue(contents.find('Bounds Right: 32') >= 0)
7393

7494
def test_inject_v2_equirect_mono_vp9(self):
7595
contents = self.inject_metadata(['-i',

0 commit comments

Comments
 (0)