screengif 0.0.1
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 +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: []
|