vtools 0.0.1

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.
Files changed (47) hide show
  1. data/INSTALL +0 -0
  2. data/LICENSE +20 -0
  3. data/README.md +131 -0
  4. data/Rakefile +29 -0
  5. data/bin/vtools +22 -0
  6. data/doc/CONFIG.md +36 -0
  7. data/doc/HOOKS.md +37 -0
  8. data/doc/LIB_EXAMPLE.md +109 -0
  9. data/extconf.rb +7 -0
  10. data/lib/vtools.rb +79 -0
  11. data/lib/vtools/config.rb +91 -0
  12. data/lib/vtools/convert_options.rb +155 -0
  13. data/lib/vtools/converter.rb +98 -0
  14. data/lib/vtools/errors.rb +21 -0
  15. data/lib/vtools/handler.rb +43 -0
  16. data/lib/vtools/harvester.rb +71 -0
  17. data/lib/vtools/job.rb +48 -0
  18. data/lib/vtools/options.rb +101 -0
  19. data/lib/vtools/shared_methods.rb +131 -0
  20. data/lib/vtools/storage.rb +67 -0
  21. data/lib/vtools/thumbnailer.rb +93 -0
  22. data/lib/vtools/thumbs_options.rb +80 -0
  23. data/lib/vtools/version.rb +6 -0
  24. data/lib/vtools/version.rb~ +4 -0
  25. data/lib/vtools/video.rb +158 -0
  26. data/setup.rb +1585 -0
  27. data/spec/config_spec.rb +142 -0
  28. data/spec/convert_options_spec.rb +284 -0
  29. data/spec/converter_spec.rb +167 -0
  30. data/spec/errors_spec.rb +39 -0
  31. data/spec/fixtures/outputs/file_with_iso-8859-1.txt +35 -0
  32. data/spec/fixtures/outputs/file_with_no_audio.txt +18 -0
  33. data/spec/fixtures/outputs/file_with_non_supported_audio.txt +29 -0
  34. data/spec/fixtures/outputs/file_with_start_value.txt +19 -0
  35. data/spec/fixtures/outputs/file_with_surround_sound.txt +19 -0
  36. data/spec/handler_spec.rb +81 -0
  37. data/spec/harvester_spec.rb +189 -0
  38. data/spec/job_spec.rb +130 -0
  39. data/spec/options_spec.rb +52 -0
  40. data/spec/shared_methods_spec.rb +351 -0
  41. data/spec/spec_helper.rb +20 -0
  42. data/spec/storage_spec.rb +106 -0
  43. data/spec/thumbnailer_spec.rb +178 -0
  44. data/spec/thumbs_options_spec.rb +159 -0
  45. data/spec/video_spec.rb +274 -0
  46. data/vtools.gemspec +29 -0
  47. metadata +177 -0
