meme_captain 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/ChangeLog +4 -0
  2. data/README.md +0 -45
  3. data/lib/meme_captain.rb +0 -6
  4. data/lib/meme_captain/caption.rb +6 -4
  5. data/lib/meme_captain/caption_choice.rb +3 -1
  6. data/lib/meme_captain/image_list.rb +0 -5
  7. data/lib/meme_captain/text_pos.rb +8 -6
  8. data/lib/meme_captain/version.rb +1 -1
  9. data/meme_captain.gemspec +5 -18
  10. metadata +2 -181
  11. data/config.ru +0 -50
  12. data/doc/lightweight_front_end.md +0 -64
  13. data/doc/setup.md +0 -13
  14. data/lib/meme_captain/image_list/cache.rb +0 -49
  15. data/lib/meme_captain/image_list/fetch.rb +0 -28
  16. data/lib/meme_captain/image_list/fetch_error.rb +0 -17
  17. data/lib/meme_captain/image_list/source_image.rb +0 -30
  18. data/lib/meme_captain/meme_data.rb +0 -33
  19. data/lib/meme_captain/norm_params.rb +0 -92
  20. data/lib/meme_captain/pretty_format.rb +0 -12
  21. data/lib/meme_captain/server.rb +0 -303
  22. data/lib/meme_captain/source_fetch_fail.rb +0 -28
  23. data/lib/meme_captain/upload.rb +0 -30
  24. data/public/css/screen.css +0 -80
  25. data/public/favicon.ico +0 -0
  26. data/public/js/fabric.min.js +0 -4
  27. data/public/js/jquery-1.7.2.min.js +0 -4
  28. data/public/js/meme_captain.js +0 -391
  29. data/public/source_images.json +0 -234
  30. data/public/thumbs.jpg +0 -0
  31. data/public/thumbs_1330486916.jpg +0 -0
  32. data/public/thumbs_1333591668.jpg +0 -0
  33. data/public/thumbs_1334189407.jpg +0 -0
  34. data/public/thumbs_1334973608.jpg +0 -0
  35. data/public/thumbs_1336623277.jpg +0 -0
  36. data/public/thumbs_1336624196.jpg +0 -0
  37. data/public/thumbs_1339811279.jpg +0 -0
  38. data/public/tmp/.gitignore +0 -0
  39. data/script/thumb_sprites.rb +0 -76
  40. data/spec/image_list/fetch_spec.rb +0 -33
  41. data/spec/norm_params_spec.rb +0 -223
  42. data/spec/pretty_format_spec.rb +0 -9
  43. data/views/404.erb +0 -17
  44. data/views/index.erb +0 -130
  45. data/watermark.png +0 -0
