format_parser 0.25.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/lib/format_parser/version.rb +1 -1
- data/lib/parsers/moov_parser.rb +26 -7
- data/lib/parsers/moov_parser/decoder.rb +31 -0
- data/lib/parsers/mp3_parser.rb +5 -5
- data/spec/parsers/moov_parser_spec.rb +20 -0
- data/spec/parsers/mp3_parser_spec.rb +14 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87adbfef15c2281ab6a13f151b857409f0ffad0ecc5270d9d0bbc5cebe207cdb
|
4
|
+
data.tar.gz: 332e3c4efd4ae01b3cf47c669debba7cc1aee5c264bef503720f632f6d801054
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 231d768d4b69b2c2f29bcb861888a7fbb0f4242eec5c9313d6428c9053b4fb4e7d20b8615d731a695c278ac59d3bf07b4977d217892aa1fce8fa7adc9d415efa
|
7
|
+
data.tar.gz: 732fae92e71f6a25fa98b7d88fa70b82404f00c234a6debd641e20ad8ffd9e45c78270f20adb8ad5593cd815e128a2a409ec639351c985b79763d4bfd821fec9
|
data/CHANGELOG.md
CHANGED
data/lib/parsers/moov_parser.rb
CHANGED
@@ -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
|
-
|
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
|
|
data/lib/parsers/mp3_parser.rb
CHANGED
@@ -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('
|
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.
|
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-
|
12
|
+
date: 2020-10-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ks
|