format_parser 0.25.2 → 0.26.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 290b2369d2fe089202a76ab9d7b659f8de58fa44b9a40718130105ae7026036a
4
- data.tar.gz: 53e983a8639cc42377ab50d7364b5099cf1f3308f8108f92b6194040546ea2e8
3
+ metadata.gz: 1074a8172a2830a11df0fb7874936c2b8abab8d74fd39985f9c9f7b72d5b348c
4
+ data.tar.gz: 5eafd2d610cd30bc85a056bd1c31331bcb49e8ce6b5b538cd13e280b138be7db
5
5
  SHA512:
6
- metadata.gz: 5f58620ab165b1c47a8a18fe82081d48cb0b285b4f9638146c31d1bb2f839247b36750a7f85be5a9ea14b8db3fc2ca175b86ab4f8b29f87bd1ea10caed57746c
7
- data.tar.gz: b9e87723a7cc1d5ecf04a23c28cbfc433c120337275aa51d76396a9a1371bb1b683b22c18ab4e84d8870a76048e26f7c5d7f90b4b5f9b868bf5a0dcb6771640c
6
+ metadata.gz: 5ce396a71fedd82b8041bcb6c833e559c7eef74886e73095eaf0b3d21e0c0d49b1620a83aba9796a8134dd5a7fc679156cd967cf82cba95b5941941be73d70c4
7
+ data.tar.gz: '0395e5a8fb35e860060e9c3b040b788aaad97eb5883f2d662b418156f1f3986bc4a26a814b9a8552ce7dcbd271da27e977cfb77a5e5b155ebd35db6a49a97719'
@@ -2,8 +2,9 @@ rvm:
2
2
  - 2.2.10
3
3
  - 2.3.8
4
4
  - 2.4.9
5
- - 2.5.7
6
- - 2.6.5
5
+ - 2.5.8
6
+ - 2.6.6
7
+ - 2.7.2
7
8
  - jruby
8
9
  sudo: false
9
10
  cache: bundler
@@ -1,3 +1,22 @@
1
+ ## 0.26.0
2
+ * Add support for M3U format files
3
+
4
+ ## 0.25.6
5
+ * Fix FormatParser.parse (with `results: :first`) to be deterministic
6
+
7
+ ## 0.25.5
8
+ * DPX: Fix DPXParser to support images without aspect ratio
9
+
10
+ ## 0.25.4
11
+ * MP3: Fix MP3Parser to return nil for TIFF files
12
+ * Add support to ruby 2.7
13
+
14
+ ## 0.25.3
15
+ * MP3: Fix parser to not skip the first bytes if it's not an ID3 header
16
+
17
+ ## 0.25.2
18
+ * Hotfix Moov parser
19
+
1
20
  ## 0.25.1
2
21
  * MOV: Fix error "negative length"
3
22
  * MOV: Fix reading dimensions in multi-track files
@@ -83,32 +83,59 @@ of software. Ideally, this file is going to be something you have produced yours
83
83
  and you are permitted to share under the MIT license provisions.
84
84
 
85
85
  When writing a parser, please try to ensure it returns a usable result as soon as possible,
86
- or no result as soon as possible (once you know the file is not fit for your specific parser).
86
+ or `nil` as soon as possible (once you know the file is not fit for your specific parser).
87
87
  Bear in mind that we enforce read budgets per-parser, so you will not be allowed to perform
88
88
  too many reads, or perform reads which are too large.
89
89
 
90
- In order to create new parsers, it is recommended to make a well-named class with an instance method `call`.
90
+ In order to create new parsers, make a well-named class with an instance method `call`,
91
+ and to register a single instance of that class as the parser - so that only one object needs to be stored
92
+ in memory when parsing multiple inputs. In that case your object must be **thread-safe and stateless** - this
93
+ is really important since FormatParser is thread-safe and multiple parsing procedures may be in progress
94
+ concurrently against the same parser object. You can also create a Proc if your parser is fairly trivial.
91
95
 
