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.
@@ -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] or raise ArgumentError.new('Input is needed')
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 command input, output, time
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
- if 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
- else
24
- puts "Time #{time} is incorrect" if verbose
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
@@ -1,3 +1,3 @@
1
1
  module VideoScreenshoter
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -9,9 +9,10 @@ module VideoScreenshoter
9
9
 
10
10
  def run
11
11
  times.each do |time|
12
- cmd = command(input, output_fullpath(time), time)
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
- VideoScreenshoter.new(:input => @input, :output_dir => @output_dir, :times => [1, -2, '50%', '-10%']).make_screenshots
8
+ `rm -r #{@output_dir}` if File.exists?(@output_dir)
9
9
  end
10
- should 'create screenshots' do
11
- [1, 2, 3, 5].each do |sec|
12
- assert File.exists? File.join(@output_dir, "scr00#{sec}.jpg")
13
- assert File.size(File.join(@output_dir, "scr00#{sec}.jpg")) > 0
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
- VideoScreenshoter.new(:input => @input, :output_dir => @output_dir, :times => ['10%', '50%', '-10%']).make_thumbnails
53
+ `rm -r #{@output_dir}` if File.exists? @output_dir
34
54
  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
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
@@ -24,4 +24,5 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency "shoulda-context"
25
25
 
26
26
  spec.requirements << 'ffmpeg'
27
+ spec.requirements << 'imagemagick'
27
28
  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.1.1
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-05-17 00:00:00.000000000 Z
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: -2399110922537615442
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: -2399110922537615442
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: