vcs_ruby 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/vcs.rb CHANGED
@@ -74,12 +74,12 @@ optparse = OptionParser.new do|opts|
74
74
  opts.on('-C [CAPTURER]', '--capture [CAPTURER]', arguments['--capturer'], 'Capturer: ' + Tools::list_arguments(arguments["--capturer"])) do |capturer|
75
75
  options[:capturer] = capturer
76
76
  end
77
- opts.on( '-T [TITLE]', '--title [TITLE]', 'Set ending time. No caps beyond this.') do |title|
77
+ opts.on( '-T [TITLE]', '--title [TITLE]', 'Set Title') do |title|
78
78
  options[:title] = title
79
79
  end
80
80
  opts.on( '-o [FILE]', '--output [FILE]', 'File name of output. When ommited will be derived from the input filename. Can be repeated for multiple files.') do |file|
81
81
  options[:output] << file
82
- end
82
+ end
83
83
  opts.on( '-s [SIGNATURE]', '--signature [SIGNATURE]', 'Change the image signature to your preference.') do |signature|
84
84
  options[:signature] = signature
85
85
  end
@@ -141,8 +141,8 @@ optparse.parse!
141
141
 
142
142
  Tools::print_help optparse if options[:help] || ARGV.empty?
143
143
 
144
- Tools::verbose = options[:verbose]
145
- Tools::quiet = options[:quiet]
144
+ Configuration.instance.verbose = options[:verbose]
145
+ Configuration.instance.quiet = options[:quiet]
146
146
 
147
147
  # Invoke ContactSheet
148
148
 
