grosser-smusher 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,52 @@
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) from a given folder
11
+
12
+ Install
13
+ =======
14
+ install ruby + rubygems
15
+ sudo gem install grosser-smusher --source http://gems.github.com/
16
+
17
+ Usage
18
+ =====
19
+ # your pictures MUST be online in order to be optimized
20
+ smusher /apps/x/public/images www.x.com/images
21
+ smusher LOCAL_FOLDER REMOTE_FOLDER
22
+
23
+ Protection
24
+ ==========
25
+ Smusher makes .backup copies of any image before optimizing.
26
+ Any image that returns a failure code, is larger than before,
27
+ or is empty will be reverted.
28
+
29
+ Example
30
+ ======
31
+ smusher /apps/ts/public/images xx.com/images
32
+ sushing http://xx.com/images/social/facebook_icon.png -> /apps/rs/public/images/social/facebook_icon.png
33
+ 2887 -> 132 = 4%
34
+
35
+ sushing http://xx.com/images/social/myspace_icon.png -> /apps/rs/public/images/social/myspace_icon.png
36
+ 3136 -> 282 = 8%
37
+
38
+ sushing http://xx.com/images/dvd/dvd_1.png -> /apps/rs/public/images/dvd/dvd_1.png
39
+ 5045 -> 9677 = 191%
40
+ reverted!
41
+ ...
42
+
43
+ TODO
44
+ ====
45
+ - no need for files to be online (direct upload)
46
+ - windows support?
47
+
48
+ Author
49
+ ======
50
+ Michael Grosser
51
+ grosser.michael@gmail.com
52
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
data/Rakefile ADDED
@@ -0,0 +1,20 @@
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
+ #Gemspec
10
+ require 'echoe'
11
+ porject_name = 'smusher'
12
+ Echoe.new(porject_name , '0.1') do |p|
13
+ p.description = "Automatic Lossless Reduction Of All Your Images"
14
+ p.url = "http://github.com/grosser/#{porject_name}"
15
+ p.author = "Michael Grosser"
16
+ p.email = "grosser.michael@gmail.com"
17
+ p.dependencies = %w[rake json]
18
+ end
19
+
20
+ task :update_gemspec => [:manifest, :build_gemspec]
data/bin/smusher ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'smusher'
4
+
5
+ folder, url = ARGV
6
+ if ARGV.length < 2 or not File.directory?(folder)
7
+ puts "smusher DIRECTORY_TO_SMUSH BASE_URL"
8
+ puts "smusher /apps/x/public/images www.x.com/images"
9
+ exit
10
+ end
11
+
12
+ Smusher.store_smushed_folder(url,folder)
data/lib/smusher.rb ADDED
@@ -0,0 +1,114 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ module Smusher
5
+ extend self
6
+ #http://smushit.com/ws.php?img=http%3A%2F%2Fwww.famfamfam.com%2Flab%2Ficons%2Fsilk%2Ficons%2Fdrink_empty.png&task=89266837334214400&id=paste2
7
+ SMUSHIT_FAILURE_SIZE = 9667
8
+ EMPTY_FILE_SIZE = 4
9
+
10
+ def store_smushed_image(url,file)
11
+ url = sanitize_url(url)
12
+ with_protection(file) do
13
+ with_logging(url,file) do
14
+ write_smushed_data(url,file)
15
+ end
16
+ end
17
+ end
18
+
19
+ def store_smushed_folder(base_url,folder)
20
+ base_url = sanitize_url(base_url)
21
+ folder = sanitize_folder(folder)
22
+
23
+ images_in_folder(folder).each do |file|
24
+ relative_path_to_folder = file.sub(folder+'/','')
25
+ url = "#{base_url}/#{relative_path_to_folder}"
26
+ store_smushed_image(url,file)
27
+ puts ''
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def write_smushed_data(url,file)
34
+ File.open(file,'w') do |f|
35
+ data = smushed_image_data_for(url)
36
+ raise data if data =~ /<html>/i
37
+ f.puts data
38
+ end
39
+ end
40
+
41
+ def sanitize_url(url)
42
+ url = url.sub(%r[/$],'')#remove tailing slash
43
+ url = url.sub(url,"http://#{url}") unless url.include?('://')#add http if protocol is missing
44
+ url
45
+ end
46
+
47
+ def sanitize_folder(folder)
48
+ folder.sub(%r[/$],'')#remove tailing slash
49
+ end
50
+
51
+ def images_in_folder(folder)
52
+ images = %w[png jpg jpeg JPG].map {|ext| "#{folder}/**/*.#{ext}"}
53
+ FileList[*images]
54
+ end
55
+
56
+ def with_protection(file)
57
+ if File.exist?(file)
58
+ backup = "#{file}.backup"
59
+ FileUtils.cp(file,backup)#backup
60
+
61
+ before = size(file)
62
+ yield
63
+ after = size(file)
64
+
65
+ if after == EMPTY_FILE_SIZE or after == SMUSHIT_FAILURE_SIZE or after > before
66
+ FileUtils.mv(backup,file,:force=>true)#revert
67
+ puts "reverted!"
68
+ else
69
+ FileUtils.rm(backup)
70
+ end
71
+ else#is created
72
+ yield
73
+ after = size(file)
74
+ if after == SMUSHIT_FAILURE_SIZE or after == EMPTY_FILE_SIZE
75
+ FileUtils.rm file
76
+ end
77
+ end
78
+ end
79
+
80
+ def size(file)
81
+ File.exist?(file) ? File.size(file) : 0
82
+ end
83
+
84
+ def with_logging(url,file)
85
+ puts "sushing #{url} -> #{file}"
86
+
87
+ before = size(file)
88
+ yield
89
+ after = size(file)
90
+
91
+ result = before == 0 ? "CREATED" : "#{(100*after)/before}%"
92
+ puts "#{before} -> #{after}".ljust(40) + " = #{result}"
93
+ end
94
+
95
+ def smushed_image_data_for(url)
96
+ require 'cgi'
97
+ url = CGI.escape url
98
+
99
+ require 'net/http'
100
+ require 'net/https'
101
+ require 'rubygems'
102
+ require 'json'
103
+
104
+ http = Net::HTTP.new('smushit.com')
105
+ path = "/ws.php?img=#{url}&task=89266837334214400&id=paste2"
106
+
107
+ resp, data = http.get(path, nil)
108
+ raise "oops #{resp}" unless resp.is_a? Net::HTTPOK
109
+
110
+ path = "/#{JSON.parse(data)['dest']}"
111
+ resp, data = http.get(path, nil)
112
+ data
113
+ end
114
+ end
data/smusher.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{smusher}
5
+ s.version = "0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Michael Grosser"]
9
+ s.date = %q{2009-01-24}
10
+ s.default_executable = %q{smusher}
11
+ s.description = %q{Automatic Lossless Reduction Of All Your Images}
12
+ s.email = %q{grosser.michael@gmail.com}
13
+ s.executables = ["smusher"]
14
+ s.extra_rdoc_files = ["lib/smusher.rb", "bin/smusher", "README.markdown"]
15
+ s.files = ["Manifest", "nbproject/private/rake-d.txt", "nbproject/project.xml", "nbproject/project.properties", "lib/smusher.rb", "spec/out/people.jpg", "spec/spec_helper.rb", "spec/smusher_spec.rb", "spec/images/logo.gif", "spec/images/woman.jpeg", "spec/images/add.png", "spec/images/drink_empty.png", "spec/images/people.jpg", "spec/images/water.JPG", "spec/reduced/fam.png", "bin/smusher", "Rakefile", "README.markdown", "smusher.gemspec"]
16
+ s.has_rdoc = true
17
+ s.homepage = %q{http://github.com/grosser/smusher}
18
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Smusher", "--main", "README.markdown"]
19
+ s.require_paths = ["lib"]
20
+ s.rubyforge_project = %q{smusher}
21
+ s.rubygems_version = %q{1.3.1}
22
+ s.summary = %q{Automatic Lossless Reduction Of All Your Images}
23
+
24
+ if s.respond_to? :specification_version then
25
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
26
+ s.specification_version = 2
27
+
28
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
29
+ s.add_runtime_dependency(%q<rake>, [">= 0"])
30
+ s.add_runtime_dependency(%q<json>, [">= 0"])
31
+ s.add_development_dependency(%q<echoe>, [">= 0"])
32
+ else
33
+ s.add_dependency(%q<rake>, [">= 0"])
34
+ s.add_dependency(%q<json>, [">= 0"])
35
+ s.add_dependency(%q<echoe>, [">= 0"])
36
+ end
37
+ else
38
+ s.add_dependency(%q<rake>, [">= 0"])
39
+ s.add_dependency(%q<json>, [">= 0"])
40
+ s.add_dependency(%q<echoe>, [">= 0"])
41
+ end
42
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,183 @@
1
+ ROOT = File.expand_path(File.dirname(__FILE__))
2
+ require File.join(ROOT,"spec_helper")
3
+
4
+ URL = "http://famfamfam.com/lab/icons/silk/icons/drink_empty.png"
5
+ ESCAPED_URL = "http%3A%2F%2Fwww.famfamfam.com%2Flab%2Ficons%2Fsilk%2Ficons%2Fdrink_empty.png"
6
+
7
+ describe :smusher do
8
+ def size
9
+ File.size(@file)
10
+ end
11
+
12
+ before do
13
+ #prepare output folder
14
+ @out = File.join(ROOT,'out')
15
+ FileUtils.rm_r @out, :force=>true
16
+ FileUtils.mkdir @out
17
+ FileUtils.cp(File.join(ROOT,'images','people.jpg'), @out)
18
+
19
+ @file = File.join(@out,'people.jpg')
20
+ end
21
+
22
+ describe :store_smushed_image do
23
+ it "stores the image in an reduced size" do
24
+ original_size = size
25
+ Smusher.store_smushed_image(URL,@file)
26
+ size.should < original_size
27
+ end
28
+
29
+ it "uses cleaned url" do
30
+ Smusher.expects(:write_smushed_data).with("http://xx",@file)
31
+ Smusher.store_smushed_image('xx',@file)
32
+ end
33
+ end
34
+
35
+ describe :store_smushed_folder do
36
+ before do
37
+ FileUtils.rm @file
38
+ @files = []
39
+ %w[add.png drink_empty.png].each do |name|
40
+ file = File.join(ROOT,'images',name)
41
+ @files << File.join(@out,name)
42
+ FileUtils.cp file, @out
43
+ end
44
+ @before = @files.map {|f|File.size(f)}
45
+ end
46
+
47
+ it "smushes all images" do
48
+ Smusher.store_smushed_folder(File.dirname(URL),@out)
49
+ new_sizes = @files.map {|f|File.size(f)}
50
+ puts new_sizes * ' x '
51
+ new_sizes.size.times {|i| new_sizes[i].should < @before[i]}
52
+ end
53
+ end
54
+
55
+ describe :sanitize_url do
56
+ it "cleans a url" do
57
+ Smusher.send(:sanitize_url,'xx').should == "http://xx"
58
+ end
59
+
60
+ it "does not cleans a url if it contains a protocol" do
61
+ Smusher.send(:sanitize_url,'ftp://xx').should == "ftp://xx"
62
+ end
63
+ end
64
+
65
+ describe :sanitize_folder do
66
+ it "cleans a folders trailing slash" do
67
+ Smusher.send(:sanitize_folder,"xx/").should == 'xx'
68
+ end
69
+
70
+ it "does not clean if there is no trailing slash" do
71
+ Smusher.send(:sanitize_folder,"/x/ccx").should == '/x/ccx'
72
+ end
73
+ end
74
+
75
+ describe :images_in_folder do
76
+ it "finds all non-gif images" do
77
+ folder = File.join(ROOT,'images')
78
+ all = %w[add.png drink_empty.png people.jpg water.JPG woman.jpeg].map{|name|"#{folder}/#{name}"}
79
+ result = Smusher.send(:images_in_folder,folder)
80
+ (all+result).uniq.size.should == all.size
81
+ end
82
+
83
+ it "finds nothing if folder is empty" do
84
+ Smusher.send(:images_in_folder,File.join(ROOT,'empty')).should == []
85
+ end
86
+ end
87
+
88
+ describe :with_protection do
89
+ def failure_data
90
+ 'x' * (Smusher::SMUSHIT_FAILURE_SIZE-1)
91
+ end
92
+
93
+ def write(data)
94
+ File.open(@file,'w') {|f|f.puts data}
95
+ end
96
+
97
+ def copy
98
+ FileUtils.cp(File.join(ROOT,'images','people.jpg'),@file)
99
+ end
100
+
101
+ before do
102
+ @before = size
103
+ @before.should_not == 0
104
+ end
105
+
106
+ it "reverts a file that got larger" do
107
+ Smusher.send(:with_protection,@file) do
108
+ write(File.open(@file).read + 'x')
109
+ @before.should_not == size
110
+ end
111
+ @before.should == size
112
+ end
113
+
114
+ it "does not revert a file that got created" do
115
+ FileUtils.rm @file
116
+ File.exist?(@file).should be_false
117
+ Smusher.send(:with_protection,@file) do
118
+ copy
119
+ end
120
+ File.exist?(@file).should be_true
121
+ end
122
+
123
+ it "reverts a file that got empty" do
124
+ Smusher.send(:with_protection,@file) do
125
+ write nil
126
+ size.should == Smusher::EMPTY_FILE_SIZE
127
+ end
128
+ size.should == @before
129
+ end
130
+
131
+ it "reverts a file that has error-suggesting size" do
132
+ #a file larger that failure data
133
+ write(failure_data+failure_data)
134
+ @before = size
135
+ @before.should > Smusher::SMUSHIT_FAILURE_SIZE
136
+
137
+ #gets overwritten by failure data size
138
+ Smusher.send(:with_protection,@file) do
139
+ write failure_data
140
+ size.should == Smusher::SMUSHIT_FAILURE_SIZE
141
+ end
142
+
143
+ #and should be reverted
144
+ size.should_not == Smusher::SMUSHIT_FAILURE_SIZE
145
+ size.should == @before
146
+ end
147
+
148
+ it "reverts a file that got created and has error suggesting size" do
149
+ FileUtils.rm @file
150
+ Smusher.send(:with_protection,@file) do
151
+ write failure_data
152
+ File.exist?(@file).should be_true
153
+ end
154
+ File.exist?(@file).should be_false
155
+ end
156
+ end
157
+
158
+ describe :size do
159
+ it "find the size of a file" do
160
+ Smusher.send(:size,@file).should == File.size(@file)
161
+ end
162
+
163
+ it "returns 0 for missing file" do
164
+ Smusher.send(:size,File.join(ROOT,'xxxx','dssdfsddfs')).should == 0
165
+ end
166
+ end
167
+
168
+ describe :logging do
169
+ it "yields" do
170
+ val = 0
171
+ Smusher.send(:with_logging,URL,@file) {val = 1}
172
+ val.should == 1
173
+ end
174
+ end
175
+
176
+ describe :smushed_image_data_for do
177
+ it "loads the reduced image" do
178
+ expected_result = File.join(ROOT,'reduced','fam.png')
179
+ received = (Smusher.send(:smushed_image_data_for,URL)+"\n")
180
+ received.should == File.open(expected_result).read
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,25 @@
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
+ # ---- bugfix
15
+ #`exit?': undefined method `run?' for Test::Unit:Module (NoMethodError)
16
+ #can be solved with require test/unit but this will result in extra test-output
17
+ unless defined? Test::Unit
18
+ module Test
19
+ module Unit
20
+ def self.run?
21
+ true
22
+ end
23
+ end
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grosser-smusher
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Michael Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-24 00:00:00 -08:00
13
+ default_executable: smusher
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: json
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: "0"
32
+ version:
33
+ - !ruby/object:Gem::Dependency
34
+ name: echoe
35
+ version_requirement:
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: "0"
41
+ version:
42
+ description: Automatic Lossless Reduction Of All Your Images
43
+ email: grosser.michael@gmail.com
44
+ executables:
45
+ - smusher
46
+ extensions: []
47
+
48
+ extra_rdoc_files:
49
+ - lib/smusher.rb
50
+ - bin/smusher
51
+ - README.markdown
52
+ files:
53
+ - Manifest
54
+ - nbproject/private/rake-d.txt
55
+ - nbproject/project.xml
56
+ - nbproject/project.properties
57
+ - lib/smusher.rb
58
+ - spec/out/people.jpg
59
+ - spec/spec_helper.rb
60
+ - spec/smusher_spec.rb
61
+ - spec/images/logo.gif
62
+ - spec/images/woman.jpeg
63
+ - spec/images/add.png
64
+ - spec/images/drink_empty.png
65
+ - spec/images/people.jpg
66
+ - spec/images/water.JPG
67
+ - spec/reduced/fam.png
68
+ - bin/smusher
69
+ - Rakefile
70
+ - README.markdown
71
+ - smusher.gemspec
72
+ has_rdoc: true
73
+ homepage: http://github.com/grosser/smusher
74
+ post_install_message:
75
+ rdoc_options:
76
+ - --line-numbers
77
+ - --inline-source
78
+ - --title
79
+ - Smusher
80
+ - --main
81
+ - README.markdown
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: "1.2"
95
+ version:
96
+ requirements: []
97
+
98
+ rubyforge_project: smusher
99
+ rubygems_version: 1.2.0
100
+ signing_key:
101
+ specification_version: 2
102
+ summary: Automatic Lossless Reduction Of All Your Images
103
+ test_files: []
104
+