format_parser 0.25.0 → 0.25.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c24092543b6c98c713b07a45c3e3a2990332858397e11f6709470c32343a62f3
4
- data.tar.gz: 0222b05ddfa1efa7cee364db7ad61ae7d44806b9622d6d6012fbb50e1b2e8138
3
+ metadata.gz: 87adbfef15c2281ab6a13f151b857409f0ffad0ecc5270d9d0bbc5cebe207cdb
4
+ data.tar.gz: 332e3c4efd4ae01b3cf47c669debba7cc1aee5c264bef503720f632f6d801054
5
5
  SHA512:
6
- metadata.gz: 67a3a64166115ef70b2043c05e9c105c2939752b1de34086421a2b96311880673d1c668927eecf904e06db368cc9979218d666004b59a187f52e569f47e9d2a3
7
- data.tar.gz: 5f4034dbcc2a92cb4908c6609465fbb81ec5011f42f22e9c5477b72a26ee4b59bbaa1191a13a5405d71f68ac8b8c0ceac652befd9eb2c1ea9fc5af000b221828
6
+ metadata.gz: 231d768d4b69b2c2f29bcb861888a7fbb0f4242eec5c9313d6428c9053b4fb4e7d20b8615d731a695c278ac59d3bf07b4977d217892aa1fce8fa7adc9d415efa
7
+ data.tar.gz: 732fae92e71f6a25fa98b7d88fa70b82404f00c234a6debd641e20ad8ffd9e45c78270f20adb8ad5593cd815e128a2a409ec639351c985b79763d4bfd821fec9
@@ -1,3 +1,8 @@
1
+ ## 0.25.1
2
+ * MOV: Fix error "negative length"
3
+ * MOV: Fix reading dimensions in multi-track files
4
+ * MP3: Fix parse of the Xing header to not raise errors
5
+
1
6
  ## 0.25.0
2
7
  * MP3: add suport to id3 v2.4.x
3
8
  * JPEG: Update gem exifr to 1.3.8 to fix a bug
@@ -1,3 +1,3 @@
1
1
  module FormatParser
2
- VERSION = '0.25.0'
2
+ VERSION = '0.25.1'
3
3
  end
@@ -38,14 +38,8 @@ class FormatParser::MOOVParser
38
38
  ftyp_atom = decoder.find_first_atom_by_path(atom_tree, 'ftyp')
39
39
  file_type = ftyp_atom.field_value(:major_brand)
40
40
 
41
- width = nil
42
- height = nil
43
-
44
41
  # Try to find the width and height in the tkhd
45
- if tkhd = decoder.find_first_atom_by_path(atom_tree, 'moov', 'trak', 'tkhd')
46
- width = tkhd.field_value(:track_width).first
47
- height = tkhd.field_value(:track_height).first
48
- end
42
+ width, height = parse_dimensions(decoder, atom_tree)
49
43
 
50
44
  # Try to find the "topmost" duration (respecting edits)
51
45
  if mdhd = decoder.find_first_atom_by_path(atom_tree, 'moov', 'mvhd')
@@ -78,6 +72,31 @@ class FormatParser::MOOVParser
78
72
  FTYP_MAP.fetch(file_type.downcase, :mov)
79
73
  end
80
74
 
75
+ # The dimensions are located in tkhd atom, but in some files it is necessary
76
+ # to get it below the video track, because it can have other tracks such as
77
+ # audio which does not have the dimensions.
78
+ # More details in https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-DontLinkElementID_147
79
+ #
80
+ # Returns [width, height] if the dimension is found
81
+ # Returns [nil, nil] if the dimension is not found
82
+ def parse_dimensions(decoder, atom_tree)
83
+ video_trak_atom = decoder.find_video_trak_atom(atom_tree)
84
+
85
+ tkhd = begin
86
+ if video_trak_atom
87
+ decoder.find_first_atom_by_path([video_trak_atom], 'trak', 'tkhd')
88
+ else
89
+ decoder.find_first_atom_by_path(atom_tree, 'moov', 'trak', 'tkhd')
90
+ end
91
+ end
92
+
93
+ if tkhd
94
+ [tkhd.field_value(:track_width).first, tkhd.field_value(:track_height).first]
95
+ else
96
+ [nil, nil]
97
+ end
98
+ end
99
+
81
100
  # An MPEG4/MOV/M4A will start with the "ftyp" atom. The atom must have a length
82
101
  # of at least 8 (to accomodate the atom size and the atom type itself) plus the major
83
102
  # and minor version fields. If we cannot find it we can be certain this is not our file.
@@ -1,6 +1,7 @@
1
1
  # Handles decoding of MOV/MPEG4 atoms/boxes in a stream. Will recursively
2
2
  # read atoms and parse their data fields if applicable. Also contains
3
3
  # a few utility functions for finding atoms in a list etc.
4
+ # To know more about Atoms: https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html
4
5
  class FormatParser::MOOVParser::Decoder
5
6
  include FormatParser::IOUtils
6
7
 
@@ -47,6 +48,34 @@ class FormatParser::MOOVParser::Decoder
47
48
  find_first_atom_by_path(requisite.children || [], *atom_types)
48
49
  end
49
50
 
