meme_captain 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/ChangeLog +33 -0
- data/README.md +4 -0
- data/bin/memecaptain +68 -0
- data/config.ru +13 -0
- data/lib/meme_captain/caption_choice.rb +12 -5
- data/lib/meme_captain/draw.rb +7 -5
- data/lib/meme_captain/image_list/cache.rb +49 -0
- data/lib/meme_captain/image_list/fetch.rb +26 -0
- data/lib/meme_captain/image_list/source_image.rb +28 -0
- data/lib/meme_captain/image_list/watermark.rb +24 -0
- data/lib/meme_captain/image_list.rb +5 -0
- data/lib/meme_captain/meme.rb +3 -2
- data/lib/meme_captain/meme_data.rb +34 -0
- data/lib/meme_captain/server.rb +124 -41
- data/lib/meme_captain/version.rb +1 -1
- data/lib/meme_captain.rb +2 -2
- data/meme_captain.gemspec +4 -0
- data/public/source_images.json +142 -0
- data/public/thumbs.jpg +0 -0
- data/script/thumb_sprites.rb +17 -4
- data/views/404.erb +17 -0
- data/views/index.erb +100 -83
- data/watermark.png +0 -0
- metadata +64 -15
- data/img_cache/source/.gitignore +0 -0
- data/lib/meme_captain/filesystem_cache.rb +0 -70
- data/lib/meme_captain/mime_type.rb +0 -20
data/.gitignore
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
img_cache/*
|
1
|
+
img_cache/*
|
2
|
+
*~
|
data/ChangeLog
CHANGED
@@ -1,3 +1,36 @@
|
|
1
|
+
0.0.9 2012-01-19
|
2
|
+
|
3
|
+
Design and markup improvements on the site.
|
4
|
+
|
5
|
+
Allow use of all type metrics when comparing caption choices.
|
6
|
+
|
7
|
+
Add ability to use the ids in the URLs of generated images to make
|
8
|
+
new images from the same source by using the id as the source image
|
9
|
+
URL (an example id is abcdef.jpg).
|
10
|
+
|
11
|
+
Add a 404 page to the site.
|
12
|
+
|
13
|
+
Deprecate concept of temporary URLs and permanent URLs for images
|
14
|
+
generated from the site.
|
15
|
+
|
16
|
+
Use MongoDB for the site datastore.
|
17
|
+
|
18
|
+
Add "memecaptain" executable for creating memes using the gem from the
|
19
|
+
command line.
|
20
|
+
|
21
|
+
Add a watermark to images created on the site.
|
22
|
+
|
23
|
+
Shrink large source images to a maximum of 800 pixels per side for
|
24
|
+
images created on the site.
|
25
|
+
|
26
|
+
Define local source images in JSON instead of in the template and load
|
27
|
+
them with Ajax.
|
28
|
+
|
29
|
+
Add Google Image Search to the site.
|
30
|
+
|
31
|
+
Add new source images to the site: all the things 2, cool story bro,
|
32
|
+
aw yeah, Boromir, Ned Stark.
|
33
|
+
|
1
34
|
0.0.8
|
2
35
|
2011-11-23
|
3
36
|
|
data/README.md
CHANGED
@@ -47,6 +47,10 @@ Example:
|
|
47
47
|
http://memecaptain.com/g?u=http%3A%2F%2Fmemecaptain.com%2Fyao_ming.jpg&tt=sure+i%27ll+test&tb=the+api
|
48
48
|
```
|
49
49
|
|
50
|
+
Note: tempUrl is deprecated and will now always be the same as permUrl. It is
|
51
|
+
left for compability with older clients.
|
52
|
+
|
53
|
+
|
50
54
|
```json
|
51
55
|
{
|
52
56
|
permUrl: "http://memecaptain.com/i?u=http%3A%2F%2Fmemecaptain.com%2Fyao_ming.jpg&tt=sure+i%27ll+test&tb=the+api"
|
data/bin/memecaptain
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mime/types'
|
4
|
+
require 'open-uri'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
require 'meme_captain'
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
|
11
|
+
option_parser = OptionParser.new do |opts|
|
12
|
+
opts.banner = <<-eos
|
13
|
+
Usage: memecaptain INPUT_IMAGE [OPTIONS]
|
14
|
+
|
15
|
+
INPUT_IMAGE can be a file path, URL or - for stdin.
|
16
|
+
|
17
|
+
eos
|
18
|
+
|
19
|
+
opts.on('-t', '--top-text TEXT', 'top text') do |text|
|
20
|
+
options[:top_text] = text
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on('-b', '--bottom-text TEXT', 'bottom text') do |text|
|
24
|
+
options[:bottom_text] = text
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('-o', '--output PATH', 'output path (- for stdout)') do |path|
|
28
|
+
options[:output] = path
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on('-f', '--font FONT', 'font') do |font|
|
32
|
+
options[:font] = font
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
option_parser.parse!
|
37
|
+
|
38
|
+
unless ARGV.empty?
|
39
|
+
input = ARGV[0]
|
40
|
+
|
41
|
+
input_io = if input == '-'
|
42
|
+
$stdin
|
43
|
+
else
|
44
|
+
open input, 'rb'
|
45
|
+
end
|
46
|
+
|
47
|
+
meme_options = options.select do |k,v|
|
48
|
+
[
|
49
|
+
:font,
|
50
|
+
].include? k
|
51
|
+
end
|
52
|
+
|
53
|
+
output_image = MemeCaptain.meme(
|
54
|
+
input_io, options[:top_text], options[:bottom_text], meme_options)
|
55
|
+
|
56
|
+
input_io.close
|
57
|
+
|
58
|
+
output = if options[:output]
|
59
|
+
options[:output] == '-' ? $stdout : options[:output]
|
60
|
+
else
|
61
|
+
"meme.#{MIME::Types[output_image.mime_type][0].extensions[0]}"
|
62
|
+
end
|
63
|
+
|
64
|
+
output_image.write(output) { self.quality = 100 }
|
65
|
+
else
|
66
|
+
$stderr.puts option_parser
|
67
|
+
exit 1
|
68
|
+
end
|
data/config.ru
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
2
|
|
3
|
+
require 'mongo'
|
4
|
+
require 'mongo_mapper'
|
3
5
|
require 'rack'
|
4
6
|
|
5
7
|
require 'meme_captain'
|
@@ -9,4 +11,15 @@ use Rack::Sendfile
|
|
9
11
|
|
10
12
|
use Rack::Static, :urls => %w{/tmp}, :root => 'public'
|
11
13
|
|
14
|
+
MongoMapper.connection = Mongo::Connection.new
|
15
|
+
MongoMapper.database = 'memecaptain'
|
16
|
+
|
17
|
+
MemeCaptain::MemeData.ensure_index :meme_id
|
18
|
+
|
19
|
+
MemeCaptain::MemeData.ensure_index [
|
20
|
+
[:source_url, 1],
|
21
|
+
[:top_text, 1],
|
22
|
+
[:bottom_text, 1],
|
23
|
+
]
|
24
|
+
|
12
25
|
run MemeCaptain::Server
|
@@ -4,18 +4,24 @@ module MemeCaptain
|
|
4
4
|
class CaptionChoice
|
5
5
|
include Comparable
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@fits = fits
|
7
|
+
def initialize(pointsize, metrics, text, bound_width, bound_height)
|
9
8
|
@pointsize = pointsize
|
9
|
+
@metrics = metrics
|
10
10
|
@text = text
|
11
|
+
@bound_width = bound_width
|
12
|
+
@bound_height = bound_height
|
11
13
|
end
|
12
14
|
|
13
15
|
def num_lines
|
14
16
|
text.count("\n") + 1
|
15
17
|
end
|
16
18
|
|
19
|
+
def fits
|
20
|
+
metrics.width <= bound_width and metrics.height <= bound_height
|
21
|
+
end
|
22
|
+
|
17
23
|
def fits_i
|
18
|
-
fits ? 1: 0
|
24
|
+
fits ? 1 : 0
|
19
25
|
end
|
20
26
|
|
21
27
|
def <=>(other)
|
@@ -24,10 +30,11 @@ module MemeCaptain
|
|
24
30
|
other.fits ? -other.num_lines : other.num_lines]
|
25
31
|
end
|
26
32
|
|
27
|
-
attr_accessor :fits
|
28
33
|
attr_accessor :pointsize
|
34
|
+
attr_accessor :metrics
|
29
35
|
attr_accessor :text
|
30
|
-
|
36
|
+
attr_accessor :bound_width
|
37
|
+
attr_accessor :bound_height
|
31
38
|
end
|
32
39
|
|
33
40
|
end
|
data/lib/meme_captain/draw.rb
CHANGED
@@ -6,20 +6,22 @@ module MemeCaptain
|
|
6
6
|
# Calculate the largest pointsize for text that will be in a width x
|
7
7
|
# height box.
|
8
8
|
#
|
9
|
-
# Return [pointsize,
|
10
|
-
#
|
9
|
+
# Return [pointsize, metrics] where pointsize is the largest pointsize and
|
10
|
+
# metrics is the RMagick multiline type metrics of the best fit.
|
11
11
|
def calc_pointsize(width, height, text, min_pointsize)
|
12
12
|
current_pointsize = min_pointsize
|
13
13
|
|
14
|
-
|
14
|
+
metrics = nil
|
15
15
|
|
16
16
|
loop {
|
17
17
|
self.pointsize = current_pointsize
|
18
|
+
last_metrics = metrics
|
18
19
|
metrics = get_multiline_type_metrics(text)
|
20
|
+
|
19
21
|
if metrics.width > width or metrics.height > height
|
20
22
|
if current_pointsize > min_pointsize
|
21
23
|
current_pointsize -= 1
|
22
|
-
|
24
|
+
metrics = last_metrics
|
23
25
|
end
|
24
26
|
break
|
25
27
|
else
|
@@ -27,7 +29,7 @@ module MemeCaptain
|
|
27
29
|
end
|
28
30
|
}
|
29
31
|
|
30
|
-
[current_pointsize,
|
32
|
+
[current_pointsize, metrics]
|
31
33
|
end
|
32
34
|
|
33
35
|
end
|
@@ -0,0 +1,49 @@
|
|
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
|
@@ -0,0 +1,26 @@
|
|
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
|
+
end
|
15
|
+
unless curl.response_code == 200
|
16
|
+
raise "Error loading source image url #{url}"
|
17
|
+
end
|
18
|
+
|
19
|
+
from_blob curl.body_str
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
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
|
+
if size == 1 and (columns > max_side or rows > max_side)
|
16
|
+
resize_to_fit! max_side
|
17
|
+
end
|
18
|
+
|
19
|
+
watermark_mc watermark_img
|
20
|
+
|
21
|
+
strip!
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
|
3
|
+
module MemeCaptain
|
4
|
+
|
5
|
+
module ImageList
|
6
|
+
|
7
|
+
# Mix-in for Magick::ImageList to add watermark.
|
8
|
+
module Watermark
|
9
|
+
|
10
|
+
# Watermark this image using another image.
|
11
|
+
def watermark_mc(watermark_img)
|
12
|
+
self.each do |frame|
|
13
|
+
frame.composite!(watermark_img, Magick::SouthEastGravity,
|
14
|
+
-frame.page.width + frame.columns + frame.page.x,
|
15
|
+
-frame.page.height + frame.rows + frame.page.y,
|
16
|
+
Magick::OverCompositeOp)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/meme_captain/meme.rb
CHANGED
@@ -69,10 +69,11 @@ module MemeCaptain
|
|
69
69
|
}.uniq
|
70
70
|
|
71
71
|
choices = wrap_tries.map do |wrap_try|
|
72
|
-
pointsize,
|
72
|
+
pointsize, metrics = draw.calc_pointsize(
|
73
73
|
text_width, text_height, wrap_try, min_pointsize)
|
74
74
|
|
75
|
-
CaptionChoice.new(
|
75
|
+
CaptionChoice.new(pointsize, metrics, wrap_try, text_width,
|
76
|
+
text_height)
|
76
77
|
end
|
77
78
|
|
78
79
|
choice = choices.max
|
@@ -0,0 +1,34 @@
|
|
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 :top_text, String
|
18
|
+
key :bottom_text, String
|
19
|
+
|
20
|
+
key :request_count, Integer
|
21
|
+
key :last_request, Time
|
22
|
+
|
23
|
+
key :creator_ip, String
|
24
|
+
|
25
|
+
timestamps!
|
26
|
+
|
27
|
+
def requested!
|
28
|
+
increment :request_count => 1
|
29
|
+
set :last_request => Time.now
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/lib/meme_captain/server.rb
CHANGED
@@ -1,84 +1,167 @@
|
|
1
1
|
require 'digest/sha1'
|
2
|
-
require 'uri'
|
3
2
|
|
4
|
-
require 'curb'
|
5
3
|
require 'json'
|
4
|
+
require 'rack'
|
6
5
|
require 'sinatra/base'
|
7
6
|
|
8
|
-
require 'meme_captain'
|
9
|
-
|
10
7
|
module MemeCaptain
|
11
8
|
|
12
9
|
class Server < Sinatra::Base
|
13
10
|
|
14
|
-
|
15
|
-
|
16
|
-
set :
|
11
|
+
set :root, File.expand_path(File.join('..', '..'), File.dirname(__FILE__))
|
12
|
+
set :source_img_max_side, 800
|
13
|
+
set :watermark, Magick::ImageList.new(File.expand_path(
|
14
|
+
File.join('..', '..', 'watermark.png'), File.dirname(__FILE__)))
|
17
15
|
|
18
16
|
get '/' do
|
19
17
|
@u = params[:u]
|
20
18
|
@tt= params[:tt]
|
21
19
|
@tb = params[:tb]
|
22
20
|
|
21
|
+
@root_url = url('/')
|
22
|
+
|
23
23
|
erb :index
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
26
|
+
def normalize_params(p)
|
27
|
+
result = {
|
28
|
+
'u' => p[:u],
|
29
|
+
# convert to empty string if null
|
30
|
+
'tt' => p[:tt].to_s,
|
31
|
+
'tb' => p[:tb].to_s,
|
32
|
+
}
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
}
|
34
|
+
# if the id of an existing meme is passed in as the source url, use the
|
35
|
+
# source image of that meme for the source image
|
36
|
+
if result['u'][%r{^[a-f0-9]+\.(?:gif|jpg|png)$}]
|
37
|
+
if existing_as_source = MemeData.find_by_meme_id(result['u'])
|
38
|
+
result['u'] = existing_as_source.source_url
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# hash with string keys that can be accessed by symbol
|
43
|
+
Hash.new { |hash,key| hash[key.to_s] if Symbol === key }.merge(result)
|
44
|
+
end
|
42
45
|
|
43
|
-
|
44
|
-
|
46
|
+
def gen(p)
|
47
|
+
norm_params = normalize_params(p)
|
48
|
+
|
49
|
+
if existing = MemeData.first(
|
50
|
+
:source_url => norm_params[:u],
|
51
|
+
:top_text => norm_params[:tt],
|
52
|
+
:bottom_text => norm_params[:tb]
|
53
|
+
)
|
54
|
+
existing
|
55
|
+
else
|
56
|
+
if same_source = MemeData.find_by_source_url(norm_params[:u])
|
57
|
+
source_fs_path = same_source.source_fs_path
|
58
|
+
else
|
59
|
+
source_img = ImageList::SourceImage.new
|
60
|
+
source_img.fetch! norm_params[:u]
|
61
|
+
source_img.prepare! settings.source_img_max_side, settings.watermark
|
62
|
+
source_fs_path = source_img.cache(norm_params[:u], 'source_cache')
|
63
|
+
end
|
64
|
+
|
65
|
+
open(source_fs_path, 'rb') do |source_io|
|
66
|
+
meme_img = MemeCaptain.meme(source_io, norm_params[:tt],
|
67
|
+
norm_params[:tb])
|
68
|
+
meme_img.extend ImageList::Cache
|
45
69
|
|
46
|
-
meme_img.to_blob {
|
47
|
-
self.quality = 100
|
48
70
|
# convert non-animated gifs to png
|
49
|
-
if
|
50
|
-
|
71
|
+
if meme_img.format == 'GIF' and meme_img.size == 1
|
72
|
+
meme_img.format = 'PNG'
|
51
73
|
end
|
52
|
-
|
53
|
-
|
74
|
+
|
75
|
+
params_s = norm_params.sort.map(&:join).join
|
76
|
+
meme_hash = Digest::SHA1.hexdigest(params_s)
|
77
|
+
|
78
|
+
meme_id = nil
|
79
|
+
(6..meme_hash.size).each do |len|
|
80
|
+
meme_id = "#{meme_hash[0,len]}.#{meme_img.extension}"
|
81
|
+
break unless MemeData.where(:meme_id => meme_id).count > 0
|
82
|
+
end
|
83
|
+
|
84
|
+
meme_fs_path = meme_img.cache(params_s, File.join('public', 'meme'))
|
85
|
+
|
86
|
+
meme_img.write(meme_fs_path) {
|
87
|
+
self.quality = 100
|
88
|
+
}
|
89
|
+
|
90
|
+
meme_data = MemeData.new(
|
91
|
+
:meme_id => meme_id,
|
92
|
+
:fs_path => meme_fs_path,
|
93
|
+
:mime_type => meme_img.mime_type,
|
94
|
+
:size => File.size(meme_fs_path),
|
95
|
+
|
96
|
+
:source_url => norm_params[:u],
|
97
|
+
:source_fs_path => source_fs_path,
|
98
|
+
:top_text => norm_params[:tt],
|
99
|
+
:bottom_text => norm_params[:tb],
|
100
|
+
|
101
|
+
:request_count => 0,
|
102
|
+
|
103
|
+
:creator_ip => request.ip
|
104
|
+
)
|
105
|
+
|
106
|
+
meme_data.save! :safe => true
|
107
|
+
|
108
|
+
meme_data
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
54
112
|
end
|
55
113
|
|
56
114
|
get '/g' do
|
115
|
+
raise Sinatra::NotFound if params[:u].to_s.empty?
|
116
|
+
|
57
117
|
begin
|
58
|
-
|
118
|
+
meme_data = gen(params)
|
59
119
|
|
60
|
-
|
61
|
-
temp_url.path = processed_cache_path.sub('public', '')
|
62
|
-
temp_url.query = nil
|
120
|
+
meme_url = url("/#{meme_data.meme_id}")
|
63
121
|
|
64
|
-
|
65
|
-
|
122
|
+
template_query = [
|
123
|
+
[:u, meme_data.meme_id],
|
124
|
+
[:tt, meme_data.top_text],
|
125
|
+
[:tb, meme_data.bottom_text],
|
126
|
+
].map { |k,v|
|
127
|
+
"#{Rack::Utils.escape(k)}=#{Rack::Utils.escape(v)}" }.join('&')
|
66
128
|
|
67
129
|
[200, { 'Content-Type' => 'application/json' }, {
|
68
|
-
'tempUrl' =>
|
69
|
-
'permUrl' =>
|
130
|
+
'tempUrl' => meme_url,
|
131
|
+
'permUrl' => meme_url,
|
132
|
+
'templateUrl' => url("/?#{template_query}"),
|
70
133
|
}.to_json]
|
71
134
|
rescue => error
|
72
135
|
[500, { 'Content-Type' => 'text/plain' }, error.to_s]
|
73
136
|
end
|
74
137
|
end
|
75
138
|
|
139
|
+
def serve_img(meme_data)
|
140
|
+
meme_data.requested!
|
141
|
+
|
142
|
+
content_type meme_data.mime_type
|
143
|
+
|
144
|
+
FileBody.new meme_data.fs_path
|
145
|
+
end
|
146
|
+
|
76
147
|
get '/i' do
|
77
|
-
|
148
|
+
raise Sinatra::NotFound if params[:u].to_s.empty?
|
149
|
+
|
150
|
+
serve_img(gen(params))
|
151
|
+
end
|
152
|
+
|
153
|
+
get %r{^/([a-f0-9]+\.(?:gif|jpg|png))$} do
|
154
|
+
if meme_data = MemeData.find_by_meme_id(params[:captures][0])
|
155
|
+
serve_img meme_data
|
156
|
+
else
|
157
|
+
raise Sinatra::NotFound
|
158
|
+
end
|
159
|
+
end
|
78
160
|
|
79
|
-
|
161
|
+
not_found do
|
162
|
+
@root_url = url('/')
|
80
163
|
|
81
|
-
|
164
|
+
erb :'404'
|
82
165
|
end
|
83
166
|
|
84
167
|
helpers do
|
data/lib/meme_captain/version.rb
CHANGED
data/lib/meme_captain.rb
CHANGED
@@ -2,8 +2,8 @@ require 'meme_captain/caption'
|
|
2
2
|
require 'meme_captain/caption_choice'
|
3
3
|
require 'meme_captain/draw'
|
4
4
|
require 'meme_captain/file_body'
|
5
|
-
require 'meme_captain/
|
5
|
+
require 'meme_captain/image_list'
|
6
6
|
require 'meme_captain/meme'
|
7
|
-
require 'meme_captain/
|
7
|
+
require 'meme_captain/meme_data'
|
8
8
|
require 'meme_captain/server'
|
9
9
|
require 'meme_captain/version'
|
data/meme_captain.gemspec
CHANGED
@@ -15,13 +15,17 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.email = %w{matthewm@boedicker.org}
|
16
16
|
|
17
17
|
%w{
|
18
|
+
bson_ext
|
18
19
|
curb
|
19
20
|
json
|
20
21
|
mime-types
|
22
|
+
mongo
|
23
|
+
mongo_mapper
|
21
24
|
rack
|
22
25
|
rmagick
|
23
26
|
sinatra
|
24
27
|
}.each { |g| s.add_dependency g }
|
25
28
|
|
26
29
|
s.files = `git ls-files`.split("\n")
|
30
|
+
s.executables = %w{memecaptain}
|
27
31
|
end
|