92
- `call` accepts the IO-ish object as an argument, parses data that it reads from it,
93
- and then returns the metadata for the file (if it could recover any) or `nil` if it couldn't. All files pass
94
- through all parsers by default, so if you are dealing with a file that is not "your" format - return `nil` from
95
- your method or `break` your Proc as early as possible. A blank `return` works fine too.
96
+ If it will be difficult to have your parser thread-safe you can register your class itself as
97
+ the parser and define the `self.call` method to parse using a fresh instance every time, allowing
98
+ object-level state:
99
+
100
+ ```ruby
101
+ class MyParser
102
+ def self.call(io)
103
+ new.call(io)
104
+ end
105
+
106
+ def call(io)
107
+ @state = ...
108
+ end
109
+ ```
110
+
111
+ `call` accepts a single argument - an IO-ish object which is guaranteed to respond to the same methods as the
112
+ ones defined in `IOConstraint` - that is, it is a strict subset of a standard Ruby IO object. *All reads from
113
+ this IO object are guaranteed to be returned in binary encoding.* The IO will be at offset of 0 when your parsing
114
+ proc receives it and there will be no concurrent calls to that object until your proc returns.
96
115
 
97
- The IO will at the minimum support the subset of the IO API defined in `IOConstraint`
116
+ Your parsing procedure may read from this IO object, and should return either a `Result`-like object with
117
+ the file metadata (if it could recover any) or `nil` if it couldn't. All files pass
118
+ through all parsers by default, so if you are dealing with a file that is not "your" format - return `nil` from
119
+ your method or `break` your Proc as early as possible. A blank `return` works fine too as it actually returns `nil`.
98
120
 
99
- Your parser has to be registered using `FormatParser.register_parser` with the information on the formats
100
- and file natures it provides.
121
+ Your parser then needs to be registered using `FormatParser.register_parser` with the information on the formats
122
+ and file natures it provides. This allows FormatParser to skip your parser if, say, the user only want to parse for
123
+ `:image` nature files but your parser parses `:audio`.
101
124
 
102
- Down below you can find the most basic parser implementation:
125
+ Down below you can find the most basic parser implementation which parses an imaginary `IMGA` file format:
103
126
 
104
127
  ```ruby
105
128
  MyParser = ->(io) {
106
- # ... do some parsing with `io`
129
+ # ... Read the magic bytes from the start of IO - the IO is
130
+ # guaranteed to be fed to you at offset 0, start-of-file.
107
131
  magic_bytes = io.read(4)
132
+
108
133
  # breaking the block returns `nil` to the caller signaling "no match"
109
134
  break if magic_bytes != 'IMGA'
110
135
 
136
+ # Our file format stores the width and height as 2 32-bit unsigned integers
111
137
  parsed_witdh, parsed_height = io.read(8).unpack('VV')
138
+
112
139
  # ...and return the FileInformation::Image object with the metadata.
113
140
  FormatParser::Image.new(
114
141
  format: :imga,
@@ -135,8 +162,8 @@ class MyParser
135
162
  # ... do some parsing with `io`
136
163
  # The instance will be discarded after parsing, so using instance variables
137
164
  # is permitted - they are not shared between calls to `call`
138
- @magic_bytes = io.read(4)
139
- break if @magic_bytes != 'IMGA'
165
+ magic_bytes = io.read(4)
166
+ break if magic_bytes != 'IMGA'
140
167
  parsed_witdh, parsed_height = io.read(8).unpack('VV')
141
168
  FormatParser::Image.new(
142
169
  format: :imga,
@@ -145,23 +172,33 @@ class MyParser
145
172
  )
146
173
  end
147
174
 
148
- FormatParser.register_parser self, natures: :image, formats: :bmp
175
+ # Note that we register an instance of the class, not the class. It is the
176
+ # instance that responds to `call()` and we can do this because our object
177
+ # is stateless.
178
+ FormatParser.register_parser new, natures: :image, formats: :bmp
149
179
  end
150
180
  ```
151
181
 
