smusher 0.3.8

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.
@@ -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