convertr 0.0.0 → 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.
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