wgif 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/Brewfile +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +90 -0
- data/Rakefile +5 -0
- data/bin/wgif +4 -0
- data/lib/wgif.rb +9 -0
- data/lib/wgif/cli.rb +125 -0
- data/lib/wgif/download_bar.rb +28 -0
- data/lib/wgif/downloader.rb +84 -0
- data/lib/wgif/exceptions.rb +24 -0
- data/lib/wgif/gif_maker.rb +19 -0
- data/lib/wgif/installer.rb +34 -0
- data/lib/wgif/version.rb +3 -0
- data/lib/wgif/video.rb +58 -0
- data/lib/wgif/video_cache.rb +18 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/wgif/cli_spec.rb +191 -0
- data/spec/wgif/download_bar_spec.rb +38 -0
- data/spec/wgif/downloader_spec.rb +72 -0
- data/spec/wgif/gif_maker_spec.rb +26 -0
- data/spec/wgif/installer_spec.rb +82 -0
- data/spec/wgif/video_cache_spec.rb +16 -0
- data/spec/wgif/video_spec.rb +76 -0
- data/wgif.gemspec +34 -0
- metadata +222 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 29562cbe754b572ffd1fc5e20a397b96405a703b
|
4
|
+
data.tar.gz: 3c7307a0a239c7867d2d6a205c14153e8b7c44f3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5953cef31a710c37e9d99e229673005c4737f20952150b398a678088b45ee67b61835d5311a1271a0aaae454b9299de2e3c1253efd135bdcc3b9ed65c22024b1
|
7
|
+
data.tar.gz: bfb4c0a042e3baefaca3f35621787041e0870f4c98a2cfba4cac5cb4e4cb0ec939a6b8ee941d1752588268cc3b748a8ac8fb38ea4b9c110363179e72e50f2ee5
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0-p247
|
data/.travis.yml
ADDED
data/Brewfile
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Connor Mendenhall
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# WGif
|
2
|
+
![Travis Build](https://travis-ci.org/ecmendenhall/wgif.png?branch=master)
|
3
|
+
|
4
|
+
WGif is a command line tool for creating animated GIFs from YouTube videos.
|
5
|
+
|
6
|
+
##TL;DR
|
7
|
+
```
|
8
|
+
Usage: wgif [YouTube URL] [output file] [options]
|
9
|
+
|
10
|
+
-f, --frames N Number of frames in the final gif. (Default 20)
|
11
|
+
-s, --start HH:MM:SS Start creating gif from input video at this timestamp. (Default 00:00:00)
|
12
|
+
-d, --duration seconds Number of seconds of input video to capture. (Default 1)
|
13
|
+
-w, --width pixels Width of the gif in pixels. (Default 480px)
|
14
|
+
|
15
|
+
Example:
|
16
|
+
|
17
|
+
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif -s 00:03:30 -d 2 -w 400
|
18
|
+
```
|
19
|
+
|
20
|
+
## Installation (Mac OS X)
|
21
|
+
To install from Rubygems:
|
22
|
+
|
23
|
+
```sh
|
24
|
+
$ gem install wgif
|
25
|
+
```
|
26
|
+
|
27
|
+
To install from source, run
|
28
|
+
|
29
|
+
```sh
|
30
|
+
$ gem build wgif.gemspec
|
31
|
+
```
|
32
|
+
|
33
|
+
and
|
34
|
+
|
35
|
+
```sh
|
36
|
+
$ gem install wgif-0.0.1.pre.gem
|
37
|
+
```
|
38
|
+
|
39
|
+
to install the executable.
|
40
|
+
|
41
|
+
WGif uses FFmpeg for video transcoding and ImageMagick to optimize GIFs.
|
42
|
+
To install dependencies with [Homebrew](http://brew.sh/), just run
|
43
|
+
|
44
|
+
```sh
|
45
|
+
$ wgif install
|
46
|
+
```
|
47
|
+
|
48
|
+
## Making a GIF
|
49
|
+
WGif expects two arguments: a YouTube video URL and a name for the GIF it creates. So,
|
50
|
+
|
51
|
+
```sh
|
52
|
+
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif
|
53
|
+
```
|
54
|
+
|
55
|
+
Is enough to create a GIF of [Bjork explaining her television](https://www.youtube.com/watch?v=1A78yTvIY1k). Without any extra parameters, WGif starts at
|
56
|
+
the beginning of the video, and creates a 20-frame, 480px GIF of the first second. Since GIFs are more
|
57
|
+
art than science, you'll probably want to tweak the size, duration, and number of frames.
|
58
|
+
|
59
|
+
Start by isolating the section of the video you'd like to GIF. Bjork starts her advice about dishonest
|
60
|
+
Icelandic poets around 3 minutes 30 seconds, and it lasts about two seconds. Pass the start timestamp with
|
61
|
+
`-s` or `--start` and the duration with `-d` or `--duration`:
|
62
|
+
|
63
|
+
```sh
|
64
|
+
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif --start 00:03:30 -d 2
|
65
|
+
```
|
66
|
+
|
67
|
+
A good start, but the GIF is way too big: around 5.6 megabytes. We can pass `-f` or `--frames` to specify the
|
68
|
+
total number of frames in the finished GIF. This defaults to 20, so let's drop a few to reduce the file size:
|
69
|
+
|
70
|
+
```sh
|
71
|
+
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif --start 00:03:30 -d 2 -f 18
|
72
|
+
```
|
73
|
+
|
74
|
+
Down to 2.2 megabytes, but still not small enough to post on my Sugarcubes fan-Tumblr. Let's scale it down a little
|
75
|
+
with the `-w` or `--width` flag:
|
76
|
+
|
77
|
+
```sh
|
78
|
+
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif --start 00:03:30 -d 2 -f 18 --width 350
|
79
|
+
```
|
80
|
+
|
81
|
+
And here it is:
|
82
|
+
|
83
|
+
![Bjork](http://i.imgur.com/NZXWwey.gif)
|
84
|
+
### "You shouldn't let poets lie to you."
|
85
|
+
|
86
|
+
## Contributions
|
87
|
+
Are welcome via pull request.
|
88
|
+
|
89
|
+
## License
|
90
|
+
This project is MIT licensed. See [LICENSE.txt](https://github.com/ecmendenhall/wgif/blob/master/LICENSE.txt) for details.
|
data/Rakefile
ADDED
data/bin/wgif
ADDED
data/lib/wgif.rb
ADDED
data/lib/wgif/cli.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'wgif/exceptions'
|
3
|
+
require 'wgif/downloader'
|
4
|
+
require 'wgif/gif_maker'
|
5
|
+
require 'wgif/installer'
|
6
|
+
|
7
|
+
module WGif
|
8
|
+
class CLI
|
9
|
+
|
10
|
+
attr_accessor :parser
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@options = {}
|
14
|
+
@defaults = {
|
15
|
+
trim_from: '00:00:00',
|
16
|
+
duration: 1.0,
|
17
|
+
dimensions: '480'
|
18
|
+
}
|
19
|
+
@parser = OptionParser.new do |opts|
|
20
|
+
opts.on('-f N',
|
21
|
+
'--frames N',
|
22
|
+
'Number of frames in the final gif. (Default 20)') {
|
23
|
+
|n| @options[:frames] = n.to_i
|
24
|
+
}
|
25
|
+
opts.on('-s HH:MM:SS',
|
26
|
+
'--start HH:MM:SS',
|
27
|
+
'Start creating gif from input video at this timestamp. (Default 00:00:00)') {
|
28
|
+
|ts| @options[:trim_from] = ts
|
29
|
+
}
|
30
|
+
opts.on('-d seconds',
|
31
|
+
'--duration seconds',
|
32
|
+
'Number of seconds of input video to capture. (Default 5)') {
|
33
|
+
|d| @options[:duration] = d.to_f
|
34
|
+
}
|
35
|
+
opts.on('-w pixels',
|
36
|
+
'--width pixels',
|
37
|
+
'Width of the gif in pixels. (Default 500px)') {
|
38
|
+
|gs| @options[:dimensions] = gs
|
39
|
+
}
|
40
|
+
|
41
|
+
opts.on_tail('-h',
|
42
|
+
'--help',
|
43
|
+
'Print help information.') {
|
44
|
+
print_help
|
45
|
+
exit
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_args(args)
|
51
|
+
options = @defaults.merge(parse_options args)
|
52
|
+
options.merge(url: args[0], output: args[1])
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse_options(args)
|
56
|
+
@parser.parse! args
|
57
|
+
@options
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_args(parsed_args)
|
61
|
+
raise WGif::InvalidUrlException unless parsed_args[:url] =~ /\Ahttps?\:\/\/.*\z/
|
62
|
+
raise WGif::InvalidTimestampException unless parsed_args[:trim_from] =~ /\A\d{1,2}(?::\d{2})+(?:\.\d*)?\z/
|
63
|
+
raise WGif::MissingOutputFileException unless parsed_args[:output]
|
64
|
+
end
|
65
|
+
|
66
|
+
def make_gif(cli_args)
|
67
|
+
WGif::Installer.new.run if cli_args[0] == 'install'
|
68
|
+
rescue_errors do
|
69
|
+
args = parse_args cli_args
|
70
|
+
validate_args(args)
|
71
|
+
video = Downloader.new.get_video(args[:url])
|
72
|
+
clip = video.trim(args[:trim_from], args[:duration])
|
73
|
+
frames = clip.to_frames(frames: args[:frames])
|
74
|
+
GifMaker.new.make_gif(frames, args[:output], args[:dimensions])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def rescue_errors
|
81
|
+
begin
|
82
|
+
yield
|
83
|
+
rescue WGif::InvalidUrlException
|
84
|
+
print_error "That looks like an invalid URL. Check the syntax."
|
85
|
+
rescue WGif::InvalidTimestampException
|
86
|
+
print_error "That looks like an invalid timestamp. Check the syntax."
|
87
|
+
rescue WGif::MissingOutputFileException
|
88
|
+
print_error 'Please specify an output file.'
|
89
|
+
rescue WGif::VideoNotFoundException
|
90
|
+
print_error "WGif can't find a valid YouTube video at that URL."
|
91
|
+
rescue WGif::ClipEncodingException
|
92
|
+
print_error "WGif encountered an error transcoding the video."
|
93
|
+
rescue SystemExit => e
|
94
|
+
raise e
|
95
|
+
rescue Exception => e
|
96
|
+
print_error <<-error
|
97
|
+
Something went wrong creating your GIF. The details:
|
98
|
+
|
99
|
+
#{e}
|
100
|
+
#{e.backtrace.join("\n")}
|
101
|
+
|
102
|
+
Please open an issue at: https://github.com/ecmendenhall/wgif/issues/new
|
103
|
+
error
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def print_error(message)
|
108
|
+
puts message, "\n"
|
109
|
+
print_help
|
110
|
+
exit 1
|
111
|
+
end
|
112
|
+
|
113
|
+
def print_help
|
114
|
+
puts "Usage: wgif [YouTube URL] [output file] [options]", "\n"
|
115
|
+
puts @parser.summarize, "\n"
|
116
|
+
puts <<-example
|
117
|
+
Example:
|
118
|
+
|
119
|
+
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif -s 00:03:30 -d 2 -w 400
|
120
|
+
|
121
|
+
example
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'ruby-progressbar'
|
2
|
+
|
3
|
+
module WGif
|
4
|
+
class DownloadBar
|
5
|
+
|
6
|
+
FORMAT = '==> %p%% |%B|'
|
7
|
+
SMOOTHING = 0.8
|
8
|
+
|
9
|
+
attr_reader :progress_bar
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@progress_bar = ProgressBar.create(
|
13
|
+
format: FORMAT,
|
14
|
+
smoothing: SMOOTHING,
|
15
|
+
total: @size
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_total(size)
|
20
|
+
@progress_bar.total = size
|
21
|
+
end
|
22
|
+
|
23
|
+
def increment_progress(size)
|
24
|
+
@progress_bar.progress += size
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'viddl-rb'
|
2
|
+
require 'typhoeus'
|
3
|
+
require 'wgif/download_bar'
|
4
|
+
require 'wgif/exceptions'
|
5
|
+
require 'wgif/video'
|
6
|
+
require 'wgif/video_cache'
|
7
|
+
require 'uri'
|
8
|
+
require 'cgi'
|
9
|
+
|
10
|
+
module WGif
|
11
|
+
class Downloader
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@cache = WGif::VideoCache.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def video_url youtube_url
|
18
|
+
begin
|
19
|
+
urls = ViddlRb.get_urls(youtube_url)
|
20
|
+
urls.first
|
21
|
+
rescue
|
22
|
+
raise WGif::VideoNotFoundException
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def video_id youtube_url
|
27
|
+
begin
|
28
|
+
uri = URI(youtube_url)
|
29
|
+
params = CGI.parse(uri.query)
|
30
|
+
params['v'].first
|
31
|
+
rescue
|
32
|
+
raise WGif::InvalidUrlException
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_video youtube_url
|
37
|
+
id = video_id youtube_url
|
38
|
+
if cached_clip = @cache.get(id)
|
39
|
+
return cached_clip
|
40
|
+
else
|
41
|
+
temp = load_clip(id, youtube_url)
|
42
|
+
video = WGif::Video.new(id, temp.path)
|
43
|
+
video
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def create_progress_bar request, output_file
|
50
|
+
size = nil
|
51
|
+
download_bar = WGif::DownloadBar.new
|
52
|
+
|
53
|
+
request.on_headers do |response|
|
54
|
+
size = response.headers['Content-Length'].to_i
|
55
|
+
download_bar.update_total(size)
|
56
|
+
end
|
57
|
+
|
58
|
+
request.on_body do |chunk|
|
59
|
+
output_file.write(chunk)
|
60
|
+
download_bar.increment_progress(chunk.size)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def request_clip youtube_url, output_file
|
65
|
+
clip_url = self.video_url youtube_url
|
66
|
+
request = Typhoeus::Request.new clip_url
|
67
|
+
create_progress_bar(request, output_file)
|
68
|
+
request.run
|
69
|
+
end
|
70
|
+
|
71
|
+
def load_clip id, youtube_url
|
72
|
+
FileUtils.mkdir_p "/tmp/wgif"
|
73
|
+
temp = File.open("/tmp/wgif/#{id}", 'wb')
|
74
|
+
begin
|
75
|
+
clip = request_clip(youtube_url, temp)
|
76
|
+
raise WGif::VideoNotFoundException unless clip.response_code == 200
|
77
|
+
ensure
|
78
|
+
temp.close
|
79
|
+
end
|
80
|
+
temp
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module WGif
|
2
|
+
|
3
|
+
class InvalidUrlException < Exception
|
4
|
+
end
|
5
|
+
|
6
|
+
class InvalidTimestampException < Exception
|
7
|
+
end
|
8
|
+
|
9
|
+
class InvalidDurationException < Exception
|
10
|
+
end
|
11
|
+
|
12
|
+
class InvalidFramesException < Exception
|
13
|
+
end
|
14
|
+
|
15
|
+
class MissingOutputFileException < Exception
|
16
|
+
end
|
17
|
+
|
18
|
+
class VideoNotFoundException < Exception
|
19
|
+
end
|
20
|
+
|
21
|
+
class ClipEncodingException < Exception
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|