format_parser 0.29.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +2 -6
- data/CHANGELOG.md +10 -0
- data/Gemfile +5 -0
- data/format_parser.gemspec +1 -1
- data/lib/format_parser/version.rb +1 -1
- data/lib/parsers/moov_parser/decoder.rb +36 -0
- data/lib/parsers/moov_parser.rb +52 -4
- data/lib/video.rb +4 -0
- data/spec/integration/active_storage/rails_app.rb +6 -3
- data/spec/parsers/moov_parser_spec.rb +4 -0
- metadata +12 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8bdd6daa59e43bbe6033562b804d3b1b9685c6e2aa5ef3f8d527c74516e1f6c
|
4
|
+
data.tar.gz: d6035f0b3c819085ffd23c4988e48145976ba1f98547d16589b2a4b4aa3e7aa7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a2f6243d295589972c63e45b719f19a5761c872103c32bfaacaef9cee9b85551c18ddaaf3c4095567c358958e7b7bf78800fc3e5a8e6c9f774eab383dc3160c
|
7
|
+
data.tar.gz: 73ba4f1099ccb4c490b50a8f531be843679159a6417980b2a61b905724905cf6b7e884127b66096e2237982dc6743e7f057bea0062472c1779e4d754dd5388ff
|
data/.github/workflows/main.yml
CHANGED
@@ -16,9 +16,6 @@ jobs:
|
|
16
16
|
- 2.7
|
17
17
|
- 2.6
|
18
18
|
- 2.5
|
19
|
-
- 2.4
|
20
|
-
- 2.3
|
21
|
-
- 2.2
|
22
19
|
- jruby
|
23
20
|
steps:
|
24
21
|
- name: Checkout
|
@@ -65,12 +62,11 @@ jobs:
|
|
65
62
|
- 2.7
|
66
63
|
- 2.6
|
67
64
|
- 2.5
|
68
|
-
- 2.4
|
69
|
-
- 2.3
|
70
|
-
- 2.2
|
71
65
|
- jruby
|
72
66
|
experimental: [false]
|
73
67
|
include:
|
68
|
+
- ruby: 3.1
|
69
|
+
experimental: true
|
74
70
|
- ruby: 3.0
|
75
71
|
experimental: true
|
76
72
|
steps:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## 1.2.0
|
2
|
+
* Add support for `codecs` in moov_parser for video metadata
|
3
|
+
|
4
|
+
## 1.1.0
|
5
|
+
* Add support for `frame_rate` in moov_parser
|
6
|
+
|
7
|
+
## 1.0.0
|
8
|
+
* Dropping support for Ruby 2.2.X, 2.3.X and 2.4.X
|
9
|
+
* MP3: Fix negative length reads in edge cases by bumping `id3tag` version to `v0.14.2`
|
10
|
+
|
1
11
|
## 0.29.1
|
2
12
|
* Fix handling of 200 responses with `parse_http` as well as handling of very small responses which do not need range access
|
3
13
|
|
data/Gemfile
CHANGED
data/format_parser.gemspec
CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
|
|
32
32
|
|
33
33
|
spec.add_dependency 'ks', '~> 0.0'
|
34
34
|
spec.add_dependency 'exifr', '~> 1', '>= 1.3.8'
|
35
|
-
spec.add_dependency 'id3tag', '~> 0.14'
|
35
|
+
spec.add_dependency 'id3tag', '~> 0.14', '>= 0.14.2'
|
36
36
|
spec.add_dependency 'faraday', '~> 0.13'
|
37
37
|
spec.add_dependency 'faraday_middleware', '~> 0.14'
|
38
38
|
spec.add_dependency 'measurometer', '~> 1'
|
@@ -72,6 +72,7 @@ class FormatParser::MOOVParser::Decoder
|
|
72
72
|
|
73
73
|
trak_atoms.find do |trak_atom|
|
74
74
|
hdlr_atom = find_first_atom_by_path([trak_atom], 'trak', 'mdia', 'hdlr')
|
75
|
+
next if hdlr_atom.nil?
|
75
76
|
hdlr_atom.atom_fields[:component_type] == 'mhlr' && hdlr_atom.atom_fields[:component_subtype] == 'vide'
|
76
77
|
end
|
77
78
|
end
|
@@ -111,6 +112,41 @@ class FormatParser::MOOVParser::Decoder
|
|
111
112
|
}
|
112
113
|
end
|
113
114
|
|
115
|
+
def parse_stts_atom(io, _)
|
116
|
+
version = read_byte_value(io)
|
117
|
+
is_v1 = version == 1
|
118
|
+
stts = {
|
119
|
+
version: version,
|
120
|
+
flags: read_bytes(io, 3),
|
121
|
+
number_of_entries: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
|
122
|
+
entries: []
|
123
|
+
}
|
124
|
+
stts[:number_of_entries].times {
|
125
|
+
stts[:entries] << {
|
126
|
+
sample_count: read_32bit_uint(io),
|
127
|
+
sample_duration: read_32bit_uint(io)
|
128
|
+
}
|
129
|
+
}
|
130
|
+
stts
|
131
|
+
end
|
132
|
+
|
133
|
+
def parse_stsd_atom(io, _)
|
134
|
+
version = read_byte_value(io)
|
135
|
+
is_v1 = version == 1
|
136
|
+
stsd = {
|
137
|
+
version: version,
|
138
|
+
flags: read_bytes(io, 3),
|
139
|
+
number_of_entries: is_v1 ? read_64bit_uint(io) : read_32bit_uint(io),
|
140
|
+
codecs: []
|
141
|
+
}
|
142
|
+
stsd[:number_of_entries].times {
|
143
|
+
codec_length = read_32bit_uint(io)
|
144
|
+
stsd[:codecs] << read_bytes(io, 4)
|
145
|
+
io.seek(io.pos + codec_length - 8) # 8 bytes is the header length containing the codec length and the codec name that we just did read
|
146
|
+
}
|
147
|
+
stsd
|
148
|
+
end
|
149
|
+
|
114
150
|
def parse_mdhd_atom(io, _)
|
115
151
|
version = read_byte_value(io)
|
116
152
|
is_v1 = version == 1
|
data/lib/parsers/moov_parser.rb
CHANGED
@@ -48,9 +48,9 @@ class FormatParser::MOOVParser
|
|
48
48
|
width, height = parse_dimensions(decoder, atom_tree)
|
49
49
|
|
50
50
|
# Try to find the "topmost" duration (respecting edits)
|
51
|
-
if
|
52
|
-
timescale =
|
53
|
-
duration =
|
51
|
+
if mvhd = decoder.find_first_atom_by_path(atom_tree, 'moov', 'mvhd')
|
52
|
+
timescale = mvhd.field_value(:tscale)
|
53
|
+
duration = mvhd.field_value(:duration)
|
54
54
|
media_duration_s = duration / timescale.to_f
|
55
55
|
end
|
56
56
|
|
@@ -68,9 +68,11 @@ class FormatParser::MOOVParser
|
|
68
68
|
format: format_from_moov_type(file_type),
|
69
69
|
width_px: width,
|
70
70
|
height_px: height,
|
71
|
+
frame_rate: parse_time_to_sample_atom(decoder, atom_tree)&.truncate(2),
|
71
72
|
media_duration_seconds: media_duration_s,
|
72
73
|
content_type: MP4_MIXED_MIME_TYPE,
|
73
|
-
|
74
|
+
codecs: parse_sample_description_atom(decoder, atom_tree),
|
75
|
+
intrinsics: atom_tree
|
74
76
|
)
|
75
77
|
end
|
76
78
|
end
|
@@ -115,5 +117,51 @@ class FormatParser::MOOVParser
|
|
115
117
|
maybe_atom_size >= minimum_ftyp_atom_size && maybe_ftyp_atom_signature == 'ftyp'
|
116
118
|
end
|
117
119
|
|
120
|
+
# Sample information is found in the 'time-to-sample' stts atom.
|
121
|
+
# The media atom mdhd is needed too in order to get the movie timescale
|
122
|
+
def parse_time_to_sample_atom(decoder, atom_tree)
|
123
|
+
video_trak_atom = decoder.find_video_trak_atom(atom_tree)
|
124
|
+
|
125
|
+
stts = if video_trak_atom
|
126
|
+
decoder.find_first_atom_by_path([video_trak_atom], 'trak', 'mdia', 'minf', 'stbl', 'stts')
|
127
|
+
else
|
128
|
+
decoder.find_first_atom_by_path(atom_tree, 'moov', 'trak', 'mdia', 'minf', 'stbl', 'stts')
|
129
|
+
end
|
130
|
+
|
131
|
+
mdhd = if video_trak_atom
|
132
|
+
decoder.find_first_atom_by_path([video_trak_atom], 'trak', 'mdia', 'mdhd')
|
133
|
+
else
|
134
|
+
decoder.find_first_atom_by_path(atom_tree, 'moov', 'trak', 'mdia', 'mdhd')
|
135
|
+
end
|
136
|
+
|
137
|
+
if stts && mdhd
|
138
|
+
timescale = mdhd.atom_fields[:tscale]
|
139
|
+
sample_duration = stts.field_value(:entries).first[:sample_duration]
|
140
|
+
if timescale.nil? || timescale == 0 || sample_duration.nil? || sample_duration == 0
|
141
|
+
nil
|
142
|
+
else
|
143
|
+
timescale.to_f / sample_duration
|
144
|
+
end
|
145
|
+
else
|
146
|
+
nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def parse_sample_description_atom(decoder, atom_tree)
|
151
|
+
video_trak_atom = decoder.find_video_trak_atom(atom_tree)
|
152
|
+
|
153
|
+
stsd = if video_trak_atom
|
154
|
+
decoder.find_first_atom_by_path([video_trak_atom], 'trak', 'mdia', 'minf', 'stbl', 'stsd')
|
155
|
+
else
|
156
|
+
decoder.find_first_atom_by_path(atom_tree, 'moov', 'trak', 'mdia', 'minf', 'stbl', 'stsd')
|
157
|
+
end
|
158
|
+
|
159
|
+
if stsd
|
160
|
+
stsd.field_value(:codecs)
|
161
|
+
else
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
118
166
|
FormatParser.register_parser new, natures: :video, formats: FTYP_MAP.values, priority: 1
|
119
167
|
end
|
data/lib/video.rb
CHANGED
@@ -8,9 +8,13 @@ module FormatParser
|
|
8
8
|
|
9
9
|
attr_accessor :height_px
|
10
10
|
|
11
|
+
attr_accessor :frame_rate
|
12
|
+
|
11
13
|
# Type of the file (e.g :mp3)
|
12
14
|
attr_accessor :format
|
13
15
|
|
16
|
+
attr_accessor :codecs
|
17
|
+
|
14
18
|
# Duration of the media object (be it audio or video) in seconds,
|
15
19
|
# as a Float
|
16
20
|
attr_accessor :media_duration_seconds
|
@@ -53,19 +53,22 @@ end
|
|
53
53
|
require 'minitest/autorun'
|
54
54
|
require 'open-uri'
|
55
55
|
|
56
|
+
fixtures_dir = File.join(File.dirname(__FILE__), '../../fixtures')
|
57
|
+
|
56
58
|
describe User do
|
57
59
|
describe "profile_picture's metadatas" do
|
58
60
|
it 'parse metadatas with format_parser' do
|
61
|
+
fixture_path = fixtures_dir + '/PNG/cat.png'
|
59
62
|
user = User.create
|
60
63
|
user.profile_picture.attach(
|
61
64
|
filename: 'cat.png',
|
62
|
-
io:
|
65
|
+
io: File.open(fixture_path, 'rb')
|
63
66
|
)
|
64
67
|
|
65
68
|
user.profile_picture.analyze
|
66
69
|
|
67
|
-
_(user.profile_picture.metadata[:width_px]).must_equal
|
68
|
-
_(user.profile_picture.metadata[:height_px]).must_equal
|
70
|
+
_(user.profile_picture.metadata[:width_px]).must_equal 600
|
71
|
+
_(user.profile_picture.metadata[:height_px]).must_equal 600
|
69
72
|
_(user.profile_picture.metadata[:color_mode]).must_equal 'rgba'
|
70
73
|
end
|
71
74
|
end
|
@@ -94,6 +94,7 @@ describe FormatParser::MOOVParser do
|
|
94
94
|
expect(result.format).to eq(:mov)
|
95
95
|
expect(result.width_px).to eq(1920)
|
96
96
|
expect(result.height_px).to eq(1080)
|
97
|
+
expect(result.codecs).to eq(['apcn'])
|
97
98
|
end
|
98
99
|
|
99
100
|
it 'parses an MP4 video file and provides the necessary metadata' do
|
@@ -106,6 +107,8 @@ describe FormatParser::MOOVParser do
|
|
106
107
|
expect(result.format).to eq(:mov)
|
107
108
|
expect(result.width_px).to eq(160)
|
108
109
|
expect(result.height_px).to eq(90)
|
110
|
+
expect(result.frame_rate).to eq(14.98)
|
111
|
+
expect(result.codecs).to eq(['avc1'])
|
109
112
|
end
|
110
113
|
|
111
114
|
it 'provides filename hints' do
|
@@ -122,6 +125,7 @@ describe FormatParser::MOOVParser do
|
|
122
125
|
expect(result.format).to eq(:mov)
|
123
126
|
expect(result.width_px).to eq(640)
|
124
127
|
expect(result.height_px).to eq(360)
|
128
|
+
expect(result.frame_rate).to eq(30)
|
125
129
|
end
|
126
130
|
|
127
131
|
it 'does not raise error when a meta atom has size 0' do
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: format_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Noah Berman
|
8
8
|
- Julik Tarkhanov
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-04-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ks
|
@@ -52,6 +52,9 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0.14'
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 0.14.2
|
55
58
|
type: :runtime
|
56
59
|
prerelease: false
|
57
60
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -59,6 +62,9 @@ dependencies:
|
|
59
62
|
- - "~>"
|
60
63
|
- !ruby/object:Gem::Version
|
61
64
|
version: '0.14'
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 0.14.2
|
62
68
|
- !ruby/object:Gem::Dependency
|
63
69
|
name: faraday
|
64
70
|
requirement: !ruby/object:Gem::Requirement
|
@@ -294,7 +300,7 @@ licenses:
|
|
294
300
|
- MIT (Hippocratic)
|
295
301
|
metadata:
|
296
302
|
allowed_push_host: https://rubygems.org
|
297
|
-
post_install_message:
|
303
|
+
post_install_message:
|
298
304
|
rdoc_options: []
|
299
305
|
require_paths:
|
300
306
|
- lib
|
@@ -309,8 +315,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
309
315
|
- !ruby/object:Gem::Version
|
310
316
|
version: '0'
|
311
317
|
requirements: []
|
312
|
-
rubygems_version: 3.
|
313
|
-
signing_key:
|
318
|
+
rubygems_version: 3.3.4
|
319
|
+
signing_key:
|
314
320
|
specification_version: 4
|
315
321
|
summary: A library for efficient parsing of file metadata
|
316
322
|
test_files: []
|