@@ -0,0 +1,141 @@
1
+ #
2
+ # FFmpeg Abstraction
3
+ #
4
+
5
+ require 'capturer'
6
+ require 'command'
7
+
8
+ module VCSRuby
9
+ class FFmpeg < Capturer
10
+
11
+ HEADER = 10
12
+ ENCODING_SUPPORT = 2
13
+ VIDEO_CODEC = 3
14
+ NAME = 8
15
+
16
+ attr_reader :info, :video_streams, :audio_streams
17
+
18
+ def initialize video
19
+ @video = video
20
+ @ffmpeg = Command.new :ffmpeg, 'ffmpeg'
21
+ @ffprobe = Command.new :ffmpeg, 'ffprobe'
22
+
23
+ detect_version if available?
24
+ end
25
+
26
+ def file_valid?
27
+ return probe_meta_information
28
+ end
29
+
30
+ def name
31
+ :ffmpeg
32
+ end
33
+
34
+ def available?
35
+ @ffmpeg.available? && !libav?
36
+ end
37
+
38
+ def libav?
39
+ @libav
40
+ end
41
+
42
+ def detect_version
43
+ info = @ffmpeg.execute('-version')
44
+ match = /avconv ([\d|.|-|:]*)/.match(info)
45
+ @libav = !!match
46
+ match = /ffmpeg version ([\d|.]*)/.match(info)
47
+ if match
48
+ @version = match[1]
49
+ end
50
+ end
51
+
52
+ def grab time, image_path
53
+ @ffmpeg.execute "-y -ss #{time.total_seconds} -i \"#{@video.full_path}\" -an -dframes 1 -vframes 1 -vcodec #{format} -f rawvideo \"#{image_path}\""
54
+ end
55
+
56
+ def available_formats
57
+ # Ordered by priority
58
+ image_formats = ['png', 'tiff', 'bmp', 'mjpeg']
59
+ formats = []
60
+
61
+ list = @ffprobe.execute "-codecs"
62
+ list.lines.drop(HEADER).each do |codec|
63
+ name, e, v = format_split(codec)
64
+ formats << name if image_formats.include?(name) && e && v
65
+ end
66
+
67
+ image_formats.select{ |format| formats.include?(format) }.map(&:to_sym)
68
+ end
69
+
70
+ def to_s
71
+ "FFmpeg #{@version}"
72
+ end
73
+
74
+ private
75
+ def format_split line
76
+ e = line[ENCODING_SUPPORT] == 'E'
77
+ v = line[VIDEO_CODEC] == 'V'
78
+
79
+ name = line[NAME..-1].split(' ', 2).first
80
+ return name, e, v
81
+ rescue
82
+ return nil, false, false
83
+ end
84
+
85
+ def probe_meta_information
86
+ check_cache
87
+ parse_meta_info
88
+ return true
89
+ rescue Exception => e
90
+ puts e
91
+ return false
92
+ end
93
+
94
+ def check_cache
95
+ unless @cache
96
+ @cache = @ffprobe.execute("\"#{@video.full_path}\" -show_format -show_streams", "2>&1")
97
+ end
98
+ end
99
+
100
+ def get_hash defines
101
+ result = {}
102
+ defines.lines.each do |line|
103
+ kv = line.split("=")
104
+ result[kv[0].strip] = kv[1].strip if kv.count == 2
105
+ end
106
+ result
107
+ end
108
+
109
+ def parse_meta_info
110
+ parse_format
111
+ parse_audio_streams
112
+ parse_video_streams
113
+ end
114
+
115
+ def parse_format
116
+ @cache.scan(/\[FORMAT\](.*?)\[\/FORMAT\]/m) do |format|
117
+ @info = FFmpegMetaInfo.new(get_hash(format[0]))
118
+ end
119
+ end
120
+
121
+ def parse_audio_streams
122
+ @audio_streams = []
123
+ @cache.scan(/\[STREAM\](.*?)\[\/STREAM\]/m) do |stream|
124
+ info = get_hash(stream[0])
125
+ if info['codec_type'] == 'audio'
126
+ @audio_streams << FFmpegAudioStream.new(info)
127
+ end
128
+ end
129
+ end
130
+
131
+ def parse_video_streams
132
+ @video_streams = []
133
+ @cache.scan(/\[STREAM\](.*?)\[\/STREAM\]/m) do |stream|
134
+ info = get_hash(stream[0])
135
+ if info['codec_type'] == 'video'
136
+ @video_streams << FFmpegVideoStream.new(info)
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,38 @@
1
+ #
2
+ # Implementes AudioStream Interface for FFmpeg
3
+ #
4
+
5
+ # AudioStream = Struct.new(:codec, :channels, :channel_layout, :sample_rate, :bit_rate, :raw)
6
+ module VCSRuby
7
+ class FFmpegAudioStream
8
+ attr_reader :raw
9
+
10
+ def initialize audio_stream
11
+ @raw = audio_stream
12
+ end
13
+
14
+ def codec short = false
15
+ if short
16
+ @raw['codec_name']
17
+ else
18
+ @raw['codec_long_name']
19
+ end
20
+ end
21
+
22
+ def channels
23
+ @raw['channels'].to_i
24
+ end
25
+
26
+ def channel_layout
27
+ @raw['channel_layout']
28
+ end
29
+
30
+ def sample_rate
31
+ @raw['sample_rate'].to_i
32
+ end
33
+
34
+ def bit_rate
35
+ @raw['bit_rate'].to_i
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ #
2
+ # Implementes MetaInfo Interface for FFmpeg
3
+ #
4
+
5
+ # MetaInformation = Struct.new(:duration, :bit_rate, :size, :format, :extension, :raw)
6
+
7
+ require_relative '../time_index'
8
+
9
+ module VCSRuby
10
+ class FFmpegMetaInfo
11
+ attr_reader :raw
12
+
13
+ def initialize meta_info
14
+ @raw = meta_info
15
+ end
16
+
17
+ def duration
18
+ TimeIndex.new(@raw['duration'].to_f)
19
+ end
20
+
21
+ def bit_rate
22
+ @raw['bit_rate'].to_i
23
+ end
24
+
25
+ def size
26
+ @raw['size'].to_i
27
+ end
28
+
29
+ def format
30
+ @raw['format_long_name']
31
+ end
32
+
33
+ def extension
34
+ @raw['format_name']
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,52 @@
1
+ #
2
+ # Implementes VideoStream Interface for FFmpeg
3
+ #
4
+
5
+ # VideoStream = Struct.new(:width, :height, :codec, :color_space, :bit_rate, :frame_rate, :aspect_ratio, :raw)
6
+
7
+ module VCSRuby
8
+ class FFmpegVideoStream
9
+ attr_reader :raw
10
+
11
+ def initialize video_stream
12
+ @raw = video_stream
13
+ end
14
+
15
+ def width
16
+ @raw['width'].to_i
17
+ end
18
+
19
+ def height
20
+ @raw['height'].to_i
21
+ end
22
+
23
+ def codec short = false
24
+ if short
25
+ @raw['codec_name']
26
+ else
27
+ @raw['codec_long_name']
28
+ end
29
+ end
30
+
31
+ def color_space
32
+ if ["unknown", "", nil].include? @raw['color_space']
33
+ @raw['pix_fmt']
34
+ else
35
+ @raw['color_space']
36
+ end
37
+ end
38
+
39
+ def bit_rate
40
+ @raw['bit_rate'].to_i
41
+ end
42
+
43
+
44
+ def frame_rate
45
+ Rational(@raw['r_frame_rate'])
46
+ end
47
+
48
+ def aspect_ratio
49
+ @raw['display_aspect_ratio']
50
+ end
51
+ end
52
+ end
@@ -2,15 +2,15 @@
2
2
  # MPlayer Abstraction
