ActiveStorage only supports image variants

ActiveStorage is a great addition to the framework but it only allows image variants and not e.g audio or video variants.

FFmpeg offers many filters to transform audio, video and text tracks and is already used in ActiveStorage for video preview/analysis, so why not build from there?

Our app would very much benefit from such a feature. At least having a mechanism to register custom variant transformers for specific content types would be welcome.

Here is a demo which works on my machine (some details are left out):

# remove silent gaps from audio
my_attachment.variant(
  ffmpeg_opts: "-af silenceremove=stop_periods=-1:stop_duration=1:stop_threshold=-90dB"
)
# activestorage/app/models/active_storage/variation.rb

    TRANSFORMERS = {
      image: 'ImageProcessingTransformer',
      audio: 'FfmpegTransformer',
      video: 'FfmpegTransformer'
    }

    def transformer(content_type)
      type = content_type.split('/')[0].to_sym
      if transformer = TRANSFORMERS[type]
        ActiveStorage::Transformers::class_eval(transformer).new(transformations)
      end
    end
# activestorage/lib/active_storage/transformers/ffmpeg_transformer.rb

module ActiveStorage
  module Transformers
    class FfmpegTransformer < Transformer
      private
      def process(file, format:)
        format ||= File.extname(file.path)
        options = transformations[:ffmpeg_opts]
        open_tempfile(format) do |tempfile|
          system "ffmpeg -y -i #{file.path} #{options} #{tempfile.path}"
        end
      end
      
      def open_tempfile(format)
        tempfile = Tempfile.new(["ffmpeg_transformer", ".#{format}"], binmode: true)
        yield tempfile
      ensure
        tempfile&.close!
      end
  end
end
2 Likes

@Betsy_Haibel what do you think, would the Rails team consider a PR or is it intended to stick to image variants only?

I think they’d at least be interested in a PR to support custom variant transformers. I would open an issue on the Rails repo, describe the problem space and your current best stab at an API design, and ask for feedback on that design. When you do so, please remember to mention that you’re interested in writing a PR for it that’s upstreamed from your application.

1 Like

Thank you @Betsy_Haibel, I have submitted a PR to enable variants for custom media types in ActiveStorage. For anyone reading this, it would be lovely to have feedback. CC @georgeclaghorn. Thanks!

1 Like

Yay! Thank you!

I know that it’s easier for the Rails maintainers to review small PRs, so thank you for keeping this one relatively focused – I had a moment of “oh no” when I saw 200 lines added, but then I realized it a lot of it was documentation.

Let me know if you’d like help breaking the PR up even further, though.

@Betsy_Haibel that’d be great :+1:, here are some questions:

  1. can we already remove MiniMagickTransformer from the codebase (in a separate PR), as it has a deprecation warning saying it should disappear in Rails 6.1? That would simplify the PR for sure.
  2. I’ve renamed config.active_storage.variable_content_types to *.variable_image_content_types to make clear they only apply to the ImageProcessingTransformer (this is a breaking change). I could revert that change to avoid the breaking change and make PR smaller.
  3. I had to modify ActiveStorage::Variation to receive blob which is required by accept?. Any suggestions for alternative approaches?
  4. I could reduce the amount of comments in the code (some of which are redundant) and keep the explanations only in the ActiveStorage guide

That sounds like a great start – that piece sounds like it has well-defined edges and is easy to slice off.

I would definitely revert that change here (and possibly open a separate PR for it after this PR is accepted.) Breaking changes are going to be a lot scarier for the maintainers than adding new features.

Not off the top of my head, but I’ll think more about it.

I definitely wouldn’t remove the comments! More documentation is great. Signalling to the maintainers in the PR description that this is mostly comments might be helpful though. :slight_smile:

I’ve submitted a separate PR:

@Betsy_Haibel thanks for the suggestions, I’ve implemented them and updated the PR.

I am quite excited about this as it would open many possibilities to transform on the fly any ActiveStorage file in many creative ways, using Ruby or system commands.

My hope is that this could land in Rails 6.1, do you think it’s possible?

1 Like

I don’t know! I don’t have a commit bit, and I’m not deeply embedded in the code, so I can’t speak to technical details – I can only go off of what the maintainers are telling me about making PRs smaller and breaking-change migration paths clearer.

OK! Let’s wait and see :crossed_fingers: