Skip to content

Commit

Permalink
add convert script
Browse files Browse the repository at this point in the history
  • Loading branch information
codez committed Feb 20, 2017
1 parent aaa1869 commit a001a19
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 22 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@
/*.mp3

/test/coverage

# Ignore show name mapping
/config/show_names.yml
4 changes: 4 additions & 0 deletions app/services/audio_encoding/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ def codec
name.demodulize.underscore
end

def lossless?
bitrates.size == 1
end

end

end
Expand Down
17 changes: 13 additions & 4 deletions app/services/audio_processor/ffmpeg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ class Ffmpeg < Base
year: :date }.freeze

def transcode(new_path, audio_format, tags = {})
options = codec_options(audio_format)
assert_directory(new_path)
audio.transcode(new_path,
options.merge(validate: true, custom: metadata_args(tags)))
if same_format?(audio_format)
preserving_transcode(new_path, custom: metadata_args(tags))
else
options = codec_options(audio_format).merge(validate: true, custom: metadata_args(tags))
audio.transcode(new_path, options)
end
end

def trim(new_path, start, duration)
Expand Down Expand Up @@ -119,10 +122,16 @@ def codec_options(audio_format)
audio_bitrate: audio_format.bitrate,
audio_channels: audio_format.channels
}
options.delete(:audio_bitrate) if audio_format.codec == 'flac'
options.delete(:audio_bitrate) if audio_format.encoding.lossless?
options
end

def same_format?(audio_format)
audio_format.codec == codec &&
audio_format.channels == channels &&
(audio_format.encoding.lossless? || audio_format.bitrate == bitrate)
end

def assert_directory(file)
FileUtils.mkdir_p(File.dirname(file))
end
Expand Down
13 changes: 13 additions & 0 deletions app/services/convert.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Convert

# rubocop:disable Lint/RescueException
# make sure we get notified in absolutely all cases.

def self.run(directory)
Converter.new(directory).run
rescue Exception => e
Rails.logger.error("FATAL #{e}\n #{e.backtrace.join("\n ")}")
ExceptionNotifier.notify_exception(e)
end

end
66 changes: 66 additions & 0 deletions app/services/convert/converter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
module Convert
class Converter

SHOW_NAME_YAML = Rails.root.join('config', 'show_names.yml')

class_attribute :recording_class

attr_reader :directory

def initialize(directory)
@directory = directory
end

def run # rubocop:disable Metrics/MethodLength
recordings = []
audio_files do |file|
current = recording_class.new(file)
if recordings.blank? || recordings.last.sequel?(current)
recordings << current
else
convert(recordings)
recordings = [current]
end
end
convert(recordings)
end

private

def audio_files(&block)
Dir.glob(File.join(directory, "*.#{recording_class.extension}"), &block)
end

def convert(recordings)
return if recordings.blank?

limited = !recordings.first.audio_encoding.lossless?
Import::Importer.new(build_mapping(recordings), limited_master: limited).run
end

def build_mapping(recordings)
Import::BroadcastMapping.new.tap do |mapping|
show_name = fetch_show_name(recordings.first)
mapping.assign_show(name: show_name)
mapping.assign_broadcast(label: show_name,
started_at: recordings.first.started_at,
finished_at: recordings.last.finished_at)
recordings.each { |r| mapping.add_recording_if_overlapping(r) }
end
end

def fetch_show_name(recording)
show_name_mapping[recording.show_name] || recording.show_name.titleize
end

def show_name_mapping
@show_name_mapping ||=
if File.exist?(SHOW_NAME_YAML)
YAML.load(File.read(SHOW_NAME_YAML))
else
{}
end
end

end
end
15 changes: 15 additions & 0 deletions app/services/convert/mp3_recording.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Convert
class Mp3Recording < Recording

self.extension = 'mp3'

def init_values
# mp3rec-05-5-20170203-080000-3600-sec-der_morgen.wav.mp3
parts = File.basename(path).match(/\-(\d{8})\-(\d{4})\d{2}\-(\d{4})-sec-(.+)\.\w+.mp3$/)
@started_at = Time.zone.parse(parts[1] + ' ' + parts[2])
@duration = parts[3].to_i.seconds
@show_name = parts[4]
end

end
end
33 changes: 33 additions & 0 deletions app/services/convert/recording.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Convert
class Recording < Import::Recording

class_attribute :extension

attr_reader :show_name, :started_at, :duration

def initialize(path)
super(path)
init_values
end

def sequel?(other)
show_name == other.show_name &&
other.started_at - finished_at < DURATION_TOLERANCE.seconds
end

def mark_imported
# no-op
end

def audio_encoding
AudioEncoding.for_extension(extension)
end

private

def init_values
# set @started_at, @duration and @show_name in subclass
end

end
end
37 changes: 30 additions & 7 deletions app/services/import/archiver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ class Archiver

include Loggable

attr_reader :mapping, :master
attr_reader :mapping, :master, :options

def initialize(mapping, master)
def initialize(mapping, master, options = {})
@mapping = mapping
@master = master
@options = options
end

def run
Expand All @@ -34,7 +35,7 @@ def build_audio_files
end

def audio_formats
(archive_formats + playback_formats).collect(&:audio_format).uniq
(archive_audio_formats + playback_formats.collect(&:audio_format)).uniq
end

def build_audio_file(format)
Expand Down Expand Up @@ -65,8 +66,30 @@ def tags
year: b.started_at.year }
end

def archive_formats
mapping.profile.archive_formats
def archive_audio_formats
@archive_audio_formats ||=
if options[:limited_master]
limited_archive_audio_formats
else
actual_archive_audio_formats
end
end

def actual_archive_audio_formats
mapping.profile.archive_formats.collect(&:audio_format)
end

def limited_archive_audio_formats
processor = AudioProcessor.new(master)
actual_archive_audio_formats
.reject { |f| f.encoding.lossless? }
.collect { |f| limited_audio_format(f, processor.bitrate, processor.channels) }
end

def limited_audio_format(source, bitrate, channels)
AudioFormat.new(source.codec,
[source.bitrate, bitrate].min,
[source.channels, channels].min)
end

def playback_formats
Expand All @@ -75,10 +98,10 @@ def playback_formats

def formats_covered_by_archive_formats
[''].tap do |condition|
archive_formats.each do |f|
archive_audio_formats.each do |f|
condition.first << ' OR ' if condition.first.present?
condition.first << '(codec = ? AND ((bitrate = ? AND channels <= ?) OR bitrate <= ?))'
condition.push(f.codec, f.initial_bitrate, f.initial_channels, f.initial_bitrate)
condition.push(f.codec, f.bitrate, f.channels, f.bitrate)
end
end
end
Expand Down
23 changes: 12 additions & 11 deletions app/services/import/importer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ class Importer

include Loggable

attr_reader :mapping
attr_reader :mapping, :options

def initialize(mapping)
def initialize(mapping, options = {})
@mapping = mapping
@options = options
end

def run
Expand Down Expand Up @@ -72,14 +73,6 @@ def determine_best_recordings
end
end

def warn_for_too_short_recordings(recordings)
recordings.select(&:audio_duration_too_short?).each do |r|
exception = Recording::TooShortError.new(r)
error(exception.message)
ExceptionNotifier.notify_exception(exception, data: { mapping: mapping })
end
end

def compose_master(recordings)
warn_for_too_short_recordings(recordings)
inform("Composing master file for broadcast #{mapping} out of the following recordings:\n" +
Expand All @@ -88,13 +81,21 @@ def compose_master(recordings)
end

def import_into_archive(master)
Archiver.new(mapping, master.path).run
Archiver.new(mapping, master.path, options).run
inform("Broadcast #{mapping} successfully imported.")
end

def mark_recordings_as_imported
mapping.recordings.each(&:mark_imported)
end

def warn_for_too_short_recordings(recordings)
recordings.select(&:audio_duration_too_short?).each do |r|
exception = Recording::TooShortError.new(r)
error(exception.message)
ExceptionNotifier.notify_exception(exception, data: { mapping: mapping })
end
end

end
end
7 changes: 7 additions & 0 deletions bin/convert
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env ruby

# Converts a directory of audio files into the archive.

require File.expand_path('../../config/environment', __FILE__)

Convert.run(ARGV[0])
1 change: 1 addition & 0 deletions config/initializers/raar_service_classes.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Define default classes.
AudioProcessor.klass ||= AudioProcessor::Ffmpeg
Import::BroadcastMapping::Builder.klass ||= Import::BroadcastMapping::Builder::AirtimeDb
Convert::Converter.recording_class ||= Convert::Mp3Recording

0 comments on commit a001a19

Please sign in to comment.