meme_captain 0.1.0 → 0.1.1

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.
@@ -10,9 +10,16 @@ module MemeCaptain
10
10
 
11
11
  set :root, File.expand_path(File.join('..', '..'), File.dirname(__FILE__))
12
12
  set :source_img_max_side, 800
13
+ set :upload_prefix, 'up/'
13
14
  set :watermark, Magick::ImageList.new(File.expand_path(
14
15
  File.join('..', '..', 'watermark.png'), File.dirname(__FILE__)))
15
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
+
16
23
  get '/' do
17
24
  @u = params[:u]
18
25
 
@@ -33,107 +40,110 @@ module MemeCaptain
33
40
  erb :index
34
41
  end
35
42
 
36
- def convert_metric(metric, default)
37
- case
38
- when metric.to_s.empty?; default
39
- when metric.index('.'); metric.to_f
40
- else; metric.to_i
41
- end
42
- end
43
-
44
43
  def normalize_params(p)
45
- result = {
46
- 'u' => p[:u],
47
-
48
- # convert to empty string if null
49
- 't1' => p[:t1].to_s,
50
- 't2' => p[:t2].to_s,
51
- }
52
-
53
- result['t1x'] = convert_metric(p[:t1x], 0.05)
54
- result['t1y'] = convert_metric(p[:t1y], 0)
55
- result['t1w'] = convert_metric(p[:t1w], 0.9)
56
- result['t1h'] = convert_metric(p[:t1h], 0.25)
57
-
58
- result['t2x'] = convert_metric(p[:t2x], 0.05)
59
- result['t2y'] = convert_metric(p[:t2y], 0.75)
60
- result['t2w'] = convert_metric(p[:t2w], 0.9)
61
- result['t2h'] = convert_metric(p[:t2h], 0.25)
44
+ result = NormParams.new(p)
62
45
 
63
46
  # if the id of an existing meme is passed in as the source url, use the
64
47
  # source image of that meme for the source image
65
- if result['u'][%r{^[a-f0-9]+\.(?:gif|jpg|png)$}]
66
- if existing_as_source = MemeData.find_by_meme_id(result['u'])
67
- result['u'] = existing_as_source.source_url
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
68
51
  end
69
52
  end
70
53
 
71
- # hash with string keys that can be accessed by symbol
72
- Hash.new { |hash,key| hash[key.to_s] if Symbol === key }.merge(result)
54
+ result
73
55
  end
74
56
 
75
57
  def gen(p)
58
+ logger.debug "params:\n#{MemeCaptain.pretty_format(p)}"
76
59
  norm_params = normalize_params(p)
60
+ logger.debug 'normalized params:'
61
+ logger.debug(MemeCaptain.pretty_format(norm_params))
77
62
 
78
63
  if existing = MemeData.first(
79
- :source_url => norm_params[:u],
80
-
81
- 'texts.0.text' => norm_params[:t1],
82
- 'texts.0.x' => norm_params[:t1x],
83
- 'texts.0.y' => norm_params[:t1y],
84
- 'texts.0.w' => norm_params[:t1w],
85
- 'texts.0.h' => norm_params[:t1h],
86
-
87
- 'texts.1.text' => norm_params[:t2],
88
- 'texts.1.x' => norm_params[:t2x],
89
- 'texts.1.y' => norm_params[:t2y],
90
- 'texts.1.w' => norm_params[:t2w],
91
- 'texts.1.h' => norm_params[:t2h]
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
92
77
  )
78
+ logger.debug 'found existing meme:'
79
+ logger.debug(MemeCaptain.pretty_format(existing))
93
80
  existing
94
81
  else
95
- if same_source = MemeData.find_by_source_url(norm_params[:u])
82
+ if same_source = MemeData.find_by_source_url(norm_params.u)
83
+ logger.debug 'found existing source image'
96
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
97
92
  else
98
- if source_fetch_fail = SourceFetchFail.find_by_url(norm_params[:u])
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))
99
96
  source_fetch_fail.requested!
100
97
  halt 500, 'Error loading source image url'
101
98
  else
102
99
  source_img = ImageList::SourceImage.new
103
100
  begin
104
- source_img.fetch! norm_params[:u]
101
+ logger.debug "fetch source image: #{norm_params.u}"
102
+ source_img.fetch! norm_params.u
105
103
  rescue => error
