format_parser 0.25.4 → 0.28.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 +104 -0
- data/CHANGELOG.md +15 -0
- data/README.md +4 -0
- data/format_parser.gemspec +1 -0
- data/lib/archive.rb +3 -0
- data/lib/audio.rb +3 -0
- data/lib/document.rb +1 -0
- data/lib/format_parser.rb +18 -3
- data/lib/format_parser/version.rb +1 -1
- data/lib/image.rb +3 -0
- data/lib/parsers/aiff_parser.rb +4 -1
- data/lib/parsers/bmp_parser.rb +3 -0
- data/lib/parsers/cr2_parser.rb +2 -0
- data/lib/parsers/dpx_parser.rb +19 -8
- data/lib/parsers/flac_parser.rb +2 -0
- data/lib/parsers/gif_parser.rb +2 -0
- data/lib/parsers/jpeg_parser.rb +2 -0
- data/lib/parsers/m3u_parser.rb +23 -0
- data/lib/parsers/moov_parser.rb +10 -1
- data/lib/parsers/mp3_parser.rb +3 -2
- data/lib/parsers/ogg_parser.rb +3 -2
- data/lib/parsers/pdf_parser.rb +2 -2
- data/lib/parsers/png_parser.rb +2 -0
- data/lib/parsers/psd_parser.rb +2 -0
- data/lib/parsers/tiff_parser.rb +10 -2
- data/lib/parsers/wav_parser.rb +3 -0
- data/lib/parsers/zip_parser.rb +5 -3
- data/lib/parsers/zip_parser/office_formats.rb +5 -5
- data/lib/remote_io.rb +7 -1
- data/lib/text.rb +19 -0
- data/lib/video.rb +3 -0
- data/spec/format_parser_spec.rb +20 -0
- data/spec/parsers/aiff_parser_spec.rb +1 -0
- data/spec/parsers/bmp_parser_spec.rb +8 -0
- data/spec/parsers/cr2_parser_spec.rb +1 -0
- data/spec/parsers/dpx_parser_spec.rb +1 -0
- data/spec/parsers/flac_parser_spec.rb +1 -0
- data/spec/parsers/gif_parser_spec.rb +1 -0
- data/spec/parsers/jpeg_parser_spec.rb +1 -0
- data/spec/parsers/m3u_parser_spec.rb +41 -0
- data/spec/parsers/moov_parser_spec.rb +4 -1
- data/spec/parsers/mp3_parser_spec.rb +1 -0
- data/spec/parsers/ogg_parser_spec.rb +1 -0
- data/spec/parsers/pdf_parser_spec.rb +1 -0
- data/spec/parsers/png_parser_spec.rb +1 -0
- data/spec/parsers/psd_parser_spec.rb +1 -0
- data/spec/parsers/tiff_parser_spec.rb +1 -0
- data/spec/parsers/wav_parser_spec.rb +1 -0
- data/spec/parsers/zip_parser_spec.rb +2 -0
- data/spec/remote_fetching_spec.rb +11 -0
- data/spec/remote_io_spec.rb +38 -13
- metadata +21 -4
- data/.travis.yml +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2212d92c3c0ca7aab0e60c9aa6559d0508ec2b8b39e4b2067990d97a7d78327
|
4
|
+
data.tar.gz: 27736f9d52ea1e73a147d96d38608e268040648e61d2b913ca38f164f2d2d0b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f63f74444df32d016fc25dc5c3731671690185b7ecf80ffc06da350b37d82b566f451508091245fec255c816121a115286dd04fb626aa4c1b694836fd645d97
|
7
|
+
data.tar.gz: ee1917287bb7c50bd71d9a7756321e30fbc73aa5e3a4f42e4844eb9494dfe77e89a9a26622732772c9b29c4ea673f3401324d2e6bf67e5bdaa5dac991d12f821
|
@@ -0,0 +1,104 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
env:
|
6
|
+
BUNDLE_PATH: vendor/bundle
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
lint:
|
10
|
+
name: Code Style
|
11
|
+
runs-on: ubuntu-18.04
|
12
|
+
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
13
|
+
strategy:
|
14
|
+
matrix:
|
15
|
+
ruby:
|
16
|
+
- 2.7
|
17
|
+
- 2.6
|
18
|
+
- 2.5
|
19
|
+
- 2.4
|
20
|
+
- 2.3
|
21
|
+
- 2.2
|
22
|
+
- jruby
|
23
|
+
steps:
|
24
|
+
- name: Checkout
|
25
|
+
uses: actions/checkout@v2
|
26
|
+
- name: Setup Ruby
|
27
|
+
uses: ruby/setup-ruby@v1
|
28
|
+
with:
|
29
|
+
ruby-version: ${{ matrix.ruby }}
|
30
|
+
- name: Gemfile Cache
|
31
|
+
uses: actions/cache@v2
|
32
|
+
with:
|
33
|
+
path: Gemfile.lock
|
34
|
+
key: ${{ runner.os }}-gemlock-${{ matrix.ruby }}-${{ hashFiles('Gemfile', 'format_parser.gemspec') }}
|
35
|
+
restore-keys: |
|
36
|
+
${{ runner.os }}-gemlock-${{ matrix.ruby }}-
|
37
|
+
- name: Bundle Cache
|
38
|
+
id: cache-gems
|
39
|
+
uses: actions/cache@v2
|
40
|
+
with:
|
41
|
+
path: vendor/bundle
|
42
|
+
key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ hashFiles('Gemfile', 'Gemfile.lock', 'format_parser.gemspec') }}
|
43
|
+
restore-keys: |
|
44
|
+
${{ runner.os }}-gems-${{ matrix.ruby }}-
|
45
|
+
${{ runner.os }}-gems-
|
46
|
+
- name: Bundle Install
|
47
|
+
if: steps.cache-gems.outputs.cache-hit != 'true'
|
48
|
+
run: bundle install --jobs 4 --retry 3
|
49
|
+
- name: Rubocop Cache
|
50
|
+
uses: actions/cache@v2
|
51
|
+
with:
|
52
|
+
path: ~/.cache/rubocop_cache
|
53
|
+
key: ${{ runner.os }}-rubocop-${{ hashFiles('.rubocop.yml') }}
|
54
|
+
restore-keys: |
|
55
|
+
${{ runner.os }}-rubocop-
|
56
|
+
- name: Rubocop
|
57
|
+
run: bundle exec rubocop
|
58
|
+
test:
|
59
|
+
name: Specs
|
60
|
+
runs-on: ubuntu-18.04
|
61
|
+
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
62
|
+
strategy:
|
63
|
+
matrix:
|
64
|
+
ruby:
|
65
|
+
- 2.7
|
66
|
+
- 2.6
|
67
|
+
- 2.5
|
68
|
+
- 2.4
|
69
|
+
- 2.3
|
70
|
+
- 2.2
|
71
|
+
- jruby
|
72
|
+
experimental: [false]
|
73
|
+
include:
|
74
|
+
- ruby: 3.0
|
75
|
+
experimental: true
|
76
|
+
steps:
|
77
|
+
- name: Checkout
|
78
|
+
uses: actions/checkout@v2
|
79
|
+
- name: Setup Ruby
|
80
|
+
uses: ruby/setup-ruby@v1
|
81
|
+
with:
|
82
|
+
ruby-version: ${{ matrix.ruby }}
|
83
|
+
- name: Gemfile Cache
|
84
|
+
uses: actions/cache@v2
|
85
|
+
with:
|
86
|
+
path: Gemfile.lock
|
87
|
+
key: ${{ runner.os }}-gemlock-${{ matrix.ruby }}-${{ hashFiles('Gemfile', 'format_parser.gemspec') }}
|
88
|
+
restore-keys: |
|
89
|
+
${{ runner.os }}-gemlock-${{ matrix.ruby }}-
|
90
|
+
- name: Bundle Cache
|
91
|
+
id: cache-gems
|
92
|
+
uses: actions/cache@v2
|
93
|
+
with:
|
94
|
+
path: vendor/bundle
|
95
|
+
key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ hashFiles('Gemfile', 'Gemfile.lock', 'format_parser.gemspec') }}
|
96
|
+
restore-keys: |
|
97
|
+
${{ runner.os }}-gems-${{ matrix.ruby }}-
|
98
|
+
${{ runner.os }}-gems-
|
99
|
+
- name: Bundle Install
|
100
|
+
if: steps.cache-gems.outputs.cache-hit != 'true'
|
101
|
+
run: bundle install --jobs 4 --retry 3
|
102
|
+
- name: RSpec
|
103
|
+
continue-on-error: ${{ matrix.experimental }}
|
104
|
+
run: bundle exec rake parallel:spec
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## 0.28.0
|
2
|
+
* Change `FormatParser.parse_http` to follow HTTP redirects
|
3
|
+
|
4
|
+
## 0.27.0
|
5
|
+
* Add `#content_type` on `Result` return values which makes sense for the detected filetype
|
6
|
+
|
7
|
+
## 0.26.0
|
8
|
+
* Add support for M3U format files
|
9
|
+
|
10
|
+
## 0.25.6
|
11
|
+
* Fix FormatParser.parse (with `results: :first`) to be deterministic
|
12
|
+
|
13
|
+
## 0.25.5
|
14
|
+
* DPX: Fix DPXParser to support images without aspect ratio
|
15
|
+
|
1
16
|
## 0.25.4
|
2
17
|
* MP3: Fix MP3Parser to return nil for TIFF files
|
3
18
|
* Add support to ruby 2.7
|
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
|
|
data/format_parser.gemspec
CHANGED
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency 'exifr', '~> 1', '>= 1.3.8'
|
35
35
|
spec.add_dependency 'id3tag', '~> 0.14'
|
36
36
|
spec.add_dependency 'faraday', '~> 0.13'
|
37
|
+
spec.add_dependency 'faraday_middleware', '~> 0.14'
|
37
38
|
spec.add_dependency 'measurometer', '~> 1'
|
38
39
|
|
39
40
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
data/lib/archive.rb
CHANGED
@@ -26,6 +26,9 @@ module FormatParser
|
|
26
26
|
# it can be placed here
|
27
27
|
attr_accessor :intrinsics
|
28
28
|
|
29
|
+
# The MIME type of the archive
|
30
|
+
attr_accessor :content_type
|
31
|
+
|
29
32
|
# Only permits assignments via defined accessors
|
30
33
|
def initialize(**attributes)
|
31
34
|
attributes.map { |(k, v)| public_send("#{k}=", v) }
|
data/lib/audio.rb
CHANGED
@@ -35,6 +35,9 @@ module FormatParser
|
|
35
35
|
# it can be placed here
|
36
36
|
attr_accessor :intrinsics
|
37
37
|
|
38
|
+
# The MIME type of the sound file
|
39
|
+
attr_accessor :content_type
|
40
|
+
|
38
41
|
# Only permits assignments via defined accessors
|
39
42
|
def initialize(**attributes)
|
40
43
|
attributes.map { |(k, v)| public_send("#{k}=", v) }
|
data/lib/document.rb
CHANGED
data/lib/format_parser.rb
CHANGED
@@ -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
|
@@ -49,8 +50,10 @@ module FormatParser
|
|
49
50
|
parser_provided_formats = Array(formats)
|
50
51
|
parser_provided_natures = Array(natures)
|
51
52
|
PARSER_MUX.synchronize do
|
52
|
-
|
53
|
-
|
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
|
@@ -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]
|
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
|
data/lib/image.rb
CHANGED
@@ -64,6 +64,9 @@ module FormatParser
|
|
64
64
|
# it can be placed here
|
65
65
|
attr_accessor :intrinsics
|
66
66
|
|
67
|
+
# The MIME type of the image file
|
68
|
+
attr_accessor :content_type
|
69
|
+
|
67
70
|
# Only permits assignments via defined accessors
|
68
71
|
def initialize(**attributes)
|
69
72
|
attributes.map { |(k, v)| public_send("#{k}=", v) }
|
data/lib/parsers/aiff_parser.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
class FormatParser::AIFFParser
|
2
2
|
include FormatParser::IOUtils
|
3
3
|
|
4
|
+
AIFF_MIME_TYPE = 'audio/x-aiff'
|
5
|
+
|
4
6
|
# Known chunk types we can omit when parsing,
|
5
7
|
# grossly lifted from http://www.muratnkonar.com/aiff/
|
6
8
|
KNOWN_CHUNKS = [
|
@@ -70,7 +72,8 @@ class FormatParser::AIFFParser
|
|
70
72
|
num_audio_channels: channels,
|
71
73
|
audio_sample_rate_hz: sample_rate.to_i,
|
72
74
|
media_duration_frames: sample_frames,
|
73
|
-
media_duration_seconds: duration_in_seconds
|
75
|
+
media_duration_seconds: duration_in_seconds,
|
76
|
+
content_type: AIFF_MIME_TYPE,
|
74
77
|
)
|
75
78
|
end
|
76
79
|
|
data/lib/parsers/bmp_parser.rb
CHANGED
@@ -5,6 +5,7 @@ class FormatParser::BMPParser
|
|
5
5
|
|
6
6
|
VALID_BMP = 'BM'
|
7
7
|
PERMISSIBLE_PIXEL_ARRAY_LOCATIONS = 26..512
|
8
|
+
BMP_MIME_TYPE = 'image/bmp'
|
8
9
|
|
9
10
|
def likely_match?(filename)
|
10
11
|
filename =~ /\.bmp$/i
|
@@ -42,6 +43,7 @@ class FormatParser::BMPParser
|
|
42
43
|
width_px: width,
|
43
44
|
height_px: height,
|
44
45
|
color_mode: :rgb,
|
46
|
+
content_type: BMP_MIME_TYPE,
|
45
47
|
intrinsics: {
|
46
48
|
data_order: data_order,
|
47
49
|
bits_per_pixel: bit_depth
|
@@ -63,6 +65,7 @@ class FormatParser::BMPParser
|
|
63
65
|
width_px: width,
|
64
66
|
height_px: height.abs,
|
65
67
|
color_mode: :rgb,
|
68
|
+
content_type: BMP_MIME_TYPE,
|
66
69
|
intrinsics: {
|
67
70
|
vertical_resolution: vertical_res,
|
68
71
|
horizontal_resolution: horizontal_res,
|
data/lib/parsers/cr2_parser.rb
CHANGED
@@ -6,6 +6,7 @@ class FormatParser::CR2Parser
|
|
6
6
|
|
7
7
|
TIFF_HEADER = [0x49, 0x49, 0x2a, 0x00]
|
8
8
|
CR2_HEADER = [0x43, 0x52, 0x02, 0x00]
|
9
|
+
CR2_MIME_TYPE = 'image/x-canon-cr2'
|
9
10
|
|
10
11
|
def likely_match?(filename)
|
11
12
|
filename =~ /\.cr2$/i
|
@@ -39,6 +40,7 @@ class FormatParser::CR2Parser
|
|
39
40
|
display_height_px: exif_data.rotated? ? w : h,
|
40
41
|
orientation: exif_data.orientation_sym,
|
41
42
|
intrinsics: {exif: exif_data},
|
43
|
+
content_type: CR2_MIME_TYPE,
|
42
44
|
)
|
43
45
|
rescue EXIFR::MalformedTIFF
|
44
46
|
nil
|
data/lib/parsers/dpx_parser.rb
CHANGED
@@ -6,6 +6,11 @@ class FormatParser::DPXParser
|
|
6
6
|
BE_MAGIC = 'SDPX'
|
7
7
|
LE_MAGIC = BE_MAGIC.reverse
|
8
8
|
|
9
|
+
# There is no official MIME type for DPX, so we have
|
10
|
+
# to invent something useful. We will prefix it with x-
|
11
|
+
# to indicate that it is a vendor subtype
|
12
|
+
DPX_MIME_TYPE = 'image/x-dpx'
|
13
|
+
|
9
14
|
class ByteOrderHintIO < SimpleDelegator
|
10
15
|
def initialize(io, is_little_endian)
|
11
16
|
super(io)
|
@@ -35,18 +40,23 @@ class FormatParser::DPXParser
|
|
35
40
|
w = dpx_structure.fetch(:image).fetch(:pixels_per_line)
|
36
41
|
h = dpx_structure.fetch(:image).fetch(:lines_per_element)
|
37
42
|
|
43
|
+
display_w = w
|
44
|
+
display_h = h
|
45
|
+
|
38
46
|
pixel_aspect_w = dpx_structure.fetch(:orientation).fetch(:horizontal_pixel_aspect)
|
39
47
|
pixel_aspect_h = dpx_structure.fetch(:orientation).fetch(:vertical_pixel_aspect)
|
40
|
-
pixel_aspect = pixel_aspect_w / pixel_aspect_h.to_f
|
41
48
|
|
42
|
-
|
49
|
+
# Find display height and width based on aspect only if the file structure has pixel aspects
|
50
|
+
if pixel_aspect_h != 0 && pixel_aspect_w != 0
|
51
|
+
pixel_aspect = pixel_aspect_w / pixel_aspect_h.to_f
|
43
52
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
53
|
+
image_aspect = w / h.to_f * pixel_aspect
|
54
|
+
|
55
|
+
if image_aspect > 1
|
56
|
+
display_h = (display_w / image_aspect).round
|
57
|
+
else
|
58
|
+
display_w = (display_h * image_aspect).round
|
59
|
+
end
|
50
60
|
end
|
51
61
|
|
52
62
|
FormatParser::Image.new(
|
@@ -56,6 +66,7 @@ class FormatParser::DPXParser
|
|
56
66
|
display_width_px: display_w,
|
57
67
|
display_height_px: display_h,
|
58
68
|
intrinsics: dpx_structure,
|
69
|
+
content_type: DPX_MIME_TYPE,
|
59
70
|
)
|
60
71
|
end
|
61
72
|
|
data/lib/parsers/flac_parser.rb
CHANGED
@@ -4,6 +4,7 @@ class FormatParser::FLACParser
|
|
4
4
|
MAGIC_BYTES = 4
|
5
5
|
MAGIC_BYTE_STRING = 'fLaC'
|
6
6
|
BLOCK_HEADER_BYTES = 4
|
7
|
+
FLAC_MIME_TYPE = 'audio/x-flac'
|
7
8
|
|
8
9
|
def likely_match?(filename)
|
9
10
|
filename =~ /\.flac$/i
|
@@ -61,6 +62,7 @@ class FormatParser::FLACParser
|
|
61
62
|
audio_sample_rate_hz: sample_rate,
|
62
63
|
media_duration_seconds: duration,
|
63
64
|
media_duration_frames: total_samples,
|
65
|
+
content_type: FLAC_MIME_TYPE,
|
64
66
|
intrinsics: {
|
65
67
|
bits_per_sample: bits_per_sample,
|
66
68
|
minimum_frame_size: minimum_frame_size,
|
data/lib/parsers/gif_parser.rb
CHANGED
@@ -3,6 +3,7 @@ class FormatParser::GIFParser
|
|
3
3
|
|
4
4
|
HEADERS = ['GIF87a', 'GIF89a'].map(&:b)
|
5
5
|
NETSCAPE_AND_AUTHENTICATION_CODE = 'NETSCAPE2.0'
|
6
|
+
GIF_MIME_TYPE = 'image/gif'
|
6
7
|
|
7
8
|
def likely_match?(filename)
|
8
9
|
filename =~ /\.gif$/i
|
@@ -45,6 +46,7 @@ class FormatParser::GIFParser
|
|
45
46
|
height_px: h,
|
46
47
|
has_multiple_frames: is_animated,
|
47
48
|
color_mode: :indexed,
|
49
|
+
content_type: GIF_MIME_TYPE
|
48
50
|
)
|
49
51
|
end
|
50
52
|
|
data/lib/parsers/jpeg_parser.rb
CHANGED
@@ -12,6 +12,7 @@ class FormatParser::JPEGParser
|
|
12
12
|
APP1_MARKER = 0xE1 # maybe EXIF
|
13
13
|
EXIF_MAGIC_STRING = "Exif\0\0".b
|
14
14
|
MUST_FIND_NEXT_MARKER_WITHIN_BYTES = 1024
|
15
|
+
JPEG_MIME_TYPE = 'image/jpeg'
|
15
16
|
|
16
17
|
def self.likely_match?(filename)
|
17
18
|
filename =~ /\.jpe?g$/i
|
@@ -88,6 +89,7 @@ class FormatParser::JPEGParser
|
|
88
89
|
display_height_px: dh,
|
89
90
|
orientation: flat_exif.orientation_sym,
|
90
91
|
intrinsics: {exif: flat_exif},
|
92
|
+
content_type: JPEG_MIME_TYPE
|
91
93
|
)
|
92
94
|
|
93
95
|
return result
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class FormatParser::M3UParser
|
2
|
+
include FormatParser::IOUtils
|
3
|
+
|
4
|
+
HEADER = '#EXTM3U'
|
5
|
+
M3U8_MIME_TYPE = 'application/vnd.apple.mpegurl' # https://en.wikipedia.org/wiki/M3U#Internet_media_types
|
6
|
+
|
7
|
+
def likely_match?(filename)
|
8
|
+
filename =~ /\.m3u8?$/i
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(io)
|
12
|
+
io = FormatParser::IOConstraint.new(io)
|
13
|
+
|
14
|
+
header = safe_read(io, 7)
|
15
|
+
return unless HEADER.eql?(header)
|
16
|
+
|
17
|
+
FormatParser::Text.new(
|
18
|
+
format: :m3u,
|
19
|
+
content_type: M3U8_MIME_TYPE,
|
20
|
+
)
|
21
|
+
end
|
22
|
+
FormatParser.register_parser new, natures: :text, formats: :m3u
|
23
|
+
end
|
data/lib/parsers/moov_parser.rb
CHANGED
@@ -11,6 +11,12 @@ class FormatParser::MOOVParser
|
|
11
11
|
'm4a ' => :m4a,
|
12
12
|
}
|
13
13
|
|
14
|
+
# https://tools.ietf.org/html/rfc4337#section-2
|
15
|
+
# There is also video/quicktime which we should be able to capture
|
16
|
+
# here, but there is currently no detection for MOVs versus MP4s
|
17
|
+
MP4_AU_MIME_TYPE = 'audio/mp4'
|
18
|
+
MP4_MIXED_MIME_TYPE = 'video/mp4'
|
19
|
+
|
14
20
|
def likely_match?(filename)
|
15
21
|
filename =~ /\.(mov|m4a|ma4|mp4|aac|m4v)$/i
|
16
22
|
end
|
@@ -49,10 +55,12 @@ class FormatParser::MOOVParser
|
|
49
55
|
end
|
50
56
|
|
51
57
|
# M4A only contains audio, while MP4 and friends can contain video.
|
52
|
-
|
58
|
+
fmt = format_from_moov_type(file_type)
|
59
|
+
if fmt == :m4a
|
53
60
|
FormatParser::Audio.new(
|
54
61
|
format: format_from_moov_type(file_type),
|
55
62
|
media_duration_seconds: media_duration_s,
|
63
|
+
content_type: MP4_AU_MIME_TYPE,
|
56
64
|
intrinsics: atom_tree,
|
57
65
|
)
|
58
66
|
else
|
@@ -61,6 +69,7 @@ class FormatParser::MOOVParser
|
|
61
69
|
width_px: width,
|
62
70
|
height_px: height,
|
63
71
|
media_duration_seconds: media_duration_s,
|
72
|
+
content_type: MP4_MIXED_MIME_TYPE,
|
64
73
|
intrinsics: atom_tree,
|
65
74
|
)
|
66
75
|
end
|