3
3
  #
4
4
 
5
- require 'command'
6
5
  require 'capturer'
7
- require 'fileutils'
6
+ require 'command'
8
7
 
9
8
  module VCSRuby
10
9
  class MPlayer < Capturer
11
10
 
12
11
  HEADER = 2
13
12
 
13
+ attr_reader :info, :video_streams, :audio_streams
14
14
  def initialize video
15
15
  @video = video
16
16
  @mplayer = Command.new :mplayer, 'mplayer'
@@ -18,6 +18,10 @@ module VCSRuby
18
18
  detect_version if available?
19
19
  end
20
20
 
21
+ def file_valid?
22
+ return probe_meta_information
23
+ end
24
+
21
25
  def name
22
26
  :mplayer
23
27
  end
@@ -25,55 +29,16 @@ module VCSRuby
25
29
  def available?
26
30
  @mplayer.available?
27
31
  end
28
-
32
+
29
33
  def detect_version
30
34
  info = @mplayer.execute('')
31
35
  match = /MPlayer (.*),/.match(info)
32
36
  @version = match[1] if match
33
37
  end
34
38
 
35
- def length
36
- load_probe
37
- @length
38
- end
39
-
40
- def width
41
- load_probe
42
- @width
43
- end
44
-
45
- def height
46
- load_probe
47
- @height
48
- end
49
-
50
- def video_codec
51
- load_probe
52
- @video_codec
53
- end
54
-
55
- def audio_codec
56
- load_probe
57
- @audio_codec
58
- end
59
-
60
- def fps
61
- load_probe
62
- @fps
63
- end
64
-
65
- def file_pattern n
66
- if format == :jpeg
67
- "#{"%08d" % n}.jpg"
68
- else
69
- "#{"%08d" % n}.#{format.to_s}"
70
- end
71
- end
72
-
73
39
  def grab time, image_path
74
- @mplayer.execute "-sws 9 -ao null -benchmark -vo #{@format} -quiet -frames 5 -ss #{time.total_seconds} \"#{@video}\""
75
- (1..4).each { |n| FileUtils.rm(file_pattern(n)) }
76
- FileUtils.mv(file_pattern(5), image_path)
40
+ @mplayer.execute "-vo png -ss #{time.total_seconds} -endpos 0 \"#{@video.full_path}\""
41
+ FileUtils.mv(file_pattern(1), image_path)
77
42
  end
78
43
 
79
44
  def available_formats
@@ -90,35 +55,55 @@ module VCSRuby
90
55
  image_formats.select{ |format| formats.include?(format) }.map(&:to_sym)
91
56
  end
92
57
 
93
- def format_split line
94
- name = line.strip.split(' ', 2).first
95
- end
96
-
97
58
  def to_s
98
59
  "MPlayer #{@version}"
99
60
  end
61
+
100
62
  private
