Skip to content

Commit

Permalink
Enable spatial audio metadata injection for mp4 files.
Browse files Browse the repository at this point in the history
  • Loading branch information
damienkelly committed Jan 15, 2016
1 parent b82b0cf commit d23f7be
Show file tree
Hide file tree
Showing 7 changed files with 490 additions and 15 deletions.
81 changes: 78 additions & 3 deletions spatialmedia/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,20 @@ def action_open(self):
self.set_message("Opened file: %s\n" % ntpath.basename(self.in_file))

console = Console()
metadata = spherical.parse_metadata(self.in_file, console.append)
parsedMetadata = spherical.parse_metadata(self.in_file, console.append)

metadata = None
audio_metadata = None
if parsedMetadata:
metadata = parsedMetadata.video
audio_metadata = parsedMetadata.audio

for line in console.log:
if "Error" in line:
self.set_error("Failed to load file %s"
% ntpath.basename(self.in_file))
self.var_spherical.set(0)
self.var_spatial_audio.set(0)
self.disable_state()
self.button_open.configure(state="normal")
self.button_quit.configure(state="normal")
Expand All @@ -75,10 +82,18 @@ def action_open(self):
self.enable_state()
self.checkbox_spherical.configure(state="normal")

infile = os.path.abspath(self.in_file)
file_extension = os.path.splitext(infile)[1].lower()
self.enable_spatial_audio =\
True if (file_extension == ".mp4") else False

if not metadata:
self.var_spherical.set(0)
self.var_3d.set(0)

if not audio_metadata:
self.var_spatial_audio.set(0)

if metadata:
metadata = metadata.itervalues().next()
self.var_spherical.set(1)
Expand All @@ -93,6 +108,12 @@ def action_open(self):
else:
self.var_3d.set(0)

if audio_metadata:
self.var_spatial_audio.set(1)
self.options_ambisonics["text"] =\
audio_metadata.get_metadata_string()


self.update_state()

def action_inject_delay(self):
Expand All @@ -103,8 +124,19 @@ def action_inject_delay(self):
xml = spherical.generate_spherical_xml(stereo=stereo)

console = Console()
c = console.append

This comment has been minimized.

Copy link
@dcower

dcower Jan 15, 2016

I think you can get rid of this line (looks unused?).

This comment has been minimized.

Copy link
@damienkelly

damienkelly Jan 15, 2016

Author Owner

Done.


metadata = spherical.Metadata()
metadata.video = xml

if self.var_spatial_audio.get():
# Default ambisonics audio metadata
audio_metadata = {'ambisonic_order': 1,
'ambisonic_type': 'periphonic'}
metadata.audio = audio_metadata

spherical.inject_metadata(
self.in_file, self.save_file, xml, console.append)
self.in_file, self.save_file, metadata, console.append)
self.set_message("Successfully saved file to %s\n"
% ntpath.basename(self.save_file))
self.button_open.configure(state="normal")
Expand All @@ -131,6 +163,9 @@ def action_inject(self):
def action_set_spherical(self):
self.update_state()

def action_set_spatial_audio(self):
self.update_state()

def action_set_3d(self):
self.update_state()

Expand All @@ -140,8 +175,10 @@ def enable_state(self):

def disable_state(self):
self.checkbox_spherical.configure(state="disabled")
self.checkbox_spatial_audio.configure(state="disabled")
self.checkbox_3D.configure(state="disabled")
self.options_projection.configure(state="disabled")
self.options_ambisonics.configure(state="disabled")
self.button_inject.configure(state="disabled")
self.button_open.configure(state="disabled")
self.button_quit.configure(state="disabled")
Expand All @@ -152,10 +189,22 @@ def update_state(self):
self.checkbox_3D.configure(state="normal")
self.options_projection.configure(state="normal")
self.button_inject.configure(state="normal")
if self.enable_spatial_audio:
self.checkbox_spatial_audio.configure(state="normal")
if self.var_spatial_audio.get():
self.options_ambisonics.configure(state="normal")
else:
self.options_ambisonics.configure(state="disable")
else:
self.checkbox_3D.configure(state="disabled")
self.options_projection.configure(state="disabled")
self.button_inject.configure(state="disabled")
self.checkbox_spatial_audio.configure(state="disabled")
if self.var_spatial_audio.get():
self.options_ambisonics.configure(state="normal")
else:
self.options_ambisonics.configure(state="disable")
self.options_ambisonics.configure(state="disabled")

def set_error(self, text):
self.label_message["text"] = text
Expand Down Expand Up @@ -185,6 +234,20 @@ def create_widgets(self):
self.checkbox_spherical["command"] = self.action_set_spherical
self.checkbox_spherical.grid(row=row, column=column, padx=14, pady=2)

# Spatial Audio Checkbox
row += 1
column = 0
self.label_spatial_audio = Label(self)
self.label_spatial_audio["text"] = "Spatial Audio"
self.label_spatial_audio.grid(row=row, column=column)

column += 1
self.var_spatial_audio = IntVar()
self.checkbox_spatial_audio = \
Checkbutton(self, variable=self.var_spatial_audio)
self.checkbox_spatial_audio["command"] = self.action_set_spatial_audio
self.checkbox_spatial_audio.grid(row=row, column=column, padx=0, pady=0)

