convertr 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
+ load 'lib/tasks/convertr.rake'
3
4
 
4
5
  begin
5
6
  require 'jeweler'
@@ -11,7 +12,10 @@ begin
11
12
  gem.homepage = "http://github.com/tulaman/convertr"
12
13
  gem.authors = ["Ilya Lityuga", "Alexander Svetkin"]
13
14
  gem.add_development_dependency "shoulda", ">= 0"
14
- gem.add_development_dependency "activerecord", ">= 3.0.0"
15
+ gem.add_development_dependency "mocha", ">= 0"
16
+ gem.add_development_dependency "factory_girl", ">= 0"
17
+ gem.add_dependency "activerecord", ">= 3.0.0"
18
+ gem.requirements << "ffmpeg, any version"
15
19
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
20
  end
17
21
  Jeweler::GemcutterTasks.new
@@ -39,7 +43,7 @@ rescue LoadError
39
43
  end
40
44
  end
41
45
 
42
- task :test => :check_dependencies
46
+ task :test => [:check_dependencies, 'convertr:prepare_test']
43
47
 
44
48
  task :default => :test
45
49
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.0.1
data/bin/convertr CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $LOAD_PATH << File.join(File.dirname(__FILE__), '..')
4
- require 'lib/convertr'
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require 'convertr'
5
5
 
6
6
  if ARGV.any? { |arg| %w(--version -v).any? { |flag| arg == flag } }
7
7
  puts "Convertr #{Convertr::Version}"
8
8
  exit 0
9
9
  end
10
10
 
11
- Convertr::Runner.new(ARGV)
11
+ Convertr::Runner.new(ARGV).run
data/convertr.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{convertr}
8
- s.version = "0.0.0"
8
+ s.version = "0.0.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ilya Lityuga", "Alexander Svetkin"]
12
- s.date = %q{2010-10-08}
12
+ s.date = %q{2010-10-12}
13
13
  s.default_executable = %q{convertr}
14
14
  s.description = %q{Convertr works with database and handles converting tasks. It fetches files from remote sources and converts them to appropriate formats with ffmpeg}
15
15
  s.email = %q{ilya.lityuga@gmail.com}
@@ -28,17 +28,44 @@ Gem::Specification.new do |s|
28
28
  "bin/convertr",
29
29
  "convertr.gemspec",
30
30
  "lib/convertr.rb",
31
+ "lib/convertr/convertor.rb",
32
+ "lib/convertr/file.rb",
33
+ "lib/convertr/migration.rb",
34
+ "lib/convertr/runner.rb",
35
+ "lib/convertr/scheduler.rb",
36
+ "lib/convertr/scheduler/all_bt_first.rb",
37
+ "lib/convertr/scheduler/bt_600_first.rb",
38
+ "lib/convertr/scheduler_factory.rb",
39
+ "lib/convertr/task.rb",
40
+ "lib/tasks/convertr.rake",
41
+ "test/database.yml",
42
+ "test/factories/files.rb",
43
+ "test/factories/tasks.rb",
44
+ "test/fixtures/files.yml",
45
+ "test/fixtures/tasks.yml",
31
46
  "test/helper.rb",
32
- "test/test_convertr.rb"
47
+ "test/settings.yml",
48
+ "test/test_convertor.rb",
49
+ "test/test_file.rb",
50
+ "test/test_scheduler_allbtfirst.rb",
51
+ "test/test_scheduler_factory.rb",
52
+ "test/test_task.rb"
33
53
  ]
