Class: FormatParser::MOOVParser::Decoder

Inherits:
Object
  • Object
show all
Defined in:
lib/parsers/moov_parser/decoder.rb

Overview

Handles decoding of MOV/MPEG4 atoms/boxes in a stream. Will recursively read atoms and parse their data fields if applicable. Also contains a few utility functions for finding atoms in a list etc.

Defined Under Namespace

Classes: Atom

Constant Summary collapse

KNOWN_BRANCH_ATOM_TYPES =

Atoms (boxes) that are known to only contain children, no data fields

%w(moov mdia trak clip edts minf dinf stbl udta meta)
KNOWN_BRANCH_AND_LEAF_ATOM_TYPES =

Atoms (boxes) that are known to contain both leaves and data fields

%w(meta)
MAX_ATOMS_AT_LEVEL =

Limit how many atoms we scan in sequence, to prevent derailments

128

Instance Method Summary collapse

Instance Method Details

#extract_atom_stream(io, max_read, current_branch = []) ⇒ Object

Recursive descent parser - will drill down to atoms which we know are permitted to have leaf/branch atoms within itself, and will attempt to recover the data fields for leaf atoms



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/parsers/moov_parser/decoder.rb', line 204

def extract_atom_stream(io, max_read, current_branch = [])
  initial_pos = io.pos
  atoms = []
  MAX_ATOMS_AT_LEVEL.times do
    atom_pos = io.pos

    break if atom_pos - initial_pos >= max_read

    size_and_type = io.read(4 + 4)
    break if size_and_type.to_s.bytesize < 8

    atom_size, atom_type = size_and_type.unpack('Na4')

    # If atom_size is specified to be 1, it is larger than what fits into the
    # 4 bytes and we need to read it right after the atom type
    atom_size = read_64bit_uint(io) if atom_size == 1

    # We are allowed to read what comes after
    # the atom size and atom type, but not any more than that
    size_of_atom_type_and_size = io.pos - atom_pos
    atom_size_sans_header = atom_size - size_of_atom_type_and_size

    children, fields = if KNOWN_BRANCH_AND_LEAF_ATOM_TYPES.include?(atom_type)
      parse_atom_children_and_data_fields(io, atom_size_sans_header, atom_type, current_branch)
    elsif KNOWN_BRANCH_ATOM_TYPES.include?(atom_type)
      [extract_atom_stream(io, atom_size_sans_header, current_branch + [atom_type]), nil]
    else # Assume leaf atom
      [nil, parse_atom_fields_per_type(io, atom_size_sans_header, atom_type)]
    end

    atoms << Atom.new(atom_pos, atom_size, atom_type, current_branch + [atom_type], children, fields)

    io.seek(atom_pos + atom_size)
  end
  atoms
end

#find_first_atom_by_path(atoms, *atom_types) ⇒ Object

Finds the first atom in the given Array of Atom structs that matches the type, drilling down if a list of atom names is given



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/parsers/moov_parser/decoder.rb', line 32

def find_first_atom_by_path(atoms, *atom_types)
  type_to_find = atom_types.shift
  requisite = atoms.find { |e| e.atom_type == type_to_find }

  # Return if we found our match
  return requisite if atom_types.empty?

  # Return nil if we didn't find the match at this nesting level
  return unless requisite

  # ...otherwise drill further down
  find_first_atom_by_path(requisite.children || [], *atom_types)
end

#parse_atom_children_and_data_fields(io, atom_size_sans_header, atom_type, current_branch) ⇒ Object



196
197
198
199
# File 'lib/parsers/moov_parser/decoder.rb', line 196

def parse_atom_children_and_data_fields(io, atom_size_sans_header, atom_type, current_branch)
  parse_atom_fields_per_type(io, atom_size_sans_header, atom_type)
  extract_atom_stream(io, atom_size_sans_header, current_branch + [atom_type])
end

#parse_atom_fields_per_type(io, atom_size, atom_type) ⇒ Object



