active_analysis 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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