@@ -1,64 +0,0 @@
1
- If you want to host your own custom Meme Captain site but do not want to set
2
- up the ruby backend you can use the memecaptain.com backend. In this scenario,
3
- the HTML, Javscript and CSS are hosted on your web server and the meme images
4
- are created on and hosted on memecaptain.com.
5
-
6
- This is an option if you are on shared hosting and do not have access to
7
- ruby or MongoDB or do not want to deal with setting them up.
8
-
9
- Download the files at these locations and save them in your document root:
10
-
11
- * http://memecaptain.com/
12
- * http://memecaptain.com/css/screen.css
13
- * http://memecaptain.com/js/fabric.min.js
14
- * http://memecaptain.com/js/meme_captain.js
15
- * http://memecaptain.com/source_images.json
16
-
17
- The css in js should go in css and js directories in your document root so
18
- that they match the url paths.
19
-
20
- Find this line in js/meme_captain.js
21
-
22
- ```
23
- genUrl = '/g',
24
- ```
25
-
26
- and change it to
27
-
28
- ```
29
- genUrl = 'http://memecaptain.com/g',
30
- ```
31
-
32
- # Changing the Default Source Images
33
-
34
- The source image thumbnails that show up on the page are driven by a JSON
35
- description and a single image that contain CSS sprites. The JSON and image
36
- must be regenerated to change them.
37
-
38
- The rmagick ruby gem must be installed:
39
-
40
- ```sh
41
- gem install rmagick
42
- ```
43
-
44
- Get and run the script:
45
-
46
- ```sh
47
- wget https://raw.github.com/mmb/meme_captain/master/script/thumb_sprites.rb
48
- RUBYOPT='r rubygems'
49
- ruby thumb_sprites.rb -u URL_ROOT -s SOURCE_URL_PREFIX SOURCE_IMAGE_PATH/*
50
- ```
51
-
52
- SOURCE_IMAGE_PATH is the directory where the source images are stored.
53
- URL_ROOT is the root url of the Meme Captain installation. SOURCE_URL_PREFIX
54
- is the part between the root url and the image filenames in the image urls.
55
-
56
- For example if the images are stored in /var/www/meme.com/source and the
57
- url root is http://meme.com/, use:
58
-
59
- ```sh
60
- ruby thumb_sprites.rb -u http://meme.com/ -s source/ /var/www/meme.com/source/*
61
- ```
62
-
63
- Copy the generated source_images.json and thumbs_xxx.jpg files to the Meme
64
- Captain document root.
@@ -1,13 +0,0 @@
1
- # Requirements
2
-
3
- * a Ruby/Rack web application environment (such as
4
- [Passenger](http://www.modrails.com/))
5
-
6
- * Ruby gem dependencies (see the gemspec)
7
-
8
- * [MongoDB](http://www.mongodb.org/)
9
-
10
- You will most likely need a dedicated or virtual private server to run
11
- MongoDB as most shared hosting does not support it. See the lightweight front
12
- end document for how host the static HTML/CSS/Javascript only and use
13
- memecaptain.com for the backend.
@@ -1,49 +0,0 @@
1
- require 'digest/sha1'
2
- require 'fileutils'
3
- require 'mime/types'
4
-
5
- module MemeCaptain
6
-
7
- module ImageList
8
-
9
- # Mix-in for Magick::ImageList to add saving to the filesystem based on a
10
- # hash.
11
- module Cache
12
-
13
- # Get the extension for this image.
14
- def extension
15
- {
16
- 'image/jpeg' => 'jpg',
17
- }[mime_type] || MIME::Types[mime_type][0].extensions[0]
18
- end
19
-
20
- # Store this image in the filesystem and return its path.
21
- def cache(hash_base, dir)
22
- hashe = Digest::SHA1.hexdigest(hash_base)
23
-
24
- cache_dir = File.join(dir, hashe[0,3])
25
- FileUtils.mkdir_p cache_dir
26
-
27
- file_part = hashe[3..-1]
28
- fs_path = File.join(cache_dir, "#{file_part}.#{extension}")
29
-
30
- # If there is a collision add 0's until the filename is unique.
31
- zeroes = 0
32
- while File.exist? fs_path
33
- zeroes += 1
34
- fs_path = File.join(cache_dir,
35
- "#{file_part}#{'0' * zeroes}.#{extension}")
36
- end
37
-
38
- write(fs_path) {
39
- self.quality = 100
40
- }
41
-
42
- fs_path
43
- end
44
-
45
- end
46
-
47
- end
48
-
49
- end
@@ -1,28 +0,0 @@
1
- require 'curb'
2
-
3
- module MemeCaptain
4
-
5
- module ImageList
6
-
7
- # Mix-in for Magick::ImageList to add loading from a URL.
8
- module Fetch
9
-
10
- # Load this image from a URL.
11
- def fetch!(url)
12
- curl = Curl::Easy.perform(url) do |c|
13
- c.useragent = 'Meme Captain http://memecaptain.com/'
14
- c.follow_location = true
15
- c.max_redirects = 3
16
- end
17
- unless curl.response_code == 200
18
- raise FetchError.new(curl.response_code)
19
- end
20
-
21
- from_blob curl.body_str
22
- end
23
-
24
- end
25
-
26
- end
27
-
28
- end
@@ -1,17 +0,0 @@
1
- module MemeCaptain
2
-
3
- module ImageList
4
-
5
- # Error for source image fetch failures.
6
- class FetchError < StandardError
7
-
8
- def initialize(response_code)
9
- @response_code = response_code
10
- end
11
-
12
- attr_accessor :response_code
13
- end
14
-
15
- end
16
-
17
- end
@@ -1,30 +0,0 @@
1
- require 'RMagick'
2
-
3
- module MemeCaptain
4
-
5
- module ImageList
6
-
7
- # Source image for meme generation.
8
- class SourceImage < Magick::ImageList
9
- include Cache
10
- include Fetch
11
- include Watermark
12
-
13
- # Shrink image if necessary and add watermark.
14
- def prepare!(max_side, watermark_img)
15
- auto_orient!
16
-
17
- if size == 1 and (columns > max_side or rows > max_side)
18
- resize_to_fit! max_side
19
- end
20
-
21
- watermark_mc watermark_img
22
-
23
- strip!
24
- end
25
-
26
- end
27
-
28
- end
29
-
30
- end
@@ -1,33 +0,0 @@
1
- require 'mongo_mapper'
2
-
3
- module MemeCaptain
4
-
5
- class MemeData
6
- include MongoMapper::Document
7
-
8
- set_collection_name 'meme'
9
-
10
- key :meme_id, String
11
- key :fs_path, String
12
- key :mime_type, String
13
- key :size, Integer
14
-
15
- key :source_url, String
16
- key :source_fs_path, String
17
- key :texts, Array
18
-
19
- key :request_count, Integer
20
- key :last_request, Time
21
-
22
- key :creator_ip, String
23
-
24
- timestamps!
25
-
26
- def requested!
27
- increment :request_count => 1
28
- set :last_request => Time.now
29
- end
30
-
31
- end
32
-
33
- end
@@ -1,92 +0,0 @@
1
- module MemeCaptain
2
-
3
- # Normalize query string parameters.
4
- #
5
- # Provide defaults, do some basic validation and convert some parameters
6
- # to floats or integers.
7
- class NormParams
8
-
9
- def initialize(params={})
10
- @u = ''
11
-
12
- @t1 = ''
13
- @t2 = ''
14
-
15
- @t1x = 0.05
16
- @t1y = 0
17
- @t1w = 0.9
18
- @t1h = 0.25
19
-
20
- @t2x = 0.05
21
- @t2y = 0.75
22
- @t2w = 0.9
23
- @t2h = 0.25
24
-
25
- load params
26
- end
27
-
28
- # Load query string parameters.
29
- #
30
- # Do some basic validation and convert some parameters to floats or
31
- # integers.
32
- def load(params)
33
- params.select { |k,v|
34
- [
35
- :u,
36
- :t1,
37
- :t2,
38
- ].include? k.to_sym }.each do |k,v|
39
- send "#{k}=", v.to_s
40
- end
41
-
42
- params.select { |k,v|
43
- [
44
- :t1x,
45
- :t1y,
46
- :t1w,
47
- :t1h,
48
-
49
- :t2x,
50
- :t2y,
51
- :t2w,
52
- :t2h,
53
- ].include?(k.to_sym) and !v.to_s.empty? }.each do |k,v|
54
- send "#{k}=", convert_metric(v)
55
- end
56
- end
57
-
58
- # Return a sorted string representation of the fields.
59
- def signature
60
- instance_variables.sort.map { |v|
61
- name = v[1..-1] # remove @
62
- "#{name}#{instance_variable_get(v)}"
63
- }.join
64
- end
65
-
66
- attr_accessor :u
67
-
68
- attr_accessor :t1
69
- attr_accessor :t2
70
-
71
- attr_accessor :t1x
72
- attr_accessor :t1y
73
- attr_accessor :t1w
74
- attr_accessor :t1h
75
-
76
- attr_accessor :t2x
77
- attr_accessor :t2y
78
- attr_accessor :t2w
79
- attr_accessor :t2h
80
-
81
- private
82
-
83
- # Convert a metric string to a float or integer.
84
- #
85
- # Expects a string.
86
- def convert_metric(metric)
87
- metric.index('.') ? metric.to_f : metric.to_i
88
- end
89
-
90
- end
91
-
92
- end
@@ -1,12 +0,0 @@
1
- require 'pp'
2
-
3
- module MemeCaptain
4
-
5
- module_function
6
-
7
- # Format an object like pp would would and return the formatted string.
8
- def pretty_format(o)
9
- PP.pp o, dump = ''
10
- end
11
-
12
- end
@@ -1,303 +0,0 @@
1
- require 'digest/sha1'
2
-
3
- require 'json'
4
- require 'rack'
5
- require 'sinatra/base'
6
-
7
- module MemeCaptain
8
-
9
- class Server < Sinatra::Base
10
-
11
- set :root, File.expand_path(File.join('..', '..'), File.dirname(__FILE__))
12
- set :source_img_max_side, 800
13
- set :upload_prefix, 'up/'
14
- set :watermark, Magick::ImageList.new(File.expand_path(
15
- File.join('..', '..', 'watermark.png'), File.dirname(__FILE__)))
16
-
17
- # uncomment this to allow other sites to use this site's backend for
18
- # generating images, used to allow third-party static only sites
19
- # to generate images using memecaptain.com
20
-
21
- # set :protection, :except => :json_csrf
22
-
23
- get '/' do
24
- @u = params[:u]
25
-
26
- @t1 = params[:t1]
27
- @t1x = params[:t1x]
28
- @t1y = params[:t1y]
29
- @t1w = params[:t1w]
30
- @t1h = params[:t1h]
31
-
32
- @t2 = params[:t2]
33
- @t2x = params[:t2x]
34
- @t2y = params[:t2y]
35
- @t2w = params[:t2w]
36
- @t2h = params[:t2h]
37
-
38
- @root_url = url('/')
39
-
40
- erb :index
41
- end
42
-
43
- def normalize_params(p)
44
- result = NormParams.new(p)
45
-
46
- # if the id of an existing meme is passed in as the source url, use the
47
- # source image of that meme for the source image
48
- if result.u[%r{^[a-f0-9]+\.(?:gif|jpg|png)$}]
49
- if existing_as_source = MemeData.find_by_meme_id(result.u)
50
- result.u = existing_as_source.source_url
51
- end
52
- end
53
-
54
- result
55
- end
56
-
57
- def gen(p)
58
- logger.debug "params:\n#{MemeCaptain.pretty_format(p)}"
59
- norm_params = normalize_params(p)
60
- logger.debug 'normalized params:'
61
- logger.debug(MemeCaptain.pretty_format(norm_params))
62
-
63
- if existing = MemeData.first(
64
- :source_url => norm_params.u,
65
-
66
- 'texts.0.text' => norm_params.t1,
67
- 'texts.0.x' => norm_params.t1x,
68
- 'texts.0.y' => norm_params.t1y,
69
- 'texts.0.w' => norm_params.t1w,
70
- 'texts.0.h' => norm_params.t1h,
71
-
72
- 'texts.1.text' => norm_params.t2,
73
- 'texts.1.x' => norm_params.t2x,
74
- 'texts.1.y' => norm_params.t2y,
75
- 'texts.1.w' => norm_params.t2w,
76
- 'texts.1.h' => norm_params.t2h
77
- )
78
- logger.debug 'found existing meme:'
79
- logger.debug(MemeCaptain.pretty_format(existing))
80
- existing
81
- else
82
- if same_source = MemeData.find_by_source_url(norm_params.u)
83
- logger.debug 'found existing source image'
84
- source_fs_path = same_source.source_fs_path
85
- elsif (norm_params.u.index(settings.upload_prefix) == 0) and
86
- (upload = Upload.find_by_upload_id(
87
- norm_params.u[settings.upload_prefix.size..-1]))
88
-
89
- logger.debug 'source image is upload:'
90
- logger.debug MemeCaptain.pretty_format(upload)
91
- source_fs_path = upload.fs_path
92
- else
93
- if source_fetch_fail = SourceFetchFail.find_by_url(norm_params.u)
94
- logger.debug 'skipping fetch of previously failed source image:'
95
- logger.debug(MemeCaptain.pretty_format(source_fetch_fail))
96
- source_fetch_fail.requested!
97
- halt 500, 'Error loading source image url'
98
- else
99
- source_img = ImageList::SourceImage.new
100
- begin
101
- logger.debug "fetch source image: #{norm_params.u}"
102
- source_img.fetch! norm_params.u
103
- rescue => error
104
- new_source_fetch_fail = SourceFetchFail.new(
105
- :attempt_count => 1,
106
- :orig_ip => request.ip,
107
- :response_code => error.respond_to?(:response_code) ?
108
- error.response_code : nil,
109
- :url => norm_params.u
110
- )
111
- logger.debug 'source image fetch failed:'
112
- logger.debug(MemeCaptain.pretty_format(new_source_fetch_fail))
113
- new_source_fetch_fail.save!
114
- halt 500, 'Error loading source image url'
115
- end
116
- source_img.prepare! settings.source_img_max_side, settings.watermark
117
- source_fs_path = source_img.cache(norm_params.u, 'source_cache')
118
- source_img.each { |frame| frame.destroy! }
119
- end
120
- end
121
-
122
- logger.debug "source image filesystem path: #{source_fs_path}"
123
-
124
- open(source_fs_path, 'rb') do |source_io|
125
- t1 = TextPos.new(norm_params.t1, norm_params.t1x, norm_params.t1y,
126
- norm_params.t1w, norm_params.t1h)
127
-
128
- t2 = TextPos.new(norm_params.t2, norm_params.t2x, norm_params.t2y,
129
- norm_params.t2w, norm_params.t2h)
130
-
131
- meme_img = MemeCaptain.meme(source_io, [t1, t2])
132
- meme_img.extend ImageList::Cache
133
-
134
- # convert source images in formats other than jpeg, gif or png
135
- # to png
136
- unless %w{JPEG GIF PNG}.include?(meme_img.format)
137
- meme_img.format = 'PNG'
138
- end
139
-
140
- # convert non-animated gifs to png
141
- if meme_img.format == 'GIF' and meme_img.size == 1
142
- meme_img.format = 'PNG'
143
- end
144
-
145
- sig = norm_params.signature
146
- meme_hash = Digest::SHA1.hexdigest(sig)
147
-
148
- meme_id = nil
149
- (6..meme_hash.size).each do |len|
150
- meme_id = "#{meme_hash[0,len]}.#{meme_img.extension}"
151
- break unless MemeData.where(:meme_id => meme_id).count > 0
152
- end
153
-
154
- meme_fs_path = meme_img.cache(sig, File.join('public', 'meme'))
155
-
156
- logger.debug "meme filesystem path: #{meme_fs_path}"
157
-
158
- meme_img.write(meme_fs_path) {
159
- self.quality = 100
160
- }
161
-
162
- meme_data = MemeData.new(
163
- :meme_id => meme_id,
164
- :fs_path => meme_fs_path,
165
- :mime_type => meme_img.mime_type,
166
- :size => File.size(meme_fs_path),
167
-
168
- :source_url => norm_params.u,
169
- :source_fs_path => source_fs_path,
170
-
171
- :texts => [{
172
- :text => norm_params.t1,
173
- :x => norm_params.t1x,
174
- :y => norm_params.t1y,
175
- :w => norm_params.t1w,
176
- :h => norm_params.t1h,
177
- }, {
178
- :text => norm_params.t2,
179
- :x => norm_params.t2x,
180
- :y => norm_params.t2y,
181
- :w => norm_params.t2w,
182
- :h => norm_params.t2h,
183
- }],
184
-
185
- :request_count => 0,
186
-
187
- :creator_ip => request.ip
188
- )
189
-
190
- meme_img.each { |frame| frame.destroy! }
191
-
192
- logger.debug "meme data:\n#{MemeCaptain.pretty_format(meme_data)}"
193
-
194
- meme_data.save! :safe => true
195
-
196
- meme_data
197
- end
198
-
199
- end
200
- end
201
-
202
- get '/g' do
203
- begin
204
- meme_data = gen(params)
205
-
206
- [200, { 'Content-Type' => 'application/json' }, {
207
- 'imageUrl' => url("/#{meme_data.meme_id}"),
208
- 'templateUrl' => url("/?u=#{Rack::Utils.escape(meme_data.meme_id)}"),
209
- }.to_json]
210
- rescue => error
211
- logger.error "error generating image: #{error.class} #{error.message}"
212
- logger.error(MemeCaptain.pretty_format(error.backtrace))
213
- [500, { 'Content-Type' => 'text/plain' }, 'Error generating image']
214
- end
215
- end
216
-
217
- def serve_img(meme_data)
218
- meme_data.requested!
219
-
220
- if meme_data.respond_to?(:texts)
221
- meme_text = meme_data.texts.map do |text|
222
- Rack::Utils.escape(text['text'])
223
- end.join('&')
224
-
225
- headers 'Meme-Text' => meme_text
226
- end
227
-
228
- send_file meme_data.fs_path, :type => meme_data.mime_type
229
- end
230
-
231
- get '/i' do
232
- raise Sinatra::NotFound if params[:u].to_s.empty?
233
-
234
- serve_img(gen(params))
235
- end
236
-
237
- get %r{^/([a-f0-9]+\.(?:gif|jpg|png))$} do
238
- if meme_data = MemeData.find_by_meme_id(params[:captures][0])
239
- serve_img meme_data
240
- else
241
- raise Sinatra::NotFound
242
- end
243
- end
244
-
245
- post '/upload' do
246
- redirect('/') unless params[:upload]
247
-
248
- img = ImageList::SourceImage.new
249
- img.from_blob(params[:upload][:tempfile].read)
250
- img.prepare! settings.source_img_max_side, settings.watermark
251
- fs_path = img.cache(params[:upload][:filename], 'source_cache')
252
-
253
- filename_hash = Digest::SHA1.hexdigest(params[:upload][:filename])
254
-
255
- len = 6
256
- upload_id = filename_hash[0,len]
257
- while Upload.where(:upload_id => upload_id).count > 0
258
- upload_id = if len < filename_hash.size
259
- len += 1
260
- filename_hash[0,len]
261
- else
262
- "#{upload_id}0"
263
- end
264
- end
265
-
266
- upload = Upload.new(
267
- :upload_id => upload_id,
268
- :fs_path => fs_path,
269
- :mime_type => img.mime_type,
270
- :size => File.size(fs_path),
271
- :request_count => 0,
272
- :creator_ip => request.ip
273
- )
274
-
275
- img.each { |frame| frame.destroy! }
276
-
277
- upload.save! :safe => true
278
-
279
- redirect "/?u=#{settings.upload_prefix}#{Rack::Utils.escape(upload_id)}"
280
- end
281
-
282
- get %r{/#{settings.upload_prefix}(.+)} do
283
- if upload = Upload.find_by_upload_id(params[:captures][0])
284
- serve_img upload
285
- else
286
- raise Sinatra::NotFound
287
- end
288
- end
289
-
290
- not_found do
291
- @root_url = url('/')
292
-
293
- erb :'404'
294
- end
295
-
296
- helpers do
297
- include Rack::Utils
298
- alias_method :h, :escape_html
299
- end
300
-
301
- end
302
-
303
- end