188
189
190
191
192
193
194
# File 'lib/parsers/moov_parser/decoder.rb', line 188

def parse_atom_fields_per_type(io, atom_size, atom_type)
  if respond_to?("parse_#{atom_type}_atom", true)
    send("parse_#{atom_type}_atom", io, atom_size)
  else
    nil # We can't look inside this leaf atom
  end
end

#parse_dref_atom(io, _) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/parsers/moov_parser/decoder.rb', line 130

def parse_dref_atom(io, _)
  dict = {
    version: read_byte_value(io),
    flags: read_bytes(io, 3),
    num_entries: read_32bit_uint(io),
  }
  num_entries = dict[:num_entries]
  entries = (1..num_entries).map do
    entry = {
      size: read_32bit_uint(io),
      type: read_bytes(io, 4),
      version: read_bytes(io, 1),
      flags: read_bytes(io, 3),
    }
    entry[:data] = read_bytes(io, entry[:size] - 12)
    entry
  end
  dict[:entries] = entries
  dict
end

#parse_elst_atom(io, _) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/parsers/moov_parser/decoder.rb', line 151

def parse_elst_atom(io, _)
  dict = {
    version: read_byte_value(io),
    flags: read_bytes(io, 3),
    num_entries: read_32bit_uint(io),
  }
  is_v1 = dict[:version] == 1 # Usual is 0, version 1 has 64bit durations
  num_entries = dict[:num_entries]
  entries = (1..num_entries).map do
    {
      track_duration: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
      media_time: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
      media_rate: read_32bit_uint(io),
    }
  end
  dict[:entries] = entries
  dict
end

#parse_ftyp_atom(io, atom_size) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/parsers/moov_parser/decoder.rb', line 46

def parse_ftyp_atom(io, atom_size)
  # Subtract 8 for the atom_size+atom_type,
  # and 8 once more for the major_brand and minor_version. The remaining
  # numbr of bytes is reserved for the compatible brands, 4 bytes per
  # brand.
  num_brands = (atom_size - 8 - 8) / 4
  {
    major_brand: read_bytes(io, 4),
    minor_version: read_binary_coded_decimal(io),
    compatible_brands: (1..num_brands).map { read_bytes(io, 4) },
  }
end

#parse_hdlr_atom(io, atom_size) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/parsers/moov_parser/decoder.rb', line 170

def parse_hdlr_atom(io, atom_size)
  sub_io = StringIO.new(io.read(atom_size - 8))
  {
    version: read_byte_value(sub_io),
    flags: read_bytes(sub_io, 3),
    component_type: read_bytes(sub_io, 4),
    component_subtype: read_bytes(sub_io, 4),
    component_manufacturer: read_bytes(sub_io, 4),
    component_flags: read_bytes(sub_io, 4),
    component_flags_mask: read_bytes(sub_io, 4),
    component_name: sub_io.read,
  }
end

#parse_mdhd_atom(io, _) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/parsers/moov_parser/decoder.rb', line 81

def parse_mdhd_atom(io, _)
  version = read_byte_value(io)
  is_v1 = version == 1
  {
    version: version,
    flags: read_bytes(io, 3),
    ctime: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
    mtime: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
    tscale: read_32bit_uint(io),
    duration: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
    language: read_32bit_uint(io),
    quality: read_32bit_uint(io),
  }
end

#parse_meta_atom(io, atom_size) ⇒ Object



184
185
186
# File 'lib/parsers/moov_parser/decoder.rb', line 184

def parse_meta_atom(io, atom_size)
  parse_hdlr_atom(io, atom_size)
end

#parse_mvhd_atom(io, _) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/parsers/moov_parser/decoder.rb', line 107

