├── .gitignore ├── public ├── robots.txt ├── images │ ├── giffer.gif │ ├── icon-pause.png │ ├── icon-play.png │ ├── alert-overlay.png │ ├── background-id.png │ ├── icon-zoom-minus.png │ └── icon-zoom-plus.png ├── js │ ├── giffer.js │ └── jquery-1.4.1.min.js └── style.css ├── environment.rb ├── config.ru ├── application.rb ├── views ├── index.haml └── style.sass └── README.textile /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /public/images/giffer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/HEAD/public/images/giffer.gif -------------------------------------------------------------------------------- /public/images/icon-pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/HEAD/public/images/icon-pause.png -------------------------------------------------------------------------------- /public/images/icon-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/HEAD/public/images/icon-play.png -------------------------------------------------------------------------------- /public/images/alert-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/HEAD/public/images/alert-overlay.png -------------------------------------------------------------------------------- /public/images/background-id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/HEAD/public/images/background-id.png -------------------------------------------------------------------------------- /public/images/icon-zoom-minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/HEAD/public/images/icon-zoom-minus.png -------------------------------------------------------------------------------- /public/images/icon-zoom-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/HEAD/public/images/icon-zoom-plus.png -------------------------------------------------------------------------------- /environment.rb: -------------------------------------------------------------------------------- 1 | configure do 2 | set :views, "#{File.dirname(__FILE__)}/views" 3 | LOGGER = Logger.new("log/sinatra.log") 4 | end 5 | 6 | helpers do 7 | def logger 8 | LOGGER 9 | end 10 | 11 | def base_dir 12 | "public/media" 13 | end 14 | 15 | def partial(page, options={}) 16 | haml page, options.merge!(:layout => false) 17 | end 18 | end -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'logger' 3 | require 'rack' 4 | require 'sinatra' 5 | require 'pow' 6 | require 'haml' 7 | require 'base64' 8 | require 'sass/plugin/rack' 9 | require 'environment' 10 | require 'application' 11 | 12 | use Sass::Plugin::Rack 13 | Sass::Plugin.options[:css_location] = "./public" 14 | Sass::Plugin.options[:template_location] = "./views" 15 | 16 | log = File.new("log/sinatra.log", "a+") 17 | STDOUT.reopen(log) 18 | STDERR.reopen(log) 19 | 20 | run Sinatra::Application -------------------------------------------------------------------------------- /application.rb: -------------------------------------------------------------------------------- 1 | get '/' do 2 | haml :index 3 | end 4 | 5 | post '/gifit' do 6 | pwd = File.join(Dir.pwd, 'tmp') 7 | params[:upload].each_with_index do |upload, i| 8 | filetype = upload[1]['content_type'].split('/')[1].downcase 9 | File.open(File.join(pwd, "#{i}.#{filetype}"), 'w') {|f| f.write(Base64.decode64(upload[1]['data'])) } 10 | end 11 | images = Dir.glob(File.join(pwd, "*.{jpg,jpeg,png}")) 12 | images.each do |image| 13 | filename = File.basename(image).split('.')[0] 14 | output = "#{filename}.gif" 15 | `convert #{File.expand_path(image)} #{File.join(pwd, output)}` 16 | `rm #{File.expand_path(image)}` 17 | end 18 | gifname = "#{Time.now.to_i}.gif" 19 | `gifsicle --delay=10 --loop #{pwd}/*.gif > #{File.join(Dir.pwd, "public", "images", "gifs", gifname)}` 20 | `rm #{pwd}/*` 21 | "/images/gifs/#{gifname}" 22 | end -------------------------------------------------------------------------------- /views/index.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %link{:rel => "stylesheet", :type => "text/css", :href => "style.css" } 5 | %script{ :src => "js/jquery-1.4.1.min.js", :type => "text/javascript" } 6 | %script{ :src => "js/jquery-ui.js", :type => "text/javascript" } 7 | %script{ :src => "js/giffer.js", :type => "text/javascript" } 8 | %title 9 | KATHY LEE GIFFER 10 | %body 11 | #controller 12 | .cell.one-third.playback 13 | .cell.two-thirds.wayfinding 14 | KATHY LEE GIFFER 15 | .cell.one-third.meta 16 | .button.zoom-minus 17 | .button.zoom-plus 18 | #drop 19 | %p.instructions{:style => 'text-align: center; color: white'} 20 | DRAG DROP SOME PHOTOZZZZ HERE
21 | AND I WILL GIFFERIZE THEM FOR YOU 22 | %center 23 | .loader{:style => "display: none"} 24 | %img{:src => "/images/giffer.gif"} 25 | %h1{:style => "color: white"} 26 | GIFFERING IN PROGRESS - PLEASE HOLD 27 | .sweetgif{:style => "margin: 10px"} 28 | %button{:class => 'blue large awesomebutton gifit', :style => "display: none"} 29 | KATHY LEE GIF IT! 30 | 31 | #grid 32 | %ul 33 | 34 | %li.template 35 | .cell 36 | %img.image 37 | .id 38 | .digit 39 | 40 | %p.info 41 | 2010 Max Ogden. 42 | %a{:href => "http://github.com/maxogden/kathyleegiffer"} 43 | Get the source on Github -------------------------------------------------------------------------------- /views/style.sass: -------------------------------------------------------------------------------- 1 | @import "/blueprint/reset.css" 2 | @import "/blueprint/typography.css" 3 | 4 | body 5 | margin: 0 6 | background: #252525 7 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif 8 | 9 | #controller 10 | text-align: center 11 | width: 100% 12 | height: 100px 13 | color: #ddd 14 | 15 | .cell 16 | top: 40px 17 | position: relative 18 | float: left 19 | 20 | .one-third 21 | width: 20% 22 | 23 | .two-thirds 24 | width: 60% 25 | 26 | .button 27 | opacity: .3 28 | display: inline-block 29 | width: 24px 30 | height: 24px 31 | margin-left: 20px 32 | 33 | &:hover 34 | opacity: .75 35 | 36 | .play 37 | background: url(images/icon-play.png) no-repeat 38 | 39 | .pause 40 | background: url(images/icon-pause.png) no-repeat 41 | 42 | .zoom-plus 43 | background: url(images/icon-zoom-plus.png) no-repeat 44 | 45 | .zoom-minus 46 | background: url(images/icon-zoom-minus.png) no-repeat 47 | 48 | .wayfinding 49 | font-size: 18px 50 | 51 | #grid 52 | clear: both 53 | position: absolute 54 | 55 | ul 56 | padding: 0 57 | margin: 0 58 | 59 | li 60 | float: left 61 | position: relative 62 | list-style: none 63 | 64 | .cell 65 | float: left 66 | display: inline-block 67 | width: 400px 68 | height: 400px 69 | margin: 20px 70 | overflow: none 71 | -webkit-box-shadow: 0px 0px 5px #202020 72 | 73 | .image 74 | width: 100% 75 | height: 100% 76 | display: inline 77 | 78 | .id 79 | position: absolute 80 | top: 20px 81 | background: url(images/background-id.png) no-repeat 82 | width: 40px 83 | height: 40px 84 | color: #fdfdfd 85 | font-size: 10px 86 | text-align: center 87 | opacity: .75 88 | 89 | .digit 90 | -webkit-transform: rotate(-45deg) 91 | position: absolute 92 | padding: 7px 93 | opacity: 1 94 | font-weight: bold -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Kathy Lee Giffer 2 | 3 | HTML5 drag and drop animated GIF creation! Uses Gifsicle and Ruby to create the GIFs on the server 4 | 5 | !http://i.imgur.com/o6KbD.png! 6 | 7 | h2. Requirements 8 | * rubygems: sinatra, pow, haml and their dependencies 9 | * @convert@ (installed with the ImageMagick package) 10 | * @gifsicle@ ("http://www.lcdf.org/gifsicle/":http://www.lcdf.org/gifsicle/) 11 | * modern web browser with the FileReader API (tested against recent firefox, chrome, and safari builds) 12 | 13 | h2. Installation (brew for OS X, for Ubuntu replace brew with apt-get) 14 | 15 |
16 |   
17 |     brew install imagemagick
18 |     brew install gifsicle
19 |     gem install sinatra pow haml unicorn
20 |     mkdir tmp
21 |     mkdir log
22 |     mkdir public/images/gifs
23 |     unicorn config.ru
24 |   
25 | 
26 | 27 | h1. Contributors 28 | 29 | codez by Max Ogden 30 | perdy styles by Effalo Corp 31 | 32 | h1. License 33 | 34 | (The MIT License) 35 | 36 | Copyright (c) 2010 Max Ogden 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining 39 | a copy of this software and associated documentation files (the 40 | 'Software'), to deal in the Software without restriction, including 41 | without limitation the rights to use, copy, modify, merge, publish, 42 | distribute, sublicense, and/or sell copies of the Software, and to 43 | permit persons to whom the Software is furnished to do so, subject to 44 | the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be 47 | included in all copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 50 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 51 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 52 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 53 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 54 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 55 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 56 | -------------------------------------------------------------------------------- /public/js/giffer.js: -------------------------------------------------------------------------------- 1 | var attachments = []; 2 | 3 | $(document).ready(function(){ 4 | 5 | if (!FileReader) { 6 | $('#drop').html('

You need to upgrade to a browser that implements the HTML5 FileReader API in order to use this

'); 7 | } 8 | 9 | function updateListCounts() { 10 | $("#grid li").each(function() { 11 | var li = this; 12 | var count = $('li').index($(li)) + 1; 13 | $(li).find('.digit').text(count); 14 | }); 15 | }; 16 | 17 | function imagesForUpload() { 18 | var images = $("#grid ul").sortable('toArray'); 19 | return $.map(images, function(image){ 20 | var item = $("#" + image); 21 | return { 22 | "content_type" : item.data('content_type'), 23 | "data" : item.data('data') 24 | }; 25 | }); 26 | } 27 | 28 | $('.gifit').live('click', function(){ 29 | $('.sweetgif').html(''); 30 | $(this).hide(); 31 | $('.loader').show(); 32 | $.ajax({ 33 | type: 'POST', 34 | url: '/gifit', 35 | data: {upload: imagesForUpload()}, 36 | success: function(data) { 37 | $('.loader').hide(); 38 | $('.sweetgif').html(''); 39 | $('.gifit').show(); 40 | getDraggy(); 41 | } 42 | }); 43 | }) 44 | 45 | $('.zoom-minus').click(function(){ 46 | $('#grid .cell') 47 | .css('width', $('#grid .cell').width() - 50 + 'px') 48 | .css('height', $('#grid .cell').height() - 50 + 'px'); 49 | }); 50 | 51 | $('.zoom-plus').click(function(){ 52 | $('#grid .cell') 53 | .css('width', $('#grid .cell').width() + 50 + 'px') 54 | .css('height', $('#grid .cell').height() + 50 + 'px'); 55 | }); 56 | 57 | function addPhoto(file) { 58 | var image = file.result; 59 | var newImg = $('.template:first').clone(); 60 | newImg.find('.image').attr('src', image).removeClass('template'); 61 | newImg.data('data', file.result.split(",")[1]); 62 | newImg.data('content_type', file.file.type); 63 | newImg.attr('id', (((1+Math.random())*0x10000)|0).toString(16).substring(1)); 64 | $("#grid ul").append(newImg); 65 | newImg.show(); 66 | } 67 | 68 | var getBinaryDataReader, file; 69 | var SPROINGG = (function() { 70 | 71 | function hasStupidChromeBug() { 72 | return typeof(FileReader.prototype.addEventListener) !== "function"; 73 | }; 74 | 75 | function isImage(type) { 76 | return type === "image/png" || type === "image/jpeg"; 77 | }; 78 | 79 | function renderAttachments() { 80 | $("#grid ul").html(''); 81 | 82 | var i, tmp, html = "