101
- def parsed?
102
- !!@parsed
63
+ def format_split line
64
+ name = line.strip.split(' ', 2).first
65
+ end
66
+ def probe_meta_information
67
+ check_cache
68
+ parse_meta_info
69
+ return true
70
+ rescue Exception => e
71
+ puts e
72
+ return false
103
73
  end
104
74
 
105
- def load_probe
106
- unless parsed?
107
- parse_identify(@mplayer.execute("-ao null -vo null -identify -frames 0 -quiet \"#{@video}\""))
75
+ def check_cache
76
+ unless @cache
77
+ @cache = @mplayer.execute("-ao null -vo null -identify -frames 0 -really-quiet \"#{@video.full_path}\"")
108
78
  end
109
79
  end
110
80
 
111
- def parse_identify info
112
- info.lines.each do |line|
113
- key, value = line.split('=', 2)
114
- @length = TimeIndex.new value.to_f if key == 'ID_LENGTH'
115
- @width = value.to_i if key == 'ID_VIDEO_WIDTH'
116
- @height = value.to_i if key == 'ID_VIDEO_HEIGHT'
117
- @video_codec = value.chomp if key == 'ID_VIDEO_FORMAT'
118
- @audio_codec = value.chomp if key == 'ID_AUDIO_FORMAT'
119
- @fps = value.to_f if key == 'ID_VIDEO_FPS'
81
+ def get_hash defines
82
+ result = {}
83
+ defines.lines.each do |line|
84
+ kv = line.split("=")
85
+ result[kv[0].strip] = kv[1].strip if kv.count == 2
86
+ end
87
+ result
88
+ end
89
+
90
+ def parse_meta_info
91
+ mplayer_hash = get_hash(@cache)
92
+ @info = MPlayerMetaInfo.new(mplayer_hash, File.size(@video.full_path))
93
+
94
+ # Todo: Handling of multiple streams
95
+ @video_streams = []
96
+ @video_streams << MPlayerVideoStream.new(mplayer_hash)
97
+ @audio_streams = []
98
+ @audio_streams << MPlayerAudioStream.new(mplayer_hash)
99
+ end
100
+
101
+ def file_pattern n
102
+ if format == :jpeg
103
+ "#{"%08d" % n}.jpg"
104
+ else
105
+ "#{"%08d" % n}.#{format.to_s}"
120
106
  end
121
- @parsed = true
122
107
  end
123
108
  end
124
109
  end
@@ -0,0 +1,35 @@
1
+ #
2
+ # Implementes AudioStream Interface for MPlayer
3
+ #
4
+
5
+ # AudioStream = Struct.new(:codec, :channels, :channel_layout, :sample_rate, :bit_rate, :raw)
6
+ module VCSRuby
7
+ class MPlayerAudioStream
8
+ attr_reader :raw
9
+
10
+ def initialize audio_stream
11
+ @raw = audio_stream
12
+
13
+ end
14
+
15
+ def codec short = false
16
+ @raw['ID_AUDIO_CODEC']
17
+ end
18
+
19
+ def channels
20
+ @raw['ID_AUDIO_NCH'].to_i
21
+ end
22
+
23
+ def channel_layout
24
+ ''
25
+ end
26
+
27
+ def sample_rate
28
+ @raw['ID_AUDIO_RATE'].to_i
29
+ end
30
+
31
+ def bit_rate
32
+ @raw['ID_AUDIO_BITRATE'].to_i
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,40 @@
1
+ #
2
+ # Implementes MetaInfo Interface for MPlayer
3
+ #
4
+
5
+ # MetaInformation = Struct.new(:duration, :bit_rate, :size, :format, :extension, :raw)
6
+
7
+ require_relative '../time_index'
8
+
9
+ module VCSRuby
10
+ class MPlayerMetaInfo
11
+ attr_reader :raw
12
+
13
+ def initialize meta_info, filesize
14
+ @raw = meta_info
15
+ @filesize = filesize
16
+ end
17
+
18
+ def duration
19
+ TimeIndex.new(@raw['ID_LENGTH'].to_f)
20
+ end
21
+
22
+ def bit_rate
23
+ @raw['ID_AUDIO_BITRATE'].to_i + @raw['ID_VIDEO_BITRATE'].to_i
24
+ end
25
+
26
+ def size
27
+ @filesize
28
+ end
29
+
30
+ def format
31
+ extension
32
+ end
33
+
34
+ def extension
35
+ ext = File.extname(@raw['ID_FILENAME'])
36
+ ext[0] = ''
37
+ ext
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,44 @@
1
+ #
2
+ # Implementes VideoStream Interface for MPlayer
3
+ #
4
+
5
+ # VideoStream = Struct.new(:width, :height, :codec, :color_space, :bit_rate, :frame_rate, :aspect_ratio, :raw)
6
+
7
+ module VCSRuby
8
+ class MPlayerVideoStream
9
+ attr_reader :raw
10
+
11
+ def initialize video_stream
12
+ @raw = video_stream
13
+ end
14
+
15
+ def width
16
+ @raw['ID_VIDEO_WIDTH'].to_i
17
+ end
18
+
19
+ def height
20
+ @raw['ID_VIDEO_HEIGHT'].to_i
21
+ end
22
+
23
+ def codec short = false
24
+ @raw['ID_VIDEO_FORMAT']
25
+ end
26
+
27
+ def color_space
28
+ ''
29
+ end
30
+
31
+ def bit_rate
32
+ @raw['ID_VIDEO_BITRATE'].to_i
33
+ end
34
+
35
+
36
+ def frame_rate
37
+ @raw['ID_VIDEO_FPS'].to_f
38
+ end
39
+
40
+ def aspect_ratio
41
+ ''
42
+ end
43
+ end
44
+ end
data/lib/capturer.rb CHANGED
@@ -2,42 +2,38 @@
2
2
  # Capturer Baseclass
3
3
  #
4
4
 
5
+ require 'vcs'
6
+
5
7
  module VCSRuby
6
8
  class Capturer
9
+ $formats = { :png => 'png', :bmp => 'bmp', :tiff => 'tif', :mjpeg => 'jpg', :jpeg => 'jpg', :jpg => 'jpg' }
10
+
7
11
  def available?
8
12
  false
9
13
  end
10
14
 
11
- def name
12
- raise "NotImplementedException"
13
- end
15
+ def self.initialize_capturers video
16
+ capturers = []
14
17
 
15
- def load_video
16
- raise "NotImplementedException"
18
+ puts "Available capturers: #{available_capturers.map{ |c| c.to_s }.join(', ')}" if Tools.verbose?
17
19
  end
18
20
 
19
- def length
20
- raise "NotImplementedException"
21
- end
21
+ def self.create
22
+ capturers = []
22
23
 
23
- def width
24
- raise "NotImplementedException"
25
- end
24
+ capturers << LibAV.new(video)
25
+ capturers << MPlayer.new(video)
26
+ capturers << FFmpeg.new(video)
26
27
 
27
- def height
28
- raise "NotImplementedException"
28
+ return capturers.first
29
29
  end
30
30
 
31
- def grab time, image_path
32
- raise "NotImplementedException"
33
- end
34
-
35
- def available_formats
36
- raise "NotImplementedException"
31
+ def format
32
+ @format || available_formats.first
37
33
  end
38
34
 
39
- def format
40
- @format
35
+ def format_extension
36
+ $formats[format]
41
37
  end
42
38
 
43
39
  def format= format
data/lib/command.rb CHANGED
@@ -17,7 +17,7 @@ module VCSRuby
17
17
 
18
18
  def execute parameter, streams = 0, no_error = false
19
19
  raise "Command '#{name}' not available" unless available?
20
- puts "#{@command} #{parameter} #{streams}" if Tools.verbose?
20
+ puts "#{@command} #{parameter}" if Configuration.instance.verbose?
21
21
  result = nil
22
22
  if Tools::windows?
23
23
  streams = '2> nul' if streams === 0
@@ -40,7 +40,9 @@ private
40
40
  ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
41
41
  exts.each do |ext|
42
42
  exe = File.join(path, "#{cmd}#{ext}")
43
- return exe if File.executable?(exe) && !File.directory?(exe)
43
+ if File.executable?(exe) && !File.directory?(exe)
44
+ return exe
45
+ end
44
46
  end
45
47
  end
46
48
  return nil