def parse_mvhd_atom(io, _)
  version = read_byte_value(io)
  is_v1 = version == 1
  {
    version: version,
    flags: read_bytes(io, 3),
    ctime: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
    mtime: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
    tscale: read_32bit_uint(io),
    duration: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
    preferred_rate: read_32bit_uint(io),
    reserved: read_bytes(io, 10),
    matrix_structure: (1..9).map { read_32bit_fixed_point(io) },
    preview_time: read_32bit_uint(io),
    preview_duration: read_32bit_uint(io),
    poster_time: read_32bit_uint(io),
    selection_time: read_32bit_uint(io),
    selection_duration: read_32bit_uint(io),
    current_time: read_32bit_uint(io),
    next_trak_id: read_32bit_uint(io),
  }
end

#parse_tkhd_atom(io, _) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/parsers/moov_parser/decoder.rb', line 59

def parse_tkhd_atom(io, _)
  version = read_byte_value(io)
  is_v1 = version == 1
  {
    version: version,
    flags: read_chars(io, 3),
    ctime: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
    mtime: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
    trak_id: read_32bit_uint(io),
    reserved_1: read_chars(io, 4),
    duration: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
    reserved_2: read_chars(io, 8),
    layer: read_16bit_uint(io),
    alternate_group: read_16bit_uint(io),
    volume: read_16bit_uint(io),
    reserved_3: read_chars(io, 2),
    matrix_structure: (1..9).map { read_32bit_fixed_point(io) },
    track_width: read_32bit_fixed_point(io),
    track_height: read_32bit_fixed_point(io),
  }
end

#parse_vmhd_atom(io, _) ⇒ Object



96
97
98
99
100
101
102
103
104
105
# File 'lib/parsers/moov_parser/decoder.rb', line 96

def parse_vmhd_atom(io, _)
  {
    version: read_byte_value(io),
    flags: read_bytes(io, 3),
    graphics_mode: read_bytes(io, 2),
    opcolor_r: read_32bit_uint(io),
    opcolor_g: read_32bit_uint(io),
    opcolor_b: read_32bit_uint(io),
  }
end

#read_16bit_fixed_point(io) ⇒ Object



241
242
243
# File 'lib/parsers/moov_parser/decoder.rb', line 241

def read_16bit_fixed_point(io)
  _whole, _fraction = io.read(2).unpack('CC')
end

#read_16bit_uint(io) ⇒ Object



261
262
263
# File 'lib/parsers/moov_parser/decoder.rb', line 261

def read_16bit_uint(io)
  io.read(2).unpack('n').first
end

#read_32bit_fixed_point(io) ⇒ Object



245
246
247
# File 'lib/parsers/moov_parser/decoder.rb', line 245

def read_32bit_fixed_point(io)
  _whole, _fraction = io.read(4).unpack('nn')
end

#read_32bit_uint(io) ⇒ Object



265
266
267
# File 'lib/parsers/moov_parser/decoder.rb', line 265

def read_32bit_uint(io)
  io.read(4).unpack('N').first
end

#read_64bit_uint(io) ⇒ Object



269
270
271
# File 'lib/parsers/moov_parser/decoder.rb', line 269

def read_64bit_uint(io)
  io.read(8).unpack('Q>').first
end

#read_binary_coded_decimal(io) ⇒ Object



273
274
275
276
# File 'lib/parsers/moov_parser/decoder.rb', line 273

def read_binary_coded_decimal(io)
  bcd_string = io.read(4)
  [bcd_string].pack('H*').unpack('C*')
end

#read_byte_value(io) ⇒ Object



253
254
255
# File 'lib/parsers/moov_parser/decoder.rb', line 253

def read_byte_value(io)
  io.read(1).unpack('C').first
end

#read_bytes(io, n) ⇒ Object



257
258
259
# File 'lib/parsers/moov_parser/decoder.rb', line 257

def read_bytes(io, n)
  io.read(n)
end

#read_chars(io, n) ⇒ Object



249
250
251
# File 'lib/parsers/moov_parser/decoder.rb', line 249

def read_chars(io, n)
  io.read(n)
end