51
+ def find_atoms_by_path(atoms, atom_types)
52
+ type_to_find = atom_types.shift
53
+ requisites = atoms.select { |e| e.atom_type == type_to_find }
54
+
55
+ # Return if we found our match
56
+ return requisites if atom_types.empty?
57
+
58
+ # Return nil if we didn't find the match at this nesting level
59
+ return unless requisites
60
+
61
+ # ...otherwise drill further down
62
+ find_atoms_by_path(requisites.flat_map(&:children).compact || [], atom_types)
63
+ end
64
+
65
+ # A file can have multiple tracks. To identify the type it is necessary to check
66
+ # the fields `omponent_subtype` in hdlr atom under the trak atom
67
+ # More details in https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-DontLinkElementID_147
68
+ def find_video_trak_atom(atoms)
69
+ trak_atoms = find_atoms_by_path(atoms, ['moov', 'trak'])
70
+
71
+ return [] if trak_atoms.empty?
72
+
73
+ trak_atoms.find do |trak_atom|
74
+ hdlr_atom = find_first_atom_by_path([trak_atom], 'trak', 'mdia', 'hdlr')
75
+ hdlr_atom.atom_fields[:component_type] == 'mhlr' && hdlr_atom.atom_fields[:component_subtype] == 'vide'
76
+ end
77
+ end
78
+
50
79
  def parse_ftyp_atom(io, atom_size)
51
80
  # Subtract 8 for the atom_size+atom_type,
52
81
  # and 8 once more for the major_brand and minor_version. The remaining
@@ -194,6 +223,8 @@ class FormatParser::MOOVParser::Decoder
194
223
  end
195
224
 
196
225
  def parse_meta_atom(io, atom_size)
226
+ return if atom_size == 0 # this atom can be empty
227
+
197
228
  parse_hdlr_atom(io, atom_size)
198
229
  end
199
230
 
@@ -249,16 +249,16 @@ class FormatParser::MP3Parser
249
249
  io.seek(xing_offset + 4) # Include the length of "Xing" itself
250
250
 
251
251
  # https://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header#XINGHeader
252
- header_flags, _ = io.read(4).unpack('s>s>')
252
+ header_flags, _ = io.read(4).unpack('i>')
253
253
  frames = byte_count = toc = vbr_scale = nil
254
254
 
255
- frames = io.read(4).unpack('N1').first if header_flags & 1 # FRAMES FLAG
255
+ frames = io.read(4).unpack('N1').first if header_flags & 1 != 0 # FRAMES FLAG
256
256
 
257
- byte_count = io.read(4).unpack('N1').first if header_flags & 2 # BYTES FLAG
257
+ byte_count = io.read(4).unpack('N1').first if header_flags & 2 != 0 # BYTES FLAG
258
258
 
259
- toc = io.read(100).unpack('C100') if header_flags & 4 # TOC FLAG
259
+ toc = io.read(100).unpack('C100') if header_flags & 4 != 0 # TOC FLAG
260
260
 
261
- vbr_scale = io.read(4).unpack('N1').first if header_flags & 8 # VBR SCALE FLAG
261
+ vbr_scale = io.read(4).unpack('N1').first if header_flags & 8 != 0 # VBR SCALE FLAG
262
262
 
263
263
  VBRHeader.new(frames: frames, byte_count: byte_count, toc_entries: toc, vbr_scale: vbr_scale)
264
264
  end
@@ -108,4 +108,24 @@ describe FormatParser::MOOVParser do
108
108
  it 'provides filename hints' do
109
109
  expect(subject).to be_likely_match('file.m4v')
110
110
  end
111
+
112
+ it 'reads correctly the video dimensions' do
113
+ mov_path = fixtures_dir + '/MOOV/MOV/Test_Dimensions.mov'
114
+
115
+ result = subject.call(File.open(mov_path, 'rb'))
116
+
117
+ expect(result).not_to be_nil
118
+ expect(result.nature).to eq(:video)
119
+ expect(result.format).to eq(:mov)
120
+ expect(result.width_px).to eq(640)
121
+ expect(result.height_px).to eq(360)
122
+ end
123
+
124
+ it 'does not raise error when a meta atom has size 0' do
125
+ mov_path = fixtures_dir + '/MOOV/MOV/Test_Meta_Atom_With_Size_Zero.mov'
126
+
127
+ result = subject.call(File.open(mov_path, 'rb'))
128
+ expect(result).not_to be_nil
129
+ expect(result.format).to eq(:mov)
130
+ end
111
131
  end
@@ -15,6 +15,20 @@ describe FormatParser::MP3Parser do
15
15
  expect(parsed.media_duration_seconds).to be_within(0.1).of(0.836)
16
16
  end
17
17
 
18
+ it 'reads the Xing header without raising errors' do
19
+ fpath = fixtures_dir + '/MP3/test_xing_header.mp3'
20
+ parsed = subject.call(File.open(fpath, 'rb'))
21
+
22
+ expect(parsed).not_to be_nil
23
+
24
+ expect(parsed.nature).to eq(:audio)
25
+ expect(parsed.format).to eq(:mp3)
26
+ expect(parsed.num_audio_channels).to eq(2)
27
+ expect(parsed.audio_sample_rate_hz).to eq(48000)
28
+ expect(parsed.intrinsics).not_to be_nil
29
+ expect(parsed.media_duration_seconds).to be_within(0.1).of(0.0342)
30
+ end
31
+
18
32
  it 'does not misdetect a PNG' do
19
33
  fpath = fixtures_dir + '/PNG/anim.png'
20
34
  parsed = subject.call(File.open(fpath, 'rb'))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: format_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.25.0
4
+ version: 0.25.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Berman
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-09-30 00:00:00.000000000 Z
12
+ date: 2020-10-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ks