@@ -0,0 +1,48 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module VTools
4
+
5
+ # job instance
6
+ class Job
7
+ attr_reader :video, :id
8
+
9
+ # constructor
10
+ def initialize config
11
+ @id = self.object_id.to_i
12
+ @config = validate config
13
+ @video = Video.new @config.file
14
+ end
15
+
16
+ # execute job
17
+ def execute
18
+ # start callback
19
+ Handler.exec :job_started, @video, @config.action
20
+
21
+ result = @video.get_info # we always get info
22
+
23
+ case @config.action
24
+ when /^convert$/i
25
+ result = @video.convert @config.setup # will return video
26
+ when /^thumbs$/i
27
+ result = @video.create_thumbs @config.setup # will return thumbs array
28
+ end
29
+
30
+ # final callbacks
31
+ Handler.exec :job_finished, result, @video, @config.action
32
+ result
33
+ end
34
+
35
+ # parse video options
36
+ def validate options
37
+ unless options.action =~ /^convert|thumbs|info$/ && options.file && !options.file.empty?
38
+ raise ConfigError, "Invalid action (config: #{options.marshal_dump})"
39
+ else
40
+ return options if options.action =~ /^info$/
41
+ # empty set error
42
+ raise ConfigError, "Configuration is empty" if !options.setup || options.setup.empty?
43
+ end
44
+ options
45
+ end
46
+
47
+ end # Job
48
+ end # VTools
@@ -0,0 +1,101 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module VTools
4
+
5
+ # Takes care about passed via ARGV options
6
+ class Options
7
+ class << self
8
+ # parse config data
9
+ def parse! args
10
+ # slice options afer "--" sign if given
11
+ # catch help & version calls
12
+ case
13
+ when(args.include? '--')
14
+ argv = args[ ( args.index('--') + 1)..-1]
15
+ when(args.include? '-h')
16
+ argv = ['-h']
17
+ when(args.include? '-v')
18
+ argv = ['-v']
19
+ else
20
+ return
21
+ end
22
+
23
+ # parse passed options
24
+ OptionParser.new do |opts|
25
+
26
+ dot = ' ' * 4
27
+ opts.banner = "Usage: vtools <command> <daemon options> -- <options>\n" +
28
+ "\n" +
29
+ "Commands:\n" +
30
+ "#{dot}start start an instance of the application\n" +
31
+ "#{dot}stop stop all instances of the application\n" +
32
+ "#{dot}restart stop all instances and restart them afterwards\n" +
33
+ "#{dot}reload send a SIGHUP to all instances of the application\n" +
34
+ "#{dot}run start the application and stay on top\n" +
35
+ "#{dot}zap set the application to a stopped state\n" +
36
+ "#{dot}status show status (PID) of application instances\n" +
37
+ "\n" +
38
+ "Daemon options:\n" +
39
+
40
+ "#{dot}-t, --ontop Stay on top (does not daemonize)\n" +
41
+ "#{dot}-f, --force Force operation\n" +
42
+ "#{dot}-n, --no_wait Do not wait for processes to stop"
43
+
44
+ opts.separator ""
45
+ opts.separator "Options:"
46
+
47
+ # add config file
48
+ opts.on("-c", "--config-file FILE", "Use configuration file") do |f|
49
+ CONFIG[:config_file] = f
50
+ end
51
+
52
+ # add log file
53
+ opts.on("-l", "--log-file [FILE]", "Log process into file (default STDOUT)") do |l|
54
+ CONFIG[:logging] = true
55
+ CONFIG[:log_file] = "#{CONFIG[:PWD]}/#{l}" if l
56
+ end
57
+
58
+ # include path
59
+ opts.on("-I", "--include PATH",
60
+ "specify $LOAD_PATH (may be used more than once)") do |path|
61
+ $LOAD_PATH.unshift(*path.split(/:/))
62
+ end
63
+
64
+ # connect additional library
65
+ opts.on("-r", "--require LIBRARY",
66
+ "require the library, before daemon starts") do |library|
67
+ CONFIG[:library] << library
68
+ end
69
+
70
+ opts.separator ""
71
+
72
+ # VTools version
73
+ opts.on_tail("-v", "--version", "Show current version") do |operator|
74
+ puts VERSION.join(',')
75
+ exit
76
+ end
77
+
78
+ # options help
79
+ opts.on_tail("-h", "--help", "Show this message") do
80
+ puts opts
81
+ exit
82
+ end
83
+
84
+ # do parse!
85
+ begin
86
+ if argv.empty?
87
+ argv = ["-h"]
88
+ STDERR.puts "ERROR: No command given\n\n"
89
+ end
90
+
91
+ opts.parse!(argv)
92
+ Handler.exec :config_parsed # callback
93
+ rescue OptionParser::ParseError => e
94
+ STDERR.puts "ERROR: #{e.message}\n\n", opts
95
+ exit(-1)
96
+ end
97
+ end # OptionParser.new
98
+ end # parse!
99
+ end # class <<
100
+ end # Options
101
+ end # VTools
@@ -0,0 +1,131 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module VTools
4
+
5
+ # shared methods
6
+ module SharedMethods
7
+ # both static & instance bindings
8
+ module Common
9
+
10
+ @@logger = nil
11
+
12
+ # custom logger
13
+ def logger= logger
14
+ @@logger = logger
15
+ end
16
+
17
+ # logger mechanics
18
+ def log level, message = ""
19
+
20
+ if CONFIG[:logging]
21
+ unless @@logger
22
+ output = CONFIG[:log_file] || STDOUT
23
+ logger = Logger.new(output, 1000, 1024000)
24
+ logger.level = Logger::INFO
25
+ @@logger = logger
26
+ end
27
+
28
+ @@logger.send(level, message) if @@logger
29
+ end
30
+ end
31
+
32
+ # converts json to the ruby object
33
+ # returns nil on invalid JSON
34
+ def json_to_obj json_str
35
+ hash_to_obj(parse_json(json_str))
36
+ end
37
+
38
+ # convert hash into object
39
+ def hash_to_obj hash
40
+ OpenStruct.new(hash) rescue raise ConfigError, "Can't convert setup to object"
41
+ end
42
+
43
+ # parse json string into hash
44
+ def parse_json str
45
+ JSON.parse str rescue raise ConfigError, "Invalid JSON"
46
+ end
47
+
48
+ # set symbols in place of string keys
49
+ def keys_to_sym hash
50
+ return hash unless hash.is_a? Hash
51
+ hash.inject({}){ |opts,(k,v)| opts[k.to_sym] = v; opts }
52
+ end
53
+
54
+ # config accessor
55
+ def config data
56
+ CONFIG[data]
57
+ end
58
+
59
+ # calls TCP/IP applications
60
+ def network_call url
61
+ require "socket"
62
+
63
+ url =~ %r#^([a-z]+://)?(?:www.)?([^/:]+)(:[\d]+)?(.*)$#
64
+ protocol, host, port, route =
65
+ ($1 || '')[0...-3], $2, ($3 || ":80")[1..-1].to_i, "/#{$4.to_s.gsub(/^\//, '')}"
66
+
67
+ begin
68
+ sock = TCPSocket.open(host, port)
69
+ sock.print "GET #{route} HTTP/1.0\r\n\r\n"
70
+ response = sock.read.split("\r\n\r\n", 2).reverse[0]
71
+ sock.close
72
+ rescue => e
73
+ log :error, e
74
+ end
75
+
76
+ response
77
+ end
78
+
79
+ # function to create correct subdirectories to the file
80
+ def generate_path file_name, scope = "video"
81
+ storage = CONFIG[:"#{scope}_storage"]
82
+ return instance_exec(file_name, &storage) if storage.is_a? Proc
83
+ (!storage || storage.empty? ? CONFIG[:PWD] : storage).to_s.strip.gsub(%r#/$#, '')
84
+ end
85
+
86
+ # path generator setter
87
+ def path_generator scope = nil, &block
88
+ if scope
89
+ scope = "thumb" unless scope == "video"
90
+ CONFIG[:"#{scope}_storage"] = block
91
+ else
92
+ CONFIG[:thumb_storage] = CONFIG[:video_storage] = block
93
+ end if block_given?
94
+ end
95
+
96
+ # encoding fixer for iso-8859-1
97
+ def fix_encoding(output)
98
+ output[/test/] # Running a regexp on the string throws error if it's not UTF-8
99
+ rescue ArgumentError
100
+ output.force_encoding("ISO-8859-1")
101
+ end
102
+ end # Common
103
+
104
+ # class bindings
105
+ module Static
106
+ include Common
107
+ # load external libs
108
+ def load_libs
109
+ CONFIG[:library].each do |lib|
110
+ begin
111
+ require lib
112
+ rescue LoadError => e
113
+ print "Library file could not be found (#{lib})\n"
114
+ rescue SyntaxError => e
115
+ print "Library may contain non ascii characters (#{lib}).\n\n" +
116
+ "Try to set file encoding to 'binary'.\n\n"
117
+ end
118
+ end
119
+ end
120
+ end # Static
121
+
122
+ # instance bindings
123
+ module Instance
124
+ include Common
125
+ end # Instance
126
+
127
+ # extend it
128
+ def self.included (klass) klass.extend Static end
129
+ include Instance
130
+ end # SharedMethods
131
+ end # VTools
@@ -0,0 +1,67 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module VTools
4
+
5
+ # Interface API to the Message Queue or Database Server (AdapterInterface)
6
+ class Storage
7
+ include SharedMethods
8
+
9
+ @actions = {}
10
+
11
+ class << self
12
+
13
+ # constructor (cretes connection)
14
+ def connect
15
+ fails __method__ unless @actions[:connect]
16
+ @actions[:connect].call
17
+ end
18
+
19
+ # recv basic method
20
+ def recv
21
+ fails __method__ unless @actions[:recv]
22
+ @actions[:recv].call
23
+ end
24
+
25
+ # send masic method
26
+ def send data
27
+ fails __method__ unless @actions[:send]
28
+ @actions[:send].call(data)
29
+ end
30
+
31
+ # callback setter to connect to the storage
32
+ def connect_action &block
33
+ @actions[:connect] = block
34
+ end
35
+
36
+ # callback setter to recieve data
37
+ def recv_action &block
38
+ @actions[:recv] = block
39
+ end
40
+
41
+ # callback setter to send data when done successfully
42
+ # Storage#send will pass Hash with content:
43
+ # :data => Job.execution_result,
44
+ # :action => Job.executed_action
45
+ def send_action &block
46
+ @actions[:send] = block
47
+ end
48
+
49
+ # callback setter for the collection
50
+ # usage:
51
+ # VTools::Storage.setup do
52
+ # connect_action { ... }
53
+ # send_action { |data| ... }
54
+ # recv_action { ... }
55
+ def setup &block
56
+ instance_eval &block if block_given?
57
+ end
58
+
59
+ private
60
+ # errors generator
61
+ def fails meth
62
+ p "fails orig: #{meth}"
63
+ raise NotImplementedError, "VTools::Storage##{meth}_action must be set"
64
+ end
65
+ end # class << self
66
+ end # Storage
67
+ end # VTools
@@ -0,0 +1,93 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module VTools
4
+
5
+ # Takes care about generating thumbnails
6
+ # requires ffmpegthumbnailer
7
+ class Thumbnailer
8
+ include SharedMethods
9
+
10
+ # constructor
11
+ def initialize video
12
+ @video = video
13
+ end
14
+
15
+ # thumbnailer job
16
+ def run
17
+
18
+ options = @video.thumbs_options
19
+ errors = []
20
+ thumbs = []
21
+ postfix = options[:postfix]
22
+ @total = options[:thumb_count].to_i
23
+
24
+ @output_file = "#{generate_path @video.name, "thumb"}/#{@video.name}_"
25
+ command = "#{CONFIG[:thumb_binary]} -i '#{@video.path}' #{options} "
26
+
27
+ # callback
28
+ Handler.exec :before_thumb, @video, options
29
+
30
+ # process cicle
31
+ @total.times do |count|
32
+
33
+ seconds = (options && options[:t]) || set_point(options || count)
34
+
35
+ file = "#{@output_file}#{ postfix || (options && options[:t]) || count }.jpg"
36
+ exec = "#{command} -t #{seconds} -o '#{file}' 2>&1"
37
+ options = nil
38
+
39
+ Open3.popen3(exec) do |stdin, stdout, stderr|
40
+ # save thumb if no error
41
+ if (error = VTools.fix_encoding(stdout.readlines).join(" ")).empty?
42
+ thumbs << thumb = {:path => file, :offset => time_offset(seconds)}
43
+
44
+ Handler.exec :in_thumb, @video, thumb # callbacks
45
+ else
46
+ errors << "#{error} (#{file})"
47
+ end
48
+ end
49
+ end
50
+
51
+ # callbacks
52
+ if errors.empty?
53
+ Handler.exec :thumb_success, @video, thumbs
54
+ else
55
+ errors = " Errors: #{errors.flatten.join(";").gsub(/\n/, ' ')}. "
56
+ Handler.exec :thumb_error, @video, errors
57
+ raise ProcessError, "Thumbnailer error: #{errors}" if thumbs.empty? && @total > 0
58
+ end
59
+
60
+ thumbs
61
+ end
62
+
63
+ private
64
+ # permits to set checkpoint
65
+ # for the current thumb
66
+ def set_point config
67
+
68
+ # start point as config hash given
69
+ checkpoint = if config.is_a? Hash
70
+
71
+ case config[:thumb_start_point].to_s
72
+ when /(\d+(\.\d+)?)%$/ # shift in percents
73
+ offset = $1.to_i
74
+ when /(\d+)$/ # shift in seconds
75
+ offset = time_offset $1
76
+ end
77
+ elsif config.is_a? Integer # thumb number
78
+ offset = (config * 100 / @total).to_i
79
+ end
80
+
81
+ offset ||= 0
82
+ end
83
+
84
+ # calculates time offset string
85
+ # by given seconds length
86
+ def time_offset seconds
87
+ shift = (@video.duration > seconds.to_f ? seconds : @video.duration).to_i
88
+ hours, mins = (shift / 360), (shift / 60)
89
+ sec = shift - (hours * 360 + mins * 60)
90
+ "%02d:%02d:%02d" % [hours, mins, sec]
91
+ end
92
+ end # Thumbnailer
93
+ end # VTools
@@ -0,0 +1,80 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module VTools
4
+
5
+ # options for the thumbnailer
6
+ class ThumbsOptions < Hash
7
+ include SharedMethods
8
+
9
+ # constructor
10
+ def initialize options = {}
11
+
12
+ @ignore = [:thumb_count, :thumb_start_point, :quality, :width, :time, :postfix]
13
+ # default values
14
+ merge!(
15
+ :thumb_count => 0,
16
+ :thumb_start_point => 0,
17
+ )
18
+
19
+ parse! options
20
+ end
21
+
22
+ # redefine native method
23
+ # for more readable options
24
+ def []= index, value
25
+ case
26
+ when [:quality, :q].include?(index)
27
+ former = {:q => value, :quality => value}
28
+ when [:width, :s].include?(index)
29
+ former = {:s => value, :width => value}
30
+ when [:time, :t].include?(index)
31
+ former = {:t => value, :time => value}
32
+ else
33
+ return super
34
+ end
35
+ merge! former
36
+ value
37
+ end
38
+
39
+ # to string
40
+ def to_s
41
+ params = collect do |key, value|
42
+ "-#{key} #{value}" unless @ignore.include?(key)
43
+ end.compact
44
+
45
+ params.join " "
46
+ end
47
+
48
+ # options parser
49
+ def parse! options
50
+
51
+ case
52
+ # predefined
53
+ when options.is_a?(String) && CONFIG[:thumb_set].include?(options.to_sym)
54
+ # get config data
55
+ s, q, count, start_point = CONFIG[:thumb_set][options.to_sym]
56
+ options = {:thumb_count => count, :thumb_start_point => start_point, :s => s, :q => q}
57
+
58
+ # niether string nor hash..
59
+ when !options.is_a?(Hash)
60
+ raise ConfigError, "Options should be a Hash or String (predefined set)"
61
+ # convert keys to symbols
62
+ else
63
+ options = keys_to_sym options
64
+ # check inline predefined
65
+ parse! options.delete(:set) if options.has_key? :set
66
+ end
67
+
68
+ perform options
69
+ merge! options
70
+ end
71
+
72
+ private
73
+ # revalidate special options
74
+ def perform hash
75
+ { :quality => :q, :width => :s, :time => :t }.each do |name, orig|
76
+ hash[orig] = hash[name] if hash.include?(name)
77
+ end
78
+ end
79
+ end # ThumbsOptions
80
+ end # VTools