active_analysis 0.2.1 → 0.3.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/README.md +12 -0
- data/lib/active_analysis.rb +3 -0
- data/lib/active_analysis/addon.rb +20 -0
- data/lib/active_analysis/addon/image_addon.rb +17 -0
- data/lib/active_analysis/addon/image_addon/optimal_quality.rb +49 -0
- data/lib/active_analysis/addon/image_addon/white_background.rb +63 -0
- data/lib/active_analysis/analyzer/image_analyzer.rb +6 -2
- data/lib/active_analysis/engine.rb +10 -0
- data/lib/active_analysis/gem_version.rb +2 -2
- metadata +9 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75cde4e32a958cb79b87b7c333a4cb39fb3bd6be937c00bb4c8cd2e2012741b2
|
4
|
+
data.tar.gz: 1729529848f522024d7ce5774dab64344b7efe70358ab832793fa992e6bc6c2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a9ccbaeb56aad39604101559f9ec0909d551d315714ccf5b3313be824aa6a46138a67bb96404e426864bc7868e971890a76c4e95279bc0967571128e87d1975
|
7
|
+
data.tar.gz: 7b43f43265abaa0adb94b4722b1cf26cb74a9d20de9b103b0a706df906960dd7ec2c5f5f701572d75a05503b1058c41c50c9baf2b97bde3b5728d272b661de7a
|
data/README.md
CHANGED
@@ -63,6 +63,18 @@ A modification of the original video analyzer. Requires the [FFmpeg](https://www
|
|
63
63
|
- Audio (true if file has an audio channel, false if not)
|
64
64
|
- Video (true if file has an video channel, false if not)
|
65
65
|
|
66
|
+
## Addons
|
67
|
+
Active Analysis allows additional features to be added to the image analyzers through addons. To create an addon simply inherit the `Addon` class and add it to the addons array in the configuration.
|
68
|
+
```ruby
|
69
|
+
Rails.application.configure do |config|
|
70
|
+
config.active_analysis.addons << ActiveAnalysis::Addon::ImageAddon::OptimalQuality
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
The following addons available:
|
75
|
+
- ImageAddon::OptimalQuality: An EXPERIMENTAL addon that calculates the optimal image quality using a DSSIM of 0.001. This addon is SLOOOOOOW.
|
76
|
+
- ImageAddon::WhiteBackground: An EXPERIMENTAL addon that checks if the image has a white background. Requires both vips and image magick to be installed.
|
77
|
+
|
66
78
|
## Development
|
67
79
|
|
68
80
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/active_analysis.rb
CHANGED
@@ -7,9 +7,12 @@ module ActiveAnalysis
|
|
7
7
|
autoload :FixtureSet
|
8
8
|
|
9
9
|
mattr_accessor :logger
|
10
|
+
|
10
11
|
mattr_accessor :image_library
|
11
12
|
mattr_accessor :image_analyzer
|
12
13
|
mattr_accessor :audio_analyzer
|
13
14
|
mattr_accessor :pdf_analyzer
|
14
15
|
mattr_accessor :video_analyzer
|
16
|
+
|
17
|
+
mattr_accessor :addons
|
15
18
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveAnalysis
|
4
|
+
# This is an abstract base class for analyzer addons, which extract extra metadata from blobs.
|
5
|
+
class Addon
|
6
|
+
attr_reader :file
|
7
|
+
|
8
|
+
def self.accept?(blob)
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(file)
|
13
|
+
@file = file
|
14
|
+
end
|
15
|
+
|
16
|
+
def metadata
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../addon"
|
4
|
+
|
5
|
+
module ActiveAnalysis
|
6
|
+
# This is an abstract base class for image addons
|
7
|
+
class Addon::ImageAddon < Addon
|
8
|
+
def self.accept?(blob)
|
9
|
+
blob.image?
|
10
|
+
end
|
11
|
+
|
12
|
+
def metadata
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../image_addon"
|
4
|
+
|
5
|
+
module ActiveAnalysis
|
6
|
+
class Addon::ImageAddon::OptimalQuality < Addon::ImageAddon
|
7
|
+
def metadata
|
8
|
+
{ optimal_quality: calculate_optimal_quality }
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def calculate_optimal_quality
|
13
|
+
quality = 85
|
14
|
+
|
15
|
+
loop do
|
16
|
+
new_quality = quality - 5
|
17
|
+
dssim = calculate_dssim(new_quality)
|
18
|
+
break if dssim > 0.001 || quality <= 50
|
19
|
+
quality = new_quality
|
20
|
+
end
|
21
|
+
|
22
|
+
quality
|
23
|
+
rescue
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def calculate_dssim(quality)
|
28
|
+
image_with_quality(quality) do |image|
|
29
|
+
dssim = `dssim #{file.path} #{image.path}`
|
30
|
+
Float dssim.split.first
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def image_with_quality(quality)
|
35
|
+
extname = File.extname(file.path)
|
36
|
+
basename = File.basename(file.path, extname)
|
37
|
+
|
38
|
+
Tempfile.create(["#{basename}_#{quality}", extname]) do |tempfile|
|
39
|
+
processor.apply(saver: { format: "jpg", quality: quality }).call(file.path, destination: tempfile.path)
|
40
|
+
yield tempfile
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def processor
|
45
|
+
ActiveAnalysis.image_library == :vips ? ::ImageProcessing::Vips : ::ImageProcessing::MiniMagick
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../image_addon"
|
4
|
+
|
5
|
+
module ActiveAnalysis
|
6
|
+
class Addon::ImageAddon::WhiteBackground < Addon::ImageAddon
|
7
|
+
def metadata
|
8
|
+
{ white_background: white_background? }
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def white_background?
|
13
|
+
corners = extract_corner_areas(file)
|
14
|
+
colors = corners.map { |corner| primary_color_for(corner) }
|
15
|
+
colors.all? { |color| color.all? { |value| value > 250 } }
|
16
|
+
rescue
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract_corner_areas(image)
|
21
|
+
paths = []
|
22
|
+
|
23
|
+
image_path = ActiveAnalysis.image_analyzer == :vips ? image.filename : image.path
|
24
|
+
basename = SecureRandom.urlsafe_base64
|
25
|
+
width = image.width
|
26
|
+
height = image.height
|
27
|
+
size = 8
|
28
|
+
|
29
|
+
paths << Rails.root.join("tmp", "#{basename}_top_left.jpg")
|
30
|
+
`vips im_extract_area #{image_path} #{paths.last} 0 0 #{size} #{size}`
|
31
|
+
|
32
|
+
paths << Rails.root.join("tmp", "#{basename}_top_right.jpg")
|
33
|
+
`vips im_extract_area #{image_path} #{paths.last} #{width - size} 0 #{size} #{size}`
|
34
|
+
|
35
|
+
paths << Rails.root.join("tmp", "#{basename}_bottom_right.jpg")
|
36
|
+
`vips im_extract_area #{image_path} #{paths.last} #{width - size} #{height - size} #{size} #{size}`
|
37
|
+
|
38
|
+
paths << Rails.root.join("tmp", "#{basename}_bottom_left.jpg")
|
39
|
+
`vips im_extract_area #{image_path} #{paths.last} 0 #{height - size} #{size} #{size}`
|
40
|
+
|
41
|
+
paths
|
42
|
+
end
|
43
|
+
|
44
|
+
def primary_color_for(filepath)
|
45
|
+
histogram = generate_color_histogram(filepath)
|
46
|
+
sorted = sort_by_frequency(histogram)
|
47
|
+
extract_dominant_rgb(sorted)
|
48
|
+
end
|
49
|
+
|
50
|
+
def generate_color_histogram(path)
|
51
|
+
`convert #{path} +dither -colors 5 -define histogram:unique-colors=true -format "%c" histogram:info:`
|
52
|
+
end
|
53
|
+
|
54
|
+
def sort_by_frequency(histogram)
|
55
|
+
histogram.each_line.map { |line| parts = line.split(":"); [parts[0].to_i, parts[1]] }.sort_by { |line| line[0] }.reverse
|
56
|
+
end
|
57
|
+
|
58
|
+
def extract_dominant_rgb(array)
|
59
|
+
array.map { |line| line[1].match(/\(([\d.,]+)/).captures.first.split(",").take(3).map(&:to_i) }.first
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -19,9 +19,9 @@ module ActiveAnalysis
|
|
19
19
|
def metadata
|
20
20
|
read_image do |image|
|
21
21
|
if rotated_image?(image)
|
22
|
-
{ width: image.height, height: image.width, opaque: opaque?(image) }
|
22
|
+
{ width: image.height, height: image.width, opaque: opaque?(image), **addons(image) }.compact
|
23
23
|
else
|
24
|
-
{ width: image.width, height: image.height, opaque: opaque?(image) }
|
24
|
+
{ width: image.width, height: image.height, opaque: opaque?(image), **addons(image) }.compact
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -38,6 +38,10 @@ module ActiveAnalysis
|
|
38
38
|
def opaque?(image)
|
39
39
|
raise NotImplementedError
|
40
40
|
end
|
41
|
+
|
42
|
+
def addons(image)
|
43
|
+
ActiveAnalysis.addons.select { |addon| addon.accept?(blob) }.map { |addon_class| addon_class.new(image).metadata }.reduce({}, :merge).compact
|
44
|
+
end
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "active_storage"
|
4
4
|
|
5
5
|
require "marcel"
|
6
|
+
require "image_processing"
|
6
7
|
require "ruby-vips"
|
7
8
|
require "mini_magick"
|
8
9
|
|
@@ -14,21 +15,30 @@ require_relative "analyzer/audio_analyzer"
|
|
14
15
|
require_relative "analyzer/video_analyzer"
|
15
16
|
require_relative "analyzer/pdf_analyzer"
|
16
17
|
|
18
|
+
require_relative "addon"
|
19
|
+
require_relative "addon/image_addon"
|
20
|
+
require_relative "addon/image_addon/optimal_quality"
|
21
|
+
require_relative "addon/image_addon/white_background"
|
22
|
+
|
17
23
|
module ActiveAnalysis
|
18
24
|
class Engine < ::Rails::Engine
|
19
25
|
isolate_namespace ActiveAnalysis
|
20
26
|
|
21
27
|
config.active_analysis = ActiveSupport::OrderedOptions.new
|
28
|
+
config.active_analysis.addons = []
|
29
|
+
|
22
30
|
config.eager_load_namespaces << ActiveAnalysis
|
23
31
|
|
24
32
|
initializer "active_analysis.configs" do
|
25
33
|
config.after_initialize do |app|
|
26
34
|
ActiveAnalysis.logger = app.config.active_analysis.logger || Rails.logger
|
35
|
+
|
27
36
|
ActiveAnalysis.image_library = app.config.active_analysis.image_library || app.config.active_storage.variant_processor || :mini_magick
|
28
37
|
ActiveAnalysis.image_analyzer = app.config.active_analysis.image_analyzer || true
|
29
38
|
ActiveAnalysis.audio_analyzer = app.config.active_analysis.audio_analyzer || true
|
30
39
|
ActiveAnalysis.pdf_analyzer = app.config.active_analysis.pdf_analyzer || true
|
31
40
|
ActiveAnalysis.video_analyzer = app.config.active_analysis.video_analyzer || true
|
41
|
+
ActiveAnalysis.addons = app.config.active_analysis.addons || []
|
32
42
|
end
|
33
43
|
end
|
34
44
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_analysis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Breno Gazzola
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-06-
|
11
|
+
date: 2021-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activestorage
|
@@ -39,47 +39,19 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '6.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: image_processing
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '1.2'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: ruby-vips
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '2.0'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '2.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: platform_agent
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '1.0'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '1.0'
|
54
|
+
version: '1.2'
|
83
55
|
- !ruby/object:Gem::Dependency
|
84
56
|
name: sqlite3
|
85
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -185,6 +157,10 @@ files:
|
|
185
157
|
- app/views/layouts/active_analysis/application.html.erb
|
186
158
|
- config/routes.rb
|
187
159
|
- lib/active_analysis.rb
|
160
|
+
- lib/active_analysis/addon.rb
|
161
|
+
- lib/active_analysis/addon/image_addon.rb
|
162
|
+
- lib/active_analysis/addon/image_addon/optimal_quality.rb
|
163
|
+
- lib/active_analysis/addon/image_addon/white_background.rb
|
188
164
|
- lib/active_analysis/analyzer.rb
|
189
165
|
- lib/active_analysis/analyzer/audio_analyzer.rb
|
190
166
|
- lib/active_analysis/analyzer/image_analyzer.rb
|