meme_captain 0.0.1 → 0.0.2
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.
- data/.gitignore +1 -0
- data/README.textile +7 -8
- data/config.ru +5 -0
- data/img_cache/processed/.gitignore +0 -0
- data/img_cache/source/.gitignore +0 -0
- data/lib/meme_captain.rb +4 -40
- data/lib/meme_captain/content_type.rb +16 -0
- data/lib/meme_captain/filesystem_cache.rb +39 -0
- data/lib/meme_captain/meme.rb +52 -0
- data/lib/meme_captain/server.rb +57 -0
- data/meme_captain.gemspec +3 -1
- data/views/index.erb +51 -0
- metadata +42 -5
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
img_cache/*
|
data/README.textile
CHANGED
@@ -1,20 +1,19 @@
|
|
1
|
-
|
1
|
+
Ruby gem to create meme images (images with text added at the top and bottom).
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
Also includes Sinatra app that exposes API over HTTP which is currently
|
4
|
+
running "http://memecaptain.com/":http://memecaptain.com/
|
5
|
+
|
6
|
+
Works with animated gifs.
|
6
7
|
|
7
8
|
<pre>
|
8
9
|
<code>
|
9
10
|
require 'meme_captain'
|
10
11
|
|
11
|
-
i = MemeCaptain.meme('luke300.jpeg', 'meme test', 'meme generator test')
|
12
|
-
i.display
|
13
|
-
|
14
12
|
require 'open-uri'
|
15
13
|
|
16
|
-
open('http://
|
14
|
+
open('http://image_from_web_or_local_file.jpg', 'rb') do |f|
|
17
15
|
i = MemeCaptain.meme(f, 'test', '1 2 3')
|
16
|
+
i.display
|
18
17
|
i.write('out.jpg')
|
19
18
|
end
|
20
19
|
</code>
|
data/config.ru
ADDED
File without changes
|
File without changes
|
data/lib/meme_captain.rb
CHANGED
@@ -1,40 +1,4 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
module_function
|
6
|
-
|
7
|
-
def meme(path_or_io, line1, line2, options={})
|
8
|
-
img = Magick::ImageList.new
|
9
|
-
if path_or_io.respond_to?(:read)
|
10
|
-
img.from_blob(path_or_io.read)
|
11
|
-
else
|
12
|
-
img.read(path_or_io)
|
13
|
-
end
|
14
|
-
|
15
|
-
options = {
|
16
|
-
:fill => 'white',
|
17
|
-
:font => 'Impact-Regular',
|
18
|
-
:gravity => Magick::CenterGravity,
|
19
|
-
:size => "#{img.columns}x#{img.rows / 4}",
|
20
|
-
:stroke => 'black',
|
21
|
-
:stroke_width => 1,
|
22
|
-
:background_color => 'none',
|
23
|
-
}.merge(options)
|
24
|
-
|
25
|
-
line1_caption = Magick::Image.read("caption:#{line1.upcase}") {
|
26
|
-
options.each { |k,v| self.send("#{k}=", v) }
|
27
|
-
}
|
28
|
-
|
29
|
-
line2_caption = Magick::Image.read("caption:#{line2.upcase}") {
|
30
|
-
options.each { |k,v| self.send("#{k}=", v) }
|
31
|
-
}
|
32
|
-
|
33
|
-
img[0].composite!(line1_caption[0], Magick::NorthGravity,
|
34
|
-
Magick::OverCompositeOp)
|
35
|
-
|
36
|
-
img[0].composite!(line2_caption[0], Magick::SouthGravity,
|
37
|
-
Magick::OverCompositeOp)
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
1
|
+
require 'meme_captain/content_type'
|
2
|
+
require 'meme_captain/meme'
|
3
|
+
require 'meme_captain/server'
|
4
|
+
require 'meme_captain/filesystem_cache'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module MemeCaptain
|
2
|
+
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# Determine content type from blob of image data.
|
6
|
+
def content_type(img_data)
|
7
|
+
if img_data[0,2].unpack('H4')[0] == 'ffd8'
|
8
|
+
'image/jpeg'
|
9
|
+
elsif img_data[0,8].unpack('H16')[0] == '89504e470d0a1a0a'
|
10
|
+
'image/png'
|
11
|
+
elsif %w{474946383961 474946383761}.include?(img_data[0,6].unpack('H12')[0])
|
12
|
+
'image/gif'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module MemeCaptain
|
2
|
+
|
3
|
+
# Cache data on filesystem.
|
4
|
+
class FilesystemCache
|
5
|
+
|
6
|
+
def initialize(root_dir); @root_dir = root_dir; end
|
7
|
+
|
8
|
+
# Get data from cache.
|
9
|
+
#
|
10
|
+
# On cache miss, if block given call block to get data from source and
|
11
|
+
# cache it. If no block given return nil on cache miss.
|
12
|
+
def get(id)
|
13
|
+
file_path = id_path(id)
|
14
|
+
|
15
|
+
if File.exist?(file_path)
|
16
|
+
open(file_path, 'rb') { |f| f.read }
|
17
|
+
else
|
18
|
+
put(id, yield) if block_given?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Put data in the cache and return it.
|
23
|
+
def put(id, data)
|
24
|
+
open(id_path(id), 'w') do |f|
|
25
|
+
f.flock(File::LOCK_EX)
|
26
|
+
f.write(data)
|
27
|
+
f.flock(File::LOCK_UN)
|
28
|
+
end
|
29
|
+
data
|
30
|
+
end
|
31
|
+
|
32
|
+
def id_path(id)
|
33
|
+
File.join(root_dir, File.expand_path(id, '/'))
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :root_dir
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
|
3
|
+
module MemeCaptain
|
4
|
+
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Create a meme image.
|
8
|
+
# Input can be an IO object or a blob of data.
|
9
|
+
def meme(input, line1, line2, options={})
|
10
|
+
img = Magick::ImageList.new
|
11
|
+
if input.respond_to?(:read)
|
12
|
+
img.from_blob(input.read)
|
13
|
+
else
|
14
|
+
img.from_blob(input)
|
15
|
+
end
|
16
|
+
|
17
|
+
options = {
|
18
|
+
:fill => 'white',
|
19
|
+
:font => 'Impact',
|
20
|
+
:gravity => Magick::CenterGravity,
|
21
|
+
:size => "#{img.columns * 1.8}x#{img.rows / 2}",
|
22
|
+
:stroke => 'black',
|
23
|
+
:stroke_width => 2,
|
24
|
+
:background_color => 'none',
|
25
|
+
}.merge(options)
|
26
|
+
|
27
|
+
line1_caption = Magick::Image.read("caption:#{line1.to_s.upcase}") {
|
28
|
+
options.each { |k,v| self.send("#{k}=", v) }
|
29
|
+
}
|
30
|
+
line1_caption[0].resize!(line1_caption[0].columns / 2,
|
31
|
+
line1_caption[0].rows / 2, Magick::LanczosFilter, 1.25)
|
32
|
+
|
33
|
+
line2_caption = Magick::Image.read("caption:#{line2.to_s.upcase}") {
|
34
|
+
options.each { |k,v| self.send("#{k}=", v) }
|
35
|
+
}
|
36
|
+
line2_caption[0].resize!(line2_caption[0].columns / 2,
|
37
|
+
line2_caption[0].rows / 2, Magick::LanczosFilter, 1.25)
|
38
|
+
|
39
|
+
img.each do |frame|
|
40
|
+
frame.composite!(line1_caption[0], Magick::NorthGravity,
|
41
|
+
Magick::OverCompositeOp)
|
42
|
+
|
43
|
+
frame.composite!(line2_caption[0], Magick::SouthGravity,
|
44
|
+
Magick::OverCompositeOp)
|
45
|
+
|
46
|
+
frame.strip!
|
47
|
+
end
|
48
|
+
img
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
require 'curb'
|
4
|
+
require 'sinatra/base'
|
5
|
+
|
6
|
+
require 'meme_captain'
|
7
|
+
|
8
|
+
module MemeCaptain
|
9
|
+
|
10
|
+
class Server < Sinatra::Base
|
11
|
+
|
12
|
+
get '/' do
|
13
|
+
@img_tag = if params[:u]
|
14
|
+
"<img src=\"#{h request.fullpath.sub(%r{^/}, '/i')}\" />"
|
15
|
+
else
|
16
|
+
''
|
17
|
+
end
|
18
|
+
|
19
|
+
@u = params[:u]
|
20
|
+
@tt= params[:tt]
|
21
|
+
@tb = params[:tb]
|
22
|
+
|
23
|
+
erb :index
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/i' do
|
27
|
+
@processed_cache ||= MemeCaptain::FilesystemCache.new(
|
28
|
+
'img_cache/processed')
|
29
|
+
@source_cache ||= MemeCaptain::FilesystemCache.new('img_cache/source')
|
30
|
+
|
31
|
+
processed_id = Digest::SHA1.hexdigest(params.sort.map(&:join).join)
|
32
|
+
processed_img_data = @processed_cache.get(processed_id) {
|
33
|
+
source_id = Digest::SHA1.hexdigest(params[:u])
|
34
|
+
source_img_data = @source_cache.get(source_id) {
|
35
|
+
Curl::Easy.perform(params[:u]).body_str
|
36
|
+
}
|
37
|
+
MemeCaptain.meme(source_img_data, params[:tt], params[:tb]).to_blob {
|
38
|
+
self.quality = 100
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
headers = {
|
43
|
+
'Content-Type' => MemeCaptain.content_type(processed_img_data),
|
44
|
+
'ETag' => "\"#{Digest::SHA1.hexdigest(processed_img_data)}\"",
|
45
|
+
}
|
46
|
+
|
47
|
+
[ 200, headers, processed_img_data ]
|
48
|
+
end
|
49
|
+
|
50
|
+
helpers do
|
51
|
+
include Rack::Utils
|
52
|
+
alias_method :h, :escape_html
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/meme_captain.gemspec
CHANGED
@@ -4,7 +4,7 @@ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = 'meme_captain'
|
7
|
-
s.version = '0.0.
|
7
|
+
s.version = '0.0.2'
|
8
8
|
s.summary = 'create meme images'
|
9
9
|
s.description = s.summary
|
10
10
|
s.homepage = 'https://github.com/mmb/meme_captain'
|
@@ -14,7 +14,9 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.required_rubygems_version = '>= 1.3.6'
|
15
15
|
|
16
16
|
%w{
|
17
|
+
curb
|
17
18
|
rmagick
|
19
|
+
sinatra
|
18
20
|
}.each { |g| s.add_dependency g }
|
19
21
|
|
20
22
|
s.files = `git ls-files`.split("\n")
|
data/views/index.erb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
|
4
|
+
<head>
|
5
|
+
<title>Meme Captain</title>
|
6
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
7
|
+
</head>
|
8
|
+
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<p><a href="/">Meme Captain</a></p>
|
12
|
+
|
13
|
+
<p>Add text to images from the internet.</p>
|
14
|
+
|
15
|
+
<%= @img_tag %>
|
16
|
+
|
17
|
+
<form action="" method="get">
|
18
|
+
|
19
|
+
<table>
|
20
|
+
|
21
|
+
<tr>
|
22
|
+
<td><label for="u" />Source image URL: </label></td>
|
23
|
+
<td><input type="text" id="u" name="u" size="64" value="<%= h @u %>"/></td>
|
24
|
+
</tr>
|
25
|
+
|
26
|
+
<tr>
|
27
|
+
<td><label for="tt" />Top text: </label></td>
|
28
|
+
<td><input type="text" id="tt" name="tt" size="64" value="<%= h @tt %>" /></td>
|
29
|
+
</tr>
|
30
|
+
|
31
|
+
<tr>
|
32
|
+
<td><label for="tb" />Bottom text: </label></td>
|
33
|
+
<td><input type="text" id="tb" name="tb" size="64" value="<%= h @tb %>" /></td>
|
34
|
+
</tr>
|
35
|
+
|
36
|
+
<tr>
|
37
|
+
<td></td>
|
38
|
+
<td><input type="submit" value="Create Image" /></td>
|
39
|
+
</tr>
|
40
|
+
|
41
|
+
</table>
|
42
|
+
|
43
|
+
</form>
|
44
|
+
|
45
|
+
<p>by Matthew M. Boedicker <a href="mailto:matthewm@boedicker.org">matthewm@boedicker.org</a></p>
|
46
|
+
|
47
|
+
<p><a href="https://github.com/mmb/meme_captain">source code</a></p>
|
48
|
+
|
49
|
+
</body>
|
50
|
+
|
51
|
+
</html>
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: meme_captain
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matthew M. Boedicker
|
@@ -15,11 +15,11 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-04-
|
18
|
+
date: 2011-04-20 00:00:00 -04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: curb
|
23
23
|
prerelease: false
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
@@ -32,6 +32,34 @@ dependencies:
|
|
32
32
|
version: "0"
|
33
33
|
type: :runtime
|
34
34
|
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rmagick
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: sinatra
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
35
63
|
description: create meme images
|
36
64
|
email:
|
37
65
|
- matthewm@boedicker.org
|
@@ -42,10 +70,19 @@ extensions: []
|
|
42
70
|
extra_rdoc_files: []
|
43
71
|
|
44
72
|
files:
|
73
|
+
- .gitignore
|
45
74
|
- COPYING
|
46
75
|
- README.textile
|
76
|
+
- config.ru
|
77
|
+
- img_cache/processed/.gitignore
|
78
|
+
- img_cache/source/.gitignore
|
47
79
|
- lib/meme_captain.rb
|
80
|
+
- lib/meme_captain/content_type.rb
|
81
|
+
- lib/meme_captain/filesystem_cache.rb
|
82
|
+
- lib/meme_captain/meme.rb
|
83
|
+
- lib/meme_captain/server.rb
|
48
84
|
- meme_captain.gemspec
|
85
|
+
- views/index.erb
|
49
86
|
has_rdoc: true
|
50
87
|
homepage: https://github.com/mmb/meme_captain
|
51
88
|
licenses: []
|