wgif 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/README.md +27 -10
- data/Rakefile +8 -3
- data/lib/wgif.rb +10 -10
- data/lib/wgif/argument_parser.rb +94 -0
- data/lib/wgif/cli.rb +31 -87
- data/lib/wgif/download_bar.rb +0 -1
- data/lib/wgif/downloader.rb +23 -27
- data/lib/wgif/exceptions.rb +8 -25
- data/lib/wgif/gif_maker.rb +3 -1
- data/lib/wgif/installer.rb +2 -3
- data/lib/wgif/uploader.rb +7 -4
- data/lib/wgif/version.rb +1 -1
- data/lib/wgif/video.rb +17 -16
- data/lib/wgif/video_cache.rb +1 -4
- data/spec/integration/empty_image_list_spec.rb +18 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/unit/wgif/argument_parser_spec.rb +153 -0
- data/spec/unit/wgif/cli_spec.rb +98 -0
- data/spec/{wgif → unit/wgif}/download_bar_spec.rb +0 -0
- data/spec/{wgif → unit/wgif}/downloader_spec.rb +14 -9
- data/spec/{wgif → unit/wgif}/gif_maker_spec.rb +0 -0
- data/spec/{wgif → unit/wgif}/installer_spec.rb +45 -35
- data/spec/unit/wgif/uploader_spec.rb +48 -0
- data/spec/{wgif → unit/wgif}/video_cache_spec.rb +0 -0
- data/spec/{wgif → unit/wgif}/video_spec.rb +27 -22
- data/wgif.gemspec +22 -22
- metadata +37 -18
- data/spec/wgif/cli_spec.rb +0 -207
- data/spec/wgif/uploader_spec.rb +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eda7edccc313c6f58f2bb806200b69011f1904c1
|
4
|
+
data.tar.gz: a2197e273c936b595a8b498e0c43deb43527ef23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e08a6c50358d79233771a2202991d0ed46eeffdfe723911b95dc97880a8583a407c62e7d3a24a15cb8aa53b9e7eb91d824e7c94e4b8717bf5c935f645525c92e
|
7
|
+
data.tar.gz: d8af1629380d458440a01bd496310bffc597892ec3f68b0ebdec8cc7af2712241b9ffb833ac4f93577a232a06a99382bf36c0f1b3d39de32cf3e527f3676ba33
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# WGif
|
2
2
|
[![Build Status](https://travis-ci.org/ecmendenhall/wgif.svg?branch=master)](https://travis-ci.org/ecmendenhall/wgif)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/ecmendenhall/wgif.png)](https://codeclimate.com/github/ecmendenhall/wgif)
|
3
4
|
|
4
5
|
WGif is a command line tool for creating animated GIFs from YouTube videos.
|
5
6
|
|
@@ -7,11 +8,13 @@ WGif is a command line tool for creating animated GIFs from YouTube videos.
|
|
7
8
|
```
|
8
9
|
Usage: wgif [YouTube URL] [output file] [options]
|
9
10
|
|
10
|
-
-f, --frames N
|
11
|
-
-s, --start HH:MM:SS
|
12
|
-
-d, --duration seconds
|
13
|
-
-
|
14
|
-
-
|
11
|
+
-f, --frames N Number of frames in the final gif. (Default 20)
|
12
|
+
-s, --start HH:MM:SS Start creating gif from input video at this timestamp. (Default 00:00:00)
|
13
|
+
-d, --duration seconds Number of seconds of input video to capture. (Default 1)
|
14
|
+
-w, --width pixels Width of the gif in pixels. (Default 480px)
|
15
|
+
-u, --upload Upload finished gif to Imgur
|
16
|
+
-p, --preview Preview finished gif with Quick Look
|
17
|
+
-h, --help Print help information.
|
15
18
|
|
16
19
|
Example:
|
17
20
|
|
@@ -34,7 +37,7 @@ $ gem build wgif.gemspec
|
|
34
37
|
and
|
35
38
|
|
36
39
|
```sh
|
37
|
-
$ gem install wgif-0.
|
40
|
+
$ gem install wgif-0.3.0.gem
|
38
41
|
```
|
39
42
|
|
40
43
|
to install the executable.
|
@@ -72,14 +75,24 @@ total number of frames in the finished GIF. This defaults to 20, so let's drop a
|
|
72
75
|
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif --start 00:03:30 -d 2 -f 18
|
73
76
|
```
|
74
77
|
|
75
|
-
|
76
|
-
|
78
|
+
To preview the output in a Quick Look window, add the `--preview` flag:
|
79
|
+
|
80
|
+
```sh
|
81
|
+
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif --start 00:03:30 -d 2 -f 18 --preview
|
82
|
+
```
|
83
|
+
|
84
|
+
You'll see a preview pop up in a Quick Look window like this one:
|
85
|
+
|
86
|
+
![Preview](http://i.imgur.com/cccOnpY.png)
|
87
|
+
|
88
|
+
Dropping frames shrunk the file to 2.2 megabytes, but it's still not small enough to post on my Sugarcubes fan-Tumblr.
|
89
|
+
Let's scale it down a little with the `-w` or `--width` flag:
|
77
90
|
|
78
91
|
```sh
|
79
92
|
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif --start 00:03:30 -d 2 -f 18 --width 350
|
80
93
|
```
|
81
94
|
|
82
|
-
And finally, now that everything's completed add the `--upload` flag to automatically post it to Imgur:
|
95
|
+
And finally, now that everything's completed, let's add the `--upload` flag to automatically post it to Imgur:
|
83
96
|
|
84
97
|
```sh
|
85
98
|
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif --start 00:03:30 -d 2 -f 18 --width 350 --upload
|
@@ -88,12 +101,16 @@ Finished. GIF uploaded to Imgur at http://i.imgur.com/iA28DuR.gif
|
|
88
101
|
|
89
102
|
And here it is:
|
90
103
|
|
91
|
-
![Bjork](http://i.imgur.com/
|
104
|
+
![Bjork](http://i.imgur.com/iA28DuR.gif)
|
92
105
|
### "You shouldn't let poets lie to you."
|
93
106
|
|
94
107
|
## Changes
|
108
|
+
- v0.3.0, 2014/4/20: Add Quick Look preview with `--preview` flag.
|
95
109
|
- v0.2.0, 2014/4/11: Add automatic upload to Imgur with `--upload` flag.
|
96
110
|
|
111
|
+
## Contributors
|
112
|
+
Thanks to [arlandism](https://github.com/arlandism) and [ellie007](https://github.com/ellie007) for pairing on Imgur uploads.
|
113
|
+
|
97
114
|
## Contributions
|
98
115
|
Are welcome via pull request.
|
99
116
|
|
data/Rakefile
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
2
|
require 'rspec/core/rake_task'
|
3
3
|
|
4
|
-
RSpec::Core::RakeTask.new(:spec)
|
5
|
-
|
4
|
+
RSpec::Core::RakeTask.new(:spec, :tag) do |t, task_args|
|
5
|
+
unless task_args[:tag] == 'integration'
|
6
|
+
t.rspec_opts = "--tag ~integration"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
task default: :spec
|
data/lib/wgif.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
1
|
+
require 'wgif/cli'
|
2
|
+
require 'wgif/download_bar'
|
3
|
+
require 'wgif/downloader'
|
4
|
+
require 'wgif/exceptions'
|
5
|
+
require 'wgif/gif_maker'
|
6
|
+
require 'wgif/installer'
|
7
|
+
require 'wgif/uploader'
|
8
|
+
require 'wgif/version'
|
9
|
+
require 'wgif/video'
|
10
|
+
require 'wgif/video_cache'
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'wgif/exceptions'
|
3
|
+
|
4
|
+
module WGif
|
5
|
+
class ArgumentParser
|
6
|
+
|
7
|
+
URL = %r{\Ahttps?://.*\z}
|
8
|
+
TIMESTAMP = /\A\d{1,2}(?::\d{2})+(?:\.\d*)?\z/
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@options = {}
|
12
|
+
@defaults = {
|
13
|
+
trim_from: '00:00:00',
|
14
|
+
duration: 1.0,
|
15
|
+
dimensions: '480'
|
16
|
+
}
|
17
|
+
@parser = OptionParser.new do |opts|
|
18
|
+
opts.on('-f N',
|
19
|
+
'--frames N',
|
20
|
+
'Number of frames in the final gif. (Default 20)') {
|
21
|
+
|n| @options[:frames] = n.to_i
|
22
|
+
}
|
23
|
+
opts.on('-s HH:MM:SS',
|
24
|
+
'--start HH:MM:SS',
|
25
|
+
'Start creating gif from input video at this timestamp. (Default 00:00:00)') {
|
26
|
+
|ts| @options[:trim_from] = ts
|
27
|
+
}
|
28
|
+
opts.on('-d seconds',
|
29
|
+
'--duration seconds',
|
30
|
+
'Number of seconds of input video to capture. (Default 1)') {
|
31
|
+
|d| @options[:duration] = d.to_f
|
32
|
+
}
|
33
|
+
opts.on('-w pixels',
|
34
|
+
'--width pixels',
|
35
|
+
'Width of the gif in pixels. (Default 480px)') {
|
36
|
+
|gs| @options[:dimensions] = gs
|
37
|
+
}
|
38
|
+
opts.on('-u',
|
39
|
+
'--upload',
|
40
|
+
'Upload finished gif to Imgur') {
|
41
|
+
|u| @options[:upload] = u
|
42
|
+
}
|
43
|
+
opts.on('-p',
|
44
|
+
'--preview',
|
45
|
+
'Preview finished gif with Quick Look') {
|
46
|
+
|p| @options[:preview] = p
|
47
|
+
}
|
48
|
+
opts.on_tail('-h',
|
49
|
+
'--help',
|
50
|
+
'Print help information.') {
|
51
|
+
print_help
|
52
|
+
exit
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse(args)
|
58
|
+
options = parse_args(args)
|
59
|
+
validate_args(options)
|
60
|
+
options
|
61
|
+
end
|
62
|
+
|
63
|
+
def argument_summary
|
64
|
+
@parser.summarize
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_args(args)
|
68
|
+
options = @defaults.merge(parse_options args)
|
69
|
+
options.merge(url: args[0], output: args[1])
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_options(args)
|
73
|
+
@parser.parse! args
|
74
|
+
@options
|
75
|
+
end
|
76
|
+
|
77
|
+
def validate_args(args)
|
78
|
+
fail WGif::InvalidUrlException unless args[:url] =~ URL
|
79
|
+
fail WGif::InvalidTimestampException unless args[:trim_from] =~ TIMESTAMP
|
80
|
+
fail WGif::MissingOutputFileException unless args[:output]
|
81
|
+
end
|
82
|
+
|
83
|
+
def print_help
|
84
|
+
puts 'Usage: wgif [YouTube URL] [output file] [options]', "\n"
|
85
|
+
puts argument_summary, "\n"
|
86
|
+
puts <<-example
|
87
|
+
Example:
|
88
|
+
|
89
|
+
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif -s 00:03:30 -d 2 -w 400 --upload
|
90
|
+
|
91
|
+
example
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/wgif/cli.rb
CHANGED
@@ -1,114 +1,70 @@
|
|
1
|
-
require '
|
1
|
+
require 'wgif/argument_parser'
|
2
2
|
require 'wgif/exceptions'
|
3
3
|
require 'wgif/installer'
|
4
4
|
|
5
5
|
module WGif
|
6
6
|
class CLI
|
7
7
|
|
8
|
-
attr_accessor :
|
8
|
+
attr_accessor :argument_parser
|
9
9
|
|
10
10
|
def initialize
|
11
|
-
@
|
12
|
-
|
13
|
-
trim_from: '00:00:00',
|
14
|
-
duration: 1.0,
|
15
|
-
dimensions: '480'
|
16
|
-
}
|
17
|
-
@parser = OptionParser.new do |opts|
|
18
|
-
opts.on('-f N',
|
19
|
-
'--frames N',
|
20
|
-
'Number of frames in the final gif. (Default 20)') {
|
21
|
-
|n| @options[:frames] = n.to_i
|
22
|
-
}
|
23
|
-
opts.on('-s HH:MM:SS',
|
24
|
-
'--start HH:MM:SS',
|
25
|
-
'Start creating gif from input video at this timestamp. (Default 00:00:00)') {
|
26
|
-
|ts| @options[:trim_from] = ts
|
27
|
-
}
|
28
|
-
opts.on('-d seconds',
|
29
|
-
'--duration seconds',
|
30
|
-
'Number of seconds of input video to capture. (Default 5)') {
|
31
|
-
|d| @options[:duration] = d.to_f
|
32
|
-
}
|
33
|
-
opts.on('-w pixels',
|
34
|
-
'--width pixels',
|
35
|
-
'Width of the gif in pixels. (Default 500px)') {
|
36
|
-
|gs| @options[:dimensions] = gs
|
37
|
-
}
|
38
|
-
opts.on('-u',
|
39
|
-
'--upload',
|
40
|
-
'Upload finished GIF to Imgur') {
|
41
|
-
|u| @options[:upload] = !!u
|
42
|
-
}
|
11
|
+
@argument_parser = WGif::ArgumentParser.new
|
12
|
+
end
|
43
13
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
14
|
+
def make_gif(cli_args)
|
15
|
+
WGif::Installer.new.run if cli_args[0] == 'install'
|
16
|
+
load_dependencies
|
17
|
+
rescue_errors do
|
18
|
+
args = @argument_parser.parse(cli_args)
|
19
|
+
frames = convert_video(args)
|
20
|
+
GifMaker.new.make_gif(frames, args[:output], args[:dimensions])
|
21
|
+
upload(args) if args[:upload]
|
22
|
+
preview(args) if args[:preview]
|
50
23
|
end
|
51
24
|
end
|
52
25
|
|
53
|
-
|
54
|
-
|
55
|
-
|
26
|
+
private
|
27
|
+
|
28
|
+
def preview(args)
|
29
|
+
Kernel.system "qlmanage -p #{args[:output]} &>/dev/null"
|
56
30
|
end
|
57
31
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
32
|
+
def upload(args)
|
33
|
+
url = Uploader.new('d2321b02db7ba15').upload(args[:output])
|
34
|
+
puts "Finished. GIF uploaded to Imgur at #{url}"
|
61
35
|
end
|
62
36
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
37
|
+
def convert_video(args)
|
38
|
+
video = Downloader.new.get_video(args[:url])
|
39
|
+
clip = video.trim(args[:trim_from], args[:duration])
|
40
|
+
clip.to_frames(frames: args[:frames])
|
67
41
|
end
|
68
42
|
|
69
|
-
def
|
70
|
-
WGif::Installer.new.run if cli_args[0] == 'install'
|
43
|
+
def load_dependencies
|
71
44
|
require 'wgif/downloader'
|
72
45
|
require 'wgif/gif_maker'
|
73
46
|
require 'wgif/uploader'
|
74
|
-
rescue_errors do
|
75
|
-
args = parse_args cli_args
|
76
|
-
validate_args(args)
|
77
|
-
video = Downloader.new.get_video(args[:url])
|
78
|
-
clip = video.trim(args[:trim_from], args[:duration])
|
79
|
-
frames = clip.to_frames(frames: args[:frames])
|
80
|
-
GifMaker.new.make_gif(frames, args[:output], args[:dimensions])
|
81
|
-
if args[:upload]
|
82
|
-
url = Uploader.new('d2321b02db7ba15').upload(args[:output])
|
83
|
-
puts "Finished. GIF uploaded to Imgur at #{url}"
|
84
|
-
end
|
85
|
-
end
|
86
47
|
end
|
87
48
|
|
88
|
-
private
|
89
|
-
|
90
49
|
def rescue_errors
|
91
|
-
|
92
|
-
yield
|
50
|
+
yield
|
93
51
|
rescue WGif::InvalidUrlException
|
94
|
-
print_error
|
52
|
+
print_error 'That looks like an invalid URL. Check the syntax.'
|
95
53
|
rescue WGif::InvalidTimestampException
|
96
|
-
print_error
|
54
|
+
print_error 'That looks like an invalid timestamp. Check the syntax.'
|
97
55
|
rescue WGif::MissingOutputFileException
|
98
56
|
print_error 'Please specify an output file.'
|
99
57
|
rescue WGif::VideoNotFoundException
|
100
58
|
print_error "WGif can't find a valid YouTube video at that URL."
|
101
59
|
rescue WGif::ClipEncodingException
|
102
|
-
print_error
|
60
|
+
print_error 'WGif encountered an error transcoding the video.'
|
103
61
|
rescue WGif::ImgurException => e
|
104
62
|
print_error <<-error
|
105
63
|
WGif couldn't upload your GIF to Imgur. The Imgur error was:
|
106
64
|
|
107
65
|
#{e}
|
108
66
|
error
|
109
|
-
rescue
|
110
|
-
raise e
|
111
|
-
rescue Exception => e
|
67
|
+
rescue StandardError => e
|
112
68
|
print_error <<-error
|
113
69
|
Something went wrong creating your GIF. The details:
|
114
70
|
|
@@ -117,25 +73,13 @@ Something went wrong creating your GIF. The details:
|
|
117
73
|
|
118
74
|
Please open an issue at: https://github.com/ecmendenhall/wgif/issues/new
|
119
75
|
error
|
120
|
-
end
|
121
76
|
end
|
122
77
|
|
123
78
|
def print_error(message)
|
124
79
|
puts message, "\n"
|
125
|
-
print_help
|
80
|
+
@argument_parser.print_help
|
126
81
|
exit 1
|
127
82
|
end
|
128
83
|
|
129
|
-
def print_help
|
130
|
-
puts "Usage: wgif [YouTube URL] [output file] [options]", "\n"
|
131
|
-
puts @parser.summarize, "\n"
|
132
|
-
puts <<-example
|
133
|
-
Example:
|
134
|
-
|
135
|
-
$ wgif https://www.youtube.com/watch?v=1A78yTvIY1k bjork.gif -s 00:03:30 -d 2 -w 400
|
136
|
-
|
137
|
-
example
|
138
|
-
end
|
139
|
-
|
140
84
|
end
|
141
85
|
end
|
data/lib/wgif/download_bar.rb
CHANGED
data/lib/wgif/downloader.rb
CHANGED
@@ -14,29 +14,26 @@ module WGif
|
|
14
14
|
@cache = WGif::VideoCache.new
|
15
15
|
end
|
16
16
|
|
17
|
-
def video_url
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
raise WGif::VideoNotFoundException
|
23
|
-
end
|
17
|
+
def video_url(youtube_url)
|
18
|
+
urls = ViddlRb.get_urls(youtube_url)
|
19
|
+
urls.first
|
20
|
+
rescue
|
21
|
+
raise WGif::VideoNotFoundException
|
24
22
|
end
|
25
23
|
|
26
|
-
def video_id
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
raise WGif::InvalidUrlException
|
33
|
-
end
|
24
|
+
def video_id(youtube_url)
|
25
|
+
uri = URI(youtube_url)
|
26
|
+
params = CGI.parse(uri.query)
|
27
|
+
params['v'].first
|
28
|
+
rescue
|
29
|
+
raise WGif::InvalidUrlException
|
34
30
|
end
|
35
31
|
|
36
|
-
def get_video
|
32
|
+
def get_video(youtube_url)
|
37
33
|
id = video_id youtube_url
|
38
|
-
|
39
|
-
|
34
|
+
cached_clip = @cache.get(id)
|
35
|
+
if cached_clip
|
36
|
+
cached_clip
|
40
37
|
else
|
41
38
|
temp = load_clip(id, youtube_url)
|
42
39
|
video = WGif::Video.new(id, temp.path)
|
@@ -46,7 +43,7 @@ module WGif
|
|
46
43
|
|
47
44
|
private
|
48
45
|
|
49
|
-
def create_progress_bar
|
46
|
+
def create_progress_bar(request, output_file)
|
50
47
|
size = nil
|
51
48
|
download_bar = WGif::DownloadBar.new
|
52
49
|
|
@@ -56,29 +53,28 @@ module WGif
|
|
56
53
|
end
|
57
54
|
|
58
55
|
request.on_body do |chunk|
|
59
|
-
|
60
|
-
|
56
|
+
output_file.write(chunk)
|
57
|
+
download_bar.increment_progress(chunk.size)
|
61
58
|
end
|
62
59
|
end
|
63
60
|
|
64
|
-
def request_clip
|
65
|
-
clip_url =
|
61
|
+
def request_clip(youtube_url, output_file)
|
62
|
+
clip_url = video_url(youtube_url)
|
66
63
|
request = Typhoeus::Request.new clip_url
|
67
64
|
create_progress_bar(request, output_file)
|
68
65
|
request.run
|
69
66
|
end
|
70
67
|
|
71
|
-
def load_clip
|
72
|
-
FileUtils.mkdir_p
|
68
|
+
def load_clip(id, youtube_url)
|
69
|
+
FileUtils.mkdir_p '/tmp/wgif'
|
73
70
|
temp = File.open("/tmp/wgif/#{id}", 'wb')
|
74
71
|
begin
|
75
72
|
clip = request_clip(youtube_url, temp)
|
76
|
-
|
73
|
+
fail WGif::VideoNotFoundException unless clip.response_code == 200
|
77
74
|
ensure
|
78
75
|
temp.close
|
79
76
|
end
|
80
77
|
temp
|
81
78
|
end
|
82
|
-
|
83
79
|
end
|
84
80
|
end
|