meme_captain 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ img_cache/*
data/README.textile CHANGED
@@ -1,20 +1,19 @@
1
- This gem will create meme images like http://memegenerator.net/, but:
1
+ Ruby gem to create meme images (images with text added at the top and bottom).
2
2
 
3
- * will be more reliable
4
- * will have a better API
5
- * will be able to use your own images or any image from the web
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://some_image_from_the_web.jpg', 'rb') do |f|
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
@@ -0,0 +1,5 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ require 'meme_captain'
4
+
5
+ run MemeCaptain::Server
File without changes
File without changes
data/lib/meme_captain.rb CHANGED
@@ -1,40 +1,4 @@
1
- require 'RMagick'
2
-
3
- module MemeCaptain
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.1'
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: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
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-14 00:00:00 -04:00
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: rmagick
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: []