152
- ### Calling convention for preparing parsers
182
+ If your parser supports file types which have a known filename extension, you can add a method to it called `likely_match?`,
183
+ add this method on the object you register itself. For example, for the ZIP parser we use:
184
+
185
+ ```ruby
186
+ def likely_match?(filename)
187
+ filename =~ /\.(zip|docx|keynote|numbers|pptx|xlsx)$/i
188
+ end
189
+ ```
153
190
 
154
- A parser that gets registered using `register_parser` must be either:
191
+ If your parser matches the filename it is going to be applied *earlier*, saving time. Since most FormatParser users are
192
+ likely to only want the first result of the parsing, the sooner your parser gets applied - the sooner you can return the result,
193
+ avoiding unnecessary reads.
155
194
 
156
- 1) An object that can be `call()`-ed itself, with an argument that conforms to `IOConstraint`
157
- 2) An object that responds to `new` and returns something that can be `call()`-ed with with an argument that conforms to `IOConstraint`.
195
+ ### Calling convention for preparing parsers
158
196
 
159
- The second opton is recommended for most cases.
197
+ A parser that gets registered using `register_parser` must be an object that can be `call()`-ed, with an argument that conforms to `IOConstraint`
160
198
 
161
199
  FormatParser is made to be used in threaded environments, and if you use instance variables
162
- you need your parser to be isolated from it's siblings in other threads - therefore you can pass
163
- a Class on registration to have your parser instantiated for each `call()`, anew.
164
-
200
+ you need your parser to be isolated from it's siblings in other threads - create a copy for one-off use inside
201
+ your `call` method.
165
202
 
166
203
  ## Pull requests
167
204
 
