format_parser 0.29.1 → 1.2.0
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/.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: []
|