weathermen/soundstorm

View on GitHub
app/processors/segment_processor.rb

Summary

Maintainability
A
0 mins
Test Coverage
# 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