From 032e24b2049075577625282ae5d79d69f99aedff Mon Sep 17 00:00:00 2001 From: Jaisen Mathai Date: Wed, 10 Jul 2019 03:59:50 -0700 Subject: [PATCH] Use PIL(pillow) as a fallback mechanism image format identification and add HEIC support (#320) Fixes #281 and #269 When elodie imports images, imghdr.what is used to determine the image type. imghdr implementation won't support all JPEG variants. Because of this, even for a valid JPEG file, imghdr.what returns None causing elodie to skip the image during import. This commit adds a fallback mechanism which uses PIL(pillow) library to identify image formats. When imghdr fails, elodie uses pillow to identify image format. When pillow is unavailable, it will be treated as an invalid media file. Pillow is imported only when imghdr fails. --- .travis.yml | 8 +++---- Readme.md | 2 +- elodie/media/photo.py | 32 ++++++++++++++++++++++------ elodie/tests/files/imghdr-error.jpg | Bin 0 -> 2990 bytes elodie/tests/files/photo.heic | Bin 0 -> 9276 bytes elodie/tests/media/photo_test.py | 8 +++++++ requirements.txt | 1 + 7 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 elodie/tests/files/imghdr-error.jpg create mode 100644 elodie/tests/files/photo.heic diff --git a/.travis.yml b/.travis.yml index 082340e..eb852d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ virtualenv: system_site_packages: true before_install: - "sudo apt-get update -qq" - - "sudo apt-get install python-dev python-pip libimage-exiftool-perl -y" + - "sudo apt-get install python-dev python-pip -y" install: - "pip install -r elodie/tests/requirements.txt" - "pip install coveralls" @@ -19,9 +19,9 @@ before_script: - "sed -i.bak 's/debug = False/debug = True/g' elodie/constants.py" # Get exiftool 10.20 installed - "export ELODIE_DIR=${PWD}" - - "wget http://www.sno.phy.queensu.ca/~phil/exiftool/Image-ExifTool-10.20.tar.gz" - - "gzip -dc Image-ExifTool-10.20.tar.gz | tar -xf -" - - "cd Image-ExifTool-10.20" + - "wget http://www.sno.phy.queensu.ca/~phil/exiftool/Image-ExifTool-11.50.tar.gz" + - "gzip -dc Image-ExifTool-11.50.tar.gz | tar -xf -" + - "cd Image-ExifTool-11.50" - "perl Makefile.PL" - "sudo make install" - "cd ${ELODIE_DIR}" diff --git a/Readme.md b/Readme.md index c8a6bae..ae6083a 100644 --- a/Readme.md +++ b/Readme.md @@ -13,7 +13,7 @@ Getting started takes just a few minutes. Elodie relies on the great [ExifTool library by Phil Harvey](http://www.sno.phy.queensu.ca/~phil/exiftool/). You'll need to install it for your platform. -Some features for video files will only work with newer versions of ExifTool and have been tested on version 10.20 or higher. Check your version by typing `exiftool -ver` and see the [manual installation instructions for ExifTool](http://www.sno.phy.queensu.ca/~phil/exiftool/install.html#Unix) if needed. +Some features for video files will only work with newer versions of ExifTool and have been tested on version 10.20 or higher. Support for HEIC files requires version 11.50 or higher. Check your version by typing `exiftool -ver` and see the [manual installation instructions for ExifTool](http://www.sno.phy.queensu.ca/~phil/exiftool/install.html#Unix) if needed. ``` # OSX (uses homebrew, http://brew.sh/) diff --git a/elodie/media/photo.py b/elodie/media/photo.py index 8114990..1121276 100644 --- a/elodie/media/photo.py +++ b/elodie/media/photo.py @@ -12,6 +12,7 @@ import os import re import time from datetime import datetime +from PIL import Image from re import compile @@ -29,7 +30,7 @@ class Photo(Media): __name__ = 'Photo' #: Valid extensions for photo files. - extensions = ('arw', 'cr2', 'dng', 'gif', 'jpeg', 'jpg', 'nef', 'rw2') + extensions = ('arw', 'cr2', 'dng', 'gif', 'heic', 'jpeg', 'jpg', 'nef', 'rw2') def __init__(self, source=None): super(Photo, self).__init__(source) @@ -91,9 +92,28 @@ class Photo(Media): """ source = self.source - # gh-4 This checks if the source file is an image. - # It doesn't validate against the list of supported types. - if(imghdr.what(source) is None): - return False + # HEIC is not well supported yet so we special case it. + # https://github.com/python-pillow/Pillow/issues/2806 + extension = os.path.splitext(source)[1][1:].lower() + if(extension != 'heic'): + # gh-4 This checks if the source file is an image. + # It doesn't validate against the list of supported types. + if(imghdr.what(source) is None): + # imghdr won't detect all variants of images (https://bugs.python.org/issue28591) + # see https://github.com/jmathai/elodie/issues/281 + # before giving up, we use `pillow` imaging library to detect file type + # + # It is important to note that the library doesn't decode or load the + # raster data unless it really has to. When you open a file, + # the file header is read to determine the file format and extract + # things like mode, size, and other properties required to decode the file, + # but the rest of the file is not processed until later. + try: + im = Image.open(source) + except IOError: + return False - return os.path.splitext(source)[1][1:].lower() in self.extensions + if(im.format is None): + return False + + return extension in self.extensions diff --git a/elodie/tests/files/imghdr-error.jpg b/elodie/tests/files/imghdr-error.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6bc59a873af7373b9291dffe5164b715acad4e10 GIT binary patch literal 2990 zcmbu9cU03`632fbA%V~gO*BXi1_kNxC=s4YHA;j>Zwd%X5KwABief=giXbS6ARyHj zs+33!y$C9ZN)yn~i(o+s z%E<+T!MM4(;Jm_oygWR-`vipqgb(bOlsK?oTwDsJgqD&~loc13|K*UPimHZ&h9p|& zsJ0qLNnJy2_Ye>_H#aX2uNWVnn3}Y>wAx>`od!Ud3pfL$LO?PA2Lc2}fFLr!P75Fj z03c4#PXqpLAPz8u6AI(vhV!r!s`daJATXE%0_Nm|K-k$4?0o=&;1rZrJq8uBaD~ZS z5mvkYAe&3}czL6UDuoc+=VY@zdwN{(-?E>e%?i>Pb*d1ZBNow2dG%LM|!e_*lK zKfwNt3&G~%fIz?y*e(}{BZTc>1cXys6)Jel0_J)}NJi~Cm+KLPp%<=9|B^(AN!sFU;u2eV&$I+WXlOb+_Ss;W*wuS}gVSxvX^>|~)X-iY!`~w>#jx~|7iEznB9&B?fWQM7{ zT0L7NSR_`b-ab=m{6Sw7`%9}R&9gK@jF7W@FufxcJi!kr=4K#<>WF98yth9@>}}CW z42)e92k!u2m#pj1_cY`HKxY5?n8TjIqV>So2sG=M_KFs{145>Mk=$JM9efvmei*LVS6lX1$z?hjq zUMMau^1wU`N@~mVo3gL-iCt21513`WS<`=LVI&NC^TCMyTVa6h{}O)79hu5+IZvwT!s}WET&UCYCOnO)SS!$+m@r(l!U= zTs?odO^uCSRD>%WWviMI$QL)bwt>sWx6-B;_FgJG;Z<_cQz~{(x7WeR1~KM+H3N0$ zy1^NJ?Vc(6-slp`E{9vV=xF+&*3br&v;fz$mGkwo;^=d922h2K6^N znI}x-S#+s?Ut&_o@#_N>y~L=z2w&%frf#LpTMqFVPe$LZ)_2iVGEfC?Q@Rxy$nD%K zbcf9d*Tv`cSTj-gV7pGq!}@N+6M%_k4Sn62Zv1GB&wzPw*{1ws>PKbG=M_dsQ|Ah9 zOOO=j{JbRtJ&ul9jWAZo68Um0-}{QmWPSm>%&ebTTJvDw+azJ3_ci>eW6R!~C(ADC zAEd2Jds8yLpse)^7dsdSem#dPz!&$c>t4Emva|Z=5{J5sH;$7y5RTj*DM~&+b~HI7Blkb z9+7O)neC;ZNKZ?A^K1RFo!`IX{LR_@q;NBLmUx!BemfF9Fspy)LpIh7m9~G@?F=RG zDkbQg_Nc+@*^KG?zTt-Sk%XoNjy4`X{e8X#az}h9b25d<$Kx6PP`&9PQr2MoZ{7*; zxgqazzlxFH?`3w$tZeo4A7GlES{Ns)aYO~D<6k8NC|=tEW@OkC;@VO4@y{3qdQG!X zW8rLhdz9cp%4N&|_YA$>DQfeM=A=VS!j?5{u7Lq| zcid~YaPj{0JuEq|YpRH|+LehHZji6lGu9M7uw;)>k$&+uNeq&~n5|Y~MF%b|K|`-N zNp>Rv4mHSQ3m>==#ZNXG-@e+e>lI`Fd2ykSDlzL(8xqq6n}P_vJbyk?b48$4vTO%v zs-VNxZg(qu_p1!qTHOIs3F}|0E5kMEU}}c9L{>IF-6uUO#a*SU3_UkT(rgb8p=I4& zJ)%=}&)FRNPU2H&lGohz@I;szZv36?qKkR6S~>Y1_>Ldg^A4$$UB=F8?oGohnPBEeT=-n%8H>P#%0i;{Ls zPgsy6o_>%&W22SZ7wX`?>BF-6QfC#bGW*~?S8Y1fcE!zy(_RAb-qm^>04bIfZAFqn(GEa`tLA0zfiTFj5!j~F0`raJZEsN zv%@07l-S_`Z;2WwBV4d!l6UlF=}JVO!qKm4r}5NY9+ERF=-kNpIBwExPr&Q=VL|S8 zm!tUYf&~zjCjwPPNe=P$hn_ArypFx9qAfdo-Nokuy}xzMMT-2%u-wVfi4xfykQy(g z3u)vuKdQS9-4(QI4#Q-e|#}QdViH5yT4eB@5M$afg42MJZi72HjjL~ zt9@?2NHC+**=@ysz_Hz9?Jg!JX%^}}9Gt;nIxga-JUHGejp=%aGpaJ5EbupB=_K@# zr^Z_&Wy#$`LEDy>R+p7gepB8`_IXd&yJR~?OIm&wZS^a|QrD+u>n2NjCipRPxTt-5 zW!}a9#o&noZ`(eb^(&*t{4Uc?0wlEF7Pr8}Q0J~^77#7}QxE^SqyNfR{m$Uu03v3A APyhe` literal 0 HcmV?d00001 diff --git a/elodie/tests/files/photo.heic b/elodie/tests/files/photo.heic new file mode 100644 index 0000000000000000000000000000000000000000..9148a06a7aa210bc16f9793a3de9520587dc2004 GIT binary patch literal 9276 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