image_optim_pack 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +7 -0
  3. data/.rubocop.yml +51 -0
  4. data/.travis.yml +22 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +27 -0
  7. data/Makefile +312 -0
  8. data/README.markdown +77 -0
  9. data/Vagrantfile +13 -0
  10. data/acknowledgements/7z.txt +56 -0
  11. data/acknowledgements/advancecomp.txt +674 -0
  12. data/acknowledgements/bmp2png.txt +8 -0
  13. data/acknowledgements/cexcept.txt +8 -0
  14. data/acknowledgements/gifread.txt +14 -0
  15. data/acknowledgements/gifsicle.txt +343 -0
  16. data/acknowledgements/jhead.txt +12 -0
  17. data/acknowledgements/jpegoptim.txt +339 -0
  18. data/acknowledgements/libjpeg.txt +64 -0
  19. data/acknowledgements/libpng.txt +90 -0
  20. data/acknowledgements/optipng-authors.txt +26 -0
  21. data/acknowledgements/optipng.txt +21 -0
  22. data/acknowledgements/pngcrush.txt +35 -0
  23. data/acknowledgements/pngquant.txt +56 -0
  24. data/acknowledgements/zlib.txt +34 -0
  25. data/acknowledgements/zopfli-contributors.txt +6 -0
  26. data/acknowledgements/zopfli.txt +201 -0
  27. data/image_optim_pack.gemspec +26 -0
  28. data/lib/image_optim/pack.rb +110 -0
  29. data/lib/image_optim_pack.rb +1 -0
  30. data/script/livecheck +72 -0
  31. data/script/run +26 -0
  32. data/script/update_versions +17 -0
  33. data/script/vm_init +30 -0
  34. data/spec/image_optim/pack_spec.rb +114 -0
  35. data/spec/image_optim_spec.rb +109 -0
  36. data/spec/spec_helper.rb +0 -0
  37. data/vendor/darwin/i386/advpng +0 -0
  38. data/vendor/darwin/i386/gifsicle +0 -0
  39. data/vendor/darwin/i386/jhead +0 -0
  40. data/vendor/darwin/i386/jpegoptim +0 -0
  41. data/vendor/darwin/i386/jpegtran +0 -0
  42. data/vendor/darwin/i386/libjpeg.dylib +0 -0
  43. data/vendor/darwin/i386/libpng.dylib +0 -0
  44. data/vendor/darwin/i386/libz.dylib +0 -0
  45. data/vendor/darwin/i386/optipng +0 -0
  46. data/vendor/darwin/i386/pngcrush +0 -0
  47. data/vendor/darwin/i386/pngquant +0 -0
  48. data/vendor/darwin/x86_64/advpng +0 -0
  49. data/vendor/darwin/x86_64/gifsicle +0 -0
  50. data/vendor/darwin/x86_64/jhead +0 -0
  51. data/vendor/darwin/x86_64/jpegoptim +0 -0
  52. data/vendor/darwin/x86_64/jpegtran +0 -0
  53. data/vendor/darwin/x86_64/libjpeg.dylib +0 -0
  54. data/vendor/darwin/x86_64/libpng.dylib +0 -0
  55. data/vendor/darwin/x86_64/libz.dylib +0 -0
  56. data/vendor/darwin/x86_64/optipng +0 -0
  57. data/vendor/darwin/x86_64/pngcrush +0 -0
  58. data/vendor/darwin/x86_64/pngquant +0 -0
  59. data/vendor/linux/i686/advpng +0 -0
  60. data/vendor/linux/i686/gifsicle +0 -0
  61. data/vendor/linux/i686/jhead +0 -0
  62. data/vendor/linux/i686/jpegoptim +0 -0
  63. data/vendor/linux/i686/jpegtran +0 -0
  64. data/vendor/linux/i686/libjpeg.so +0 -0
  65. data/vendor/linux/i686/libpng.so +0 -0
  66. data/vendor/linux/i686/libz.so +0 -0
  67. data/vendor/linux/i686/optipng +0 -0
  68. data/vendor/linux/i686/pngcrush +0 -0
  69. data/vendor/linux/i686/pngquant +0 -0
  70. data/vendor/linux/x86_64/advpng +0 -0
  71. data/vendor/linux/x86_64/gifsicle +0 -0
  72. data/vendor/linux/x86_64/jhead +0 -0
  73. data/vendor/linux/x86_64/jpegoptim +0 -0
  74. data/vendor/linux/x86_64/jpegtran +0 -0
  75. data/vendor/linux/x86_64/libjpeg.so +0 -0
  76. data/vendor/linux/x86_64/libpng.so +0 -0
  77. data/vendor/linux/x86_64/libz.so +0 -0
  78. data/vendor/linux/x86_64/optipng +0 -0
  79. data/vendor/linux/x86_64/pngcrush +0 -0
  80. data/vendor/linux/x86_64/pngquant +0 -0
  81. metadata +196 -0
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'image_optim_pack'
5
+ s.version = '0.1.0'
6
+ s.summary = %q{Precompiled binaries for image_optim: advpng, gifsicle, jhead, jpegoptim, jpegtran, optipng, pngcrush, pngquant}
7
+ s.homepage = "http://github.com/toy/#{s.name}"
8
+ s.authors = ['Ivan Kuchin']
9
+ s.license = 'MIT'
10
+
11
+ s.rubyforge_project = s.name
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = %w[lib]
17
+
18
+ s.add_dependency 'image_optim', '~> 0.18'
19
+ s.add_dependency 'fspath', '~> 2.1'
20
+
21
+ s.add_development_dependency 'versionomy', '~> 0.4.4'
22
+ s.add_development_dependency 'rspec', '~> 3.0'
23
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('1.9.3')
24
+ s.add_development_dependency 'rubocop', '~> 0.26.0'
25
+ end
26
+ end
@@ -0,0 +1,110 @@
1
+ require 'fspath'
2
+ require 'image_optim/bin_resolver/bin'
3
+
4
+ class ImageOptim
5
+ # Handle selection of directory with binaries most suitable for current
6
+ # operating system and architecture
7
+ module Pack
8
+ # Path to binary, last two parts are expect to be os/arch
9
+ class Path
10
+ # Path provided to initialize as FSPath
11
+ attr_reader :path
12
+
13
+ # Intended os
14
+ attr_reader :os
15
+
16
+ # Inteded architecture
17
+ attr_reader :arch
18
+
19
+ # Receive path, use last part for arch and last but one part for os
20
+ def initialize(path)
21
+ @path = FSPath(path)
22
+ @os = @path.dirname.basename.to_s
23
+ @arch = @path.basename.to_s
24
+ end
25
+
26
+ # Return path converted to string
27
+ def to_s
28
+ path.to_s
29
+ end
30
+
31
+ # Cached array of BinResolver::Bin instances for each bin
32
+ def bins
33
+ @bins ||= bin_paths.map do |bin_path|
34
+ BinResolver::Bin.new(bin_path.basename.to_s, bin_path.to_s)
35
+ end
36
+ end
37
+
38
+ # Return true if all bins can execute and return version
39
+ def all_bins_working?
40
+ bins.all?(&:version)
41
+ end
42
+
43
+ # Return true if all bins can't execute and return version
44
+ def all_bins_failing?
45
+ bins.none?(&:version)
46
+ end
47
+
48
+ # List of bins which can execute and return version
49
+ def working_bins
50
+ bins.select(&:version)
51
+ end
52
+
53
+ # List of bins which can't execute and return version
54
+ def failing_bins
55
+ bins.reject(&:version)
56
+ end
57
+
58
+ private
59
+
60
+ # All children except those starting with 'lib'
61
+ def bin_paths
62
+ path.children.reject{ |child| child.basename.to_s =~ /^lib/ }
63
+ end
64
+ end
65
+
66
+ # downcased `uname -s`
67
+ OS = `uname -s`.strip.downcase
68
+
69
+ # downcased `uname -m`
70
+ ARCH = `uname -m`.strip.downcase
71
+
72
+ # Path to vendor at root of image_optim_pack
73
+ VENDOR_PATH = FSPath('../../../vendor').expand_path(__FILE__)
74
+
75
+ # List of paths
76
+ PATHS = VENDOR_PATH.glob('*/*').map{ |path| Path.new(path) }
77
+
78
+ class << self
79
+ # Return path to directory with binaries
80
+ # Yields debug messages if block given
81
+ def path
82
+ ordered_by_os_arch_match.find do |path|
83
+ yield "image_optim_pack: #{debug_message(path)}" if block_given?
84
+ path.all_bins_working?
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ # Order by match of os and architecture
91
+ def ordered_by_os_arch_match
92
+ PATHS.sort_by do |path|
93
+ [path.os == OS ? 0 : 1, path.arch == ARCH ? 0 : 1]
94
+ end
95
+ end
96
+
97
+ # Messages based on success of getting versions of bins
98
+ def debug_message(path)
99
+ case
100
+ when path.all_bins_working?
101
+ "all bins from #{path} worked"
102
+ when path.all_bins_failing?
103
+ "all bins from #{path} failed"
104
+ else
105
+ "#{path.failing_bins.map(&:name).join(', ')} from #{path} failed"
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1 @@
1
+ require 'image_optim/pack'
data/script/livecheck ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yaml'
4
+ require 'uri'
5
+ require 'net/http'
6
+ require 'versionomy'
7
+
8
+ bare = ARGV.delete('--bare')
9
+
10
+ unless [1, 2].include?(ARGV.length)
11
+ abort 'expected project name and optional current version'
12
+ end
13
+
14
+ def version(string)
15
+ Versionomy.parse(string)
16
+ end
17
+
18
+ name = ARGV[0].downcase
19
+ config = YAML.load(DATA)[name]
20
+ abort "Livecheck for #{name} was not defined" unless config
21
+
22
+ uri = URI(config['url'])
23
+ version_regex = Regexp.new(config['regexp'])
24
+ current_version = ARGV[1] && version(ARGV[1])
25
+
26
+ data = Net::HTTP.get(uri)
27
+ versions = data.scan(version_regex).uniq.map{ |match| version(match[0]) }
28
+ abort 'No versions found' if versions.empty?
29
+
30
+ latest_version = versions.max
31
+ case
32
+ when bare
33
+ print latest_version
34
+ when current_version
35
+ if latest_version > current_version
36
+ puts "#{name} #{latest_version} > #{current_version}"
37
+ end
38
+ else
39
+ puts "#{name} #{latest_version}"
40
+ end
41
+
42
+ __END__
43
+ advancecomp:
44
+ url: http://sourceforge.net/projects/advancemame/rss?path=/advancecomp
45
+ regexp: /advancecomp/(\d+(?:\.\d+)*)/
46
+ gifsicle:
47
+ url: http://www.lcdf.org/gifsicle/
48
+ regexp: gifsicle-(\d+(?:\.\d+)*)\.tar\.gz
49
+ jhead:
50
+ url: http://www.sentex.net/~mwandel/jhead/
51
+ regexp: jhead-(\d+(?:\.\d+)*)
52
+ jpegoptim:
53
+ url: http://www.kokkonen.net/tjko/src/
54
+ regexp: jpegoptim-(\d+(?:\.\d+)*)
55
+ libjpeg:
56
+ url: http://www.ijg.org/files/
57
+ regexp: jpegsrc.v(.*?).tar.gz
58
+ libpng:
59
+ url: http://sourceforge.net/projects/libpng/rss
60
+ regexp: /libpng\d*/(\d+(?:\.\d+)*)/
61
+ libz:
62
+ url: http://sourceforge.net/projects/libpng/rss?path=/zlib
63
+ regexp: /zlib/(\d+(?:\.\d+)*)/
64
+ optipng:
65
+ url: http://sourceforge.net/projects/optipng/rss
66
+ regexp: /OptiPNG/optipng-(\d+(?:\.\d+)*)/
67
+ pngcrush:
68
+ url: http://sourceforge.net/projects/pmt/rss?path=/pngcrush
69
+ regexp: /pngcrush/(\d+(?:\.\d+)*)/
70
+ pngquant:
71
+ url: http://pngquant.org/releases.html
72
+ regexp: pngquant-([0-9.]+)-src.tar.bz2
data/script/run ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -xe
4
+
5
+ MAKE_COMMAND='make all -j'
6
+
7
+ for arg in "$@"; do
8
+ if [[ "$arg" =~ ^([A-Z_]+)=(.*)$ ]]; then
9
+ eval "$(printf %q=%q "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}")"
10
+ fi
11
+ done
12
+
13
+ if [ "$(uname -s | tr A-Z a-z)" != 'darwin' ]; then
14
+ echo 'expected to run on Mac OS X'
15
+ exit 1
16
+ fi
17
+
18
+ for arch in i386 x86_64; do
19
+ $MAKE_COMMAND ARCH=$arch
20
+ done
21
+
22
+ for vm in linux_{i686,x86_64}; do
23
+ vagrant up $vm
24
+ vagrant ssh $vm -c "cd /vagrant; $MAKE_COMMAND"
25
+ [ -n "$NO_HALT" ] || vagrant halt
26
+ done
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'English'
4
+
5
+ tty = $stdout.tty?
6
+
7
+ ARGF.each do |line|
8
+ if (name = line[/^([A-Z]+)_VER *:= *.*/, 1])
9
+ $stderr << name << ' := ' unless tty
10
+ latest_version = `script/livecheck --bare #{name}`.strip
11
+ abort unless $CHILD_STATUS.success?
12
+ $stderr << latest_version << "\n" unless tty
13
+ $stdout << "#{name}_VER := #{latest_version}\n"
14
+ else
15
+ $stdout << line
16
+ end
17
+ end
data/script/vm_init ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bash
2
+
3
+ present(){
4
+ local missing=0
5
+ for name in "$@"; do
6
+ if command -v "$name" >/dev/null 2>&1; then
7
+ echo "got $name"
8
+ else
9
+ echo "no $name"
10
+ missing=1
11
+ fi
12
+ done
13
+ [ $missing -eq 0 ] || return 127
14
+ }
15
+
16
+ if ! present make wget gcc g++ chrpath perl pkg-config automake; then
17
+ if present apt-get; then
18
+ apt-get -y install make wget gcc g++ chrpath perl pkg-config automake
19
+ elif present yum; then
20
+ yum -y install make wget gcc gcc-c++ chrpath perl pkg-config automake
21
+ else
22
+ echo 'unknown package manager'
23
+ exit 1
24
+ fi
25
+ fi
26
+
27
+ if ! make --version 2>&1 | grep -q 'GNU Make'; then
28
+ echo 'non gnu make'
29
+ exit 1
30
+ fi
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/pack'
3
+
4
+ describe ImageOptim::Pack do
5
+ before do
6
+ stub_const('Pack', ImageOptim::Pack)
7
+ stub_const('Path', Pack::Path)
8
+ end
9
+
10
+ describe :path do
11
+ it 'returns path' do
12
+ expect(Pack.path).to be
13
+ end
14
+
15
+ it 'returns instance of Path' do
16
+ expect(Pack.path).to be_an(Path)
17
+ end
18
+
19
+ it 'yields debug messages' do
20
+ expect{ |block| Pack.path(&block) }.
21
+ to yield_with_args(/^image_optim_pack: all bins .* worked$/)
22
+ end
23
+ end
24
+
25
+ describe ImageOptim::Pack::Path do
26
+ describe :path do
27
+ it 'returns FSPath with path' do
28
+ expect(Path.new('path').path).to eq(FSPath('path'))
29
+ end
30
+ end
31
+
32
+ describe :to_s do
33
+ it 'returns stringified path' do
34
+ expect(Path.new(Pathname('path')).to_s).to eq('path')
35
+ end
36
+ end
37
+
38
+ describe :os do
39
+ it 'returns last but one part of path' do
40
+ expect(Path.new('path/futureos/Kqbit').os).to eq('futureos')
41
+ end
42
+ end
43
+
44
+ describe :arch do
45
+ it 'returns last but one part of path' do
46
+ expect(Path.new('path/futureos/Kqbit').arch).to eq('Kqbit')
47
+ end
48
+ end
49
+
50
+ describe :bins do
51
+ subject{ Pack.path.bins }
52
+
53
+ define :be_non_libraries do
54
+ match do |bin|
55
+ bin.name !~ /^lib/
56
+ end
57
+ end
58
+
59
+ it{ should be_an(Array) }
60
+
61
+ it{ should_not be_empty }
62
+
63
+ it{ should all(be_an(ImageOptim::BinResolver::Bin)) }
64
+
65
+ it{ should all(have_attributes(:version => be)) }
66
+
67
+ it{ should all(be_non_libraries) }
68
+ end
69
+
70
+ describe 'bin helpers' do
71
+ def bins_with_versions(*versions)
72
+ versions.map do |version|
73
+ double(:version => version, :inspect => version ? 'good' : 'bad')
74
+ end
75
+ end
76
+
77
+ def path_with_bins(*bins)
78
+ path = Path.new('path')
79
+ allow(path).to receive(:bins).and_return(bins)
80
+ path
81
+ end
82
+
83
+ describe 'all good bins' do
84
+ let(:bins){ bins_with_versions('1.3', '6.1.6') }
85
+ subject{ path_with_bins(*bins) }
86
+
87
+ it{ should be_all_bins_working }
88
+ it{ should_not be_all_bins_failing }
89
+ it{ should have_attributes(:working_bins => eq(bins)) }
90
+ it{ should have_attributes(:failing_bins => be_empty) }
91
+ end
92
+
93
+ describe 'mixed good/bad bins' do
94
+ let(:bins){ bins_with_versions('1.3', nil, '6.1.6', nil) }
95
+ subject{ path_with_bins(*bins) }
96
+
97
+ it{ should_not be_all_bins_working }
98
+ it{ should_not be_all_bins_failing }
99
+ it{ should have_attributes(:working_bins => bins.select(&:version)) }
100
+ it{ should have_attributes(:failing_bins => bins.reject(&:version)) }
101
+ end
102
+
103
+ describe 'all bad bins' do
104
+ let(:bins){ bins_with_versions(nil, nil) }
105
+ subject{ path_with_bins(*bins) }
106
+
107
+ it{ should_not be_all_bins_working }
108
+ it{ should be_all_bins_failing }
109
+ it{ should have_attributes(:working_bins => be_empty) }
110
+ it{ should have_attributes(:failing_bins => eq(bins)) }
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+ require 'image_optim'
3
+ require 'image_optim/cmd'
4
+ require 'tempfile'
5
+ require 'English'
6
+
7
+ describe ImageOptim do
8
+ def self.temp_copy(image)
9
+ image.temp_path.tap{ |path| image.copy(path) }
10
+ end
11
+
12
+ before do
13
+ stub_const('Cmd', ImageOptim::Cmd)
14
+ end
15
+
16
+ def flatten_animation(image)
17
+ if image.format == :gif
18
+ flattened = image.temp_path
19
+ flatten_command = %W[
20
+ convert
21
+ #{image.to_s.shellescape}
22
+ -coalesce
23
+ -append
24
+ #{flattened.to_s.shellescape}
25
+ ].join(' ')
26
+ expect(Cmd.run(flatten_command)).to be_truthy
27
+ flattened
28
+ else
29
+ image
30
+ end
31
+ end
32
+
33
+ def nrmse(image_a, image_b)
34
+ coalesce_a = flatten_animation(image_a)
35
+ coalesce_b = flatten_animation(image_b)
36
+ nrmse_command = %W[
37
+ compare
38
+ -metric RMSE
39
+ #{coalesce_a.to_s.shellescape}
40
+ #{coalesce_b.to_s.shellescape}
41
+ /dev/null
42
+ 2>&1
43
+ ].join(' ')
44
+ output = Cmd.capture(nrmse_command)
45
+ if [0, 1].include?($CHILD_STATUS.exitstatus)
46
+ output[/\((\d+(\.\d+)?)\)/, 1].to_f
47
+ else
48
+ fail "compare #{image_a} with #{image_b} failed with `#{output}`"
49
+ end
50
+ end
51
+
52
+ define :have_same_data_as do |expected|
53
+ match{ |actual| actual.binread == expected.binread }
54
+ end
55
+
56
+ define :have_size do
57
+ match(&:size?)
58
+ end
59
+
60
+ define :be_smaller_than do |expected|
61
+ match{ |actual| actual.size < expected.size }
62
+ end
63
+
64
+ define :be_pixel_identical_to do |expected|
65
+ match do |actual|
66
+ @diff = nrmse(actual, expected)
67
+ @diff == 0
68
+ end
69
+ failure_message do |actual|
70
+ "expected #{actual} to be pixel identical to #{expected}, got "\
71
+ "normalized root-mean-square error of #{@diff}"
72
+ end
73
+ end
74
+
75
+ image_optim = ImageOptim.new
76
+
77
+ # grab images from image_optim gem
78
+ image_optim_root = Gem.loaded_specs['image_optim'].gem_dir
79
+ images_dir = ImageOptim::ImagePath.new(image_optim_root) / 'spec/images'
80
+ test_images = images_dir.glob('**/*.*')
81
+
82
+ # select images which for which there are workers
83
+ test_images = test_images.select do |image|
84
+ image_optim.workers_for_image(image)
85
+ end
86
+
87
+ rotated = images_dir / 'orient/original.jpg'
88
+ rotate_images = images_dir.glob('orient/?.jpg')
89
+
90
+ copies = test_images.map{ |image| temp_copy(image) }
91
+ original_by_copy = Hash[copies.zip(test_images)]
92
+
93
+ image_optim.optimize_images(copies) do |copy, optimized|
94
+ fail 'expected copy to not be nil' if copy.nil?
95
+ original = original_by_copy[copy]
96
+
97
+ it "optimizes #{original}" do
98
+ expect(copy).to have_same_data_as(original)
99
+
100
+ expect(optimized).not_to be_nil
101
+ expect(optimized).to have_size
102
+ expect(optimized).to be_smaller_than(original)
103
+ expect(optimized).not_to have_same_data_as(original)
104
+
105
+ compare_to = rotate_images.include?(original) ? rotated : original
106
+ expect(optimized).to be_pixel_identical_to(compare_to)
107
+ end
108
+ end
109
+ end