video_screenshoter 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module VideoScreenshoter
|
4
|
+
class Abstract
|
5
|
+
attr_accessor :ffmpeg, :output_dir, :output_file, :input, :times, :duration, :verbose, :size
|
6
|
+
|
7
|
+
def initialize params
|
8
|
+
[:ffmpeg, :output_dir, :output_file, :verbose].each do |attr|
|
9
|
+
self.send("#{attr}=".to_sym, params[attr].nil? ? VideoScreenshoter.send(attr) : params[attr])
|
10
|
+
end
|
11
|
+
FileUtils.mkdir_p self.output_dir
|
12
|
+
self.input = params[:input] or raise ArgumentError.new('Input is needed')
|
13
|
+
self.duration = input_duration
|
14
|
+
raise ArgumentError.new('Incorrect or empty m3u8 playlist') if duration.to_i == 0
|
15
|
+
self.times = params[:times].to_a.map do |time|
|
16
|
+
if time.is_a?(String) && matches = time.match(/(.*)%$/)
|
17
|
+
time = matches[1].to_f / 100 * duration
|
18
|
+
end
|
19
|
+
time = duration + time if time < 0
|
20
|
+
time = time.to_i
|
21
|
+
time
|
22
|
+
end.uniq
|
23
|
+
self.size = params[:size] ? "-s #{params[:size]}" : ''
|
24
|
+
end
|
25
|
+
|
26
|
+
def output_fullpath time
|
27
|
+
sprintf(File.join(output_dir, output_file), time)
|
28
|
+
end
|
29
|
+
|
30
|
+
def command input, output, time
|
31
|
+
"#{ffmpeg} -i #{input} -acodec -an -ss #{time} #{size} -f image2 -vframes 1 -y #{output} 2>/dev/null 1>&2"
|
32
|
+
end
|
33
|
+
|
34
|
+
def run
|
35
|
+
raise NotImplementedError
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def input_duration
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module VideoScreenshoter
|
4
|
+
class Hls < Abstract
|
5
|
+
|
6
|
+
def initialize params
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
chunk_limits = []
|
12
|
+
chunks.inject(0.0) do |time, chunk|
|
13
|
+
chunk_limits.push({:start => time, :end => time + chunk.first, :path => chunk.last})
|
14
|
+
time + chunk.first
|
15
|
+
end
|
16
|
+
times.each do |time|
|
17
|
+
chunk_limit = chunk_limits.select { |c| time > c[:start] && time <= c[:end] }.first
|
18
|
+
path = File.join(File.dirname(input), chunk_limit[:path])
|
19
|
+
rel_time = time - chunk_limit[:start]
|
20
|
+
cmd = command(path, output_fullpath(time), rel_time)
|
21
|
+
puts cmd if verbose
|
22
|
+
`#{cmd}`
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
alias :make_screenshots :run
|
27
|
+
alias :make_thumbnails :run
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_accessor :chunks
|
32
|
+
|
33
|
+
def input_duration
|
34
|
+
(self.chunks = parse_playlist).map(&:first).inject(:+)
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_playlist
|
38
|
+
require 'open-uri'
|
39
|
+
open(input) { |f| f.read }.scan(/EXTINF:([\d.]+).*?\n(.*?)\n/).map { |c| [c.first.to_f, c.last] }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,34 +1,15 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module VideoScreenshoter
|
4
|
-
class Video
|
5
|
-
attr_accessor :ffmpeg, :output_dir, :output_file, :input, :times, :duration, :verbose, :size
|
4
|
+
class Video < Abstract
|
6
5
|
|
7
6
|
def initialize params
|
8
|
-
|
9
|
-
self.send("#{attr}=".to_sym, params[attr].nil? ? VideoScreenshoter.send(attr) : params[attr])
|
10
|
-
end
|
11
|
-
FileUtils.mkdir_p self.output_dir
|
12
|
-
self.input = params[:input] or raise ArgumentError.new('Input is needed')
|
13
|
-
self.duration = input_duration or raise ArgumentError.new('Incorrect video file')
|
14
|
-
self.times = params[:times].to_a.map do |time|
|
15
|
-
if time.is_a?(String) && matches = time.match(/(.*)%$/)
|
16
|
-
time = matches[1].to_f / 100 * duration
|
17
|
-
end
|
18
|
-
time = duration + time if time < 0
|
19
|
-
time = time.to_i
|
20
|
-
time
|
21
|
-
end
|
22
|
-
self.size = params[:size] ? "-s #{params[:size]}" : ''
|
23
|
-
end
|
24
|
-
|
25
|
-
def output_fullpath time
|
26
|
-
sprintf(File.join(output_dir, output_file), time)
|
7
|
+
super
|
27
8
|
end
|
28
9
|
|
29
10
|
def run
|
30
11
|
times.each do |time|
|
31
|
-
cmd =
|
12
|
+
cmd = command(input, output_fullpath(time), time)
|
32
13
|
puts cmd if verbose
|
33
14
|
`#{cmd}`
|
34
15
|
end
|
data/lib/video_screenshoter.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require "video_screenshoter/version"
|
2
2
|
require "fileutils"
|
3
|
+
require "video_screenshoter/abstract"
|
3
4
|
require "video_screenshoter/video"
|
5
|
+
require "video_screenshoter/hls"
|
4
6
|
|
5
7
|
module VideoScreenshoter
|
6
8
|
class << self
|
@@ -12,6 +14,8 @@ module VideoScreenshoter
|
|
12
14
|
self.verbose = false
|
13
15
|
|
14
16
|
def self.new params
|
15
|
-
|
17
|
+
raise ArgumentError.new('Incorrect type param') unless [nil, 'hls', 'video'].include? params[:type]
|
18
|
+
params[:type] ||= File.extname(params[:input]) == '.m3u8' ? 'hls' : 'video'
|
19
|
+
params[:type] == 'hls' ? VideoScreenshoter::Hls.new(params) : VideoScreenshoter::Video.new(params)
|
16
20
|
end
|
17
21
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class VideoScreenshoterTest < Test::Unit::TestCase
|
4
|
-
context '
|
4
|
+
context 'video' do
|
5
5
|
setup do
|
6
6
|
@input = 'http://techslides.com/demos/sample-videos/small.mp4'
|
7
|
-
@output_dir = '/tmp/
|
7
|
+
@output_dir = '/tmp/video_screenshots_test'
|
8
8
|
VideoScreenshoter.new(:input => @input, :output_dir => @output_dir, :times => [1, -2, '50%', '-10%']).make_screenshots
|
9
9
|
end
|
10
10
|
should 'create screenshots' do
|
@@ -14,4 +14,30 @@ class VideoScreenshoterTest < Test::Unit::TestCase
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
17
|
+
|
18
|
+
context 'hls' do
|
19
|
+
context 'with 1 level playlist' do
|
20
|
+
setup do
|
21
|
+
@input = 'http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8'
|
22
|
+
@output_dir = '/tmp/hls_screenshots_test'
|
23
|
+
end
|
24
|
+
should 'raise error' do
|
25
|
+
assert_raises(ArgumentError) { VideoScreenshoter.new(:input => @input, :output_dir => @output_dir, :times => ['10%', '50%']).make_thumbnails }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with 2 level playlist' do
|
30
|
+
setup do
|
31
|
+
@input = 'http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear4/prog_index.m3u8'
|
32
|
+
@output_dir = '/tmp/hls_screenshots_test'
|
33
|
+
VideoScreenshoter.new(:input => @input, :output_dir => @output_dir, :times => ['10%', '50%', '-10%']).make_thumbnails
|
34
|
+
end
|
35
|
+
should 'create screenshots' do
|
36
|
+
["scr1620.jpg", "scr180.jpg", "scr900.jpg"].each do |scr|
|
37
|
+
assert File.exists?(File.join(@output_dir, scr))
|
38
|
+
assert File.size(File.join(@output_dir, scr)) > 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
17
43
|
end
|
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.0
|
4
|
+
version: 0.1.0
|
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-05-
|
12
|
+
date: 2013-05-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -88,6 +88,8 @@ files:
|
|
88
88
|
- README.md
|
89
89
|
- Rakefile
|
90
90
|
- lib/video_screenshoter.rb
|
91
|
+
- lib/video_screenshoter/abstract.rb
|
92
|
+
- lib/video_screenshoter/hls.rb
|
91
93
|
- lib/video_screenshoter/version.rb
|
92
94
|
- lib/video_screenshoter/video.rb
|
93
95
|
- test/test_helper.rb
|
@@ -108,7 +110,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
110
|
version: '0'
|
109
111
|
segments:
|
110
112
|
- 0
|
111
|
-
hash:
|
113
|
+
hash: -4324957323078991671
|
112
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
115
|
none: false
|
114
116
|
requirements:
|
@@ -117,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
119
|
version: '0'
|
118
120
|
segments:
|
119
121
|
- 0
|
120
|
-
hash:
|
122
|
+
hash: -4324957323078991671
|
121
123
|
requirements:
|
122
124
|
- ffmpeg
|
123
125
|
rubyforge_project:
|