app/processors/segment_processor.rb
# frozen_string_literal: true
require_dependency 'processor'
# Process a Track's full audio file into transport segments for
# streaming.
class SegmentProcessor < Processor
delegate :mkdir, to: :tmp_path
# Create the segments and persist them to the database.
#
# @return [Boolean] Whether the operation was successful.
def save
mkdir && create && attach && rmdir
end
private
# @private
# @return [String] Name of this audio file.
def name
File.basename(audio_path, File.extname(audio_path))
end
# @private
# @return [Pathname] Path at which audio content is segmentized.
def tmp_path
Pathname.new("/tmp/#{name}")
end
# Create transport segments for the given audio file
#
# @private
# @return [Boolean] Whether the operation was successful
def create
Open3.popen3(ffmpeg_command.join(' ')) do |stdin, stdout, stderr, thread|
logger.tagged("ffmpeg #{thread.pid}") do
output = stdout.read.chomp
errors = stderr.read.chomp
logger.debug(output)
logger.error(errors) if errors.present?
end
end
tmp_path.entries.any?
end
# Upload segments generated by the `#create` method to ActiveStorage.
#
# @return [Boolean] Whether any segments are attached.
def attach
Dir[tmp_path.join('*.ts')].sort.each do |segment_path|
track.segments.attach(
io: File.open(segment_path),
filename: File.basename(segment_path)
)
end
track.segments.attached?
end
# Remove the directory where segments are stored after they are
# uploaded.
def rmdir
FileUtils.rm_rf(tmp_path)
end
# @return [Array] Command to run for generating segments.
def ffmpeg_command
[
'ffmpeg',
"-i #{audio_path}",
'-f segment',
"-segment_time #{Track::STREAM_SEGMENT_DURATION}",
"-c copy #{tmp_path}/%03d.ts"
]
end
end