screengif 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/DEVNOTES.md +54 -0
- data/Dockerfile +28 -0
- data/Gemfile +3 -0
- data/Makefile +20 -0
- data/README.md +111 -0
- data/Vagrantfile +27 -0
- data/bin/screengif +10 -0
- data/demo.gif +0 -0
- data/demo.mov +0 -0
- data/lib/screengif.rb +126 -0
- data/lib/screengif/draw_progressbar.rb +49 -0
- data/lib/screengif/options.rb +113 -0
- data/lib/screengif/util.rb +51 -0
- data/screengif.gemspec +21 -0
- metadata +93 -0
data/.gitignore
ADDED
data/DEVNOTES.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
screengif dev notes
|
2
|
+
===================
|
3
|
+
|
4
|
+
## Building the gem
|
5
|
+
|
6
|
+
See [vagrant-vbox-manage/DEVNOTES.md](https://github.com/dergachev/vagrant-vbox-snapshot/blob/master/DEVNOTES.md)
|
7
|
+
|
8
|
+
```
|
9
|
+
gem build screengif.gemspec
|
10
|
+
gem install screengif-0.0.1.gem
|
11
|
+
screengif -i=demo.mov -o demo5.gif
|
12
|
+
```
|
13
|
+
|
14
|
+
### Related solutions
|
15
|
+
|
16
|
+
* http://freezeframe.chrisantonellis.com/ (js library to freeze/play gifs)
|
17
|
+
* http://askubuntu.com/questions/107726/how-to-create-animated-gif-images-of-a-screencast
|
18
|
+
|
19
|
+
### RMagick
|
20
|
+
|
21
|
+
* http://railscasts.com/episodes/374-image-manipulation?view=asciicast
|
22
|
+
* http://ruby.bastardsbook.com/chapters/image-manipulation/
|
23
|
+
* https://github.com/markandrus/imgswiss/blob/master/pipeline.rb (ffmpeg -> imagemagick via rmagick -> gif)
|
24
|
+
|
25
|
+
* http://www.imagemagick.org/RMagick/doc/info.html#format
|
26
|
+
* http://www.imagemagick.org/RMagick/doc/draw.html#rectangle
|
27
|
+
* http://www.imagemagick.org/RMagick/doc/constants.html#GravityType
|
28
|
+
* http://www.simplesystems.org/RMagick/doc/optequiv.html
|
29
|
+
* http://www.imagemagick.org/RMagick/doc/ilist.html
|
30
|
+
* http://www.imagemagick.org/RMagick/doc/clip_path.rb.html (define_clip_path example)
|
31
|
+
|
32
|
+
### Imagemagick
|
33
|
+
|
34
|
+
* http://www.imagemagick.org/Usage/draw/
|
35
|
+
* http://www.imagemagick.org/Usage/anim_opt/
|
36
|
+
* http://www.imagemagick.org/Usage/transform/#fx
|
37
|
+
* http://www.imagemagick.org/Usage/layers/
|
38
|
+
* http://www.imagemagick.org/script/command-line-options.php#delete
|
39
|
+
* http://www.imagemagick.org/script/fx.php
|
40
|
+
* http://www.imagemagick.org/script/escape.php
|
41
|
+
* http://www.imagemagick.org/Usage/annotating/#watermarking
|
42
|
+
* http://www.imagemagick.org/Usage/anim_mods/#frame_mod
|
43
|
+
* http://www.ioncannon.net/linux/81/5-imagemagick-command-line-examples-part-1/
|
44
|
+
|
45
|
+
## Bash guides
|
46
|
+
|
47
|
+
* http://andreinc.net/2011/09/04/bash-scripting-best-practice/
|
48
|
+
* http://stackoverflow.com/questions/242538/unix-shell-script-find-out-which-directory-the-script-file-resides
|
49
|
+
|
50
|
+
## Ruby guides
|
51
|
+
|
52
|
+
* TODO: implement tests; see https://github.com/pgericson/progress_bar/blob/master/test/arguments_test.rb
|
53
|
+
* TODO: look at mixlib-{shellout,cli,logger}; https://github.com/opscode/mixlib-shellout
|
54
|
+
* TODO: look at https://github.com/bitboxer/simple_progressbar/blob/master/lib/simple_progressbar.rb
|
data/Dockerfile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
FROM ubuntu:14.04
|
2
|
+
|
3
|
+
# If host is running squid-deb-proxy on port 8000, populate /etc/apt/apt.conf.d/30proxy
|
4
|
+
# By default, squid-deb-proxy 403s unknown sources, so apt shouldn't proxy ppa.launchpad.net
|
5
|
+
RUN route -n | awk '/^0.0.0.0/ {print $2}' > /tmp/host_ip.txt
|
6
|
+
RUN echo "HEAD /" | nc `cat /tmp/host_ip.txt` 8000 | grep squid-deb-proxy \
|
7
|
+
&& (echo "Acquire::http::Proxy \"http://$(cat /tmp/host_ip.txt):8000\";" > /etc/apt/apt.conf.d/30proxy) \
|
8
|
+
&& (echo "Acquire::http::Proxy::ppa.launchpad.net DIRECT;" >> /etc/apt/apt.conf.d/30proxy) \
|
9
|
+
|| echo "No squid-deb-proxy detected on docker host"
|
10
|
+
|
11
|
+
RUN apt-get update
|
12
|
+
|
13
|
+
RUN apt-get install -y imagemagick
|
14
|
+
RUN apt-get install -y libmagickwand-dev
|
15
|
+
RUN apt-get install -y gifsicle
|
16
|
+
RUN apt-get -y install ruby1.9.1 ruby1.9.1-dev
|
17
|
+
RUN apt-get install -y build-essential curl git vim
|
18
|
+
|
19
|
+
RUN apt-get install -y python-software-properties software-properties-common
|
20
|
+
RUN add-apt-repository ppa:mc3man/trusty-media
|
21
|
+
RUN apt-get update
|
22
|
+
RUN apt-get -y install ffmpeg
|
23
|
+
|
24
|
+
RUN gem install bundler --no-rdoc --no-ri
|
25
|
+
ADD Gemfile /tmp/Gemfile
|
26
|
+
RUN cd /tmp; bundle install
|
27
|
+
|
28
|
+
WORKDIR /srv/screengif
|
data/Gemfile
ADDED
data/Makefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
build:
|
2
|
+
docker build -t dergachev/screengif .
|
3
|
+
|
4
|
+
cmd=/bin/bash
|
5
|
+
docker-run:
|
6
|
+
docker run -t -i \
|
7
|
+
-v `pwd`:/srv/screengif \
|
8
|
+
dergachev/screengif \
|
9
|
+
/bin/bash -c "umask 002; $(cmd) $(args)"
|
10
|
+
|
11
|
+
docker-bash:
|
12
|
+
$(MAKE) docker-run cmd="/bin/bash"
|
13
|
+
|
14
|
+
docker-convert:
|
15
|
+
$(MAKE) docker-run cmd="bin/screengif" args="$(args)"
|
16
|
+
|
17
|
+
docker-shell:
|
18
|
+
$(MAKE) docker-run cmd="/bin/bash"
|
19
|
+
|
20
|
+
.PHONY: build docker-run docker-convert docker-shell
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Screengif
|
2
|
+
|
3
|
+
A commandline tool to convert SCREENCAST.mov into ANIMATED.gif
|
4
|
+
|
5
|
+
Here's what happens when you apply it to [demo.mov](https://raw.github.com/dergachev/screengif/master/demo.mov)
|
6
|
+
|
7
|
+
<img src="https://raw.github.com/dergachev/screengif/master/demo.gif" width="400" />
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
```
|
12
|
+
screengif.rb - Convert your screencast into a gif.
|
13
|
+
Usage:
|
14
|
+
screengif.rb [options] [--input FILENAME.mov] [--output OUTPUTFILE.gif]
|
15
|
+
Examples:
|
16
|
+
./screengif.rb --input demo.mov --output out.gif
|
17
|
+
cat somefile.gif | ./screengif.rb --progressbar --framerate 10 --delay 50 --delay-last 5 > out.gif
|
18
|
+
|
19
|
+
Specific options:
|
20
|
+
-i, --input FILENAME.mov Use ffmpeg to convert FILENAME.mov into PPM image stream and process results.
|
21
|
+
If missing, will process PPM or GIF image stream from STDIN.
|
22
|
+
-o, --output FILENAME.gif Output resulting GIF data to FILENAME.gif. (defaults to STDOUT).
|
23
|
+
-p, --progressbar Overlay progress bar on the animation.
|
24
|
+
-d, --delay MS Animation frame delay, in tens of ms. (default: 10)
|
25
|
+
--delay-last MS Animation frame delay of last frame, in tens of ms. (default: 50)
|
26
|
+
-r, --framerate FPS Specify amount of frames per second to keep. (default: 5)
|
27
|
+
-w, --max-width PIXELS Output image max width, in pixels.
|
28
|
+
--max-height PIXELS Output image max height, in pixels.
|
29
|
+
--no-contrast Skip increasing contrast using imagemagick.
|
30
|
+
-f, --fuzz PERCENT Imagemagick fuzz factor for color reduction. (default: 5%)
|
31
|
+
--no-coalesce Skip Magick::ImageList#coalesce() if input doesn't need it.
|
32
|
+
--no-gifsicle Prevent filter the output through gifsicle. Greatly increases output file size.
|
33
|
+
-h, --help Show this message
|
34
|
+
-v, --verbose Verbose output
|
35
|
+
--version Show version
|
36
|
+
```
|
37
|
+
|
38
|
+
## Installation
|
39
|
+
|
40
|
+
### Under Docker
|
41
|
+
|
42
|
+
If you have docker running, this is the quickest way to get
|
43
|
+
started with screengif:
|
44
|
+
|
45
|
+
```
|
46
|
+
git clone git@github.com:dergachev/screengif.git
|
47
|
+
cd screengif
|
48
|
+
|
49
|
+
make build # or alternatively, 'docker pull dergachev/screengif'
|
50
|
+
|
51
|
+
make docker-convert args="-i demo.mov -o demo-docker.gif"
|
52
|
+
|
53
|
+
# input files must be relative to cloned screengif repo
|
54
|
+
cp /path/to/myMovie.mov .
|
55
|
+
|
56
|
+
make docker-convert args="-i myMovie.mov -o myMovie.gif"
|
57
|
+
```
|
58
|
+
|
59
|
+
See Dockerfile and Makefile for how it works.
|
60
|
+
|
61
|
+
### With Vagrant
|
62
|
+
|
63
|
+
If you have Vagrant and Virtualbox already installed, this is both faster and cleaner than downloading and compiling all the dependencies in OS X. To install, simply do the following:
|
64
|
+
|
65
|
+
```
|
66
|
+
vagrant up
|
67
|
+
```
|
68
|
+
|
69
|
+
The easiest way to use it is to copy your image to screengif project directory (shared in the VM under /vagrant), and run the following:
|
70
|
+
|
71
|
+
```
|
72
|
+
cp ~/screencast.mov ./screencast.mov
|
73
|
+
vagrant ssh -- '/vagrant/screengif.rb --input /vagrant/screencast.mov --output /vagrant/output/screencast.gif'
|
74
|
+
ls ./output/screencast.gif # should exist!
|
75
|
+
|
76
|
+
# when finished, destroy the VM
|
77
|
+
vagrant destroy -f
|
78
|
+
```
|
79
|
+
|
80
|
+
### On OSX, with homebrew
|
81
|
+
|
82
|
+
The following works with OS X and homebrew, assuming you have ruby 1.9.2+:
|
83
|
+
|
84
|
+
You may need to install brew-cask: https://github.com/phinze/homebrew-cask
|
85
|
+
|
86
|
+
```bash
|
87
|
+
git clone https://github.com/dergachev/screengif.git
|
88
|
+
cd screengif
|
89
|
+
|
90
|
+
# x-quartz is a dependency for gifsicle, no longer installed starting on 10.8
|
91
|
+
brew cask install xquartz
|
92
|
+
open /opt/homebrew-cask/Caskroom/xquartz/2.7.7/XQuartz.pkg # runs the XQuartz installer
|
93
|
+
|
94
|
+
brew install ffmpeg imagemagick gifsicle
|
95
|
+
gem install rmagick
|
96
|
+
```
|
97
|
+
|
98
|
+
## Tips
|
99
|
+
|
100
|
+
See https://gist.github.com/dergachev/4627207#os-x-screencast-to-animated-gif
|
101
|
+
for a guide on how to use Quicktime Player to record a screencast on OS X.
|
102
|
+
Keep in mind that other tools (like Screenflow) produce better video quality.
|
103
|
+
|
104
|
+
If you install [dropbox-screenshots](https://github.com/dergachev/dropbox-screenshots),
|
105
|
+
the following will automatically upload your new gif to Dropbox:
|
106
|
+
|
107
|
+
cp out.gif ~/Dropbox/Public/screenshots
|
108
|
+
|
109
|
+
## Resources
|
110
|
+
|
111
|
+
See [DEVNOTES](https://github.com/dergachev/screengif/blob/master/DEVNOTES.md)
|
data/Vagrantfile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
Vagrant.configure("2") do |config|
|
5
|
+
config.vm.box = "precise64"
|
6
|
+
|
7
|
+
# config.vm.provision :shell, :inline => "apt-get install -y vim curl git"
|
8
|
+
|
9
|
+
config.vm.provision :shell, :inline => <<-EOH
|
10
|
+
apt-get update
|
11
|
+
apt-get install -y ffmpeg gifsicle imagemagick libmagickwand-dev
|
12
|
+
apt-get -y install ruby1.9.1 ruby1.9.1-dev
|
13
|
+
gem install bundler --no-rdoc --no-ri
|
14
|
+
cd /vagrant
|
15
|
+
bundle install
|
16
|
+
EOH
|
17
|
+
|
18
|
+
# test that everything is installed correctly by generating ./output/demo.gif
|
19
|
+
config.vm.provision :shell, :inline => <<-EOH
|
20
|
+
cd /vagrant
|
21
|
+
test -f ./output/demo.gif && exit 0 # ensures this script runs just once
|
22
|
+
echo "Testing deployment by converting ./demo.mov to ./output/demo.gif"
|
23
|
+
mkdir -p ./output
|
24
|
+
# vagrant colors stderr red (undesirable), so redirect it to stdout
|
25
|
+
./screengif.rb --input demo.mov --output ./output/demo.gif 2>&1
|
26
|
+
EOH
|
27
|
+
end
|
data/bin/screengif
ADDED
data/demo.gif
ADDED
Binary file
|
data/demo.mov
ADDED
Binary file
|
data/lib/screengif.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'RMagick'
|
4
|
+
include Magick
|
5
|
+
|
6
|
+
require 'screengif/draw_progressbar.rb'
|
7
|
+
require 'screengif/options.rb'
|
8
|
+
require 'screengif/util.rb'
|
9
|
+
|
10
|
+
module Screengif
|
11
|
+
def self.call_ffmpeg(input_file, options, verbose)
|
12
|
+
ffmpeg_loglevel = verbose ? 'verbose' : 'warning'
|
13
|
+
if options.framerate
|
14
|
+
ffmpeg_framerate = "-r #{options.framerate}"
|
15
|
+
end
|
16
|
+
|
17
|
+
if options.max_width || options.max_height
|
18
|
+
options.max_width ||= "-1"
|
19
|
+
options.max_height ||= "-1"
|
20
|
+
ffmpeg_resize = "-vf scale=#{options.max_width}:#{options.max_height} -sws_flags lanczos"
|
21
|
+
end
|
22
|
+
# TODO: error handling
|
23
|
+
return `ffmpeg -i '#{input_file}' -loglevel #{ffmpeg_loglevel} #{ffmpeg_framerate} #{ffmpeg_resize} -f image2pipe -vcodec ppm - `
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.call_gifsicle(data)
|
27
|
+
$stderr.puts "Filtering output through gifsicle" if $verbose
|
28
|
+
# TODO: error handling
|
29
|
+
result = ''
|
30
|
+
# popen is for system calls that require setting STDIN
|
31
|
+
IO.popen('gifsicle --loop --optimize=3 --multifile -', 'r+') do |f|
|
32
|
+
f.write(data)
|
33
|
+
f.close_write
|
34
|
+
result = f.read
|
35
|
+
end
|
36
|
+
return result
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.handle_input(input_file, options, optionparser)
|
40
|
+
if (input_file)
|
41
|
+
$stderr.puts "Running ffmpeg with #{input_file}" if $verbose
|
42
|
+
input = Screengif::call_ffmpeg(input_file, options, $verbose)
|
43
|
+
elsif !$stdin.tty? # we are being piped to
|
44
|
+
$stderr.puts "Reading input from STDIN." if $verbose
|
45
|
+
input = STDIN.read
|
46
|
+
else
|
47
|
+
$stderr.puts "No input file available."
|
48
|
+
puts optionparser
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
return input
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.handle_output(output_file, output, optionparser)
|
55
|
+
if (output_file)
|
56
|
+
File.open(output_file, 'w') do |f|
|
57
|
+
f.puts output
|
58
|
+
end
|
59
|
+
elsif !STDOUT.tty?
|
60
|
+
$stderr.puts "Sending output to STDOUT" if $verbose
|
61
|
+
puts output
|
62
|
+
else
|
63
|
+
$stderr.puts "Error: No output destination available."
|
64
|
+
puts optionparser
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.start
|
70
|
+
options,optionparser = Screengif::Options.parse(ARGV)
|
71
|
+
$startTime = Time.now
|
72
|
+
|
73
|
+
input = handle_input(options.input_file, options, optionparser)
|
74
|
+
canvas = ImageList.new.from_blob(input)
|
75
|
+
|
76
|
+
if (!options.no_coalesce)
|
77
|
+
$stderr.puts "Beginning imagemagick coalescing..." if $verbose
|
78
|
+
canvas = canvas.coalesce()
|
79
|
+
$stderr.puts "Coalescing completed" if $verbose
|
80
|
+
end
|
81
|
+
|
82
|
+
statusPrinter = Screengif::StatusPrinter.new($stderr)
|
83
|
+
canvas.each_with_index do |img,index|
|
84
|
+
statusPrinter.printText("Processing image: #{index+1}/#{canvas.length}")
|
85
|
+
img.delay = (index + 1 == canvas.length) ? options.delay_last : options.delay
|
86
|
+
img.format = 'GIF'
|
87
|
+
|
88
|
+
if options.fuzz.to_i > 0
|
89
|
+
# when run as a gem, setting img.fuzz="5%" throws a wierd error; so we do this instead
|
90
|
+
# img.fuzz = "#{img.fuzz}%"
|
91
|
+
img.fuzz = QuantumRange * options.fuzz.to_i / 100.0
|
92
|
+
end
|
93
|
+
unless options.nocontrast
|
94
|
+
img = img.contrast(true)
|
95
|
+
img = img.white_threshold(QuantumRange * 0.99)
|
96
|
+
# img = img.level(QuantumRange * 0.05, QuantumRange * 0.95)
|
97
|
+
end
|
98
|
+
Screengif::DrawProgressbar.draw(img, (index.to_f+1)/canvas.length) if (options.progressbar)
|
99
|
+
canvas[index] = img
|
100
|
+
end
|
101
|
+
statusPrinter.done
|
102
|
+
|
103
|
+
# see http://stackoverflow.com/questions/958681/how-to-deal-with-memory-leaks-in-rmagick-in-ruby
|
104
|
+
GC.start
|
105
|
+
|
106
|
+
# Reduce down to 256 colors (as required by GIF), disable dithering (equivalent to +dither)
|
107
|
+
$stderr.puts "Beginning quantization... (takes a while)" if $verbose
|
108
|
+
canvas = canvas.quantize(256, RGBColorspace, NoDitherMethod)
|
109
|
+
$stderr.puts "Quantization completed." if $verbose
|
110
|
+
|
111
|
+
$stderr.puts "Beginning rmagick OptimizePlusLayer..." if $verbose
|
112
|
+
canvas = canvas.optimize_layers(OptimizePlusLayer)
|
113
|
+
$stderr.puts "Beginning rmagick OptimizeTransLayer..." if $verbose
|
114
|
+
canvas = canvas.optimize_layers(OptimizeTransLayer)
|
115
|
+
|
116
|
+
$stderr.puts "Rmagick processing completed. Outputting results..." if $verbose
|
117
|
+
output = canvas.to_blob
|
118
|
+
|
119
|
+
GC.start
|
120
|
+
|
121
|
+
output = Screengif::call_gifsicle(output) unless (options.no_gifsicle)
|
122
|
+
|
123
|
+
handle_output(options.output_file, output, optionparser)
|
124
|
+
Screengif::ConversionStats.print(options.input_file, options.output_file, input, output)
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module Screengif
|
4
|
+
class DrawProgressbar
|
5
|
+
def self.draw(img,ratio)
|
6
|
+
wid = img.columns() - 3
|
7
|
+
ht = img.rows() - 3
|
8
|
+
rectWid = 60
|
9
|
+
rectHt = 20
|
10
|
+
|
11
|
+
progressbar = Draw.new
|
12
|
+
|
13
|
+
progressbar
|
14
|
+
.fill('white')
|
15
|
+
.stroke('black')
|
16
|
+
.draw(img)
|
17
|
+
.rectangle(wid-rectWid,ht-rectHt,wid,ht)
|
18
|
+
progressbar
|
19
|
+
.fill('black')
|
20
|
+
.pointsize(12)
|
21
|
+
.stroke('transparent')
|
22
|
+
.font_weight(BoldWeight)
|
23
|
+
.gravity(NorthWestGravity)
|
24
|
+
.text(wid-rectWid+4,ht-rectHt+5,"screengif")
|
25
|
+
progressbar
|
26
|
+
.fill('black')
|
27
|
+
.stroke('transparent')
|
28
|
+
.rectangle(wid-rectWid,ht-rectHt,wid-rectWid*(1-ratio),ht)
|
29
|
+
progressbar.draw(img)
|
30
|
+
|
31
|
+
maskedDraw = Draw.new
|
32
|
+
maskedDraw.define_clip_path('clip') {
|
33
|
+
maskedDraw
|
34
|
+
.rectangle(wid-rectWid,ht-rectHt,wid-rectWid*(1-ratio),ht)
|
35
|
+
}
|
36
|
+
maskedDraw.push
|
37
|
+
maskedDraw.clip_path('clip')
|
38
|
+
maskedDraw
|
39
|
+
.fill('white')
|
40
|
+
.pointsize(12)
|
41
|
+
.stroke('transparent')
|
42
|
+
.font_weight(BoldWeight)
|
43
|
+
.gravity(NorthWestGravity)
|
44
|
+
.text(wid-rectWid+4,ht-rectHt+5,"screengif")
|
45
|
+
maskedDraw.pop
|
46
|
+
maskedDraw.draw(img)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
$verbose = false
|
8
|
+
|
9
|
+
module Screengif
|
10
|
+
class Options
|
11
|
+
def self.parse(args)
|
12
|
+
options = OpenStruct.new
|
13
|
+
|
14
|
+
options.framerate = 5
|
15
|
+
options.delay = 10
|
16
|
+
options.delay_last = 50
|
17
|
+
options.no_coalesce = false
|
18
|
+
options.progressbar = false
|
19
|
+
options.input_file = nil
|
20
|
+
options.output_file = nil
|
21
|
+
options.no_gifsicle = false
|
22
|
+
options.fuzz = 5
|
23
|
+
|
24
|
+
# options.ffmpeg_inputfile = nil
|
25
|
+
|
26
|
+
optionparser = OptionParser.new do |opts|
|
27
|
+
opts.banner = "screengif.rb - Convert your screencast into a gif.\n" +
|
28
|
+
"Usage:\n" +
|
29
|
+
"\tscreengif.rb [options] [--input FILENAME.mov] [--output OUTPUTFILE.gif]\n" +
|
30
|
+
"Examples:\n" +
|
31
|
+
"\t./screengif.rb --input demo.mov --output out.gif\n" +
|
32
|
+
"\tcat somefile.gif | ./screengif.rb --progressbar --framerate 10 --delay 50 --delay-last 5 > out.gif\n"
|
33
|
+
|
34
|
+
opts.separator ""
|
35
|
+
opts.separator "Specific options:"
|
36
|
+
|
37
|
+
opts.on("-i", "--input FILENAME.mov", "Use ffmpeg to convert FILENAME.mov into PPM image stream and process results.",
|
38
|
+
"If missing, will process PPM or GIF image stream from STDIN.") do |filename|
|
39
|
+
if (File.exists?(filename))
|
40
|
+
options.input_file = filename
|
41
|
+
options.no_coalesce = true # ffmpeg's ppm output is already coalesced
|
42
|
+
else
|
43
|
+
$stderr.puts "Unable to open filename: #{filename}"
|
44
|
+
puts opts
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
opts.on("-o", "--output FILENAME.gif", "Output resulting GIF data to FILENAME.gif. (defaults to STDOUT).") do |filename|
|
49
|
+
options.output_file = filename
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on("-d", "--delay MS", Integer, "Animation frame delay, in tens of ms. (default: 10)") do |tens_of_ms|
|
53
|
+
options.delay = tens_of_ms.to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on("-p", "--progressbar", "Overlay progress bar on the animation.") do
|
57
|
+
options.progressbar = true
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on("-d", "--delay MS", Integer, "Animation frame delay, in tens of ms. (default: 10)") do |tens_of_ms|
|
61
|
+
options.delay = tens_of_ms.to_i
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on("--delay-last MS", Integer, "Animation frame delay of last frame, in tens of ms. (default: 50)") do |tens_of_ms|
|
65
|
+
options.delay_last = tens_of_ms.to_i
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on("-r", "--framerate FPS", Integer, "Specify amount of frames per second to keep. (default: 5)") do |fps|
|
69
|
+
options.framerate = fps.to_i
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on("-w", "--max-width PIXELS", Integer, "Output image max width, in pixels.") do |pixels|
|
73
|
+
options.max_width = pixels.to_i
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.on("--max-height PIXELS", Integer, "Output image max height, in pixels.") do |pixels|
|
77
|
+
options.max_height = pixels.to_i
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on("--no-contrast", "Skip increasing contrast using imagemagick.") do
|
81
|
+
options.nocontrast = true
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on("-f", "--fuzz PERCENT", Integer, "Imagemagick fuzz factor for color reduction. (default: 5%)") do |fuzz|
|
85
|
+
options.fuzz = fuzz.to_i
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on("--no-coalesce", "Skip Magick::ImageList#coalesce() if input doesn't need it.") do
|
89
|
+
options.no_coalesce = true
|
90
|
+
end
|
91
|
+
|
92
|
+
opts.on("--no-gifsicle", "Prevent filter the output through gifsicle. Greatly increases output file size.") do
|
93
|
+
options.no_gifsicle = true
|
94
|
+
end
|
95
|
+
|
96
|
+
# Boilerplate
|
97
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
98
|
+
puts opts
|
99
|
+
exit
|
100
|
+
end
|
101
|
+
opts.on_tail("-v", "--verbose", "Verbose output") do
|
102
|
+
$verbose = true
|
103
|
+
end
|
104
|
+
opts.on_tail("--version", "Show version") do
|
105
|
+
puts "0.1"
|
106
|
+
exit
|
107
|
+
end
|
108
|
+
end
|
109
|
+
optionparser.parse!(args)
|
110
|
+
return options, optionparser
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
|
4
|
+
module Screengif
|
5
|
+
class StatusPrinter
|
6
|
+
# inspired by http://stackoverflow.com/a/6679572/9621
|
7
|
+
def initialize(stream)
|
8
|
+
@previous_size = 0
|
9
|
+
@stream = stream || $stdout
|
10
|
+
end
|
11
|
+
|
12
|
+
def printText(text)
|
13
|
+
if @previous_size > 0
|
14
|
+
@stream.print "\033[#{@previous_size}D"
|
15
|
+
@stream.print(" " * @previous_size)
|
16
|
+
@stream.print "\033[#{@previous_size}D"
|
17
|
+
end
|
18
|
+
@stream.print text
|
19
|
+
@stream.flush
|
20
|
+
@previous_size = text.gsub(/\e\[\d+m/,"").size
|
21
|
+
end
|
22
|
+
|
23
|
+
def done()
|
24
|
+
@previous_size = 0
|
25
|
+
@stream.print "\n"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class ConversionStats
|
30
|
+
def self.print(input_file, output_file, input, output)
|
31
|
+
$stderr.puts "Conversion completed in #{Time.now - $startTime} seconds."
|
32
|
+
if input_file # ffmpeg
|
33
|
+
# Duration: 00:00:04.28, start: 0.010333, bitrate: 11225 kb/s
|
34
|
+
duration = (`ffmpeg -i '#{input_file}' 2>&1 | grep Duration`).gsub(/^.*Duration:\s*([^,]+),.*$/, '\1').gsub("\n",'')
|
35
|
+
|
36
|
+
input_filesize = `ls -lh '#{input_file}' | awk '{print $5}'`.gsub("\n",'')
|
37
|
+
$stderr.puts "Input: #{input_file} (#{duration}, #{input_filesize})"
|
38
|
+
else # piped input
|
39
|
+
input_contentsize = (input.bytesize.to_f / 2**10).to_i().to_s + "K"
|
40
|
+
$stderr.puts "Input: STDIN (#{input_contentsize})"
|
41
|
+
end
|
42
|
+
if output_file
|
43
|
+
output_filesize = `ls -lh '#{output_file}' | awk '{print $5}'`.gsub("\n",'')
|
44
|
+
$stderr.puts "Output: '#{output_file}' (#{output_filesize})"
|
45
|
+
else # piped output
|
46
|
+
output_contentsize = (output.bytesize.to_f / 2**10).to_i().to_s + "K"
|
47
|
+
$stderr.puts "Output: STDOUT (#{output_contentsize})"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/screengif.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.unshift File.expand_path('../lib', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "screengif"
|
5
|
+
spec.version = "0.0.1"
|
6
|
+
spec.authors = "Alex Dergachev"
|
7
|
+
spec.email = "alex@evolvingweb.ca"
|
8
|
+
spec.summary = 'Script to convert mov files to animated gifs.'
|
9
|
+
spec.description = 'Wrapper on ffmpeg and imagemagick to convert .mov to .gif'
|
10
|
+
spec.homepage = 'https://github.com/dergachev/screengif'
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
spec.files = `git ls-files`.split($/)
|
14
|
+
spec.bindir = 'bin'
|
15
|
+
spec.executables = 'screengif'
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_path = 'lib'
|
18
|
+
|
19
|
+
spec.add_development_dependency "bundler"
|
20
|
+
spec.add_development_dependency "rake"
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: screengif
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alex Dergachev
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-04-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Wrapper on ffmpeg and imagemagick to convert .mov to .gif
|
47
|
+
email: alex@evolvingweb.ca
|
48
|
+
executables:
|
49
|
+
- screengif
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- DEVNOTES.md
|
55
|
+
- Dockerfile
|
56
|
+
- Gemfile
|
57
|
+
- Makefile
|
58
|
+
- README.md
|
59
|
+
- Vagrantfile
|
60
|
+
- bin/screengif
|
61
|
+
- demo.gif
|
62
|
+
- demo.mov
|
63
|
+
- lib/screengif.rb
|
64
|
+
- lib/screengif/draw_progressbar.rb
|
65
|
+
- lib/screengif/options.rb
|
66
|
+
- lib/screengif/util.rb
|
67
|
+
- screengif.gemspec
|
68
|
+
homepage: https://github.com/dergachev/screengif
|
69
|
+
licenses:
|
70
|
+
- MIT
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.8.23
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: Script to convert mov files to animated gifs.
|
93
|
+
test_files: []
|