smusher 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ Manifest
2
+ pkg
@@ -0,0 +1,71 @@
1
+ Problem
2
+ =======
3
+ - Images are too large because they are not optimized
4
+ - Users & your bandwidth is wasted for useless metadata
5
+ - local image optimization requires tons of programs / libaries / knowledge
6
+
7
+ Solution
8
+ ========
9
+ - *LOSSLESS* size reduction (10-97% size reduction) in the cloud
10
+ - optmizes all images(jpg+png+[gif]) from a given folder
11
+
12
+ Install
13
+ =======
14
+ install ruby + rubygems
15
+ sudo gem install httpclient smusher
16
+
17
+ Usage
18
+ =====
19
+ Optimize a single image or a whole folder in the cloud.
20
+
21
+ converting gif-s to png-s:
22
+
23
+ - called with a folder gif-s will not be converted
24
+ - called on a single .gif or wildcard, image(s) will be converted if optimizeable
25
+
26
+ Usage:
27
+ smusher /apps/x/public/images [options]
28
+ smusher /apps/x/public/images/x.png [options]
29
+ smusher /apps/x/public/images/*.png [options]
30
+
31
+ Options are:
32
+ -q, --quiet no output
33
+ -c, --convert-gifs convert all .gif`s in the given folder
34
+
35
+
36
+ Protection
37
+ ==========
38
+ Any image that returns a failure code, is larger than before,
39
+ or is empty will not be saved.
40
+
41
+ Example
42
+ ======
43
+ smusher /apps/ts/public/images
44
+ smushing /apps/rs/public/images/social/facebook_icon.png
45
+ 2887 -> 132 = 4%
46
+
47
+ smushing /apps/rs/public/images/social/myspace_icon.png
48
+ 3136 -> 282 = 8%
49
+
50
+ smushing /apps/rs/public/images/dvd/dvd_1.png
51
+ 5045 -> 4 = 0%
52
+ reverted!
53
+ ...
54
+
55
+ TODO
56
+ ====
57
+ - only optimize 'new' images -> save time when doing on each deploy
58
+ - convert gifs to png, even if the new size is the same, for consistency (atm only those which get smaller are converted)
59
+
60
+ ALTERNATIVES
61
+ ============
62
+ If you want to lossless reduce images and minify css + js, try [reduce](http://github.com/grosser/reduce).
63
+
64
+ Authors
65
+ ======
66
+ ###Contributors
67
+ - [retr0h](http://geminstallthat.wordpress.com/)
68
+
69
+ [Michael Grosser](http://pragmatig.wordpress.com)
70
+ grosser.michael@gmail.com
71
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
@@ -0,0 +1,31 @@
1
+ desc "Run all specs in spec directory"
2
+ task :default do |t|
3
+ require 'spec'
4
+ options = "--colour --format progress --loadby --reverse"
5
+ files = FileList['spec/**/*_spec.rb']
6
+ system("spec #{options} #{files}")
7
+ end
8
+
9
+ begin
10
+ require 'jeweler'
11
+ project_name = 'smusher'
12
+ Jeweler::Tasks.new do |gem|
13
+ gem.name = project_name
14
+ gem.summary = "Automatic Lossless Reduction Of All Your Images"
15
+ gem.email = "grosser.michael@gmail.com"
16
+ gem.homepage = "http://github.com/grosser/#{project_name}"
17
+ gem.authors = ["Michael Grosser"]
18
+ %w[rake json httpclient].each{|d| gem.add_dependency d}
19
+ gem.rubyforge_project = 'smusher'
20
+ end
21
+
22
+ # fake task so that rubyforge:release works
23
+ task :rdoc do
24
+ `mkdir rdoc`
25
+ `echo documentation is at http://github.com/grosser/#{project_name} > rdoc/README.rdoc`
26
+ end
27
+
28
+ Jeweler::RubyforgeTasks.new
29
+ rescue LoadError
30
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
31
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.8
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'optparse'
4
+
5
+ options = {}
6
+ OptionParser.new do |opts|
7
+ opts.banner = <<BANNER
8
+ Optimize a single image or a whole folder in the cloud.
9
+
10
+ gif`s:
11
+ - called with a folder gif`s will not be optimized
12
+ - called on a singe .gif, it will be optimized if it is optimizeable
13
+
14
+ Usage:
15
+ smusher /apps/x/public/images [options]
16
+ smusher /apps/x/public/images/x.png [options]
17
+ smusher /apps/x/public/images/*.png [options]
18
+
19
+ Options are:
20
+ BANNER
21
+ opts.on("-q", "--quiet","no output") { options[:quiet]=true }
22
+ opts.on("-c", "--convert-gifs","convert all .gif`s in the given folder") { options[:convert_gifs]=true }
23
+ opts.on("-h", "--help","Show this.") { puts opts;exit }
24
+ end.parse!
25
+
26
+ path = ARGV.first
27
+ if path.to_s.empty? or not File.exist?(path)
28
+ puts "Usage instructions: autotest --help"
29
+ exit
30
+ end
31
+
32
+ require 'smusher'
33
+ if File.directory?(path)
34
+ Smusher.optimize_images_in_folder(path,options)
35
+ else
36
+ Smusher.optimize_image(ARGV,options)
37
+ end
@@ -0,0 +1,104 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'json'
4
+ require 'open-uri'
5
+ require 'httpclient'
6
+
7
+ module Smusher
8
+ extend self
9
+
10
+ MINIMUM_IMAGE_SIZE = 20#byte
11
+
12
+ # optimize the given image
13
+ # converts gif to png, if size is lower
14
+ # can be called with a file-path or an array of files-paths
15
+ def optimize_image(files,options={})
16
+ files.each do |file|
17
+ check_options(options)
18
+ puts "THIS FILE IS EMPTY!!! #{file}" and return if size(file).zero?
19
+ success = false
20
+
21
+ with_logging(file,options[:quiet]) do
22
+ write_optimized_data(file)
23
+ success = true
24
+ end
25
+
26
+ if success
27
+ gif = /\.gif$/
28
+ `mv #{file} #{file.sub(gif,'.png')}` if file =~ gif
29
+ end
30
+ end
31
+ end
32
+
33
+ # fetch all jpg/png images from given folder and optimize them
34
+ def optimize_images_in_folder(folder,options={})
35
+ check_options(options)
36
+ images_in_folder(folder,options[:convert_gifs]).each do |file|
37
+ optimize_image(file)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def check_options(options)
44
+ known_options = [:convert_gifs,:quiet]
45
+ if options.detect{|k,v| not known_options.include?(k)}
46
+ raise "Known options: #{known_options*' '}"
47
+ end
48
+ end
49
+
50
+ def write_optimized_data(file)
51
+ optimized = optimized_image_data_for(file)
52
+
53
+ raise "Error: got larger" if size(file) < optimized.size
54
+ raise "Error: empty file downloaded" if optimized.size < MINIMUM_IMAGE_SIZE
55
+ raise "cannot be optimized further" if size(file) == optimized.size
56
+
57
+ File.open(file,'w') {|f| f.puts optimized}
58
+ end
59
+
60
+ def sanitize_folder(folder)
61
+ folder.sub(%r[/$],'')#remove tailing slash
62
+ end
63
+
64
+ def images_in_folder(folder,with_gifs=false)
65
+ folder = sanitize_folder(folder)
66
+ images = %w[png jpg jpeg JPG]
67
+ images << 'gif' if with_gifs
68
+ images.map! {|ext| "#{folder}/**/*.#{ext}"}
69
+ FileList[*images]
70
+ end
71
+
72
+ def size(file)
73
+ File.exist?(file) ? File.size(file) : 0
74
+ end
75
+
76
+ def with_logging(file,quiet)
77
+ puts "smushing #{file}" unless quiet
78
+
79
+ before = size(file)
80
+ begin; yield; rescue; puts $! unless quiet; end
81
+ after = size(file)
82
+
83
+ unless quiet
84
+ result = "#{(100*after)/before}%"
85
+ puts "#{before} -> #{after}".ljust(40) + " = #{result}"
86
+ puts ''
87
+ end
88
+ end
89
+
90
+ def optimized_image_data_for(file)
91
+ #I leave these urls here, just in case it stops working again...
92
+ # url = "http://smush.it/ws.php" # original, redirects to somewhere else..
93
+ url = 'http://ws1.adq.ac4.yahoo.com/ysmush.it/ws.php'
94
+ # url = "http://developer.yahoo.com/yslow/smushit/ws.php" # official but does not work
95
+ # url = "http://smushit.com/ysmush.it/ws.php" # used at the new page but does not hande uploads
96
+ # url = "http://smushit.eperf.vip.ac4.yahoo.com/ysmush.it/ws.php" # used at the new page but does not hande uploads
97
+ response = HTTPClient.post url, { 'files[]' => File.new(file) }
98
+ response = JSON.parse(response.body.content)
99
+ raise "smush.it: #{response['error']}" if response['error']
100
+ image_url = response['dest']
101
+ raise "no dest path found" unless image_url
102
+ open(image_url) { |source| source.read() }
103
+ end
104
+ end
@@ -0,0 +1,71 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{smusher}
8
+ s.version = "0.3.8"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael Grosser"]
12
+ s.date = %q{2009-10-05}
13
+ s.default_executable = %q{smusher}
14
+ s.email = %q{grosser.michael@gmail.com}
15
+ s.executables = ["smusher"]
16
+ s.extra_rdoc_files = [
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "README.markdown",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "bin/smusher",
25
+ "lib/smusher.rb",
26
+ "smusher.gemspec",
27
+ "spec/empty/.gitignore",
28
+ "spec/images/ad.gif",
29
+ "spec/images/add.png",
30
+ "spec/images/drink_empty.png",
31
+ "spec/images/logo.gif",
32
+ "spec/images/people.jpg",
33
+ "spec/images/water.JPG",
34
+ "spec/images/woman.jpeg",
35
+ "spec/out/ad.gif",
36
+ "spec/out/people.jpg",
37
+ "spec/reduced/add.png",
38
+ "spec/reduced/fam.png",
39
+ "spec/smusher_spec.rb",
40
+ "spec/spec_helper.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/grosser/smusher}
43
+ s.rdoc_options = ["--charset=UTF-8"]
44
+ s.require_paths = ["lib"]
45
+ s.rubyforge_project = %q{smusher}
46
+ s.rubygems_version = %q{1.3.5}
47
+ s.summary = %q{Automatic Lossless Reduction Of All Your Images}
48
+ s.test_files = [
49
+ "spec/spec_helper.rb",
50
+ "spec/smusher_spec.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
+ s.add_runtime_dependency(%q<rake>, [">= 0"])
59
+ s.add_runtime_dependency(%q<json>, [">= 0"])
60
+ s.add_runtime_dependency(%q<httpclient>, [">= 0"])
61
+ else
62
+ s.add_dependency(%q<rake>, [">= 0"])
63
+ s.add_dependency(%q<json>, [">= 0"])
64
+ s.add_dependency(%q<httpclient>, [">= 0"])
65
+ end
66
+ else
67
+ s.add_dependency(%q<rake>, [">= 0"])
68
+ s.add_dependency(%q<json>, [">= 0"])
69
+ s.add_dependency(%q<httpclient>, [">= 0"])
70
+ end
71
+ end
File without changes
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,178 @@
1
+ ROOT = File.expand_path(File.dirname(__FILE__))
2
+ require File.join(ROOT,"spec_helper")
3
+
4
+ describe :smusher do
5
+ def copy(image_name)
6
+ FileUtils.cp(File.join(ROOT,'images',image_name), @out)
7
+ end
8
+
9
+ def size
10
+ File.size(@file)
11
+ end
12
+
13
+ before do
14
+ #prepare output folder
15
+ @out = File.join(ROOT,'out')
16
+ FileUtils.rm_r @out, :force=>true
17
+ FileUtils.mkdir @out
18
+ copy 'people.jpg'
19
+ copy 'ad.gif'
20
+
21
+ @file = File.join(@out,'people.jpg')
22
+ end
23
+
24
+ describe :optimize_image do
25
+ it "stores the image in an reduced size" do
26
+ original_size = size
27
+ Smusher.optimize_image(@file)
28
+ size.should < original_size
29
+ end
30
+
31
+ it "can be called with an array of files" do
32
+ original_size = size
33
+ Smusher.optimize_image([@file])
34
+ size.should < original_size
35
+ end
36
+
37
+ it "it does nothing if size stayed the same" do
38
+ original_size = size
39
+ Smusher.expects(:optimized_image_data_for).returns File.read(@file)
40
+ Smusher.optimize_image(@file)
41
+ size.should == original_size
42
+ end
43
+
44
+ it "does not save images whoes size got larger" do
45
+ original_size = size
46
+ Smusher.expects(:optimized_image_data_for).returns File.read(@file)*2
47
+ Smusher.optimize_image(@file)
48
+ size.should == original_size
49
+ end
50
+
51
+ it "does not save images if their size is error-sugesting-small" do
52
+ original_size = size
53
+ Smusher.expects(:optimized_image_data_for).returns 'oops...'
54
+ Smusher.optimize_image(@file)
55
+ size.should == original_size
56
+ end
57
+
58
+ describe "gif handling" do
59
+ before do
60
+ copy 'logo.gif'
61
+ @file = File.join(@out,'logo.gif')
62
+ @file_png = File.join(@out,'logo.png')
63
+ end
64
+
65
+ pending_it "converts gifs to png even if they have the same size" do
66
+ copy 'ad.gif'
67
+ file = File.join(@out,'ad.gif')
68
+ original_size = size
69
+ Smusher.optimize_image(file)
70
+ File.size(File.join(@out,'ad.png')).should == original_size
71
+ end
72
+
73
+ it "stores converted .gifs in .png files" do
74
+ Smusher.optimize_image(@file)
75
+ File.exist?(@file).should == false
76
+ File.exist?(@file_png).should == true
77
+ end
78
+
79
+ it "does not rename gifs, if optimizing failed" do
80
+ Smusher.expects(:optimized_image_data_for).returns File.read(@file)
81
+ Smusher.optimize_image(@file)
82
+ File.exist?(@file).should == true
83
+ File.exist?(@file_png).should == false
84
+ end
85
+ end
86
+
87
+ describe 'options' do
88
+ it "does not produce output when :quiet is given" do
89
+ $stdout.expects(:write).never
90
+ Smusher.optimize_image(@file,:quiet=>true)
91
+ end
92
+
93
+ it "raises when an unknown option was given" do
94
+ lambda{Smusher.optimize_image(@file,:q=>true)}.should raise_error
95
+ end
96
+ end
97
+ end
98
+
99
+ describe :optimize_images_in_folder do
100
+ before do
101
+ FileUtils.rm @file
102
+ @files = []
103
+ %w[add.png drink_empty.png].each do |image_name|
104
+ copy image_name
105
+ @files << File.join(@out,image_name)
106
+ end
107
+ @before = @files.map {|f|File.size(f)}
108
+ end
109
+
110
+ it "optimizes all images" do
111
+ Smusher.optimize_images_in_folder(@out)
112
+ new_sizes = @files.map {|f|File.size(f)}
113
+ new_sizes.size.times {|i| new_sizes[i].should < @before[i]}
114
+ end
115
+
116
+ it "does not convert gifs" do
117
+ copy 'logo.gif'
118
+ Smusher.optimize_images_in_folder(@out)
119
+ File.exist?(File.join(@out,'logo.png')).should == false
120
+ end
121
+
122
+ it "converts gifs to png when option was given" do
123
+ copy 'logo.gif'
124
+ Smusher.optimize_images_in_folder(@out,:convert_gifs=>true)
125
+ File.exist?(File.join(@out,'logo.png')).should == true
126
+ end
127
+ end
128
+
129
+ describe :sanitize_folder do
130
+ it "cleans a folders trailing slash" do
131
+ Smusher.send(:sanitize_folder,"xx/").should == 'xx'
132
+ end
133
+
134
+ it "does not clean if there is no trailing slash" do
135
+ Smusher.send(:sanitize_folder,"/x/ccx").should == '/x/ccx'
136
+ end
137
+ end
138
+
139
+ describe :images_in_folder do
140
+ it "finds all non-gif images" do
141
+ folder = File.join(ROOT,'images')
142
+ all = %w[add.png drink_empty.png people.jpg water.JPG woman.jpeg].map{|name|"#{folder}/#{name}"}
143
+ result = Smusher.send(:images_in_folder,folder)
144
+ (all+result).uniq.size.should == all.size
145
+ end
146
+
147
+ it "finds nothing if folder is empty" do
148
+ Smusher.send(:images_in_folder,File.join(ROOT,'empty')).should == []
149
+ end
150
+ end
151
+
152
+ describe :size do
153
+ it "find the size of a file" do
154
+ Smusher.send(:size,@file).should == File.size(@file)
155
+ end
156
+
157
+ it "returns 0 for missing file" do
158
+ Smusher.send(:size,File.join(ROOT,'xxxx','dssdfsddfs')).should == 0
159
+ end
160
+ end
161
+
162
+ describe :logging do
163
+ it "yields" do
164
+ val = 0
165
+ Smusher.send(:with_logging,@file,false) {val = 1}
166
+ val.should == 1
167
+ end
168
+ end
169
+
170
+ describe :optimized_image_data_for do
171
+ it "loads the reduced image" do
172
+ original = File.join(ROOT,'images','add.png')
173
+ reduced = File.open(File.join(ROOT,'reduced','add.png')).read
174
+ received = (Smusher.send(:optimized_image_data_for,original))
175
+ received.should == reduced
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,19 @@
1
+ # ---- requirements
2
+ require 'rubygems'
3
+ require 'spec'
4
+ require 'mocha'
5
+ require 'lib/smusher'
6
+
7
+
8
+ # ---- rspec
9
+ Spec::Runner.configure do |config|
10
+ config.mock_with :mocha
11
+ end
12
+
13
+
14
+ # ---- Helpers
15
+ def pending_it(text,&block)
16
+ it text do
17
+ pending(&block)
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smusher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.8
5
+ platform: ruby
6
+ authors:
7
+ - Michael Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-05 00:00:00 +02:00
13
+ default_executable: smusher
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: json
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: httpclient
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description:
46
+ email: grosser.michael@gmail.com
47
+ executables:
48
+ - smusher
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README.markdown
53
+ files:
54
+ - .gitignore
55
+ - README.markdown
56
+ - Rakefile
57
+ - VERSION
58
+ - bin/smusher
59
+ - lib/smusher.rb
60
+ - smusher.gemspec
61
+ - spec/empty/.gitignore
62
+ - spec/images/ad.gif
63
+ - spec/images/add.png
64
+ - spec/images/drink_empty.png
65
+ - spec/images/logo.gif
66
+ - spec/images/people.jpg
67
+ - spec/images/water.JPG
68
+ - spec/images/woman.jpeg
69
+ - spec/out/ad.gif
70
+ - spec/out/people.jpg
71
+ - spec/reduced/add.png
72
+ - spec/reduced/fam.png
73
+ - spec/smusher_spec.rb
74
+ - spec/spec_helper.rb
75
+ has_rdoc: true
76
+ homepage: http://github.com/grosser/smusher
77
+ licenses: []
78
+
79
+ post_install_message:
80
+ rdoc_options:
81
+ - --charset=UTF-8
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ version:
96
+ requirements: []
97
+
98
+ rubyforge_project: smusher
99
+ rubygems_version: 1.3.5
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Automatic Lossless Reduction Of All Your Images
103
+ test_files:
104
+ - spec/spec_helper.rb
105
+ - spec/smusher_spec.rb