106
- SourceFetchFail.new(
104
+ new_source_fetch_fail = SourceFetchFail.new(
107
105
  :attempt_count => 1,
108
106
  :orig_ip => request.ip,
109
107
  :response_code => error.respond_to?(:response_code) ?
110
108
  error.response_code : nil,
111
- :url => norm_params[:u]
112
- ).save!
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!
113
114
  halt 500, 'Error loading source image url'
114
115
  end
115
116
  source_img.prepare! settings.source_img_max_side, settings.watermark
116
- source_fs_path = source_img.cache(norm_params[:u], 'source_cache')
117
+ source_fs_path = source_img.cache(norm_params.u, 'source_cache')
118
+ source_img.each { |frame| frame.destroy! }
117
119
  end
118
120
  end
119
121
 
122
+ logger.debug "source image filesystem path: #{source_fs_path}"
123
+
120
124
  open(source_fs_path, 'rb') do |source_io|
121
- t1 = TextPos.new(norm_params[:t1], norm_params[:t1x],
122
- norm_params[:t1y], norm_params[:t1w], norm_params[:t1h])
125
+ t1 = TextPos.new(norm_params.t1, norm_params.t1x, norm_params.t1y,
126
+ norm_params.t1w, norm_params.t1h)
123
127
 
124
- t2 = TextPos.new(norm_params[:t2], norm_params[:t2x],
125
- norm_params[:t2y], norm_params[:t2w], norm_params[:t2h])
128
+ t2 = TextPos.new(norm_params.t2, norm_params.t2x, norm_params.t2y,
129
+ norm_params.t2w, norm_params.t2h)
126
130
 
127
131
  meme_img = MemeCaptain.meme(source_io, [t1, t2])
128
132
  meme_img.extend ImageList::Cache
129
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
+
130
140
  # convert non-animated gifs to png
131
141
  if meme_img.format == 'GIF' and meme_img.size == 1
132
142
  meme_img.format = 'PNG'
133
143
  end
134
144
 
135
- params_s = norm_params.sort.map(&:join).join
136
- meme_hash = Digest::SHA1.hexdigest(params_s)
145
+ sig = norm_params.signature
146
+ meme_hash = Digest::SHA1.hexdigest(sig)
137
147
 
138
148
  meme_id = nil
139
149
  (6..meme_hash.size).each do |len|
@@ -141,7 +151,9 @@ module MemeCaptain
141
151
  break unless MemeData.where(:meme_id => meme_id).count > 0
142
152
  end
143
153
 
144
- meme_fs_path = meme_img.cache(params_s, File.join('public', 'meme'))
154
+ meme_fs_path = meme_img.cache(sig, File.join('public', 'meme'))
155
+
156
+ logger.debug "meme filesystem path: #{meme_fs_path}"
145
157
 
146
158
  meme_img.write(meme_fs_path) {
147
159
  self.quality = 100
@@ -153,21 +165,21 @@ module MemeCaptain
153
165
  :mime_type => meme_img.mime_type,
154
166
  :size => File.size(meme_fs_path),
155
167
 
156
- :source_url => norm_params[:u],
168
+ :source_url => norm_params.u,
157
169
  :source_fs_path => source_fs_path,
158
170
 
159
171
  :texts => [{
160
- :text => norm_params[:t1],
161
- :x => norm_params[:t1x],
162
- :y => norm_params[:t1y],
163
- :w => norm_params[:t1w],
164
- :h => norm_params[:t1h],
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,
165
177
  }, {
166
- :text => norm_params[:t2],
167
- :x => norm_params[:t2x],
168
- :y => norm_params[:t2y],
169
- :w => norm_params[:t2w],
170
- :h => norm_params[:t2h],
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,
171
183
  }],
172
184
 
173
185
  :request_count => 0,
@@ -175,6 +187,10 @@ module MemeCaptain
175
187
  :creator_ip => request.ip
176
188
  )
177
189
 
190
+ meme_img.each { |frame| frame.destroy! }
191
+
192
+ logger.debug "meme data:\n#{MemeCaptain.pretty_format(meme_data)}"
193
+
178
194
  meme_data.save! :safe => true
179
195
 
180
196
  meme_data
@@ -184,15 +200,16 @@ module MemeCaptain
184
200
  end
185
201
 
186
202
  get '/g' do
187
- raise Sinatra::NotFound if params[:u].to_s.empty?
188
-
189
203
  begin
190
204
  meme_data = gen(params)
191
205
 
192
206
  [200, { 'Content-Type' => 'application/json' }, {
193
- 'imageUrl' => url("/#{meme_data.meme_id}")
207
+ 'imageUrl' => url("/#{meme_data.meme_id}"),
208
+ 'templateUrl' => url("/?u=#{Rack::Utils.escape(meme_data.meme_id)}"),
194
209
  }.to_json]
195
210
  rescue => error
211
+ logger.error "error generating image: #{error.class} #{error.message}"
212
+ logger.error(MemeCaptain.pretty_format(error.backtrace))
196
213
  [500, { 'Content-Type' => 'text/plain' }, 'Error generating image']
197
214
  end
198
215
  end
@@ -200,9 +217,7 @@ module MemeCaptain
200
217
  def serve_img(meme_data)
201
218
  meme_data.requested!
202
219
 
203
- content_type meme_data.mime_type
204
-
205
- FileBody.new meme_data.fs_path
220
+ send_file meme_data.fs_path, :type => meme_data.mime_type
206
221
  end
207
222
 
208
223
  get '/i' do
@@ -219,6 +234,51 @@ module MemeCaptain
219
234
  end
220
235
  end
221
236
 
237
+ post '/upload' do
238
+ redirect('/') unless params[:upload]
239
+
240
+ img = ImageList::SourceImage.new
241
+ img.from_blob(params[:upload][:tempfile].read)
242
+ img.prepare! settings.source_img_max_side, settings.watermark
243
+ fs_path = img.cache(params[:upload][:filename], 'source_cache')
244
+
245
+ filename_hash = Digest::SHA1.hexdigest(params[:upload][:filename])
246
+
247
+ len = 6
248
+ upload_id = filename_hash[0,len]
249
+ while Upload.where(:upload_id => upload_id).count > 0
250
+ upload_id = if len < filename_hash.size
251
+ len += 1
252
+ filename_hash[0,len]
253
+ else
254
+ "#{upload_id}0"
255
+ end
256
+ end
257
+
258
+ upload = Upload.new(
259
+ :upload_id => upload_id,
260
+ :fs_path => fs_path,
261
+ :mime_type => img.mime_type,
262
+ :size => File.size(fs_path),
263
+ :request_count => 0,
264
+ :creator_ip => request.ip
265
+ )
266
+
267
+ img.each { |frame| frame.destroy! }
268
+
269
+ upload.save! :safe => true
270
+
271
+ redirect "/?u=#{settings.upload_prefix}#{Rack::Utils.escape(upload_id)}"
272
+ end
273
+
274
+ get %r{/#{settings.upload_prefix}(.+)} do
275
+ if upload = Upload.find_by_upload_id(params[:captures][0])
276
+ serve_img upload
277
+ else
278
+ raise Sinatra::NotFound
279
+ end
280
+ end
281
+
222
282
  not_found do
223
283
  @root_url = url('/')
224
284
 
@@ -0,0 +1,30 @@
1
+ require 'mongo_mapper'
2
+
3
+ module MemeCaptain
4
+
5
+ # Data about uploaded files.
6
+ class Upload
7
+ include MongoMapper::Document
8
+
9
+ set_collection_name 'upload'
10
+
11
+ key :upload_id, String
12
+ key :fs_path, String
13
+ key :mime_type, String
14
+ key :size, Integer
15
+
16
+ key :request_count, Integer
17
+ key :last_request, Time
18
+
19
+ key :creator_ip, String
20
+
21
+ timestamps!
22
+
23
+ def requested!
24
+ increment :request_count => 1
25
+ set :last_request => Time.now
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -1,3 +1,3 @@
1
1
  module MemeCaptain
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.1'
3
3
  end
data/lib/meme_captain.rb CHANGED
@@ -1,11 +1,14 @@
1
1
  require 'meme_captain/caption'
2
2
  require 'meme_captain/caption_choice'
3
3
  require 'meme_captain/draw'
4
- require 'meme_captain/file_body'
5
4
  require 'meme_captain/image_list'
6
5
  require 'meme_captain/meme'
6
+ require 'meme_captain/memebg'
7
7
  require 'meme_captain/meme_data'
8
+ require 'meme_captain/norm_params'
9
+ require 'meme_captain/pretty_format'
8
10
  require 'meme_captain/server'
9
11
  require 'meme_captain/source_fetch_fail'
10
12
  require 'meme_captain/text_pos'
13
+ require 'meme_captain/upload'
11
14
  require 'meme_captain/version'
data/meme_captain.gemspec CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |s|
22
22
  mongo
23
23
  mongo_mapper
24
24
  rack
25
+ rack-contrib
25
26
  rack-rewrite
26
27
  rmagick
27
28
  sinatra
@@ -29,6 +30,7 @@ Gem::Specification.new do |s|
29
30
 
30
31
  %w{
31
32
  rspec
33
+ webmock
32
34
  }.each { |g| s.add_development_dependency g }
33
35
 
34
36
  s.files = `git ls-files`.split("\n")
@@ -4,6 +4,10 @@ body {
4
4
  font-family : verdana, helvetica, arial, sans-serif;
5
5
  }
6
6
 
7
+ a img {
8
+ border : 0;
9
+ }
10
+
7
11
  a:link {
8
12
  color : #edfc72;
9
13
  }
@@ -64,3 +68,13 @@ input[type=text] {
64
68
  input[type=button] {
65
69
  padding : 4px 8px 4px 8px;
66
70
  }
71
+
72
+ input[type=text].attn {
73
+ background-color : #a7e464;
74
+ }
75
+
76
+ div.share {
77
+ display : inline-block;
78
+ margin-right : 1em;
79
+ vertical-align : middle;
80
+ }
Binary file