As a template for generating JPG images, I chose SVG due to its simplicity and speed of working with this format. For example, HTML as a template offers more possibilities but is more complex to implement and maintain.
The task is to generate a JPG image for OpenGraph markup (this is the image/preview for some social networks). The Rails application is hosted on Heroku. The amount of RAM is not very large, so the image generation task needs to be offloaded somewhere. It doesn't make sense to set up and pay for Sidekiq for a pet project. A good option for me turned out to be Heroku Scheduler. This standard (free) addon will run our rake task on a schedule (once a day). The task itself will find posts that do not yet have an OG image and will generate and attach it (using ActiveStorage, the image will be uploaded to AWS) to the model.
The first thing to do is to create the SVG template. First, we play around with SVG. The OG image should be 1200px x 630px.
Here is approximately how the SVG template will look:
This template has no variables. The variables will be interpreted in the .erb template. We will place this SVG template into our Rails application and add the .erb extension, for example lib/templates/og_image_template.svg.erb.
I am using the mini_magick library. So I already have in the Gemfile:
gem "mini_magick"
Next is the code itself lib/cover_image_generator.rb.
require 'mini_magick'
require 'erb'
module CoverImageGenerator
class Generator
def generate_and_attach(entry, entry_title)
begin
# Dynamically fill the SVG template
@entry_title = entry_title.truncate(160)
@blog_name = entry.blog.slug
svg_template = File.read(File.expand_path('./templates/cover_image_template.svg.erb', __dir__))
svg_content = ERB.new(svg_template).result(binding)
# Create a MiniMagick image from the SVG content
cover_image = MiniMagick::Image.read(svg_content)
# Convert to JPG
cover_image.format('jpg')
temp_file_path = entry.slug + '.jpg'
cover_image.write(temp_file_path)
# Attach the image to the entry
entry.cover_image.attach(io: File.open(temp_file_path), filename: "#{entry.slug}.jpg", content_type: 'image/jpeg')
# Clean up the temporary file
File.delete(temp_file_path)
rescue => e
# There will likely be issues with text that has specific characters that cannot be converted to JPG.
# For now, I simply ignore such titles - a solution for this problem needs to be found.
# Currently, if the model does not have an attached OG image, I show a default image.
puts "Error on: #{@entry_title}, ID: #{entry.id}"
puts e
puts "=========================================="
end
puts "Image attached to: #{@entry_title}, ID: #{entry.id}"
puts "=========================================="
end
end
end
AWS is already set up, and all that needs to be done is to add cover_image to our model (for example, Topic)
Our SVG template needs to render our variables. Also, considering the specifics of the SVG file styles, we need to break long titles into several lines.
This code needs to be added to a rake task that will run and generate OG images for all necessary objects. This code can also be used to generate an image after creating or updating (the title) an object.
Result:
Приклад створеного JPEG з SVG шаблону
This example of code/concept is not a final version. There is still work to be done.