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,3 +1,3 @@
1
1
  module VideoScreenshoter
2
- VERSION = "0.0.6"
2
+ VERSION = "0.1.0"
3
3
  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
- [: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 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 = "#{ffmpeg} -i #{input} -acodec -an -ss #{time} #{size} -f image2 -vframes 1 -y #{output_fullpath(time)} 2>/dev/null 1>&2"
12
+ cmd = command(input, output_fullpath(time), time)
32
13
  puts cmd if verbose
33
14
  `#{cmd}`
34
15
  end
@@ -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
- VideoScreenshoter::Video.new params
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 'make screenshots' do
4
+ context 'video' do
5
5
  setup do
6
6
  @input = 'http://techslides.com/demos/sample-videos/small.mp4'
7
- @output_dir = '/tmp/screenshots_test'
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.6
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-15 00:00:00.000000000 Z
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: 1571913647041927257
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: 1571913647041927257
122
+ hash: -4324957323078991671
121
123
  requirements:
122
124
  - ffmpeg
123
125
  rubyforge_project: