From b4367486bd327b79f507058d742e07c9bbb705ae Mon Sep 17 00:00:00 2001 From: Laurent Monin Date: Thu, 15 May 2014 14:35:56 +0200 Subject: [PATCH] Introduce image_info() to extract width, height from image data --- picard/util/imageinfo.py | 95 +++++++++++++++++++++++++++++++++++++++ test/data/mb.gif | Bin 0 -> 5806 bytes test/data/mb.jpg | Bin 0 -> 8550 bytes test/data/mb.png | Bin 0 -> 15692 bytes test/test_utils.py | 33 ++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 picard/util/imageinfo.py create mode 100644 test/data/mb.gif create mode 100644 test/data/mb.jpg create mode 100644 test/data/mb.png diff --git a/picard/util/imageinfo.py b/picard/util/imageinfo.py new file mode 100644 index 000000000..7d33c19b6 --- /dev/null +++ b/picard/util/imageinfo.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2014 Laurent Monin +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import StringIO +import struct + + +class ImageInfoError(Exception): + pass + + +class ImageInfoUnrecognized(Exception): + pass + + +def image_info(data): + """Parse data for jpg, gif, png metadata + If successfully recognized, it returns a tuple with: + - width + - height + - mimetype + - data length + If there is not enough data (< 16 bytes), it will raise `ImageInfoError`. + If format isn't recognized, it will raise `ImageInfoUnrecognized` + """ + + datalen = len(data) + if datalen < 16: + raise ImageInfoError('Not enough data') + + w = -1 + h = -1 + mime = '' + + # http://en.wikipedia.org/wiki/Graphics_Interchange_Format + if data[:6] in ('GIF87a', 'GIF89a'): + w, h = struct.unpack('LL', data[16:24]) + mime = 'image/png' + + # http://en.wikipedia.org/wiki/JPEG + elif data[:2] == '\xFF\xD8': # Start Of Image (SOI) marker + jpeg = StringIO.StringIO(data) + # skip SOI + jpeg.read(2) + b = jpeg.read(1) + try: + while (b and ord(b) != 0xDA): # Start Of Scan (SOS) + while (ord(b) != 0xFF): b = jpeg.read(1) + while (ord(b) == 0xFF): b = jpeg.read(1) + if ord(b) in (0xC0, 0xC1, 0xC2, 0xC5, 0xC6, 0xC7, + 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF): + jpeg.read(2) # parameter length (2 bytes) + jpeg.read(1) # data precision (1 byte) + h, w = struct.unpack('>HH', jpeg.read(4)) + mime = 'image/jpeg' + break + else: + # read 2 bytes as integer + length = int(struct.unpack('>H', jpeg.read(2))[0]) + # skip data + jpeg.read(length - 2) + b = jpeg.read(1) + except struct.error: + pass + except ValueError: + pass + + else: + raise ImageInfoUnrecognized('Unrecognized image data') + assert(w != -1) + assert(h != -1) + assert(mime != '') + return (int(w), int(h), mime, datalen) diff --git a/test/data/mb.gif b/test/data/mb.gif new file mode 100644 index 0000000000000000000000000000000000000000..81fb1385318497bb2750908e131364f748cbe935 GIT binary patch literal 5806 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
4c*aPo3z8Z5(I#X<4OLr`3uW~| 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 WQhDq2&J4(5Xn>rQvShWmaqxf8-T$-z literal 0 HcmV?d00001 diff --git a/test/test_utils.py b/test/test_utils.py index 20ab92985..3e37564b7 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -169,3 +169,36 @@ class AlbumArtistFromPathTest(unittest.TestCase): self.assertEqual(aafp(file_3, 'album', 'artist'), ('album', 'artist')) self.assertEqual(aafp(file_4, 'album', 'artist'), ('album', 'artist')) + +from picard.util.imageinfo import image_info, ImageInfoError, ImageInfoUnrecognized + + +class ImageInfoTest(unittest.TestCase): + + def test_gif(self): + file = os.path.join('test', 'data', 'mb.gif') + + with open(file, 'rb') as f: + self.assertEqual(image_info(f.read()), (140, 96, 'image/gif', 5806)) + + def test_png(self): + file = os.path.join('test', 'data', 'mb.png') + + with open(file, 'rb') as f: + self.assertEqual(image_info(f.read()), (140, 96, 'image/png', 15692)) + + def test_jpeg(self): + file = os.path.join('test', 'data', 'mb.jpg',) + + with open(file, 'rb') as f: + self.assertEqual(image_info(f.read()), (140, 96, 'image/jpeg', 8550)) + + def test_not_enough_data(self): + self.assertRaises(ImageInfoError, image_info, "x") + + def test_invalid_data(self): + self.assertRaises(ImageInfoUnrecognized, image_info, "x" * 20) + + def test_invalid_png_data(self): + data = '\x89PNG\x0D\x0A\x1A\x0A' + "x" * 20 + self.assertRaises(ImageInfoUnrecognized, image_info, data)