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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50bc86a224ca66770b89a5d682a0bfe35a1656b984d03c4ddcf241db3b806419
4
- data.tar.gz: eff170d586dd5d3b8c7b954dd93eccae0e4f95734da74bdbe0e9c5876032e8db
3
+ metadata.gz: 75cde4e32a958cb79b87b7c333a4cb39fb3bd6be937c00bb4c8cd2e2012741b2
4
+ data.tar.gz: 1729529848f522024d7ce5774dab64344b7efe70358ab832793fa992e6bc6c2a
5
5
  SHA512:
6
- metadata.gz: 7ec4bb9f83d259d7331562b2ff741bbf12514ac8521c7f61810f139a2f3a7271851766ce85859b1df58910e3d9c02feeffe2d2f8a08bee8a508bb14c85958c3f
7
- data.tar.gz: c964a651bc33f323bc71f11814e32d56328dcab774725bc7b2cddf1bf412e0ac978e7548596d863b388dcd3099d4aa538f1060f18ab410a1df16cf527e54c5fd
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.
@@ -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
 
@@ -8,8 +8,8 @@ module ActiveAnalysis
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 0
11
- MINOR = 2
12
- TINY = 1
11
+ MINOR = 3
12
+ TINY = 0
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
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.2.1
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-14 00:00:00.000000000 Z
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: mini_magick
42
+ name: image_processing
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '4.1'
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: '4.1'
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