Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

adding option for animated gif capturing (OSX/Mac only via VideoSnap)

Using VideoSnap and ffmpeg (and convert) it is possible to generate and annotate a
short animated gif for your lolcommit.  Use -a 3 or --animate=3 option in your
.git/hooks/post-commit call to capture and animate (for 3 second gif) The longer
the time, the larger the animated gif file-size. Some extra points to note;

* Currently mac only
* If -a 0 (or not present) lolcommits will operate as normal
* Uses VideoSnap https://github.com/matthutchinson/videosnap

I've tested with a few of the plugins and it appears to work OK! Also updated
README.md with instructions on how to enable and configure and an example
  • Loading branch information...
commit 105327c879c4857e1d94196b10ec7e1a7f8c5e2f 1 parent 805f94d
@matthutchinson matthutchinson authored
View
3  .travis.yml
@@ -8,7 +8,8 @@ rvm:
before_install:
# imagemagick is installed by default on normal travis image now
- sudo apt-get update -qq
- - sudo apt-get install -qq -y mplayer
+ - sudo apt-get install -qq -y mplayer
+ - sudo apt-get install ffmpeg
# - sudo apt-get install -qq -y imagemagick libmagickwand-dev
# matrix:
View
9 CHANGELOG
@@ -1,8 +1,13 @@
-0.5.0 (in development)
+0.5.0 (9 September 2013)
* TODO: figure out problems with GUI clients
* better handling of LOLCOMMITS_DELAY (thx @leewillis77, #125)
* LOLCOMMITS_DEVICE support on Linux (thx @EbenezerEdelman, #139)
* better handling of repository names (thx @drocamor and @andromedado, #145 and #146)
+ * added new LOLCOMMITS_ANIMATE (or `--animate`) option (Mac/OSX only) (#114, #108)
+ - defaults to a 320x240 sized animated gif
+ - new vendored binary videosnap - https://github.com/matthutchinson/videosnap
+ - feature requires ffmpeg
+ - README updated with details and an example
0.4.6 (12 August 2013)
* Fix for incorrect permissioning in gem issue (see #112)
@@ -26,7 +31,7 @@
0.4.1 (17 February 2013)
* add lolsrv plugin (thx @sebastianmarr!, #82)
- * enable feature to change font (thx @fukayatsu!, #89)
+ * enable feature to change font (thx @fukayatsu!, #89)
* correct activesupport gem name in bundle (thx @djbender!, #90)
* graceful detection of imagemagick not being installed (#87)
* restructure logging slightly to use Methadone::CLILogging in most places
View
14 README.md
@@ -57,20 +57,30 @@ environment variables.
* Set delay persistently (for slow to warmup webcams) - set
`LOLCOMMITS_DELAY` var to time in seconds.
* Set font file location - set `LOLCOMMITS_FONT` environment variable.
+ * Animated gifs - set `LOLCOMMITS_ANIMATE=3` (currently Mac/OSX only and requires `ffmpeg`).
* Fork lolcommits runner - set `LOLCOMMITS_FORK` environment variable
(causes capturing command to fork to a new process, speedily returning you to your terminal).
For the full list, see the [configuration variables](https://github.com/mroth/lolcommits/wiki/Configuration-Variables).
-### Plugins
+### Animated Gif Capturing
+Animated gifs (Mac/OSX only) can take a while to generate (depending on the number of seconds you capture and the capabilities of your machine). `ffmpeg` is required and can be installed with brew like so;
+
+ brew install ffmpeg
+
+To enable, just set the `LOLCOMMITS_ANIMATE` environment variable with the number of seconds to capture.
+And like regular image captures you can use the env variables `LOLCOMMITS_DEVICE` and `LOLCOMMITS_DELAY` to change the capture device or delay time (seconds) before capturing.
+If you find capturing an animated gif takes too long, try setting the `LOLCOMMITS_FORK=true` env variable.
+![Example animated lolcommit gif](http://cdn2.usa.bugleblogs.com/blogs/000/000/003/de0eb9aa695.gif "Example animated lolcommit gif")
+
+### Plugins
There are a growing amount of plugins for lolcommits to enable things like Twitter upload, translating your commit messages to lolspeak, etc. Check them out on the [plugins wiki page](https://github.com/mroth/lolcommits/wiki/Plugins).
## Troubles?
Started a [FAQ](https://github.com/mroth/lolcommits/wiki/FAQ).
## Timelapse?
-
To watch your face as it decays while you program, you can create a quick mpeg of all your lolcommits snapshots (if you have `imagemagick` and `ffmpeg` installed):
convert `find . -type f -name "*.jpg" -print0 | xargs -0 ls -tlr | awk '{print $9}'` timelapse.mpeg
View
57 bin/lolcommits
@@ -26,9 +26,11 @@ end
def die_on_fatal_conditions!
if Configuration.is_mac?
- unless File.executable? File.join(Configuration::LOLCOMMITS_ROOT, "vendor", "ext", "imagesnap", "imagesnap")
- fatal "Couldn't properly execute imagesnap for some reason, please file a bug?!"
- exit 1
+ %w(imagesnap videosnap).each do |executable|
+ unless File.executable? File.join(Configuration::LOLCOMMITS_ROOT, "vendor", "ext", executable, executable)
+ fatal "Couldn't properly execute #{executable} for some reason, please file a bug?!"
+ exit 1
+ end
end
elsif Configuration.is_linux?
if not command?('mplayer')
@@ -40,6 +42,10 @@ def die_on_fatal_conditions!
fatal "Couldn't properly read Impact font from gem package, please file a bug?!"
exit 1
end
+ if !Configuration.valid_ffmpeg_installed? && capture_animate
+ fatal "FATAL: ffmpeg does not appear to be properly installed!"
+ exit 1
+ end
unless Configuration.valid_imagemagick_installed?
fatal "FATAL: ImageMagick does not appear to be properly installed!"
exit 1
@@ -126,32 +132,42 @@ def configuration
end
end
+def capture_animate
+ if Configuration.can_animate?
+ Choice.choices[:animate] || ENV['LOLCOMMITS_ANIMATE'] || nil
+ end
+end
+
#
# IF --CAPTURE, DO CAPTURE
#
def do_capture
- capture_delay = Choice.choices[:delay] || ENV['LOLCOMMITS_DELAY'] || 0
+ capture_delay = Choice.choices[:delay] || ENV['LOLCOMMITS_DELAY'] || 0
capture_device = Choice.choices[:device] || ENV['LOLCOMMITS_DEVICE'] || nil
- capture_font = Choice.choices[:font] || ENV['LOLCOMMITS_FONT'] || nil
+ capture_font = Choice.choices[:font] || ENV['LOLCOMMITS_FONT'] || nil
fork_me? do
if Choice.choices[:test]
info "*** Capturing in test mode."
- runner = Lolcommits::Runner.new(:capture_delay => capture_delay,
- :capture_device => capture_device,
- :message => Choice.choices[:msg],
- :sha => Choice.choices[:sha],
- :config => configuration,
- :font => capture_font
- )
+ runner = Lolcommits::Runner.new(
+ :capture_delay => capture_delay,
+ :capture_device => capture_device,
+ :capture_animate => capture_animate,
+ :message => Choice.choices[:msg],
+ :sha => Choice.choices[:sha],
+ :config => configuration,
+ :font => capture_font
+ )
runner.run
Launchy.open(runner.main_image)
else
- runner = Lolcommits::Runner.new(:capture_delay => capture_delay,
- :capture_device => capture_device,
- :config => configuration,
- :font => capture_font
+ runner = Lolcommits::Runner.new(
+ :capture_delay => capture_delay,
+ :capture_device => capture_device,
+ :capture_animate => capture_animate,
+ :config => configuration,
+ :font => capture_font
)
runner.run
end
@@ -331,6 +347,15 @@ Choice.options do
desc "generate animated gif"
end
+ if Configuration.can_animate?
+ option :animate do
+ long "--animate=SECONDS"
+ short "-a"
+ cast Integer
+ desc "duration for capturing an animated gif"
+ end
+ end
+
option :fork do
long "--fork"
desc "fork the lolcommits runner to the background"
View
2  config/cucumber.yml
@@ -0,0 +1,2 @@
+---
+default: <%= (RUBY_PLATFORM =~ /darwin/) ? 'features' : '--tags ~@mac-only' %>
View
31 features/lolcommits.feature
@@ -5,6 +5,18 @@ Feature: Basic UI functionality
Then the exit status should be 0
And the banner should be present
+ Scenario: Help should show the animate option on a Mac platform
+ Given I am using a "Mac" platform
+ When I get help for "lolcommits"
+ Then the following options should be documented:
+ |--animate|which is optional|
+ |-a |which is optional|
+
+ Scenario: Help should not show the animate option on a Linux plaftorm
+ Given I am using a "Linux" platform
+ When I get help for "lolcommits"
+ Then the output should not match /\-a\, \-\-animate\=SECONDS/
+
Scenario: Enable in a naked git repository
Given a git repository named "loltest" with no "post-commit" hook
When I cd to "loltest"
@@ -170,3 +182,22 @@ Feature: Basic UI functionality
And a loldir named "randomgitrepo" with 2 lolimages
When I successfully run `lolcommits -g today`
And there should be exactly 1 gif in "../.lolcommits/randomgitrepo/archive"
+
+ @mac-only
+ Scenario: should generate an animated gif on the Mac platform
+ Given I am in a git repository named "testanimatedcapture"
+ And I do a git commit
+ And I am using a "Mac" platform
+ When I run `lolcommits --capture --animate=1`
+ Then the output should contain "*** Preserving this moment in history."
+ And a directory named "../.lolcommits/testanimatedcapture" should exist
+ And a file named "../.lolcommits/testanimatedcapture/tmp_video.mov" should not exist
+ And a directory named "../.lolcommits/testanimatedcapture/tmp_frames" should not exist
+ And there should be exactly 1 gif in "../.lolcommits/testanimatedcapture"
+
+ @fake-no-ffmpeg
+ Scenario: gracefully fail when ffmpeg is not installed and animate option is used
+ Given I am using a "Mac" platform
+ When I run `lolcommits --animate=3`
+ Then the output should contain "ffmpeg does not appear to be properly installed"
+ And the exit status should be 1
View
4 features/step_definitions/lolcommits_steps.rb
@@ -102,6 +102,10 @@
assert_equal n.to_i, `git shortlog | grep -E '^[ ]+\w+' | wc -l`.chomp.to_i
end
+Given /^I am using a "(.*?)" platform$/ do |platform_name|
+ ENV['LOLCOMMITS_FAKEPLATFORM'] = platform_name
+end
+
When /^I wait for the child process to exit in "(.*?)"$/ do |repo_name|
while File.exist?("tmp/aruba/.lolcommits/#{repo_name}/lolcommits.pid")
sleep 0.1
View
50 features/support/env.rb
@@ -5,11 +5,14 @@
include Test::Unit::Assertions
require 'faker'
require 'lolcommits/configuration'
+require File.join(File.expand_path(File.dirname(__FILE__)), 'path_helpers')
include Lolcommits
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
+World(PathHelpers)
+
Before do
# Using "announce" causes massive warnings on 1.9.2
@puts = true
@@ -36,10 +39,11 @@
# ENV['LOLCOMMITS_DIR'] = @original_loldir
ENV['HOME'] = @original_home
ENV['LAUNCHY_DRY_RUN'] = nil
+ ENV['LOLCOMMITS_FAKEPLATFORM'] = nil
end
Before('@fake-interactive-rebase') do
- # in order to fake an interactive rebase,
+ # in order to fake an interactive rebase,
# we replace the editor with a script that simply squashes a few random commits
@original_git_editor = ENV['GIT_EDITOR']
# ENV['GIT_EDITOR'] = "sed -i -e 'n;s/pick/squash/g'" #every other commit
@@ -57,38 +61,18 @@
# adjust the path so tests dont see a global imagemagick install
Before('@fake-no-imagemagick') do
-
- # make a new subdir that still contains git and mplayer
- tmpbindir = File.expand_path(File.join @dirs, "bin")
- FileUtils.mkdir_p tmpbindir
- ["git","mplayer"].each do |cmd|
- whichcmd = Lolcommits::Configuration.command_which(cmd)
- unless whichcmd.nil?
- FileUtils.ln_s whichcmd, File.join(tmpbindir, File.basename(whichcmd))
- end
- end
-
- # use a modified version of Configuration::command_which to detect where IM is installed
- # and remove that from the path
- cmd = 'mogrify'
- exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
- newpaths = ENV['PATH'].split(File::PATH_SEPARATOR).reject do |path|
- found_cmd = false
- exts.each { |ext|
- exe = "#{path}/#{cmd}#{ext}"
- found_cmd = true if File.executable? exe
- }
- found_cmd
- end
-
- # add the temporary directory with git in it back into the path
- newpaths << tmpbindir
-
- @original_path = ENV['PATH']
- ENV['PATH'] = newpaths.join(File::PATH_SEPARATOR)
- # puts ENV['PATH'] dont need to announce this for debug anymore!
+ reject_paths_with_cmd('mogrify')
end
After('@fake-no-imagemagick') do
- ENV['PATH'] = @original_path
-end
+ reset_path
+end
+
+# adjust the path so tests dont see a global ffmpeg install
+Before('@fake-no-ffmpeg') do
+ reject_paths_with_cmd('ffmpeg')
+end
+
+After('@fake-no-ffmpeg') do
+ reset_path
+end
View
39 features/support/path_helpers.rb
@@ -0,0 +1,39 @@
+module PathHelpers
+
+ def reject_paths_with_cmd(cmd)
+ @original_path = ENV['PATH']
+ # make a new subdir that still contains cmds
+ tmpbindir = File.expand_path(File.join @dirs, "bin")
+ FileUtils.mkdir_p tmpbindir
+
+ preseve_cmds_in_path(['git', 'mplayer'], tmpbindir)
+
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
+ newpaths = ENV['PATH'].split(File::PATH_SEPARATOR).reject do |path|
+ found_cmd = false
+ exts.each { |ext|
+ exe = "#{path}/#{cmd}#{ext}"
+ found_cmd = true if File.executable? exe
+ }
+ found_cmd
+ end
+
+ # add the temporary directory with git in it back into the path
+ newpaths << tmpbindir
+ ENV['PATH'] = newpaths.join(File::PATH_SEPARATOR)
+ end
+
+ def preseve_cmds_in_path(cmds, tmpbindir)
+ cmds.each do |cmd|
+ whichcmd = Lolcommits::Configuration.command_which(cmd)
+ unless whichcmd.nil?
+ FileUtils.ln_s whichcmd, File.join(tmpbindir, File.basename(whichcmd))
+ end
+ end
+ end
+
+ def reset_path
+ ENV['PATH'] = @original_path
+ end
+
+end
View
1  lib/lolcommits.rb
@@ -15,6 +15,7 @@
require 'lolcommits/configuration'
require 'lolcommits/capturer'
require 'lolcommits/capture_mac'
+require 'lolcommits/capture_mac_animated'
require 'lolcommits/capture_linux'
require 'lolcommits/capture_windows'
require 'lolcommits/capture_fake'
View
40 lib/lolcommits/capture_mac_animated.rb
@@ -0,0 +1,40 @@
+module Lolcommits
+ class CaptureMacAnimated < Capturer
+
+ def capture
+ # make a fresh frames directory
+ FileUtils.rm_rf(frames_location)
+ FileUtils.mkdir_p(frames_location)
+
+ # capture the raw video with videosnap
+ system_call "#{videosnap_bin} -s 240 #{capture_device_string}#{capture_delay_string}-t #{animated_duration} --no-audio #{video_location} > /dev/null"
+ if File.exists?(video_location)
+ # convert raw video to png frames with ffmpeg
+ system_call "ffmpeg -v quiet -i #{video_location} -t #{animated_duration} #{frames_location}/%09d.png"
+ # create the looping animated gif from frames (picks every 2nd frame with seq)
+ seq_command = "seq -f #{frames_location}/%09g.png 1 2 #{Dir["#{frames_location}/*"].length}"
+ # delay of 12 between every other frame, 24fps
+ system_call "convert -layers OptimizeTransparency -delay 12 -loop 0 `#{seq_command}` -coalesce #{snapshot_location}"
+ end
+ end
+
+ private
+ def system_call(call_str)
+ debug "Capturer: making system call for \n #{call_str}"
+ system(call_str)
+ end
+
+ def videosnap_bin
+ File.join(Configuration::LOLCOMMITS_ROOT, 'vendor', 'ext', 'videosnap', 'videosnap')
+ end
+
+ def capture_device_string
+ "-d '#{capture_device}' " if capture_device
+ end
+
+ def capture_delay_string
+ "-w '#{capture_delay}' " if capture_delay.to_i > 0
+ end
+
+ end
+end
View
3  lib/lolcommits/capturer.rb
@@ -1,7 +1,8 @@
module Lolcommits
class Capturer
include Methadone::CLILogging
- attr_accessor :capture_device, :capture_delay, :snapshot_location, :font
+ attr_accessor :capture_device, :capture_delay, :snapshot_location, :font,
+ :video_location, :frames_location, :animated_duration
def initialize(attributes = Hash.new)
attributes.each do |attr, val|
View
32 lib/lolcommits/configuration.rb
@@ -11,7 +11,9 @@ def initialize(attributes={})
end
def self.platform
- if is_fakecapture?
+ if is_fakeplatform?
+ ENV['LOLCOMMITS_FAKEPLATFORM']
+ elsif is_fakecapture?
'Fake'
elsif is_mac?
'Mac'
@@ -74,12 +76,20 @@ def images_today
images.select { |f| Date.parse(File.mtime(f).to_s) === Date.today }
end
- def raw_image
- File.join self.loldir, "tmp_snapshot.jpg"
+ def raw_image(image_file_type = 'jpg')
+ File.join self.loldir, "tmp_snapshot.#{image_file_type}"
end
- def main_image(commit_sha)
- File.join self.loldir, "#{commit_sha}.jpg"
+ def main_image(commit_sha, image_file_type = 'jpg')
+ File.join self.loldir, "#{commit_sha}.#{image_file_type}"
+ end
+
+ def video_loc
+ File.join(self.loldir, 'tmp_video.mov')
+ end
+
+ def frames_loc
+ File.join(self.loldir, 'tmp_frames')
end
def puts_plugins
@@ -145,6 +155,10 @@ def self.is_fakecapture?
(ENV['LOLCOMMITS_FAKECAPTURE'] == '1' || false)
end
+ def self.is_fakeplatform?
+ ENV['LOLCOMMITS_FAKEPLATFORM']
+ end
+
def self.valid_imagemagick_installed?
return false unless self.command_which('identify')
return false unless self.command_which('mogrify')
@@ -153,10 +167,18 @@ def self.valid_imagemagick_installed?
MiniMagick::valid_version_installed?
end
+ def self.valid_ffmpeg_installed?
+ self.command_which('ffmpeg')
+ end
+
def self.git_config_color_always?
`git config color.ui`.chomp =~ /always/
end
+ def self.can_animate?
+ platform == 'Mac'
+ end
+
# Cross-platform way of finding an executable in the $PATH.
# idea taken from http://bit.ly/qDaTbY
#
View
4 lib/lolcommits/plugins/loltext.rb
@@ -25,7 +25,7 @@ def mm_run
c.fill 'white'
c.stroke 'black'
c.strokewidth '2'
- c.pointsize '48'
+ c.pointsize(self.runner.animate? ? '24' : '48')
c.interline_spacing '-9'
c.font font_location
c.annotate '0', clean_msg(self.runner.message)
@@ -36,7 +36,7 @@ def mm_run
c.fill 'white'
c.stroke 'black'
c.strokewidth '2'
- c.pointsize '32'
+ c.pointsize(self.runner.animate? ? '21' : '32')
c.font font_location
c.annotate '0', self.runner.sha
end
View
30 lib/lolcommits/runner.rb
@@ -4,7 +4,7 @@ module Lolcommits
class Runner
attr_accessor :capture_delay, :capture_device, :message, :sha,
:snapshot_loc, :main_image, :repo, :config, :repo_internal_path,
- :font
+ :font, :capture_animate
include Methadone::CLILogging
include ActiveSupport::Callbacks
@@ -39,18 +39,34 @@ def run
run_callbacks :run do
puts "*** Preserving this moment in history."
- self.snapshot_loc = self.config.raw_image
- self.main_image = self.config.main_image(self.sha)
- capturer = "Lolcommits::Capture#{Configuration.platform}".constantize.new(
+ self.snapshot_loc = self.config.raw_image(image_file_type)
+ self.main_image = self.config.main_image(self.sha, image_file_type)
+ capturer = capturer_class.new(
:capture_device => self.capture_device,
:capture_delay => self.capture_delay,
:snapshot_location => self.snapshot_loc,
- :font => self.font
+ :font => self.font,
+ :video_location => self.config.video_loc,
+ :frames_location => self.config.frames_loc,
+ :animated_duration => self.capture_animate
)
capturer.capture
resize_snapshot!
end
end
+
+ def animate?
+ capture_animate && (capture_animate > 0)
+ end
+
+ private
+ def capturer_class
+ "Lolcommits::Capture#{Configuration.platform}#{animate? ? 'Animated' : nil}".constantize
+ end
+
+ def image_file_type
+ animate? ? 'gif' : 'jpg'
+ end
end
protected
@@ -84,8 +100,10 @@ def resize_snapshot!
def cleanup!
debug "Runner: running cleanup"
- #clean up the captured image
+ # clean up the captured image and any other raw assets
FileUtils.rm(self.snapshot_loc)
+ FileUtils.rm_f(self.config.video_loc)
+ FileUtils.rm_rf(self.config.frames_loc)
end
# register a method called "execute_lolcommits_#{plugin_name}"
View
2  lib/lolcommits/version.rb
@@ -1,3 +1,3 @@
module Lolcommits
- VERSION = "0.4.6"
+ VERSION = "0.5.0"
end
View
3  test/test_lolcommits.rb
@@ -33,10 +33,13 @@ def test_tranzlate
def test_permissions
impact_perms = File.lstat(File.join(Configuration::LOLCOMMITS_ROOT, "vendor", "fonts", "Impact.ttf")).mode & 0777
imagesnap_perms = File.lstat(File.join(Configuration::LOLCOMMITS_ROOT, "vendor", "ext", "imagesnap", "imagesnap")).mode & 0777
+ videosnap_perms = File.lstat(File.join(Configuration::LOLCOMMITS_ROOT, "vendor", "ext", "videosnap", "videosnap")).mode & 0777
assert impact_perms == 0644 || impact_perms == 0664,
"expected perms of 644/664 but instead got #{sprintf '%o', impact_perms}"
assert imagesnap_perms == 0755 || imagesnap_perms == 0775,
"expected perms of 755/775 but instead got #{sprintf '%o', imagesnap_perms}"
+ assert videosnap_perms == 0755 || videosnap_perms == 0775,
+ "expected perms of 755/775 but instead got #{sprintf '%o', videosnap_perms}"
end
# Hmm.. webcam capture breaks travis-ci tests
View
BIN  vendor/ext/videosnap/videosnap
Binary file not shown
Please sign in to comment.
Something went wrong with that request. Please try again.