Skip to main content

Add Metadata

The add_metadata plugin allows adding custom metadata to uploaded files.

Shrine.plugin :add_metadata

Metadata block

The Shrine.add_metadata method allows you to register a block that will get executed on upload, where you can return custom metadata:

require "pdf-reader" # https://github.com/yob/pdf-reader

class PdfUploader < Shrine
  add_metadata :page_count do |io|
    reader = PDF::Reader.new(io)
    reader.page_count
  end
end

The above will add page_count key to the metadata hash, and also create the #page_count reader method on the Shrine::UploadedFile.

uploaded_file.metadata["page_count"] #=> 30
# or
uploaded_file.page_count #=> 30

Skipping nil values

By default, if your block returns nil then the nil value will be stored into metadata. If you do not want to store anything when your block returns nil, you can use the skip_nil: true option:

class PdfUploader < Shrine
  add_metadata :pages, skip_nil: true do |io|
    if is_pdf?(io)
      reader = PDF::Reader.new(io)
      reader.page_count
    else
      # If this is not a PDF, then the pages metadata will not be stored
      nil
    end
  end
end

Multiple values

You can also extract multiple metadata values at once, by using add_metadata without an argument and returning a hash of metadata.

require "exif" # https://github.com/tonytonyjan/exif

class ImageUploader < Shrine
  add_metadata do |io|
    begin
      data = Exif::Data.new(io)
    rescue Exif::NotReadable # not a valid image
      next {}
    end

    { "date_time"     => data.date_time,
      "flash"         => data.flash,
      "focal_length"  => data.focal_length,
      "exposure_time" => data.exposure_time }
  end
end
uploaded_file.metadata #=>
# {
#   ...
#   "date_time" => "2019:07:20 16:16:08",
#   "flash" => 16,
#   "focal_length" => 26/1,
#   "exposure_time" => 1/500,
# }

In this case Shrine won't automatically create reader methods for the extracted metadata, but you can create them via Shrine.metadata_method:

class ImageUploader < Shrine
  # ...
  metadata_method :date_time, :flash
end
uploaded_file.date_time #=> "2019:07:20 16:16:08"
uploaded_file.flash     #=> 16

Ensuring file

The io might not always be a file object, so if you're using an analyzer which requires the source file to be on disk, you can use Shrine.with_file to ensure you have a file object.

require "streamio-ffmpeg" # https://github.com/streamio/streamio-ffmpeg

class VideoUploader < Shrine
  add_metadata do |io|
    movie = Shrine.with_file(io) do |file|
      FFMPEG::Movie.new(file.path)
    end

    { "duration"   => movie.duration,
      "bitrate"    => movie.bitrate,
      "resolution" => movie.resolution,
      "frame_rate" => movie.frame_rate }
  end
end

Uploader options

Uploader options are also yielded to the block, you can access them for more context:

add_metadata do |io, **options|
  options #=>
  # {
  #   record:   #<Photo>,
  #   name:     :image,
  #   action:   :store,
  #   metadata: { ... },
  #   ...
  # }
end

Metadata

The :metadata option holds metadata that was extracted so far:

add_metadata :foo do |io, metadata:, **|
  metadata #=>
  # {
  #   "size"      => 239823,
  #   "filename"  => "nature.jpg",
  #   "mime_type" => "image/jpeg"
  # }

  "foo"
end

add_metadata :bar do |io, metadata:, **|
  metadata #=>
  # {
  #   "size"      => 239823,
  #   "filename"  => "nature.jpg",
  #   "mime_type" => "image/jpeg",
  #   "foo"       => "foo"
  # }

  "bar"
end

Updating metadata

If you just wish to add some custom metadata to existing uploads, you can do it with UploadedFile#add_metadata (and write the changes back to the model):

attacher.file.add_metadata("foo" => "bar")
attacher.write # write changes to the model attribute

attacher.file.metadata #=> { ..., "foo" => "bar" }

You can also use the Attacher#add_metadata shorthand, which also takes care of syncing the model:

attacher.add_metadata("foo" => "bar")

attacher.file.metadata #=> { ..., "foo" => "bar" }