Class: Wavesync::Audio
- Inherits:
-
Object
- Object
- Wavesync::Audio
- Defined in:
- lib/wavesync/audio.rb
Constant Summary collapse
- SUPPORTED_FORMATS =
%w[.m4a .mp3 .wav .aif .aiff].freeze
- ID3V2_FRAME_TITLE =
'TIT2'- ID3V2_FRAME_ARTIST =
'TPE1'- ID3V2_FRAME_ALBUM =
'TALB'- ID3V2_FRAME_ALBUM_ARTIST =
'TPE2'- ID3V2_FRAME_GENRE =
'TCON'- ID3V2_FRAME_COMPOSER =
'TCOM'- ID3V2_FRAME_ENCODED_BY =
'TENC'- ID3V2_FRAME_COMPILATION =
'TCMP'- FRAME_ID_TO_FFMPEG_KEY =
{ ID3V2_FRAME_TITLE => 'title', ID3V2_FRAME_ARTIST => 'artist', ID3V2_FRAME_ALBUM => 'album', ID3V2_FRAME_ALBUM_ARTIST => 'album_artist', ID3V2_FRAME_GENRE => 'genre', ID3V2_FRAME_COMPOSER => 'composer', ID3V2_FRAME_ENCODED_BY => 'encoded_by', ID3V2_FRAME_COMPILATION => 'compilation' }.freeze
- FFMPEG_KEY_TO_FRAME_ID =
FRAME_ID_TO_FFMPEG_KEY.invert.freeze
- COMBINING_MARKS =
/\p{Mn}/
Class Method Summary collapse
-
.find_all(library_path) ⇒ Object
: (String library_path) -> Array.
Instance Method Summary collapse
-
#bit_depth ⇒ Object
: () -> Integer?.
-
#bitrate ⇒ Object
: () -> Integer?.
-
#bpm ⇒ Object
: () -> (String | Integer)?.
-
#cue_points ⇒ Object
: () -> Array[Integer, sample_offset: Integer, label: String?].
-
#duration ⇒ Object
: () -> Float.
-
#format ⇒ Object
: () -> AudioFormat.
-
#initialize(file_path) ⇒ Audio
constructor
: (String file_path) -> void.
-
#sample_rate ⇒ Object
: () -> Integer?.
-
#transcode(target_path, target_sample_rate: nil, target_file_type: nil, target_bit_depth: nil, padding_seconds: nil, metadata: {}, target_bitrate: 192) ⇒ Object
: (String target_path, ?target_sample_rate: Integer?, ?target_file_type: String?, ?target_bit_depth: Integer?, ?padding_seconds: Numeric?, ?metadata: Hash[String, String], ?target_bitrate: Integer) ?{ (String) -> void } -> bool.
-
#transliterate_tags ⇒ Object
: () -> void.
-
#transliterated_tag_changes ⇒ Object
: () -> Hash[String, String].
-
#write_bpm(bpm) ⇒ Object
: (String | Integer | Float bpm) -> void.
-
#write_cue_points(cue_points) ⇒ Object
: (Array[Integer, sample_offset: Integer, label: String?] cue_points) -> void.
Constructor Details
#initialize(file_path) ⇒ Audio
: (String file_path) -> void
21 22 23 24 25 |
# File 'lib/wavesync/audio.rb', line 21 def initialize(file_path) @file_path = file_path #: String @file_ext = File.extname(@file_path).downcase #: String @audio = Wavesync::FFMPEG::Probe.new(file_path) #: Wavesync::FFMPEG::Probe end |
Class Method Details
.find_all(library_path) ⇒ Object
: (String library_path) -> Array
14 15 16 17 18 |
# File 'lib/wavesync/audio.rb', line 14 def self.find_all(library_path) Dir.glob(File.join(library_path, '**', '*')) .select { |file| SUPPORTED_FORMATS.include?(File.extname(file).downcase) } .sort_by(&:downcase) end |
Instance Method Details
#bit_depth ⇒ Object
: () -> Integer?
38 39 40 |
# File 'lib/wavesync/audio.rb', line 38 def bit_depth @bit_depth ||= @audio.bit_depth end |
#bitrate ⇒ Object
: () -> Integer?
43 44 45 |
# File 'lib/wavesync/audio.rb', line 43 def bitrate @bitrate ||= @audio.bitrate end |
#bpm ⇒ Object
: () -> (String | Integer)?
48 49 50 51 52 |
# File 'lib/wavesync/audio.rb', line 48 def bpm return @bpm if defined?(@bpm) @bpm = bpm_from_file end |
#cue_points ⇒ Object
: () -> Array[Integer, sample_offset: Integer, label: String?]
65 66 67 68 69 |
# File 'lib/wavesync/audio.rb', line 65 def cue_points return [] unless @file_ext == '.wav' CueChunk.read(@file_path) end |
#duration ⇒ Object
: () -> Float
28 29 30 |
# File 'lib/wavesync/audio.rb', line 28 def duration @audio.duration end |
#format ⇒ Object
: () -> AudioFormat
55 56 57 58 59 60 61 62 |
# File 'lib/wavesync/audio.rb', line 55 def format AudioFormat.new( file_type: @file_ext.delete_prefix('.'), sample_rate: sample_rate, bit_depth: bit_depth, bitrate: bitrate ) end |
#sample_rate ⇒ Object
: () -> Integer?
33 34 35 |
# File 'lib/wavesync/audio.rb', line 33 def sample_rate @sample_rate ||= @audio.sample_rate end |
#transcode(target_path, target_sample_rate: nil, target_file_type: nil, target_bit_depth: nil, padding_seconds: nil, metadata: {}, target_bitrate: 192) ⇒ Object
: (String target_path, ?target_sample_rate: Integer?, ?target_file_type: String?, ?target_bit_depth: Integer?, ?padding_seconds: Numeric?, ?metadata: Hash[String, String], ?target_bitrate: Integer) ?{ (String) -> void } -> bool
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/wavesync/audio.rb', line 144 def transcode(target_path, target_sample_rate: nil, target_file_type: nil, target_bit_depth: nil, padding_seconds: nil, metadata: {}, target_bitrate: 192) ext = target_file_type || @file_ext.delete_prefix('.') temp_path = File.join(Dir.tmpdir, "wavesync_transcode_#{SecureRandom.hex}.#{ext}") begin command = Wavesync::FFMPEG.new.input(@file_path).audio_codec(transcode_codec(ext, target_bit_depth)) command.audio_bitrate("#{target_bitrate}k") if ext == 'mp3' command.sample_rate(target_sample_rate) if target_sample_rate if padding_seconds&.positive? total_duration = @audio.duration + padding_seconds command.audio_filter("apad=whole_dur=#{total_duration.round(6)}") end .each { |key, value| command.(key, value) } command.run(temp_path) yield temp_path if block_given? FileUtils.install(temp_path, target_path) true rescue Errno::ENOENT => e Logger.log_error(e, call_site: 'Audio#transcode', arguments: { target_path:, target_sample_rate:, target_file_type:, target_bit_depth:, padding_seconds:, target_bitrate: }) false ensure FileUtils.rm_f(temp_path) end end |
#transliterate_tags ⇒ Object
: () -> void
119 120 121 122 123 124 125 126 |
# File 'lib/wavesync/audio.rb', line 119 def return unless @file_ext == '.mp3' changes = transliterated_tag_changes return if changes.empty? (changes) end |
#transliterated_tag_changes ⇒ Object
: () -> Hash[String, String]
103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/wavesync/audio.rb', line 103 def transliterated_tag_changes = @audio. changes = {} #: Hash[String, String] FRAME_ID_TO_FFMPEG_KEY.each_value do |ffmpeg_key| current_value = (, ffmpeg_key) next if current_value.nil? transliterated = transliterate(current_value) changes[ffmpeg_key] = transliterated if transliterated != current_value end changes end |
#write_bpm(bpm) ⇒ Object
: (String | Integer | Float bpm) -> void
129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/wavesync/audio.rb', line 129 def write_bpm(bpm) case @file_ext when '.m4a' write_bpm_to_m4a(bpm) when '.mp3' write_bpm_to_mp3(bpm) when '.wav' write_bpm_to_wav(bpm) when '.aif', '.aiff' write_bpm_to_aiff(bpm) end @bpm = bpm end |
#write_cue_points(cue_points) ⇒ Object
: (Array[Integer, sample_offset: Integer, label: String?] cue_points) -> void
72 73 74 75 76 |
# File 'lib/wavesync/audio.rb', line 72 def write_cue_points(cue_points) temp_path = "#{@file_path}.tmp" CueChunk.write(@file_path, temp_path, cue_points) FileUtils.mv(temp_path, @file_path) end |