video_screenshoter 0.1.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|