data/README.md CHANGED
@@ -32,6 +32,7 @@ and [dimensions,](https://github.com/sstephenson/dimensions) borrowing from them
32
32
  * DOCX, PPTX, XLSX
33
33
  * OGG
34
34
  * MPEG, MPG
35
+ * M3U
35
36
 
36
37
  ...with [more](https://github.com/WeTransfer/format_parser/issues?q=is%3Aissue+is%3Aopen+label%3Aformats) on the way!
37
38
 
@@ -194,6 +195,9 @@ Unless specified otherwise in this section the fixture files are MIT licensed an
194
195
  manipulated using the [https://github.com/recurser/exif-orientation-examples](exif-orientation-examples)
195
196
  script.
196
197
 
198
+ ### M3U
199
+ - The M3U fixture files were created by one of the project maintainers
200
+
197
201
  ### .key
198
202
  - The `keynote_recognized_as_jpeg.key` file was created by the project maintainers
199
203
 
@@ -19,6 +19,7 @@ module FormatParser
19
19
  require_relative 'io_constraint'
20
20
  require_relative 'care'
21
21
  require_relative 'active_storage/blob_analyzer'
22
+ require_relative 'text'
22
23
 
23
24
  # Define Measurometer in the internal namespace as well
24
25
  # so that we stay compatible for the applications that use it
@@ -36,7 +37,7 @@ module FormatParser
36
37
  # Register a parser object to be used to perform file format detection. Each parser FormatParser
37
38
  # provides out of the box registers itself using this method.
38
39
  #
39
- # @param callable_or_responding_to_new[#call, #new] an object that either responds to #new or to #call
40
+ # @param callable_parser[#call] an object that responds to #call for parsing an IO
40
41
  # @param formats[Array<Symbol>] file formats that the parser provides
41
42
  # @param natures[Array<Symbol>] file natures that the parser provides
42
43
  # @param priority[Integer] whether the parser has to be applied first or later. Parsers that offer the safest
@@ -45,39 +46,41 @@ module FormatParser
45
46
  # with a lower priority value will be applied first, and if a single result is requested, will also return
46
47
  # first.
47
48
  # @return void
48
- def self.register_parser(callable_or_responding_to_new, formats:, natures:, priority: LEAST_PRIORITY)
49
+ def self.register_parser(callable_parser, formats:, natures:, priority: LEAST_PRIORITY)
49
50
  parser_provided_formats = Array(formats)
50
51
  parser_provided_natures = Array(natures)
51
52
  PARSER_MUX.synchronize do
52
- @parsers ||= Set.new
53
- @parsers << callable_or_responding_to_new
53
+ # It can't be a Set because the method `parsers_for` depends on the order
54
+ # that the parsers were added.
55
+ @parsers ||= []
56
+ @parsers << callable_parser unless @parsers.include?(callable_parser)
54
57
  @parsers_per_nature ||= {}
55
58
  parser_provided_natures.each do |provided_nature|
56
59
  @parsers_per_nature[provided_nature] ||= Set.new
57
- @parsers_per_nature[provided_nature] << callable_or_responding_to_new
60
+ @parsers_per_nature[provided_nature] << callable_parser
58
61
  end
59
62
  @parsers_per_format ||= {}
60
63
  parser_provided_formats.each do |provided_format|
61
64
  @parsers_per_format[provided_format] ||= Set.new
62
- @parsers_per_format[provided_format] << callable_or_responding_to_new
65
+ @parsers_per_format[provided_format] << callable_parser
63
66
  end
64
67
  @parser_priorities ||= {}
65
- @parser_priorities[callable_or_responding_to_new] = priority
68
+ @parser_priorities[callable_parser] = priority
66
69
  end
67
70
  end
68
71
 
69
72
  # Deregister a parser object (makes FormatParser forget this parser existed). Is mostly used in
70
73
  # tests, but can also be used to forcibly disable some formats completely.
71
74
  #
72
- # @param callable_or_responding_to_new[#call, #new] an object that either responds to #new or to #call
75
+ # @param callable_parser[#==] an object that is identity-equal to any other registered parser
73
76
  # @return void
74
- def self.deregister_parser(callable_or_responding_to_new)
77
+ def self.deregister_parser(callable_parser)
75
78
  # Used only in tests
76
79
  PARSER_MUX.synchronize do
77
- (@parsers || []).delete(callable_or_responding_to_new)
78
- (@parsers_per_nature || {}).values.map { |e| e.delete(callable_or_responding_to_new) }
79
- (@parsers_per_format || {}).values.map { |e| e.delete(callable_or_responding_to_new) }
80
- (@parser_priorities || {}).delete(callable_or_responding_to_new)
80
+ (@parsers || []).delete(callable_parser)
81
+ (@parsers_per_nature || {}).values.map { |e| e.delete(callable_parser) }
82
+ (@parsers_per_format || {}).values.map { |e| e.delete(callable_parser) }
83
+ (@parser_priorities || {}).delete(callable_parser)
81
84
  end
82
85
  end
83
86
 
@@ -255,7 +258,19 @@ module FormatParser
255
258
  # Order the parsers according to their priority value. The ones having a lower
256
259
  # value will sort higher and will be applied sooner
257
260
  parsers_in_order_of_priority = parsers.to_a.sort do |parser_a, parser_b|
258
- @parser_priorities[parser_a] <=> @parser_priorities[parser_b]
261
+ if @parser_priorities[parser_a] != @parser_priorities[parser_b]
262
+ @parser_priorities[parser_a] <=> @parser_priorities[parser_b]
263
+ else
264
+ # Some parsers have the same priority and we want them to be always sorted
265
+ # in the same way, to not change the result of FormatParser.parse(results: :first).
266
+ # When this changes, it can generate flaky tests or event different
267
+ # results in different environments, which can be hard to understand why.
268
+ # There is also no guarantee in the order that the elements are added in
269
+ # @@parser_priorities
270
+ # So, to have always the same order, we sort by the order that the parsers
271
+ # were registered if the priorities are the same.
272
+ @parsers.index(parser_a) <=> @parsers.index(parser_b)
273
+ end
259
274
  end
260
275
 
261
276
  # If there is one parser that is more likely to match, place it first
@@ -1,3 +1,3 @@
1
1
  module FormatParser
2
- VERSION = '0.25.2'
2
+ VERSION = '0.26.0'
3
3
  end
@@ -35,18 +35,23 @@ class FormatParser::DPXParser
35
35
  w = dpx_structure.fetch(:image).fetch(:pixels_per_line)
36
36
  h = dpx_structure.fetch(:image).fetch(:lines_per_element)
37
37
 
38
+ display_w = w
39
+ display_h = h
40
+
38
41
  pixel_aspect_w = dpx_structure.fetch(:orientation).fetch(:horizontal_pixel_aspect)
39
42
  pixel_aspect_h = dpx_structure.fetch(:orientation).fetch(:vertical_pixel_aspect)
40
- pixel_aspect = pixel_aspect_w / pixel_aspect_h.to_f
41
43
 
42
- image_aspect = w / h.to_f * pixel_aspect
44
+ # Find display height and width based on aspect only if the file structure has pixel aspects
45
+ if pixel_aspect_h != 0 && pixel_aspect_w != 0
46
+ pixel_aspect = pixel_aspect_w / pixel_aspect_h.to_f
43
47
 
44
- display_w = w
45
- display_h = h
46
- if image_aspect > 1
47
- display_h = (display_w / image_aspect).round
48
- else
49
- display_w = (display_h * image_aspect).round
48
+ image_aspect = w / h.to_f * pixel_aspect
49
+
50
+ if image_aspect > 1
51
+ display_h = (display_w / image_aspect).round
52
+ else
53
+ display_w = (display_h * image_aspect).round
54
+ end
50
55
  end
51
56
 
52
57
  FormatParser::Image.new(
@@ -0,0 +1,21 @@
1
+ class FormatParser::M3UParser
2
+ include FormatParser::IOUtils
3
+
4
+ HEADER = '#EXTM3U'
5
+
6
+ def likely_match?(filename)
7
+ filename =~ /\.m3u8?$/i
8
+ end
9
+
10
+ def call(io)
11
+ io = FormatParser::IOConstraint.new(io)
12
+
13
+ header = safe_read(io, 7)
14
+ return unless HEADER.eql?(header)
15
+
16
+ FormatParser::Text.new(
17
+ format: :m3u
18
+ )
19
+ end
20
+ FormatParser.register_parser new, natures: :text, formats: :m3u
21
+ end
@@ -29,6 +29,10 @@ class FormatParser::MP3Parser
29
29
  ZIP_LOCAL_ENTRY_SIGNATURE = "PK\x03\x04\x14\x00".b
30
30
  PNG_HEADER_BYTES = [137, 80, 78, 71, 13, 10, 26, 10].pack('C*')
31
31
 
32
+ MAGIC_LE = [0x49, 0x49, 0x2A, 0x0].pack('C4')
33
+ MAGIC_BE = [0x4D, 0x4D, 0x0, 0x2A].pack('C4')
34
+ TIFF_HEADER_BYTES = [MAGIC_LE, MAGIC_BE]
35
+
32
36
  # Wraps the Tag object returned by ID3Tag in such
33
37
  # a way that a usable JSON representation gets
34
38
  # returned
@@ -68,11 +72,16 @@ class FormatParser::MP3Parser
68
72
  return if header.start_with?(ZIP_LOCAL_ENTRY_SIGNATURE)
69
73
  return if header.start_with?(PNG_HEADER_BYTES)
70
74
 
75
+ io.seek(0)
76
+ return if TIFF_HEADER_BYTES.include?(safe_read(io, 4))
77
+
71
78
  # Read all the ID3 tags (or at least attempt to)
72
79
  io.seek(0)
73
80
  id3v1 = ID3Extraction.attempt_id3_v1_extraction(io)
74
81
  tags = [id3v1, ID3Extraction.attempt_id3_v2_extraction(io)].compact
75
82
 
83
+ io.seek(0) if tags.empty?
84
+
76
85
  # Compute how many bytes are occupied by the actual MPEG frames
77
86
  ignore_bytes_at_tail = id3v1 ? 128 : 0
78
87
  ignore_bytes_at_head = io.pos
@@ -4,6 +4,7 @@ class FormatParser::TIFFParser
4
4
 
5
5
  MAGIC_LE = [0x49, 0x49, 0x2A, 0x0].pack('C4')
6
6
  MAGIC_BE = [0x4D, 0x4D, 0x0, 0x2A].pack('C4')
7
+ HEADER_BYTES = [MAGIC_LE, MAGIC_BE]
7
8
 
8
9
  def likely_match?(filename)
9
10
  filename =~ /\.tiff?$/i
@@ -12,7 +13,7 @@ class FormatParser::TIFFParser
12
13
  def call(io)
13
14
  io = FormatParser::IOConstraint.new(io)
14
15
 
15
- return unless [MAGIC_LE, MAGIC_BE].include?(safe_read(io, 4))
16
+ return unless HEADER_BYTES.include?(safe_read(io, 4))
16
17
  io.seek(io.pos + 2) # Skip over the offset of the IFD, EXIFR will re-read it anyway
17
18
  return if cr2?(io)
18
19
 
@@ -0,0 +1,18 @@
1
+ module FormatParser
2
+ class Text
3
+ include FormatParser::AttributesJSON
4
+
5
+ NATURE = :text
6
+
7
+ attr_accessor :format
8
+
9
+ # Only permits assignments via defined accessors
10
+ def initialize(**attributes)
11
+ attributes.map { |(k, v)| public_send("#{k}=", v) }
12
+ end
13
+
14
+ def nature
15
+ NATURE
16
+ end
17
+ end
18
+ end
@@ -173,6 +173,26 @@ describe FormatParser do
173
173
  prioritized_parsers = FormatParser.parsers_for([:archive, :document, :image, :audio], [:tif, :jpg, :zip, :docx, :mp3, :aiff], 'a-file.zip')
174
174
  expect(prioritized_parsers.first).to be_kind_of(FormatParser::ZIPParser)
175
175
  end
176
+
177
+ it 'sorts the parsers by priority and name' do
178
+ parsers = FormatParser.parsers_for(
179
+ [:audio, :image],
180
+ [:cr2, :dpx, :fdx, :flac, :gif, :jpg, :mov, :mp4, :m4a, :mp3, :mpg, :mpeg, :ogg, :png, :tif, :wav]
181
+ )
182
+
183
+ expect(parsers.map { |parser| parser.class.name }).to eq([
184
+ 'FormatParser::GIFParser',
185
+ 'Class',
186
+ 'FormatParser::PNGParser',
187
+ 'FormatParser::CR2Parser',
188
+ 'FormatParser::DPXParser',
189
+ 'FormatParser::FLACParser',
190
+ 'FormatParser::MP3Parser',
191
+ 'FormatParser::OggParser',
192
+ 'FormatParser::TIFFParser',
193
+ 'FormatParser::WAVParser'
194
+ ])
195
+ end
176
196
  end
177
197
 
178
198
  describe '.register_parser and .deregister_parser' do
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::M3UParser do
4
+ let(:parsed_m3u) do
5
+ subject.call(
6
+ File.open(
7
+ Pathname.new(fixtures_dir).join('M3U').join(m3u_file),
8
+ 'rb'
9
+ )
10
+ )
11
+ end
12
+
13
+ describe 'an m3u file with missing header' do
14
+ let(:m3u_file) { 'plain_text.m3u' }
15
+
16
+ it 'does not parse the file successfully' do
17
+ expect(parsed_m3u).to be_nil
18
+ end
19
+ end
20
+
21
+ describe 'an m3u file with valid header' do
22
+ let(:m3u_file) { 'sample.m3u' }
23
+
24
+ it 'parses the file successfully' do
25
+ expect(parsed_m3u).not_to be_nil
26
+ expect(parsed_m3u.nature).to eq(:text)
27
+ expect(parsed_m3u.format).to eq(:m3u)
28
+ end
29
+ end
30
+
31
+ describe 'an m3u8 file with valid header' do
32
+ let(:m3u_file) { 'sample.m3u8' }
33
+
34
+ it 'parses the file successfully' do
35
+ expect(parsed_m3u).not_to be_nil
36
+ expect(parsed_m3u.nature).to eq(:text)
37
+ expect(parsed_m3u.format).to eq(:m3u)
38
+ end
39
+ end
40
+ end
@@ -166,6 +166,18 @@ describe FormatParser::MP3Parser do
166
166
  expect(parsed.artist). to eq('wetransfer')
167
167
  end
168
168
 
169
+ it 'does not skip the first bytes if it is not a id3 tag header' do
170
+ fpath = fixtures_dir + '/MP3/no_id3_tags.mp3'
171
+
172
+ parsed = subject.call(File.open(fpath, 'rb'))
173
+
174
+ expect(parsed).not_to be_nil
175
+
176
+ expect(parsed.nature).to eq(:audio)
177
+ expect(parsed.format).to eq(:mp3)
178
+ expect(parsed.audio_sample_rate_hz).to eq(44100)
179
+ end
180
+
169
181
  describe '#as_json' do
170
182
  it 'converts all hash keys to string when stringify_keys: true' do
171
183
  fpath = fixtures_dir + '/MP3/Cassy.mp3'
@@ -193,4 +205,12 @@ describe FormatParser::MP3Parser do
193
205
  ).to eq([ID3Tag::Tag])
194
206
  end
195
207
  end
208
+
209
+ it 'does not recognize TIFF files as MP3' do
210
+ fpath = fixtures_dir + '/TIFF/test2.tif'
211
+
212
+ parsed = subject.call(File.open(fpath, 'rb'))
213
+
214
+ expect(parsed).to be_nil
215
+ end
196
216
  end
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: 0.25.2
4
+ version: 0.26.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: 2020-10-05 00:00:00.000000000 Z
12
+ date: 2021-01-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ks
@@ -219,6 +219,7 @@ files:
219
219
  - lib/parsers/flac_parser.rb
220
220
  - lib/parsers/gif_parser.rb
221
221
  - lib/parsers/jpeg_parser.rb
222
+ - lib/parsers/m3u_parser.rb
222
223
  - lib/parsers/moov_parser.rb
223
224
  - lib/parsers/moov_parser/decoder.rb
224
225
  - lib/parsers/mp3_parser.rb
@@ -236,6 +237,7 @@ files:
236
237
  - lib/read_limiter.rb
237
238
  - lib/read_limits_config.rb
238
239
  - lib/remote_io.rb
240
+ - lib/text.rb
239
241
  - lib/video.rb
240
242
  - spec/active_storage/blob_io_spec.rb
241
243
  - spec/active_storage/rails_app_spec.rb
@@ -257,6 +259,7 @@ files:
257
259
  - spec/parsers/flac_parser_spec.rb
258
260
  - spec/parsers/gif_parser_spec.rb
259
261
  - spec/parsers/jpeg_parser_spec.rb
262
+ - spec/parsers/m3u_parser_spec.rb
260
263
  - spec/parsers/moov_parser_spec.rb
261
264
  - spec/parsers/mp3_parser_spec.rb
262
265
  - spec/parsers/mpeg_parser_spec.rb
@@ -277,7 +280,7 @@ licenses:
277
280
  - MIT (Hippocratic)
278
281
  metadata:
279
282
  allowed_push_host: https://rubygems.org
280
- post_install_message:
283
+ post_install_message:
281
284
  rdoc_options: []
282
285
  require_paths:
283
286
  - lib
@@ -292,8 +295,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
292
295
  - !ruby/object:Gem::Version
293
296
  version: '0'
294
297
  requirements: []
295
- rubygems_version: 3.1.2
296
- signing_key:
298
+ rubygems_version: 3.0.3
299
+ signing_key:
297
300
  specification_version: 4
298
301
  summary: A library for efficient parsing of file metadata
299
302
  test_files: []