# 3D
column = 0
row = row + 1
Expand All @@ -210,6 +273,18 @@ def create_widgets(self):
self.options_projection["text"] = "Equirectangular"
self.options_projection.grid(row=row, column=column, padx=14, pady=2)

# Ambisonics Type
column = 0
row = row + 1
self.label_ambisonics = Label(self)
self.label_ambisonics["text"] = "Ambisonics Type"
self.label_ambisonics.grid(row=row, column=column, padx=14, pady=2)
column += 1

self.options_ambisonics = Label(self)
self.options_ambisonics["text"] = "1st Order, ACN, SN3D, Periphonic"
self.options_ambisonics.grid(row=row, column=column, padx=14, pady=2)

# Message Box
row = row + 1
column = 0
Expand Down Expand Up @@ -261,7 +336,7 @@ def __init__(self, master=None):
master.attributes("-topmost", True)
master.focus_force()
self.after(50, lambda: master.attributes("-topmost", False))

self.enable_spatial_audio = False

def main():
root = Tk()
Expand Down
4 changes: 3 additions & 1 deletion spatialmedia/mpeg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import spatialmedia.mpeg.sa3d
import spatialmedia.mpeg.box
import spatialmedia.mpeg.constants
import spatialmedia.mpeg.container
Expand All @@ -23,7 +24,8 @@
load = mpeg4_container.load

Box = box.Box
SA3DBox = sa3d.SA3DBox
Container = container.Container
Mpeg4Container = mpeg4_container.Mpeg4Container

__all__ = ["box", "mpeg4", "container", "constants"]
__all__ = ["box", "mpeg4", "container", "constants", "sa3d"]
7 changes: 7 additions & 0 deletions spatialmedia/mpeg/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,29 @@
TAG_XML = "xml "
TAG_HDLR = "hdlr"
TAG_FTYP = "ftyp"
TAG_ESDS = "esds"
TAG_SOUN = "soun"

# Container types.
TAG_MOOV = "moov"
TAG_UDTA = "udta"
TAG_META = "meta"
TAG_TRAK = "trak"
TAG_MDIA = "mdia"
TAG_MP4A = "mp4a"
TAG_MINF = "minf"
TAG_STBL = "stbl"
TAG_STSD = "stsd"
TAG_UUID = "uuid"
TAG_SA3D = "SA3D"

CONTAINERS_LIST = [
TAG_MDIA,
TAG_MINF,
TAG_MP4A,
TAG_MOOV,
TAG_STBL,
TAG_STSD,
TAG_TRAK,
TAG_UDTA,
]
35 changes: 30 additions & 5 deletions spatialmedia/mpeg/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from spatialmedia.mpeg import box
from spatialmedia.mpeg import constants

from spatialmedia.mpeg import sa3d

def load(fh, position, end):
if position is None:
Expand All @@ -37,7 +37,10 @@ def load(fh, position, end):
name = fh.read(4)

if name not in constants.CONTAINERS_LIST:
return box.load(fh, position, end)
if name == constants.TAG_SA3D:
return sa3d.load(fh, position, end)
else:
return box.load(fh, position, end)

if size == 1:
size = struct.unpack(">Q", fh.read(8))[0]
Expand All @@ -51,13 +54,30 @@ def load(fh, position, end):
print "Error: Container box size exceeds bounds."
return None

padding = 0
stsd_version = 0
if (name == constants.TAG_STSD):
padding = 8

if (name == constants.TAG_MP4A):
current_pos = fh.tell()
fh.seek(current_pos + 8)
sample_description_version = struct.unpack(">h", fh.read(2))[0]
fh.seek(current_pos)

if sample_description_version == 1:
padding = 28+16 # Mov
else:
padding = 28 # Mp4

new_box = Container()
new_box.name = name
new_box.position = position
new_box.header_size = header_size
new_box.content_size = size - header_size
new_box.padding = padding
new_box.contents = load_multiple(
fh, position + header_size, position + size)
fh, position + header_size + padding, position + size)

if new_box.contents is None:
return None
Expand All @@ -81,16 +101,17 @@ def load_multiple(fh, position=None, end=None):
class Container(box.Box):
"""MPEG4 container box contents / behaviour."""

def __init__(self):
def __init__(self, padding=0):
self.name = ""
self.position = 0
self.header_size = 0
self.content_size = 0
self.contents = list()
self.padding = padding

def resize(self):
"""Recomputes the box size and recurses on contents."""
self.content_size = 0
self.content_size = self.padding
for element in self.contents:
if isinstance(element, Container):
element.resize()
Expand Down Expand Up @@ -176,5 +197,9 @@ def save(self, in_fh, out_fh, delta):
out_fh.write(struct.pack(">I", self.size()))
out_fh.write(self.name)

if self.padding > 0:
in_fh.seek(self.content_start())
box.tag_copy(in_fh, out_fh, self.padding)

for element in self.contents:
element.save(in_fh, out_fh, delta)
1 change: 1 addition & 0 deletions spatialmedia/mpeg/mpeg4_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def __init__(self):
self.first_mdat_box = None
self.ftyp_box = None
self.first_mdat_position = None
self.padding = 0

def merge(self, element):
"""Mpeg4 containers do not support merging."""
Expand Down
Loading

0 comments on commit d23f7be

Please sign in to comment.