format_parser 0.25.4 → 0.28.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 +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
|