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 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: []