vcs_ruby 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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