Detect MIME type of uploaded file in Ruby
Asked Answered
U

9

53

Is there a bullet proof way to detect MIME type of uploaded file in Ruby or Ruby on Rails? I'm uploading JPEGs and PNGs using SWFupload and content_type is always "application/octet-stream"

Upstart answered 5/1, 2011 at 3:52 Comment(0)
E
49

The ruby-filemagic gem will do it:

require 'filemagic'

puts FileMagic.new(FileMagic::MAGIC_MIME).file(__FILE__)
# => text/x-ruby; charset=us-ascii

This gem does not look at the file extension at all. It reads a bit of the file contents and uses that to guess the file's type.

Ecclesiastic answered 5/1, 2011 at 6:25 Comment(14)
On OS X I just needed to brew install libmagic before gem install ruby-filemagic would work. But the gem works like a charm for image/png, image/jpg, application/x-shockwave-flash, video/mp4, application/ogg, image/vnd.adobe.photoshop, application/pdf, video/x-ms-asf, etc.Statics
Just to clarify (and contrast with @NARKOZ's answer) this gem doesn't look at the extension to find the mime type, unlike the rails MIME::types option.Authoritarian
I have case when filemagic cannot recognize simple image/jpeg: FileMagic.new(FileMagic::MAGIC_MIME).file(URI.parse('https://d2qh54gyqi6t5f.cloudfront.net/boat_images/1/1813/1813941/2979796L.jpg').open.path) #=> "application/octet-stream; charset=binary" but IO.read(URI.parse('https://d2qh54gyqi6t5f.cloudfront.net/boat_images/1/1813/1813941/2979796L.jpg').open.path, 10) =~ /^#{Regexp.new("\xff\xd8\xff\xe0\x00\x10JFIF".force_encoding('binary'))}/ #=> 0 so I use it after @alain-beauvois header file checksWhithersoever
sadly, this gem doesn't seem to work on Rails 5 yet :(Mauri
@DenisUyeda The FileMagic gem is not, to my knowledge dependent upon Rails in any way, so I'm surprised to learn that it doesn't work with Rails 5.Ecclesiastic
Worked for me on Rails 5 but on Fedora/RHEL/CentOS, also needs dnf install file-devel for gem "ruby-filemagic" to compileGasket
Not exactly a lightweight solution. Filemagic installs a whole lot of dependencies...which hopefully won't collide with any of your other gems. Just sayin'.Kung
NOT bulletproof. Not working for me in a non-Rails codebase. NameError (uninitialized constant Refile::Rails).Kung
@Kung That doesn't look like an issue with filemagic at all.Ecclesiastic
@WayneConrad Stack trace says otherwise. Code that depends on broken code is broken.Kung
@Kung It's a mystery to me how the error you posted yesterday could be caused by filemagic, but you've seen some evidence I haven't. I'm sorry this solution didn't work for you. I don't know what you mean by "Code that depends on broken code is broken."Ecclesiastic
Wow, that's a heavy dependency (outside of Rails).Redstone
@Redstone I suppose it is (it binds to a C library, after all). But of all the heavy dependencies I've ever used in Ruby, this one has never given me any trouble at all, either in development or deployment. Unlike, say, rmagic, which can be pretty painful.Ecclesiastic
@WayneConrad I don't know about pain using it; all I noted was an install time of 5min and 40 installed dependencies. o.o While certainly fair for a full Rails application, for quick scripts you'd want to use something else. (Well, I see that the question is tagged for rails, but this is one of the top search results when you Google for generic Ruby.)Redstone
E
42

In Ruby on Rails you can do:

MIME::Types.type_for("filename.gif").first.content_type # => "image/gif"
Eolic answered 24/9, 2011 at 5:50 Comment(7)
> Return the list of MIME::Types which belongs to the file based on its filename extension. If platform is true, then only file types that are specific to the current platform will be returned.Auricular
Not a Valid answer, it just detect the file type based on its extention. If you name a PNG file with FLV extention it will detect it to be a => "video/x-flv"Wizard
Might be good enough if you enforce consistent file extension.Autoharp
Down-voted because the extension has nothing to do with the actual file type. There might not be an extension at all.Accouter
Rails 4.0.1: NameError: uninitialized constant MIME, what are your rails version?Stillhunt
@IvanBlack this is from a Ruby gem, mime-typesPaean
Note that you don't need Ruby on Rails. This is present in the mime-types gem which is used by RoR, but on itself is a lightweight gem.Ostosis
N
21

You can use this reliable method base on the magic header of the file :

def get_image_extension(local_file_path)
  png = Regexp.new("\x89PNG".force_encoding("binary"))
  jpg = Regexp.new("\xff\xd8\xff\xe0\x00\x10JFIF".force_encoding("binary"))
  jpg2 = Regexp.new("\xff\xd8\xff\xe1(.*){2}Exif".force_encoding("binary"))
  case IO.read(local_file_path, 10)
  when /^GIF8/
    'gif'
  when /^#{png}/
    'png'
  when /^#{jpg}/
    'jpg'
  when /^#{jpg2}/
    'jpg'
  else
    mime_type = `file #{local_file_path} --mime-type`.gsub("\n", '') # Works on linux and mac
    raise UnprocessableEntity, "unknown file type" if !mime_type
    mime_type.split(':')[1].split('/')[1].gsub('x-', '').gsub(/jpeg/, 'jpg').gsub(/text/, 'txt').gsub(/x-/, '')
  end  
end
Naumann answered 19/5, 2013 at 13:38 Comment(2)
Using string interpolation when running an external command in backticks is generally not a good idea. local_file_path could be set to ;rm -rf .. In this particular case the method would safely fail with Errno::ENOENT without wiping the current directory, but you better don't rely on that when the file name is provided by a user.Euhemerism
file is not guaranteed to be installed on every Linux distribution. For example it is not in docker://ubuntu:latest. Look before you leap.Illailladvised
F
21

The ruby-filemagic gem is good solution, but requires additional dependencies on libmagic (recently removed from CarrierWave as part of CarrierWave::MagicMimeTypes removal).

If you're interested in a pure ruby implementation, consider the MimeMagic gem! It works well for file types listed in the freedesktop.org mime database:

require 'mimemagic'

MimeMagic.by_magic(File.open('Table-Flip-Guy.jpg')).type # => "image/jpeg" 

For Microsoft Office 2007+ formats (xlsx, docx, and pptx), require the overlay (unless you're okay with the generic "application/zip" MIME type for these files)

require 'mimemagic'    
require 'mimemagic/overlay'

MimeMagic.by_magic(File.open('big_spreadsheet.xlsx')).type # => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 
Fib answered 11/5, 2016 at 1:28 Comment(1)
It is not working for docx extenstion it will return zip content type for docx file.Whipstall
D
16

As of 2021, I would claim that the best tool to compute mime types based on all the available hints (magic number, file name when the magic number does not suffice, user hints) is Marcel.

To shamelessly quote the documentation itself:

Marcel::MimeType.for Pathname.new("example.gif")
#  => "image/gif"

File.open "example.gif" do |file|
  Marcel::MimeType.for file
end
#  => "image/gif"

Marcel::MimeType.for Pathname.new("unrecognisable-data"), name: "example.pdf"
#  => "application/pdf"

Marcel::MimeType.for extension: ".pdf"
#  => "application/pdf"

Marcel::MimeType.for Pathname.new("unrecognisable-data"), name: "example", declared_type: "image/png"
#  => "image/png"

Marcel::MimeType.for StringIO.new(File.read "unrecognisable-data")
#  => "application/octet-stream"
Divisibility answered 6/10, 2021 at 12:14 Comment(3)
Is there no built in way in Rails in 2022 to get the mime type of a file?Godolphin
Checking my Gemfile.lock, ActiveStorage 7.0.4.3 is dependent on marcel and mini_mime, so this is available to you by virtue of using RailsBiform
A worthy-looking effort, though as of Dec 2023, Marcel reports the type of a file foo.txt containing ASCII hello world as being application/octet-stream. ?!?Maser
S
10

filemagic gem is good solution but depends lots of unnecessary gems. (rails, aws-sdk-core, ...)

If your app is small and only runs in Linux or OSX, consider to use file program:

require 'shellwords'
mimetype = `file --brief --mime-type - < #{Shellwords.shellescape(__FILE__)}`.strip

Note: Replace __FILE__ with any expr contains the filepath.

Sachet answered 23/4, 2017 at 12:3 Comment(0)
M
5

mimemagic gem will also do it

https://github.com/minad/mimemagic

from the oficial documentation

MimeMagic is a library to detect the mime type of a file by extension or by content. It uses the mime database provided by freedesktop.org (see http://freedesktop.org/wiki/Software/shared-mime-info/).

require 'mimemagic'
MimeMagic.by_extension('html').text?
MimeMagic.by_extension('.html').child_of? 'text/plain'
MimeMagic.by_path('filename.txt')
MimeMagic.by_magic(File.open('test.html'))
# etc...
Mauri answered 9/6, 2016 at 19:43 Comment(0)
C
1

in case you are doing this from scratch, install mimemagic gem

gem 'mimemagic'

open stream(bytes of target image)

url="https://i.ebayimg.com/images/g/rbIAAOSwojpgyQz1/s-l500.jpg"
result = URI.parse(url).open

then check data-stream's file type for example:

MimeMagic.by_magic(result).type == "image/jpeg"

even though as mentioned above

%w(JPEG GIF TIFF PNG).include?(MimeMagic.by_magic(result).type)

this might be more elegant

Cockrell answered 18/6, 2021 at 22:58 Comment(0)
S
-4

You can use

Mime::Type.lookup_by_extension(extention_name)

Thanks

Springy answered 24/9, 2013 at 9:59 Comment(3)
Downvoted because the extension has nothing to do with the actual file type. There might not be an extension.Accouter
Might be good enough if you enforce consistent file extension.Eolic
-1 because this perpetuates the belief that file extensions are related to filetype (Windows is the only place where this is accurate).Andie

© 2022 - 2024 — McMap. All rights reserved.