34
54
  s.homepage = %q{http://github.com/tulaman/convertr}
35
55
  s.rdoc_options = ["--charset=UTF-8"]
36
56
  s.require_paths = ["lib"]
57
+ s.requirements = ["ffmpeg, any version"]
37
58
  s.rubygems_version = %q{1.3.7}
38
59
  s.summary = %q{Useful utility for converting video files with ffmpeg}
39
60
  s.test_files = [
40
- "test/helper.rb",
41
- "test/test_convertr.rb"
61
+ "test/factories/files.rb",
62
+ "test/factories/tasks.rb",
63
+ "test/test_scheduler_allbtfirst.rb",
64
+ "test/test_scheduler_factory.rb",
65
+ "test/test_file.rb",
66
+ "test/helper.rb",
67
+ "test/test_task.rb",
68
+ "test/test_convertor.rb"
42
69
  ]
43
70
 
44
71
  if s.respond_to? :specification_version then
@@ -47,13 +74,19 @@ Gem::Specification.new do |s|
47
74
 
48
75
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
76
  s.add_development_dependency(%q<shoulda>, [">= 0"])
50
- s.add_development_dependency(%q<activerecord>, [">= 3.0.0"])
77
+ s.add_development_dependency(%q<mocha>, [">= 0"])
78
+ s.add_development_dependency(%q<factory_girl>, [">= 0"])
79
+ s.add_runtime_dependency(%q<activerecord>, [">= 3.0.0"])
51
80
  else
52
81
  s.add_dependency(%q<shoulda>, [">= 0"])
82
+ s.add_dependency(%q<mocha>, [">= 0"])
83
+ s.add_dependency(%q<factory_girl>, [">= 0"])
53
84
  s.add_dependency(%q<activerecord>, [">= 3.0.0"])
54
85
  end
55
86
  else
56
87
  s.add_dependency(%q<shoulda>, [">= 0"])
88
+ s.add_dependency(%q<mocha>, [">= 0"])
89
+ s.add_dependency(%q<factory_girl>, [">= 0"])
57
90
  s.add_dependency(%q<activerecord>, [">= 3.0.0"])
58
91
  end
59
92
  end
data/lib/convertr.rb CHANGED
@@ -1,3 +1,11 @@
1
+ require 'ostruct'
2
+ require 'active_record'
3
+ require 'active_record/railtie'
4
+ require 'convertr/runner'
5
+ require 'convertr/scheduler'
6
+ require 'convertr/scheduler_factory'
7
+ require 'convertr/convertor'
8
+
1
9
  module Convertr
2
10
  module Version
3
11
  class << self
@@ -6,4 +14,29 @@ module Convertr
6
14
  end
7
15
  end
8
16
  end
17
+
18
+ class Config < OpenStruct
19
+ include Singleton
20
+ end
21
+
22
+ def self.configure(config = nil)
23
+ config ||= Config.instance
24
+ yield Config.instance if block_given?
25
+ enviroment = ENV['RAILS_ENV'] || 'development'
26
+ config.db_config = YAML.load_file(config.db_config_file)[enviroment]
27
+ if config.settings_file
28
+ YAML.load_file(config.settings_file)[enviroment]['convertor'].each do |k, v|
29
+ config.send("#{k}=", v)
30
+ end
31
+ end
32
+ self.init!
33
+ end
34
+
35
+ def self.init!
36
+ conf = Config.instance
37
+ ActiveRecord::Base.establish_connection(conf.db_config)
38
+ require 'convertr/file'
39
+ require 'convertr/task'
40
+ $stderr.puts "Tables not found" && exit(1) unless Convertr::File.table_exists? && Convertr::Task.table_exists?
41
+ end
9
42
  end
@@ -0,0 +1,199 @@
1
+ require 'logger'
2
+ require 'uri'
3
+ require 'net/ftp'
4
+
5
+ class Hash # {{{ small hack for syntax sugar - hash merging by +
6
+ def +(h)
7
+ self.merge(h)
8
+ end
9
+ end # }}}
10
+
11
+ class File # {{{ filename without extension
12
+ def self.filename(filepath)
13
+ File.basename(filepath, File.extname(filepath))
14
+ end
15
+ end # }}}
16
+
17
+ module Convertr
18
+ class Convertor
19
+ CONVERTOR_STOPFILE = 'stop.convertr'
20
+ CONVERTOR_PAUSEFILE = 'pause.convertr'
21
+ CONVERTOR_PAUSE_DELAY = 120
22
+ CONVERTOR_MAX_FETCH_TIME = 600
23
+
24
+ attr_accessor :scheduler, :max_tasks, :hostname, :initial_dir, :file, :task, :file_path, :work_path, :tasks
25
+
26
+ def initialize(max_tasks = 0, scheduler = nil) # инициализация конертера {{{
27
+ @max_tasks = max_tasks
28
+ @logger = Logger.new($stderr)
29
+ @initial_dir = Dir.pwd
30
+ @scheduler = Convertr::SchedulerFactory.create(scheduler)
31
+ @hostname = `hostname`.chomp
32
+ @conf = Convertr::Config.instance
33
+ @tasks = 0
34
+ end # }}}
35
+
36
+ def run # запуск конвертора {{{
37
+ loop do
38
+ break if File.exists? CONVERTOR_STOPFILE
39
+ if !File.exists?(CONVERTOR_PAUSEFILE) and @task = @scheduler.schedule_next_task(@hostname)
40
+ task.update_attributes(
41
+ :convert_status => process_task,
42
+ :convert_stopped_at => Time.now
43
+ )
44
+ break if @max_tasks > 0 && (@tasks += 1) > @max_tasks
45
+ else
46
+ sleep(CONVERTOR_PAUSE_DELAY) && next
47
+ end
48
+ end
49
+ end # }}}
50
+
51
+ private
52
+
53
+ def process_task # выполнение конкретной задачи на конвертацию {{{
54
+ @file = @task.file
55
+ @file_path = File.join(@conf.tmp_dir, @file.name)
56
+ @working_dir = File.join( File.dirname(@file_path), File.filename(@filepath) )
57
+ @logger.info("Started #@filepath")
58
+ begin
59
+ fetch_file(@file.source_location, @file.name)
60
+ FileUtils.cd @working_dir
61
+ process_profile(profile_by_bitrate(@task.bitrate))
62
+ if @task.bitrate == 600
63
+ count = calc_thumbnails_count(@file.duration)
64
+ interval = (@file.duration / count).to_i
65
+ system(make_thumbnails_cmd(count, interval, 150, 0, 2)) or raise "thumbnails generation failed #{$?}"
66
+ end
67
+ @logger.info("Done #@filepath")
68
+ rescue StandardError => e
69
+ @logger.error e.message
70
+ return 'FAILURE'
71
+ ensure
72
+ FileUtils.cd @initial_dir
73
+ end
74
+ 'SUCCESS'
75
+ end # }}}
76
+
77
+ def fetch_file(source_url, filename) # скачивание файла по FTP {{{
78
+ FileUtils.mkpath(@working_dir)
79
+ dst_file = File.join(@working_dir, File.basename(filename))
80
+ tmp_file = dst_file + ".part"
81
+ started_at = Time.now
82
+ loop do
83
+ return if File.exists? dst_file
84
+ if File.exists? tmp_file
85
+ sleep(CONVERTOR_PAUSE_DELAY)
86
+ if Time.now > started_at + CONVERTOR_MAX_FETCH_TIME
87
+ FileUtils.rm_f tmp_file
88
+ else
89
+ next
90
+ end
91
+ end
92
+ FileUtils.touch tmp_file
93
+ break
94
+ end
95
+
96
+ url = URI.parse(source_url)
97
+ begin
98
+ ftp = Net::Ftp.open(url.host)
99
+ ftp.login(@conf.ftp_user, @conf.ftp_pass)
100
+ ftp.getbinaryfile(url.path, tmp_file, 1024)
101
+ rescue StandardError => e
102
+ ftp.close
103
+ FileUtils.rm_f tmp_file
104
+ raise e
105
+ end
106
+ ftp.close
107
+
108
+ FileUtils.rename tmp_file, dst_file
109
+ end # }}}
110
+
111
+ def profile_by_bitrate(bitrate) # bitrate -> profile {{{
112
+ case bitrate
113
+ when 300 then 'sd'
114
+ when 600 then 'hd'
115
+ when 1000 then 'hdp'
116
+ else
117
+ raise "Unsupported bitrate '#{bitrate}'"
118
+ end
119
+ end # }}}
120
+
121
+ def process_profile(pname) # конвертация, согласно профилю # {{{
122
+ profile = @conf.enc_profiles[pname]
123
+ outfile = File.join(@working_dir, File.filename(@file.name), "-#{pname}-0.mp4")
124
+ infile = File.join(@working_dir, File.basename(@file.name))
125
+ output_dir = File.join(@conf.output_dir, @working_dir)
126
+ FileUtils.mkpath output_dir unless File.exists? output_dir
127
+ FileUtils.chdir output_dir
128
+ FileUtils.mkdir pname unless File.exists? pname
129
+ FileUtils.chdir pname
130
+
131
+ @conf.enc_pass.each_with_index do |enc_pass_params, i|
132
+ cmd = mkcmd(enc_pass_params, pname, infile, i == 0 ? '/dev/null' : outfile, @file.aspect)
133
+ @logger.info(cmd)
134
+ system(cmd) or raise "system #{cmd} failed: #{$?}"
135
+ end
136
+
137
+ outfile2 = File.join(@working_dir, File.filename(@file.name), "-#{pname}.mp4")
138
+ cmd = "#{@conv.qtfaststart} #{outfile} #{outfile2}"
139
+ system(cmd) or FileUtils.rm_f(outfile) && raise("system #{cmd} failed: #{$?}")
140
+ FileUtils.cd '..'
141
+ FileUtils.rm_rf pname
142
+ end # }}}
143
+
144
+ def make_thumbnails_cmd(count, interval, w, h, postfix) # генерация тумбнейлов {{{
145
+ w = (h * @file.float_aspect).to_i if h && w.nil?
146
+ h = (w / @file.float_aspect).to_i if @file.aspect && w && h.nil?
147
+ tstamps = (1..count).collect {|i| i * interval}
148
+ params = ''
149
+ params += ' -c 20 ' if @task.crop?
150
+ params += ' -d ' if @task.deinterlace?
151
+ output_dir = File.join(@conf.output_dir, @working_dir)
152
+ pattern = File.join(@working_dir, File.filename(@file.name), "-%d-#{postfix}.jpg")
153
+ "#{@conf.tmaker} -i #{infile} #{params} -w #{w} -h #{h} -o #{output_dir} #{tstamps.join(' ')}"
154
+ end # }}}
155
+
156
+ def mkcmd(enc_pass, pname, infile, outfile, aspect) # подготовка команды на конвертацию {{{
157
+ float_aspect = aspect.split(':').first.to_f / aspect.split(':').last.to_f
158
+ params = @conf.enc_global + enc_pass + @conf.enc_profiles[pname]
159
+ # we must deinterlace movies taken from sat. dvd and rips don't have to be deinterlaced
160
+ params['deinterlace'] = '' if task.deinterlace? # option w/o a value
161
+ (w, h) = case
162
+ when aspect == '4:3' then [640, 480]
163
+ when aspect == '16:9' then [704, 396]
164
+ when float_aspect > 16.0 / 9.0 then [704, (704.0 / float_aspect).to_i]
165
+ when float_aspect < 16.0 / 9.0 && float_aspect > 4.0 / 3.0 then [640, (640.0 / float_aspect).to_i]
166
+ else
167
+ params['aspect'] = '1.33333333'
168
+ [640, 480]
169
+ end
170
+ # Frame size must be a multiple of 2
171
+ h -= 1 if h.odd?
172
+ if task.crop?
173
+ (params['croptop'], params['cropbottom'], params['cropleft'], params['cropright']) =
174
+ case aspect
175
+ when '16:9'
176
+ w += 32 && h += 18
177
+ [10, 8, 16, 16]
178
+ else # '4:3', '5:3' etc
179
+ w += 32 && h += 24
180
+ [20, 4, 16, 16]
181
+ end
182
+ end
183
+ "#{@conf.ffmpeg} -y -i #{infile} -s #{w}x#{h} #{params.map {|k, v| "-#{k} #{v}"}.sort.join(' ')} #{outfile}"
184
+ end # }}}
185
+
186
+ def calc_thumbnails_count(seconds) # выбор количества тумбнейлов {{{
187
+ case (seconds / 60).to_i
188
+ when 0..10 then 3
189
+ when 10..20 then 6
190
+ when 20..30 then 9
191
+ when 30..40 then 12
192
+ when 40..50 then 15
193
+ when 50..60 then 18
194
+ when 60..70 then 21
195
+ else 24
196
+ end
197
+ end # }}}
198
+ end
199
+ end
@@ -0,0 +1,15 @@
1
+ module Convertr
2
+ class File < ActiveRecord::Base
3
+ set_table_name :files
4
+ has_many :tasks, :class_name => 'Convertr::Task'
5
+ scope :without_convertor, where('convertor is null').order('broadcast_at asc')
6
+ scope :with_convertor, lambda { |convertor|
7
+ where(:convertor => convertor).order('broadcast_at asc')
8
+ }
9
+
10
+ def float_aspect
11
+ (w, h) = aspect.split(':')
12
+ w.to_f / h.to_f
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ module Convertr
2
+ class Migration < ActiveRecord::Migration
3
+ def self.up
4
+ create_table :files do |t|
5
+ t.string :filename
6
+ t.string :location
7
+ t.datetime :enqueue_at
8
+ t.string :convertor
9
+ t.datetime :broadcast_at
10
+ t.integer :src_size
11
+ t.integer :duration
12
+ t.string :aspect, :limit => 10
13
+ t.integer :width
14
+ t.integer :height
15
+ t.datetime :src_deleted_at
16
+ t.datetime :convertor_deleted_at
17
+ end
18
+
19
+ create_table :tasks do |t|
20
+ t.integer :file_id
21
+ t.integer :bitrate
22
+ t.string :convert_status, :limit => 20
23
+ t.boolean :convert_started
24
+ t.boolean :convert_stopped
25
+ t.string :copy_status, :limit => 20
26
+ t.boolean :copy_started
27
+ t.boolean :copy_stopped
28
+ t.boolean :crop
29
+ t.boolean :deinterlace
30
+ end
31
+ end
32
+
33
+ def self.down
34
+ drop_table :tasks
35
+ drop_table :files
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,53 @@
1
+ require 'optparse'
2
+
3
+ module Convertr
4
+ class Runner
5
+ class OptParser # {{{
6
+ def self.parse(args)
7
+ options = Convertr::Config.instance
8
+ options.db_config_file = "~/.convertr/database.yml"
9
+ options.settings_file = "~/.convertr/settings.yml"
10
+ options.max_tasks = 0
11
+ options.force_reset_database = false
12
+ opts = OptionParser.new do |opts|
13
+ opts.banner = "Usage: convertr [options]"
14
+ opts.separator ""
15
+ opts.separator "Specific options:"
16
+
17
+ opts.on("-d", "--db_config_file [PATH_TO_DB_CONFIG]",
18
+ "Specify path to database.yml file") do |dbc|
19
+ options.db_config_file = dbc
20
+ end
21
+
22
+ opts.on("-c", "--settings_file [PATH_TO_CONFIG]",
23
+ "Specify path to settings.yml file") do |c|
24
+ options.settings_file = c
25
+ end
26
+
27
+ opts.on("-m", "--max_tasks N", Integer,
28
+ "Specify the maximum tasks to complete") do |m|
29
+ options.max_tasks = m
30
+ end
31
+
32
+ opts.on("-f", "--force",
33
+ "Force recreating database tables") do |f|
34
+ options.force = f
35
+ end
36
+ end
37
+ opts.parse!(args)
38
+ options
39
+ end
40
+ end # }}}
41
+
42
+ attr_reader :db_config, :config, :options
43
+
44
+ def initialize(opts) # {{{
45
+ config = Convertr::Runner::OptParser.parse(opts)
46
+ Convertr.configure(config)
47
+ end
48
+ # }}}
49
+ def run # {{{
50
+ puts "Running"
51
+ end # }}}
52
+ end
53
+ end
@@ -0,0 +1,29 @@
1
+ module Convertr
2
+ module Scheduler
3
+ class Base
4
+ def initialize
5
+ end
6
+
7
+ def schedule_next_task(convertor)
8
+ # TODO: locking
9
+ if task = get_task_for_schedule(convertor)
10
+ schedule(task, convertor)
11
+ end
12
+ task
13
+ end
14
+
15
+ private
16
+
17
+ def get_task_for_schedule # abstract
18
+ raise "Should be overriden"
19
+ end
20
+
21
+ def schedule(task, convertor)
22
+ Convertr::Task.transaction do
23
+ task.update_attributes(:convert_status => 'PROGRESS', :convert_started_at => Time.now)
24
+ task.file.update_attribute(:convertor => convertor)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ module Convertr
2
+ module Scheduler
3
+ class AllBtFirst < Base
4
+ def get_task_for_schedule(convertor)
5
+ Convertr::Task.for_convertor(convertor).first# ||
6
+ # Convertr::File.without_convertor.select {|f| !f.tasks.empty?}.tasks.first
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ module Convertr
2
+ module Scheduler
3
+ class Bt600First < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ require 'convertr/scheduler'
2
+ Dir[File.join(File.dirname(__FILE__), 'scheduler', '*')].each {|f| require f if File.file? f}
3
+
4
+ module Convertr
5
+ class SchedulerFactory
6
+ def self.create(name = 'AllBtFirst')
7
+ name ||= 'AllBtFirst'
8
+ Convertr::Scheduler.const_get(name).new
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module Convertr
2
+ class Task < ActiveRecord::Base
3
+ set_table_name :tasks
4
+ belongs_to :file, :class_name => 'Convertr::File'
5
+ scope :not_completed, where(:convert_status => 'NONE')
6
+ scope :for_convertor, lambda { |convertor|
7
+ not_completed.includes(:file).where('files.convertor' => convertor).order('files.broadcast_at asc')
8
+ }
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ namespace :convertr do
2
+ task :load_config do
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
4
+ require 'convertr'
5
+ Convertr.configure do |c|
6
+ c.db_config_file = 'test/database.yml'
7
+ end
8
+ end
9
+
10
+ task :reset_database => :load_config do
11
+ require 'convertr/migration'
12
+ Convertr::Migration.down
13
+ Convertr::Migration.up
14
+ end
15
+
16
+ task :load_fixtures => :load_config do
17
+ require 'active_record/fixtures'
18
+ Fixtures.create_fixtures(File.join(Dir.pwd, 'test', 'fixtures'), [:files, :tasks])
19
+ end
20
+
21
+ task :prepare_test => [:reset_database, :load_fixtures]
22
+ end
data/test/database.yml ADDED
@@ -0,0 +1,39 @@
1
+ # MySQL. Versions 4.1 and 5.0 are recommended.
2
+ #
3
+ # Install the MySQL driver:
4
+ # gem install mysql2
5
+ #
6
+ # And be sure to use new-style password hashing:
7
+ # http://dev.mysql.com/doc/refman/5.0/en/old-client.html
8
+ development:
9
+ adapter: mysql2
10
+ encoding: utf8
11
+ reconnect: false
12
+ database: videomore
13
+ pool: 5
14
+ username: root
15
+ password:
16
+ socket: /var/lib/mysql/mysql.sock
17
+
18
+ # Warning: The database defined as "test" will be erased and
19
+ # re-generated from your development database when you run "rake".
20
+ # Do not set this db to the same as development or production.
21
+ test:
22
+ adapter: mysql2
23
+ encoding: utf8
24
+ reconnect: false
25
+ database: videomore_test
26
+ pool: 5
27
+ username: root
28
+ password:
29
+ socket: /var/run/mysqld/mysqld.sock
30
+
31
+ production:
32
+ adapter: mysql2
33
+ encoding: utf8
34
+ reconnect: false
35
+ database: videomore_production
36
+ pool: 5
37
+ username: root
38
+ password:
39
+ socket: /var/run/mysqld/mysqld.sock
@@ -0,0 +1,5 @@
1
+ Factory.define :file, :class => Convertr::File do |f|
2
+ f.filename 'test.avi'
3
+ f.location 'ftp://ftp.example.com/test.avi'
4
+ f.convertor 'convertorX'
5
+ end
@@ -0,0 +1,6 @@
1
+ Factory.define :task, :class => Convertr::Task do |t|
2
+ t.association :file, :factory => :file
3
+ t.bitrate 600
4
+ t.crop true
5
+ t.deinterlace true
6
+ end
@@ -0,0 +1,49 @@
1
+ with_convertor1_1:
2
+ id: 1
3
+ filename: test.avi
4
+ location: /tmp
5
+ enqueue_at: <%= Time.now %>
6
+ convertor: convertor1
7
+ broadcast_at: <%= Time.now - 3600 * 24 %>
8
+ src_size: 12345
9
+ duration: 12345
10
+ aspect: 3x4
11
+ width: 640
12
+ height: 480
13
+
14
+ with_convertor1_2:
15
+ id: 2
16
+ filename: test.avi
17
+ location: /tmp
18
+ enqueue_at: <%= Time.now %>
19
+ convertor: convertor1
20
+ broadcast_at: <%= Time.now %>
21
+ src_size: 12345
22
+ duration: 12345
23
+ aspect: 3x4
24
+ width: 640
25
+ height: 480
26
+
27
+ without_convertor_1:
28
+ id: 3
29
+ filename: test.avi
30
+ location: /tmp
31
+ enqueue_at: <%= Time.now %>
32
+ broadcast_at: <%= Time.now - 3600 * 24 %>
33
+ src_size: 12345
34
+ duration: 12345
35
+ aspect: 3x4
36
+ width: 640
37
+ height: 480
38
+
39
+ without_convertor_2:
40
+ id: 4
41
+ filename: test.avi
42
+ location: /tmp
43
+ enqueue_at: <%= Time.now %>
44
+ broadcast_at: <%= Time.now %>
45
+ src_size: 12345
46
+ duration: 12345
47
+ aspect: 3x4
48
+ width: 640
49
+ height: 480
@@ -0,0 +1,14 @@
1
+ not_completed:
2
+ file_id: 3 # without convertor
3
+ bitrate: 512
4
+ convert_status: NONE
5
+
6
+ for_convertor1_1:
7
+ file_id: 2
8
+ bitrate: 1024
9
+ convert_status: NONE
10
+
11
+ for_convertor1_2:
12
+ file_id: 2
13
+ bitrate: 1024
14
+ convert_status: NONE
data/test/helper.rb CHANGED
@@ -1,10 +1,19 @@
1
1
  require 'rubygems'
2
2
  require 'test/unit'
3
3
  require 'shoulda'
4
+ require 'shoulda/active_record'
5
+ require 'factory_girl'
6
+ require 'mocha'
4
7
 
5
8
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
- $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ #$LOAD_PATH.unshift(File.dirname(__FILE__))
7
10
  require 'convertr'
11
+ Convertr.configure do |c|
12
+ c.db_config_file = './test/database.yml'
13
+ c.settings_file = './test/settings.yml'
14
+ end
15
+ require 'factories/files'
16
+ require 'factories/tasks'
8
17
 
9
18
  class Test::Unit::TestCase
10
19
  end
data/test/settings.yml ADDED
@@ -0,0 +1,41 @@
1
+ development: &non_production_settings
2
+ convertor:
3
+ tmp_dir: /tmp
4
+ ftp_user: test
5
+ ftp_pass: test
6
+ enc_pass:
7
+ - vpre: "fastfirstpass-flash"
8
+ pass: 1
9
+ an: ""
10
+ f: mp4
11
+ - vpre: "hq-flash"
12
+ pass: 2
13
+ enc_profiles:
14
+ sd:
15
+ b: 236k
16
+ bt: 236k
17
+ ab: 64k
18
+ ac: 1
19
+ hd:
20
+ b: 472k
21
+ bt: 472k
22
+ ab: 128k
23
+ ac: 2
24
+ hdp:
25
+ b: 872k
26
+ bt: 872k
27
+ ab: 128k
28
+ ac: 2
29
+ enc_global:
30
+ acodec: libfaac
31
+ vcodec: libx264
32
+ threads: 3
33
+ ar: 44100
34
+ ffmpeg: /usr/bin/ffmpeg
35
+ tmaker: /usr/local/bin/tmaker
36
+
37
+ test:
38
+ <<: *non_production_settings
39
+
40
+ production:
41
+ <<: *non_production_settings
@@ -0,0 +1,57 @@
1
+ require 'test/helper'
2
+
3
+ class TestConvertor < Test::Unit::TestCase
4
+ context "Convertor" do
5
+ setup do
6
+ @convertor = Convertr::Convertor.new
7
+ end
8
+
9
+ should "be able to calculate thumbnails count" do
10
+ assert_equal 3, @convertor.instance_eval("calc_thumbnails_count(600)")
11
+ assert_equal 6, @convertor.instance_eval("calc_thumbnails_count(1200)")
12
+ assert_equal 9, @convertor.instance_eval("calc_thumbnails_count(1800)")
13
+ end
14
+
15
+ should "find the right profile by bitrate" do
16
+ assert_equal 'sd', @convertor.instance_eval("profile_by_bitrate(300)")
17
+ assert_equal 'hd', @convertor.instance_eval("profile_by_bitrate(600)")
18
+ assert_equal 'hdp', @convertor.instance_eval("profile_by_bitrate(1000)")
19
+ assert_raise RuntimeError do
20
+ @convertor.instance_eval("profile_by_bitrate(1234)")
21
+ end
22
+ end
23
+
24
+ context "for some task with crop and deinterlace" do
25
+ setup do
26
+ task = Factory.create(:task, :bitrate => 600, :crop => true, :deinterlace => true)
27
+ @convertor.stubs(:task).returns(task)
28
+ end
29
+ should "prepare valid command for ffmpeg" do
30
+ assert_equal '/usr/bin/ffmpeg -y -i infile.avi -s 1118x414 -ab 64k -ac 1 -acodec libfaac -ar 44100 -b 236k -bt 236k -cropbottom 8 -cropleft 16 -cropright 16 -croptop 10 -deinterlace -pass 2 -threads 3 -vcodec libx264 -vpre hq-flash outfile.mpg',
31
+ @convertor.instance_eval { mkcmd({'vpre' => 'hq-flash', 'pass' => 2}, 'sd', 'infile.avi', 'outfile.mpg', '16:9') }
32
+ end
33
+ end
34
+
35
+ context "for some task with crop and without deinterlace" do
36
+ setup do
37
+ task = Factory.create(:task, :bitrate => 600, :crop => true, :deinterlace => false)
38
+ @convertor.stubs(:task).returns(task)
39
+ end
40
+ should "prepare valid command for ffmpeg" do
41
+ assert_equal '/usr/bin/ffmpeg -y -i infile.avi -s 1118x414 -ab 64k -ac 1 -acodec libfaac -ar 44100 -b 236k -bt 236k -cropbottom 8 -cropleft 16 -cropright 16 -croptop 10 -pass 2 -threads 3 -vcodec libx264 -vpre hq-flash outfile.mpg',
42
+ @convertor.instance_eval { mkcmd({'vpre' => 'hq-flash', 'pass' => 2}, 'sd', 'infile.avi', 'outfile.mpg', '16:9') }
43
+ end
44
+ end
45
+
46
+ context "for some task without crop and without deinterlace" do
47
+ setup do
48
+ task = Factory.create(:task, :bitrate => 600, :crop => false, :deinterlace => false)
49
+ @convertor.stubs(:task).returns(task)
50
+ end
51
+ should "prepare valid command for ffmpeg" do
52
+ assert_equal '/usr/bin/ffmpeg -y -i infile.avi -s 704x396 -ab 64k -ac 1 -acodec libfaac -ar 44100 -b 236k -bt 236k -pass 2 -threads 3 -vcodec libx264 -vpre hq-flash outfile.mpg',
53
+ @convertor.instance_eval { mkcmd({'vpre' => 'hq-flash', 'pass' => 2}, 'sd', 'infile.avi', 'outfile.mpg', '16:9') }
54
+ end
55
+ end
56
+ end
57
+ end
data/test/test_file.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'test/helper'
2
+
3
+ class TestFile < Test::Unit::TestCase
4
+ context "File class" do
5
+ should "have without_convertor scope" do
6
+ files = Convertr::File.without_convertor
7
+ assert_equal 2, files.size
8
+ assert_equal 3, files.first.id
9
+ end
10
+
11
+ should "have with_convertor scope" do
12
+ files = Convertr::File.with_convertor('convertor1')
13
+ assert_equal 2, files.size
14
+ assert_equal 1, files.first.id
15
+ end
16
+ end
17
+ context "File" do
18
+ setup do
19
+ @file = Convertr::File.new
20
+ end
21
+ subject { @file }
22
+ should have_many(:tasks)
23
+ should "be valid" do
24
+ assert @file.valid?
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ require 'test/helper'
2
+
3
+ class TestSchedulerAllbtfirst < Test::Unit::TestCase
4
+ context "AllBtFirst scheduler" do
5
+ setup do
6
+ @scheduler = Convertr::SchedulerFactory.create('AllBtFirst')
7
+ end
8
+ should "find first task assigned for convertor" do
9
+ assert_equal 2, @scheduler.get_task_for_schedule('convertor1').id
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ require 'helper'
2
+
3
+ class TestSchedulerFactory < Test::Unit::TestCase
4
+ context "SchedulerFactory" do
5
+ should "create instance of AllBtFirst by default" do
6
+ assert_instance_of Convertr::Scheduler::AllBtFirst, Convertr::SchedulerFactory.create
7
+ end
8
+
9
+ should "create instance of appropriate scheduler" do
10
+ assert_instance_of Convertr::Scheduler::Bt600First, Convertr::SchedulerFactory.create('Bt600First')
11
+ end
12
+
13
+ should "return object inherited from base scheduler" do
14
+ assert_kind_of Convertr::Scheduler::Base, Convertr::SchedulerFactory.create
15
+ end
16
+ end
17
+ end
data/test/test_task.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'test/helper'
2
+
3
+ class TestTask < Test::Unit::TestCase
4
+ context "Tasks" do
5
+ should "have for_convertor scope" do
6
+ assert_equal 2, Convertr::Task.for_convertor('convertor1').size
7
+ end
8
+ should "have not_completed scope" do
9
+ assert_equal 3, Convertr::Task.not_completed.size
10
+ end
11
+ end
12
+
13
+ context "Task" do
14
+ setup do
15
+ @task = Convertr::Task.new
16
+ end
17
+ subject { @task }
18
+ should belong_to(:file)
19
+ should "be valid" do
20
+ assert @task.valid?
21
+ end
22
+ end
23
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: convertr
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 29
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 0
10
- version: 0.0.0
9
+ - 1
10
+ version: 0.0.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ilya Lityuga
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-10-08 00:00:00 +04:00
19
+ date: 2010-10-12 00:00:00 +04:00
20
20
  default_executable: convertr
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -34,9 +34,37 @@ dependencies:
34
34
  type: :development
35
35
  version_requirements: *id001
36
36
  - !ruby/object:Gem::Dependency
37
- name: activerecord
37
+ name: mocha
38
38
  prerelease: false
39
39
  requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: factory_girl
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :development
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: activerecord
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
40
68
  none: false
41
69
  requirements:
42
70
  - - ">="
@@ -47,8 +75,8 @@ dependencies:
47
75
  - 0
48
76
  - 0
49
77
  version: 3.0.0
50
- type: :development
51
- version_requirements: *id002
78
+ type: :runtime
79
+ version_requirements: *id004
52
80
  description: Convertr works with database and handles converting tasks. It fetches files from remote sources and converts them to appropriate formats with ffmpeg
53
81
  email: ilya.lityuga@gmail.com
54
82
  executables:
@@ -68,8 +96,28 @@ files:
68
96
  - bin/convertr
69
97
  - convertr.gemspec
70
98
  - lib/convertr.rb
99
+ - lib/convertr/convertor.rb
100
+ - lib/convertr/file.rb
101
+ - lib/convertr/migration.rb
102
+ - lib/convertr/runner.rb
103
+ - lib/convertr/scheduler.rb
104
+ - lib/convertr/scheduler/all_bt_first.rb
105
+ - lib/convertr/scheduler/bt_600_first.rb
106
+ - lib/convertr/scheduler_factory.rb
107
+ - lib/convertr/task.rb
108
+ - lib/tasks/convertr.rake
109
+ - test/database.yml
110
+ - test/factories/files.rb
111
+ - test/factories/tasks.rb
112
+ - test/fixtures/files.yml
113
+ - test/fixtures/tasks.yml
71
114
  - test/helper.rb
72
- - test/test_convertr.rb
115
+ - test/settings.yml
116
+ - test/test_convertor.rb
117
+ - test/test_file.rb
118
+ - test/test_scheduler_allbtfirst.rb
119
+ - test/test_scheduler_factory.rb
120
+ - test/test_task.rb
73
121
  has_rdoc: true
74
122
  homepage: http://github.com/tulaman/convertr
75
123
  licenses: []
@@ -97,13 +145,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
145
  segments:
98
146
  - 0
99
147
  version: "0"
100
- requirements: []
101
-
148
+ requirements:
149
+ - ffmpeg, any version
102
150
  rubyforge_project:
103
151
  rubygems_version: 1.3.7
104
152
  signing_key:
105
153
  specification_version: 3
106
154
  summary: Useful utility for converting video files with ffmpeg
107
155
  test_files:
156
+ - test/factories/files.rb
157
+ - test/factories/tasks.rb
158
+ - test/test_scheduler_allbtfirst.rb
159
+ - test/test_scheduler_factory.rb
160
+ - test/test_file.rb
108
161
  - test/helper.rb
109
- - test/test_convertr.rb
162
+ - test/test_task.rb
163
+ - test/test_convertor.rb
@@ -1,7 +0,0 @@
1
- require 'helper'
2
-
3
- class TestConvertr < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
6
- end
7
- end