video_screenshoter 0.1.1 → 0.2.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/lib/video_screenshoter.rb +3 -1
- data/lib/video_screenshoter/abstract.rb +33 -6
- data/lib/video_screenshoter/hls.rb +13 -8
- data/lib/video_screenshoter/version.rb +1 -1
- data/lib/video_screenshoter/video.rb +2 -1
- data/test/video_screenshoter_test.rb +51 -10
- data/video_screenshoter.gemspec +1 -0
- metadata +5 -4
data/lib/video_screenshoter.rb
CHANGED
|
@@ -6,14 +6,16 @@ require "video_screenshoter/hls"
|
|
|
6
6
|
|
|
7
7
|
module VideoScreenshoter
|
|
8
8
|
class << self
|
|
9
|
-
attr_accessor :ffmpeg, :output_dir, :output_file, :verbose
|
|
9
|
+
attr_accessor :ffmpeg, :imagemagick, :output_dir, :output_file, :verbose
|
|
10
10
|
end
|
|
11
11
|
self.ffmpeg = '/usr/local/bin/ffmpeg'
|
|
12
12
|
self.output_dir = '/tmp/screenshots'
|
|
13
13
|
self.output_file = 'scr%03d.jpg'
|
|
14
14
|
self.verbose = false
|
|
15
|
+
self.imagemagick = '/usr/bin/convert'
|
|
15
16
|
|
|
16
17
|
def self.new params
|
|
18
|
+
raise ArgumentError.new('Input is needed') unless params[:input]
|
|
17
19
|
raise ArgumentError.new('Incorrect type param') unless [nil, 'hls', 'video'].include? params[:type]
|
|
18
20
|
params[:type] ||= File.extname(params[:input]) == '.m3u8' ? 'hls' : 'video'
|
|
19
21
|
params[:type] == 'hls' ? VideoScreenshoter::Hls.new(params) : VideoScreenshoter::Video.new(params)
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module VideoScreenshoter
|
|
4
4
|
class Abstract
|
|
5
|
-
attr_accessor :ffmpeg, :output_dir, :output_file, :input, :times, :duration, :verbose, :size
|
|
5
|
+
attr_accessor :ffmpeg, :imagemagick, :output_dir, :output_file, :input, :times, :duration, :verbose, :size, :presets
|
|
6
6
|
|
|
7
7
|
def initialize params
|
|
8
|
-
[:ffmpeg, :output_dir, :output_file, :verbose].each do |attr|
|
|
8
|
+
[:ffmpeg, :imagemagick, :output_dir, :output_file, :verbose].each do |attr|
|
|
9
9
|
self.send("#{attr}=".to_sym, params[attr].nil? ? VideoScreenshoter.send(attr) : params[attr])
|
|
10
10
|
end
|
|
11
11
|
FileUtils.mkdir_p self.output_dir
|
|
12
|
-
self.input = params[:input]
|
|
12
|
+
self.input = params[:input]
|
|
13
13
|
self.duration = input_duration
|
|
14
14
|
raise ArgumentError.new('Incorrect or empty m3u8 playlist') if duration.to_i == 0
|
|
15
15
|
self.times = params[:times].to_a.map do |time|
|
|
@@ -21,16 +21,43 @@ module VideoScreenshoter
|
|
|
21
21
|
time
|
|
22
22
|
end.uniq
|
|
23
23
|
self.size = params[:size] ? "-s #{params[:size]}" : ''
|
|
24
|
+
if params[:presets] && params[:presets].is_a?(Hash)
|
|
25
|
+
self.presets = {}
|
|
26
|
+
params[:presets].each do |name, preset|
|
|
27
|
+
self.presets[name.to_sym] = !preset || preset.empty? || preset.index('-') == 0 ? preset : "-resize #{preset}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
24
30
|
end
|
|
25
31
|
|
|
26
|
-
def output_fullpath time
|
|
27
|
-
sprintf(File.join(output_dir, output_file), time)
|
|
32
|
+
def output_fullpath time, preset = nil
|
|
33
|
+
res = sprintf(File.join(output_dir, output_file), time)
|
|
34
|
+
res.sub!(File.extname(res), "_#{preset}#{File.extname(res)}") if preset
|
|
35
|
+
res
|
|
28
36
|
end
|
|
29
37
|
|
|
30
|
-
def
|
|
38
|
+
def ffmpeg_command input, output, time
|
|
31
39
|
"#{ffmpeg} -i #{input} -acodec -an -ss #{time} #{size} -f image2 -vframes 1 -y #{output} 2>/dev/null 1>&2"
|
|
32
40
|
end
|
|
33
41
|
|
|
42
|
+
def imagemagick_command input, preset_name
|
|
43
|
+
preset = presets[preset_name.to_sym]
|
|
44
|
+
if !preset || preset.empty?
|
|
45
|
+
"cp #{input} #{File.join(File.dirname(input), File.basename(input, File.extname(input)) + '_' + preset_name.to_s + File.extname(input))}"
|
|
46
|
+
else
|
|
47
|
+
"#{imagemagick} #{input} #{preset} #{File.join(File.dirname(input), File.basename(input, File.extname(input)) + '_' + preset_name.to_s + File.extname(input))}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def imagemagick_run scr
|
|
52
|
+
if presets
|
|
53
|
+
presets.each do |preset_name, preset|
|
|
54
|
+
cmd = imagemagick_command(scr, preset_name)
|
|
55
|
+
puts cmd if verbose
|
|
56
|
+
`#{cmd}`
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
34
61
|
def run
|
|
35
62
|
raise NotImplementedError
|
|
36
63
|
end
|
|
@@ -13,17 +13,22 @@ module VideoScreenshoter
|
|
|
13
13
|
chunk_limits.push({:start => time, :end => time + chunk.first, :path => chunk.last})
|
|
14
14
|
time + chunk.first
|
|
15
15
|
end
|
|
16
|
+
threads = []
|
|
16
17
|
times.each do |time|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
threads << Thread.new do
|
|
19
|
+
if chunk_limit = chunk_limits.select { |c| time >= c[:start] && time <= c[:end] }.first
|
|
20
|
+
path = File.join(File.dirname(input), chunk_limit[:path])
|
|
21
|
+
rel_time = time - chunk_limit[:start]
|
|
22
|
+
cmd = ffmpeg_command(path, output_fullpath(time), rel_time)
|
|
23
|
+
puts cmd if verbose
|
|
24
|
+
`#{cmd}`
|
|
25
|
+
imagemagick_run output_fullpath(time)
|
|
26
|
+
else
|
|
27
|
+
puts "Time #{time} is incorrect" if verbose
|
|
28
|
+
end
|
|
25
29
|
end
|
|
26
30
|
end
|
|
31
|
+
threads.each { |t| t.join }
|
|
27
32
|
end
|
|
28
33
|
|
|
29
34
|
alias :make_screenshots :run
|
|
@@ -9,9 +9,10 @@ module VideoScreenshoter
|
|
|
9
9
|
|
|
10
10
|
def run
|
|
11
11
|
times.each do |time|
|
|
12
|
-
cmd =
|
|
12
|
+
cmd = ffmpeg_command(input, output_fullpath(time), time)
|
|
13
13
|
puts cmd if verbose
|
|
14
14
|
`#{cmd}`
|
|
15
|
+
imagemagick_run output_fullpath(time)
|
|
15
16
|
end
|
|
16
17
|
end
|
|
17
18
|
|
|
@@ -5,12 +5,32 @@ class VideoScreenshoterTest < Test::Unit::TestCase
|
|
|
5
5
|
setup do
|
|
6
6
|
@input = 'http://techslides.com/demos/sample-videos/small.mp4'
|
|
7
7
|
@output_dir = '/tmp/video_screenshots_test'
|
|
8
|
-
|
|
8
|
+
`rm -r #{@output_dir}` if File.exists?(@output_dir)
|
|
9
9
|
end
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
|
|
11
|
+
context '' do
|
|
12
|
+
setup do
|
|
13
|
+
VideoScreenshoter.new(:input => @input, :output_dir => @output_dir, :times => [1, -2, '50%', '-10%']).make_screenshots
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
should 'create screenshots' do
|
|
17
|
+
[1, 2, 3, 5].each do |sec|
|
|
18
|
+
assert File.exists? File.join(@output_dir, "scr00#{sec}.jpg")
|
|
19
|
+
assert File.size(File.join(@output_dir, "scr00#{sec}.jpg")) > 0
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context 'with presets' do
|
|
25
|
+
setup do
|
|
26
|
+
VideoScreenshoter.new(:input => @input, :output_dir => @output_dir, :times => [1, -1], :presets => {:big => '1024x768', :small => '-resize 400x100^ -gravity center -crop 400x100+0+0'}, :imagemagick => '/usr/bin/convert').make_screenshots
|
|
27
|
+
end
|
|
28
|
+
should 'create screenshots' do
|
|
29
|
+
[1,4].each do |sec|
|
|
30
|
+
assert File.exists? File.join(@output_dir, "scr00#{sec}.jpg")
|
|
31
|
+
assert File.exists? File.join(@output_dir, "scr00#{sec}_big.jpg")
|
|
32
|
+
assert File.exists? File.join(@output_dir, "scr00#{sec}_small.jpg")
|
|
33
|
+
end
|
|
14
34
|
end
|
|
15
35
|
end
|
|
16
36
|
end
|
|
@@ -30,12 +50,33 @@ class VideoScreenshoterTest < Test::Unit::TestCase
|
|
|
30
50
|
setup do
|
|
31
51
|
@input = 'http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear4/prog_index.m3u8'
|
|
32
52
|
@output_dir = '/tmp/hls_screenshots_test'
|
|
33
|
-
|
|
53
|
+
`rm -r #{@output_dir}` if File.exists? @output_dir
|
|
34
54
|
end
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
|
|
56
|
+
context '' do
|
|
57
|
+
setup do
|
|
58
|
+
VideoScreenshoter.new(:input => @input, :output_dir => @output_dir, :times => ['10%', '50%', '-10%']).make_thumbnails
|
|
59
|
+
end
|
|
60
|
+
should 'create screenshots' do
|
|
61
|
+
["scr1620.jpg", "scr180.jpg", "scr900.jpg"].each do |scr|
|
|
62
|
+
assert File.exists?(File.join(@output_dir, scr))
|
|
63
|
+
assert File.size(File.join(@output_dir, scr)) > 0
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context 'with presets' do
|
|
69
|
+
setup do
|
|
70
|
+
VideoScreenshoter.imagemagick = '/usr/bin/convert'
|
|
71
|
+
VideoScreenshoter.new(:input => @input, :output_dir => @output_dir, :times => ['10%', '50%', '-10%'], :presets => {:big => '1024x768', :small => '140x100!'}).make_thumbnails
|
|
72
|
+
end
|
|
73
|
+
should 'create screenshots' do
|
|
74
|
+
["scr1620.jpg", "scr180.jpg", "scr900.jpg"].each do |scr|
|
|
75
|
+
assert File.exists?(File.join(@output_dir, scr))
|
|
76
|
+
assert File.exists?(File.join(@output_dir, scr.sub('.jpg', '_big.jpg')))
|
|
77
|
+
assert File.exists?(File.join(@output_dir, scr.sub('.jpg', '_small.jpg')))
|
|
78
|
+
end
|
|
79
|
+
assert `/usr/local/bin/ffmpeg -i #{File.join(@output_dir, "scr900_small.jpg")} 2>&1`.include?('140x100')
|
|
39
80
|
end
|
|
40
81
|
end
|
|
41
82
|
end
|
data/video_screenshoter.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: video_screenshoter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2013-
|
|
12
|
+
date: 2013-07-10 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: bundler
|
|
@@ -110,7 +110,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
110
110
|
version: '0'
|
|
111
111
|
segments:
|
|
112
112
|
- 0
|
|
113
|
-
hash:
|
|
113
|
+
hash: 1646458223958900487
|
|
114
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
115
|
none: false
|
|
116
116
|
requirements:
|
|
@@ -119,9 +119,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
119
119
|
version: '0'
|
|
120
120
|
segments:
|
|
121
121
|
- 0
|
|
122
|
-
hash:
|
|
122
|
+
hash: 1646458223958900487
|
|
123
123
|
requirements:
|
|
124
124
|
- ffmpeg
|
|
125
|
+
- imagemagick
|
|
125
126
|
rubyforge_project:
|
|
126
127
|
rubygems_version: 1.8.25
|
|
127
128
|
signing_key:
|