meme_captain 0.2.0 → 0.3.0

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