pyexiv2-0.3.2/0000775000175000017500000000000011651316146012536 5ustar osomonosomonpyexiv2-0.3.2/NEWS0000664000175000017500000002317411651315723013244 0ustar osomonosomonpyexiv2 0.3.2 "Travelling" - 2011-10-24 --------------------------------------- Dependencies: - Python ≥ 2.6 - libexiv2 ≥ 0.19 - boost.python ≥ 1.38 Bugs fixed: - 880659: Regression: pyexiv2 0.3.1 doesn’t work with Python 2.6 Contributors: - Olivier Tilloy pyexiv2 0.3.1 "Challenges" - 2011-10-23 --------------------------------------- Dependencies: - Python ≥ 2.6 - libexiv2 ≥ 0.19 - boost.python ≥ 1.38 Changes: - Compiles and tested against the latest libexiv2 (0.22) - Updated windows dependencies (iconv 1.14, libexiv2 0.22, python 2.7.2, boost 1.47.0) Bugs fixed: - 802176: UnicodeDecodeError when opening a file with unicode characters in the path - 815430: [win32] Saving previews to disk produces bogus JPEG files - 781464: Pentax MakerNote tags advertised with the wrong type, decoding their value raises a ValueError - 786253: Exif headers with zero-denominator rational (fraction) types raise DivideByZero exception unnecessarily - 797626: Cannot assign dates earlier than the year 1900 - 797644: Timezone info is not correctly passed to libexiv2 when writing IPTC and XMP tags - 823104: Setting an XMP tag from a value with incorrect type results in a confusing KeyError being raised - 696240: pyexiv2-0.3.0 docs fail to build with sphinx-1.0.5 - 736143: scons error - 813224: String representation of Rational not implemented Contributors: - Alex A. Naanou - Bob Swithers - Chris Mayo - Franz Buchinger - Hobson Lane - János Illés - Joe Borg - Olivier Tilloy - Petri Damstén - Rob Healey pyexiv2 0.3.0 "A Good Year" - 2010-12-31 ---------------------------------------- Dependencies: - Python ≥ 2.6 - libexiv2 ≥ 0.19 - boost.python ≥ 1.38 Changes: - Compiles and tested (on linux and windows) against libexiv2 0.19, 0.20, 0.21 - ImageMetadata implements the collections.MutableMapping interface - Consistent API across all types of tags to access the value(s) - Read/write access to the EXIF thumbnail - Decode and encode EXIF comments according to the specified charset - API to (un)register custom XMP namespaces - API to get, set and delete the (optional) IPTC charset - Added pickling support to tags - Use fractions.Fraction when available in the standard library (Python ≥ 2.6) Bugs fixed: - 617557: Feature request: consistent getter/setter API for EXIF/IPTC/XMP - 618540: Windows build for Python 2.7 - 644143: Failing to parse a raw value silently fails - 677267: Crashes when trying to read Xmp.lr.hierarchicalSubject - 684177: FTBFS against exiv2-0.21 - 559903: Please decode EXIF comments according to charset - 587614: Where's the good old setThumbnailData in 0.2.X series ? - 622739: Segmentation fault when replacing a tag by itself - 624283: Unit tests fail with a non-unicode locale - 624999: test_write_dont_preserve_timestamps fails - 683232: assignment of fractions.Fraction to Exif.GPSInfo.GPSAltitude leads to backtrace - 687373: Misleading exception raised when metadata not read - 688209: test_write_exif_thumbnail_to_file fails on windows - 461847: feature request: convenience function to set IPTC encoding to UTF-8 - 507620: Investigate the ability to catch stderr from libexiv2 in pyexiv - 514415: Replace the Rational class by fractions.Fraction - 549496: Feature request: Custom XMP namespaces - 628735: Pickling an {Exif,Iptc,Xmp}Tag itself - 648624: Feature request: inherit ImageMetadata from collections.MutableMapping Contributors: - Alexandre Rossi - Antti Siira - Matěj Cepl - Olivier Tilloy - Rob Healey pyexiv2 0.2.2 "Holiday" - 2010-05-27 ------------------------------------ Dependencies: - Python ≥ 2.5 - libexiv2 ≥ 0.19 - boost.python ≥ 1.38 Changes: - Fixed two memory leaks - Optimized the use of the underlying libexiv2, expect improved performances - Restored access to the image comment (was a regression from the 0.1 series) - Added an optional parameter to preserve timestamps when writing metadata - Improved API documentation Bugs fixed: - 581787: Metadata tags leak memory - 582194: Reading an IPTC tag leaks memory - 582445: Reading a tag off an image unnecessarily re-writes its value(s) - 582733: Tag values are written twice - 559931: Please restore ability to retrieve JPEG comment - 564770: Document pyexiv2.metadata preview method and properties - 461840: feature request : optionally preserve file timestamps Contributors: - Olivier Tilloy pyexiv2 0.2.1 "Employment" - 2010-04-26 --------------------------------------- Dependencies: - Python ≥ 2.5 - libexiv2 ≥ 0.19 - boost.python ≥ 1.38 Changes: This is a maintenance release that fixes a critical memory leak and makes it easier to generate the documentation and run the unit tests. Bugs fixed: - 562525: memory leak in ImageMetadata.from_buffer - 549398: Building the doc requires fiddling with the PYTHONPATH - 549399: Add a "test" target to scons to run the unit tests Contributors: - Olivier Tilloy pyexiv2 0.2.0 "Commuting" - 2010-03-25 -------------------------------------- Dependencies: - Python ≥ 2.5 - libexiv2 ≥ 0.19 - boost.python ≥ 1.38 Changes: - Almost complete, not backward compatible, rewrite of the 0.1 branch - Support reading and writing XMP metadata - Support reading images from stream - The API is fully documented, the documentation also includes a tutorial and detailed instructions for developers - Compiled and tested on Linux and Windows - The code is now reasonably covered by a battery of unit tests Bugs fixed: - 183337: Support of XMP metadata - 332419: pyexiv2 can not open files with accents in filename on windows - 343403: writing iptc field raises exception oldValues has no len() and/or newValues not iterable - 363873: Pb setting IPTC Tags - 369640: Thumbnail images from a variety of RAW images cannot be extracted - 372321: GIL remains locked during metadata writes - 389960: Can't write geotag data - 392767: Error writing metadata after copying all from another image - 421984: Iptc Keywords and SuppCat should always be tuples - 503272: Doesn't compile against libexiv2 >= 0.19 - 510392: Adding values to a repeatable IPTC tag segfaults - 514590: Longitude should accept 0 to 180 degrees - 517536: pyexiv2 module is not installed - 522216: Inserting a new tag doesn't update the list of keys - 523858: Build fails on Ubuntu Karmic (and others) - 183618: Exif.GPSInfo.{GPSLongitude,Latitude} are not decoded - 249835: pyexiv2 build ignores environmental variables for compilation - 256875: Unable to add/modify multiple value short tags - 401684: Support per-user site-packages - 401876: Exif.CanonCs.LensType not interpreted - 411730: lib is currently hardcoded in src/SConscript - 461853: feature request : version information in code - 510393: Setting an XMP seq to the empty list segfaults - 514408: segfault when reading a nonexistent file - 517298: Port ReadMetadataTestCase to 0.2 - 519566: Segmentation Fault when tried to read metadata from a non-image file - 521404: pygtk example does not quit after closing window - 379270: pyexiv2 access to pixelWidth and pixelHeight accessors? - 518732: Add support for loading image streams - 401784: Reimplement copyMetadataTo in cpp Contributors: - Damien Moore - Mark Lee - Olivier Tilloy - Rob Wallace - Xoff pyexiv2 0.1.3 - 2009-03-14 -------------------------- Dependencies: - Python 2.5 - libexiv2 0.18 - boost.python 1.34.1 Changes: - Adapted the thumbnail related methods (getThumbnailData, setThumbnailData, deleteThumbnail, dumpThumbnailToFile, setThumbnailFromJpegFile) to the new API of libexiv2 0.18. - Removed the unused __main__ from pyexiv2.py. pyexiv2 0.1.2 - 2008-02-09 -------------------------- Dependencies: - Python 2.5.1 - libexiv2 0.15 - boost.python 1.34.1 Changes: - Fixed bug #177249: pyexiv2 should install in site-packages? (reported by Chris Mayo). - Implemented feature request tracked by bug #175069: Retrieve/set the JPEG comment (feature requested by manatlan). - Really fixed bug #146313: passing the filename to the Image constructor as unicode fails (reported by Michal Čihař). - Fixed bug #173387: Error reading Exif.Photo.UserComment (reported by vincespicer). - Fixed bug #175070: Deleting a tag not previously accessed raises a KeyError exception (reported by manatlan). - Fixed bug #183618: Exif.GPSInfo.{GPSLongitude,Latitude} are not decoded (reported by dr who). - Added a method to copy all EXIF and IPTC metadata and the comment from one image to another one (patch submitted by vincespicer). - Added a rational number type (class pyexiv2.Rational) to handle rational values stored in EXIF tags. - Fixed a bug that prevented from setting an EXIF tag with multiple values. - Added some unit tests to test various basic functionalities and bug fixes. - Fixed bug #183332: Cached metadata is not converted to its correct type (reported by Christopher Ellison). pyexiv2 0.1.1 - 2007-10-14 -------------------------- Dependencies: - Python 2.5.1 - libexiv2 0.12 - boost.python 1.33.1 Changes: - Fixed a bug due to interface changes for exceptions management in libexiv2 0.13 (reported by Damon Lynch). - Added support for DESTDIR in SConscript for installation (feature requested by Michal Čihař). - Fixed bug #146313: passing the filename to the Image constructor as unicode fails (reported by Michal Čihař). - Fixed bug #146323: Multi value fields are broken (reported by Michal Čihař). - Implemented feature request tracked by bug #147534: Provide access to interpreted (translated) data (feature requested by Michal Čihař). - Implemented feature request tracked by bug #149212: Add access to tag labels (feature requested by Michal Čihař). pyexiv2 0.1 - 2007-08-30 ------------------------ Dependencies: - Python 2.5.1 - libexiv2 0.12 - boost.python 1.33.1 This is the initial release of pyexiv2. pyexiv2-0.3.2/art/0000775000175000017500000000000011651315372013324 5ustar osomonosomonpyexiv2-0.3.2/art/pyexiv2-small-14x14.png0000664000175000017500000000122411651315372017324 0ustar osomonosomonPNG  IHDRH-sBIT|d pHYs|tEXtSoftwarewww.inkscape.org<IDAT(mkaMll$u b/"R( "]."8HME\tq:t) bJM>m68rֲ^]AʔU߹t nTU@Z %W eQ{}hhv```!k0a%bUlN/mq ~ڢaLUo:0*s ZHl_>j~g*.jp88~${R>KVkWɎ9?"TP(DXq`2h%eUv0v%6r7&'s֧TiNh2X1T2rk=J${3\clv"GTWo5 image/svg+xml pyexiv2 exiv2 pyexiv2-0.3.2/art/pyexiv2-big-192x192.png0000664000175000017500000004172011651315372017140 0ustar osomonosomonPNG  IHDRRlsBIT|d pHYspMBtEXtSoftwarewww.inkscape.org< IDATxy|3#ɲ|8w I --gʖ-v%ۻЃ@+=r@C vlǷlI4ьfF#Y8>~ghG3|}$.@HFA kFA kFA kNtNE4׃n@9`ڋ 98  Bc^@оL:5!²Gqp.3jijSޅ 8**[p6%_Q4tLcLB#9n n`+W[sݨEPhڰi6pxƴ"9Ax pᆵ!FAlhǀ(YL~{xqw!üDAbhpp#Nr#"?zN o5 4mxO@.y|?p@^ @ӆq) /s"y7?eдˁ_WA\ټyc-;h@~8y8C^ @ӆ-Y- g5o`CN)3pw%8C޴M P7Ve4M| k,ٜ$h{N8eO%_6'ƭ x5#SA,m^O $>?4w%#?[zqGb61oO4mo[ޭ-lNq8A^PKol݇xOiןr39S",߂|OzÅcJ@N|`Go/y/–IK~ / *ۗc_7oCEQU1YXlYBzgg'rKZyL5Cx_|78 " wA 'URRBee%>h4g``!DJn/_Nmm-3f0G宻J88O$iG^QbppNTUM].uuuף( ojS y":ȯFprlgw>Yfz74/n{-?|.2eбk./`G~)Ћ'֨.ŋ#Ie]F]]]Bt6SN6LNFR$hIfܹ>|bzB{<r7EII׭WE!I{X#TVVb Ν3<ñc:yFrd<ܯb|,YÇ34u%I33_l۶hBV#'0sL멭Y)**rpHD$! Z@?\.\.PA?PT+Upe % @ӆM.k#;'EEEvizq\ܹ>jkk-c9bdt``?Q;$3f0oUU =dd8OXABTU5,>tttz(Boo/ǏO*|jR$9H.nd哟s4KL4m$@[ iQEQ8ә7oǶmhhhv#2ͣͨUUepp LYY-bܹ|>룿Yt)ώ;1 ,Q{χQQQ(Fv%N"8GW..װfeqZ-ϳ&d%&[|@~k b;^;Θ1*.\g}e1(CCC &>իWH?;$I,[5k֠( 477{nC1of 墴2Cm;~86Z ;וr'jyMddQ4hڰJ l]Èr̙Cii)̚5 !cddĒfʕ̛7NKAߟ,_yg!s`{^^/,322Bkk1`taepp0EX263V`Z&iæ:#xM){֬YjBCMѡ( K.K/vꫯiڬ( k׮eʕqFv=U&D"(^ X&>3gRUUeqxl;Ͼs:^8aΘ,oП#+?9d~ Ruu5~UU `566reQ^^ή]B\.K%Kj*vO~V\ a֬Y׏%Iz U>N~͓]'mAphڰiwȟ2@cc#P.cIQV^͙g 3tvZ;HyվV] jkk)--5P>ːy9ܬle@S_;%kgЮr ]vZصk`Rۛee˖zy衇ͤyN7.$QUUEuuşweGqӇxmU97hy;@\MdA@=D,̜9ЄϧC>d2{o.\a^x:["K9+Ƙ[+f̘a >nO)Np f/b(_3gVȑ# X:Z\vBf`v\DQǕc,yzoto ei m݊ኊ /^Lkkсv!B<'j-.sp.''xH/A9۲Nd O֏w+15 ~ d~F'-^xk1C-;KJugG-w}eh!xJ)$Ri]t}ΝˉNt}|kdV7  +Z黳-D@[K~US];@`m"ti&:]Zoo/uuu,_ ([̮o Y}|ƹ|pҾ|vMٔujmwoO(XئsXqGG~)]K]v9C-oA6p7J2N+?OY_ֲ 5m СC,^k&i ]Hzvw J8)Y4ZMstC ez`B 4c*h C;蠸/1{1'z+DxG"=4 ~&`=0S Hi XC;fkڃ_·?ar8u,WQQnH$³>t޲So_ծ[H!CFvNQɴ1)~]_;'&)M"1 XMycW?ô0o*oqgb x oNZ#Ʋ,S\\LEEg9r㯫DNHڲ>Q;rm='}w.p?ĄhN+?fR  ,?;V 'raQbXp! ,@UU 122B R\\Lqq1Ww^;0X9i>(b@qniʛ8 'hA8SI+5`tt4Wc$I 'o>Z[[1&{^***,h4j:v)CGr{-Ku_-@cM+ܼ|aީCZ*g0ÖZUUNm0dhhh $X {Yq'8W+M O+oz*}A kΪvAHa.^>::J__YM)**21&]HȤxz iJZ,$HF~'y@IW)R x$ߙl'y4[}|-}f[H&(?-X@gS ,f>WZ&EшCEb=Is+?˼ԀҸ:c enGK@}]ncՑ힟zS `>_O+ti}Dx eNף[!1"JTuzϖus+q(E,@yV j6Ϊdh6#}.:>:\%"TTTPUUeqNOH5@IOܒ݋ՌX`dXyOS-gz렗wl3N3%[3t"288磴4WѯOm4{y&[.?<AH^2@aA~ e7j\ ɢ|B! aL6SLWt7pz k(x<#O օvrYLft"F j4*RN `j &ͷߐ|i%f&)I%%%NfppГu"'kbY>N\GN7!Yg_PVVf? /P_i,ߟxwbIo(//eՊwr,kKn7}}}5N<sG* 6M'rXo+++?m۶ҫQNp;-=  Kof!% .^FGGj赾7[e Nzsk#,*2 ?<}}}DN08ߌ[-0A0[7КxCZחEXi}඼ ? \  ˗ey jEx<AX`ݭui58wǫRU[[ܹs&!xWy瓺j~F˪S'(`JU Mr`|bIF>L~ENˍ+ؽw  '̘'j*ǎp{D" 5;$NVknH-vKDuu5 9:x ====)G/[^?LG#m*L_y &ЄsKJ8dE@q[*/׿g!7у  PYYҥKk'^o'$sӯd̙̚5+3<ÇR䜳FD^L t<^0 |:&7䏧[{̥o/psl3QWWQիW`ppR۩S\VVFUUTUUQ^^2 cww7[l%moJ W,[/#/L+rӘOio[fLFh8]'+Yd[+IoZ7oO \~OiiAHZV`cccᄽn}u<ibܹ%\~\r%x|q&?}r6rTSNvJi m@Nw*wZOEU Y+sǿG IDAT/N\sr7Z5g#Ł®]hkksK.eÆ (.%TaΎ =ͷ/BPpS^!Ͳ~"'Z;˧n~ك~)Kޥ(&G<@˽`-t!opU|Xi㜛l$ˠ(htcx&s!OR[H@QZ暄<5oXMmr8@ ㄑ>Ҷ/:&?7sF~Kۜv̟37ǣsӟ4'[E6F%-,ciNo b{0:N(\b$1!=/[Κ+i-@\a'r~EJ^[n= 2]w?Yxqb*ltr~/eZO8%)?'.e~F~-mvw`r-M8cY{Z]2Sdr⋹k''IXZ $⹌˒ f?|} h]8/yQH~0@lHD?}m,?dߟWh~UI\z"QT*kjpB7'Ȳlus~;9L41-Vo3DYL#%8~|B+B\|߷yeOڃgS0U!~ 2-K]htgl6eb<1mΙPt3z΋/wS\l(/Uy%~(V;xmo㘘&3yxx”PUUBvH%Qk'W4}ާ0/V|B$qt ?% "Y:.ęgdk-D"Q.LߓKY0̵WF8ܮnfdtrB69-0.ԫ#?$k2oF$>7ATUpr}߷j̝Fpcnڏy8?0D(\@EQ|0EcxajB̟=‚٣k PQy: H^r؝.9,q{!%Q@~j'^ &9/ooE| i 3wq'o:k0eV1b{!FѠ,Gz#Ȓ@PUB 5~ HnpzBz2]/Z>#nKY_3>~nW;0ȗG3dIPRaFU #̬ PVFyb ؠ<.W֥JeaZ_CeZM*ŸhA%7} @_e'w{iB`uK\t8okֶR@s>ESO9;کԚ[?NؘO>ó[s[jxŕ2 óAxL5x] vjyBEpG.嚰`w/$N߄?A8lÙjǒV+s(|Ky^Bc ]z _&+wBU)خ6| Ús)D@wG6wđ߬ k,Ϯ]WI@@_mGXf:{w}(7 [?o8,`5]6ZARn`hoN(Ǖ,ɟ aN2,O,<1y ??On8Əd+!|+H(f/*Xc3fR`k l͡D&!3Ԝp8YOs Ҽ3$ i\Ȏ*WWrU3))q ^~-*r߀HWZE3t=J0o7ڢ_֭юe1HY50-o''m$%LG+Z6~m1W]^M/9bV8k]PE-$c=xH4jekg/\ͷ?:@wBtى#fJ̶0W,s|ֵܿ%783N.kaohמ0B2fRc]9"9`Amxq~k.O>E7:7CQ`R/+(|=> p#eeČj} )Yp9m˜`N @mxDL&u`tdL͕յ!!Ky@BMi*{_/OA d&Ѽwooi)SLy8;I>i=$'NϾ~Ȩ톪JX4>>a,C NHI+?5m=K/iߧ.aF᪥9U g DD_myxRValD88OkTF">X8O̢rm_.抷|Ctv o~?Ek֛aV=YeN0Y,!pݦ.f4O~`wl@Loe>72`hy+h;܋ a 9K)<[]f?wEj&K+qӠZIB0LSHD1(9t w =5s' O>XO>2B3$$J]\աogsBDFUX;wbۆhٗqĿ/xʶx>l&>reS"S3d%iG&Y4G%:}q nwռO/$O?'? 9C;? mGrIto&($e%!I~YD"vlm ¸2-D߳C'䂆:?ŭ7KxcΛ d r#rAFHΧfk1?om/#`NM^p; p#ep+ l̠ mȶ!O4b3# QPc-y,8<D5 ڻ;cY8ޜB^ǁ)TqM+LPT7I V`ZAI;D~6UU5!FUmY" )Z.;wwS7cnLӹ l~0`(殖!]uF pJh$y֝D쾋kZZkr Bm1rj A{`i8v TjTSTƬAEn0pp? 6 Eؽ8gWb9}L&Y $I n DHXdSU-ȣ;᱀Edc  rO*<Z8u!ۆh7d#N$|r ,*Q ZJ)T %z(2s xK `]=V,PE3eFFx^Ep&3IJ k>˅DLs!SH~h: -PUz`,[,8} Z'<2ʎn9SPUZb]S-{q:.08eK~fTK,?Eyy҇7bDL]OѬZ wh7((N0 d$bA  "hփUI.9}q<8O{wPU9JDGOogmG(->]*s0>ehHhAhhɉ}2Ĕ?~ķ+*Oqj-$WdzVW"tYgyY/qhO|N ?\ N[YK1Kg `d D& ? A$BYUqC{ I.GD{Q56GC9J 縙=K{m?Y:sfU»/KVYѰ#5J?c'8';_6:$,i3q\,~`z99znk :0-}nw^KQ.{8c?etiJd:V\ 3¤Q 4\ɟY_%EF@EO &/W>`;@-aN  _(25Q^Z,^Pºj|ʼnoxDeێ _ӛwM'k:)X(۶8ѣ (N!!|C\Չ] @7-Ԡ=ر='$?[/q l}9i[PS?M\<55q7]~Q(vG~>OS$'$$IPUFYC\J8{u9oY[A g?𿟁_}u%I6":'a|?2 p /MLt% YYMBAElx ^=7>k/xUUڀ\hrMfϾ3 ` >|״VOAkjjx<_hS; ƽXg[N?-G{֚8%[ 50 ,cG11A,'vM +a͛)/M1~w"K$wA|4-@YY%xo4>ضWq&P獋 z!?h6~mZ+`|[b,I]2f0ec_ϒR\t_W}[s*%HxT;w 8lxkds˩0>Gƀh7uĎ9iΈ3XX/$IH&U(Nz,mzS9-)u(޳$GLVevkw8G8}28M4+T?'\ }7_9A63<6" ^%% $2MKqiݲhd~r*5(sA5Ζ?W!?D2n0 $ITTTXHGh=`X}h?Ϸ.4Y!xGp{O~[/[E[ P(-vss`;n8mFT*IG~!#>(=n(]#. [8ɟ4i~6?(" BFwxH@Ĉ ^ǎcY5,]o1<6~փ-%/JqJj.$fR,)߻] O8 8xHKՐU륬29o~Go ~o9U6tnFvW?'jVud!&R\9,C ';w`]a=H~x8c<+I WU__R냠9~+w(lCGo?o'2~HĊ'T캤w1W?y_@Ƞ*1P&2,lv-ŽvZW*Mk/WHOӟ%WNp{OÂK/wuϣa!/=p~4Kރym}MDc[8_G~ Oή_ޔI/(Rl^*I159q+ /8-gK\Or髌I:\Ehvᶴ2#Z9/MOs/j޳π%YaUxˤEx sC{: 0^{{UbHڄ!.@KINAQ\\b]xXgTaH/"҅FvFA " Bj8W2+br%= HO 젧s+mm{6ߊpݯg_eUt6iަM`7RfD-j*Q(-G9tj,+>\|ӹ,Xb$iڊXC)WˏX;f A7~%~0L cEu&DX᫐B|ĮhY@#9 Fw~lf3xӛbe3F@U{ۈFAo'_}-opk5>6!䇂L, ,F0ЎRW@Q$E5IDATj*D8  ThL #Ptt$G?2z{]}gkF5W2=PaY̟ȬƙS]SGI`l,?9=Je$Bϱ6=egݚct~; pz<$0zʉ})Єm\[CڳxF5I}+[Qp]x\ . $Aee dvxj φ#v|D~:D 0- IRI6" Tuu5+.fr"2<2+ضk/:q=7yk}mk^iIs̆vWJE%>좩=ca:y^h/vLR BLO 04#;;-d.RY)eYP77岌[HY-˸% el]Ctv0=W}*AO䏐{C&(4DlOJOF|== XmXY?VmN`o1@ n`æe8l$?:d 'UqBA9LjOɦO%aD|>kI"hğr2$AL!?'؉o>NG J TF‚:S% ̭)CANbZtԟtdN&: p$ z됭$9} (ba&Ң8v{Bq yP޲?9T(@y(@yewEΐIENDB`pyexiv2-0.3.2/art/pyexiv2-medium-64x64.png0000664000175000017500000001120211651315372017503 0ustar osomonosomonPNG  IHDR@@iqsBIT|d pHYsu85tEXtSoftwarewww.inkscape.org<IDATx{U?N&3C ƍH6 Q<#C a}!(.eM\! "$!$f!I'wwuU~; r{n~o^j%"-7_o@ffXVXS[KY{7^b XV?6w,-"N#*ɠ("/Rk%yŇ?vdtZ<׋r"2 `X cWtWk]ǜK~_ 2 M}{;ï)W;cAffj| w\{OR1%O+}yR,ՂȺ7vօw]c {K̕;h 72V٢+6 x*_MxmY&c W,^xRQD@TӴ&5MuӦMF屯|ɢ/=~\0 嚳hQ|UʢD@4PJyRhF6-Zu) `@3lJO]rϺk=n@*$A9nf M ijj ?~bmFcc#=mmmgUHO%XQU£ Ƒ= >-PϤIhjjqXjpG}6͛aBVu<]ଢMCRMbYXr%aglr 8岠X,cKF#l2áCٷoe}݇eY Vyk:0\jVq;w;M)K5ǡT*Q*m[Т?nJDqg}cԧ^K<˖-c D"B>Dz,lƲ,4M`&X0ҹ"? js6vuF XxZ&*HErZ(ۍފ-yiqL&C&X,FD"a,n4\xIa/ qUuٿ!<}HNk-3t@)}Ǔ0 T*E}}='O:&Ofڴ:Q,zB! àNz{{)DX=+ /w3xZ poȸ$Ggg's̩9s&nL&CKK 7nX,  ~"@^/Ja,0ξY U_k𙣃18[D <"ؤi\.`3ffd2̞= 63C,zB^z꣓97& 7~D#c C1Mt:͹K8&Lvq\DQ;6"Mi. O$:mokeyʹWM-kK|Yq,aYy\.aiB3 "tttԲax<zLUdTyYo<#ʖJ%XuRJ%8˗/?m:'OJ4rz[>Q*uQJvS*l&ˑN+>}:pP(D$!ضm[meYB4~=v.u, 8S H$z BJY!L@www-U]#f(x<5+Elޮʜ2AP&;xy"BPqN>LAGGx6 '^`YŬU+&# 47xD Rbڴi躎8`aΩS`xrs#ܻ.w.x'L4Mm8PvχaPnw쫩ʋ">H$–-[ܤa۪Oo_ i>'e<+8|"){ϫ/{߻y%#HGGtT @)UlQ=f&~s9@ d~># !}՟0yh7da!@ CMW=zNJX$Ib-Ǘ nwy( 1uTt]VRR\{r$8uFG r՟,ply(g BS\mʋTD"A$a޼y|><md4`T={P(0zӊ~3Id [Ѓ:Wr Oa$,[Nh٭<,!qQJrxzJ%㘦iJ%lg?ҫ?Dyt*.n}cco\r<@Wg;̒Q,ԙ4ؼvdNUT*av]H$R:^pdhCsCHW-fIӤR2@]a5 xbTxM;XQ;oAp[.V'ܲ4OX~M?Y7鉛t8 6">_ "h ؘX7^b)G_Ui)('ҝ-ݟ_y'&k qB^DW]̜% P8J˅<΢ClipG>3xmtUf>5V00)WTo| wl^l[,, ?=WAeR>7e,;U2 Z-}3hcLN,mC yGWŢOlaq''2:]0q+"egC 9v*HOȦ՟,*G~btFf Twvx4Gҍ qǃ9\@5ꊌNT?ٻ[>kl+m'.:_歏G?9*%n Y(ֆ4 GtQ n{趣jđ (ΝaC#WE1~A&P/mL!w$ ϯq^L,?K0GJXV^ ^ӏa,@) \@mF8N0"xD,T 'yǬ)8~W(念3߷F<4<]pM`3`8 p7P#`l]q.]y}J2]y%inpB4M2bW4iYX0@Y'I`Jǖ}:_0۟DhOyfkq3!ҧ'-f̀^*~p -Dʋף Z$STr~`ya卪Acc#I_>ol-<24'p2.C';ދPZ96L0lp?YlUeWh„ (\M.UTSg{**CKchx ӭqfart]IĨ֛nH̛'Vt#3 phOd/ٰ/;AEd@1>yG:§p$ 6E*kڜ~i罋gz ؆UhC8f=wWg]/a(\e̝;m[w>Uxyt%"OSX܌?K+39L"i}Xo sEL.±OᔺO/9~lǟyILп^GO_r!TibT=IН0] <֕1bɞ}ISׅkDA- x8qwPtB-/1b@ҳzD4ZoiJ)?t_{=/}!f|LUːRd= ^uCˑ O40r[%ݨt*bm)b`/Rv!*DTZ(#P%!dG{пUȨծ3Eq y[V?HwguIENDB`pyexiv2-0.3.2/art/pyexiv2.ico0000664000175000017500000030253611651315372015437 0ustar osomonosomon hV  00 %f@@ (B; (6}(  @->U3n#{2* ]iEB>>HHH:::,;y7w7v7t7r7q7}iD=>>GGGYYYQQQ555EEE/~z8Oy7w7v7t7r7xe@ACCMMM444RRRVVV7ZdAz8gy7w7v7t7f4UQHEEEJJJPOO>`hB˾Dy7%w7ȩv7ȧt7ȉf;a]Xuuu}}}GGGpz8-y7w7qv7t7n7}_;aVIcccGGGK|8z8y7w7v7t7r7Ϣq7z8y76,|9[QFE? lw*/V$ G+DBA@90+d- n{ g}HR%@ GECB?6(dD, 7;CcITGFEC>4aS@, Yc6qz HXHGFE>+g\?+t5] KXIHGFC,kQE, ht3bKXJIHGF3gaG, LU2~_p8)p7o7o7n6NXKJIHG6yaG, 0}`p0r7q7p7o7p6NXMKJIH7|`=, R[/|[s7r7r7q7p7p6OWNMKJI6o>z;,gs-{U5+t7s7r7r7q7p7O-ONLKJ6VaL@4+rzwu*vT:x8 u7t7s7r7r7q6mIOONLK7ykbbbWWWPPPOOO999+++ 0LOQ:u9Hu7u7t7s7r7r7q6ZO:PONL6itusssxxxtttmmmdddkkkmmm```%%%M;w7ov7u7u7t7s7r7r7q7OkPCPsOx0rrfff[[[DDDJJJKKKIIIKKKJJJEEEPPP000%\g<9 x7fw7v7v7u7t7s7r7r7q6p7ßo7o7OZLT\]DDDEEEIIICCCHHHDDD888;;;GGGGGG===.Wa=@{9:x7w7v7v7u7t7s7r7r7q7p7o7W]J699>>>KKK>>>```[[[UUU999+++===LLL???(>C>w3y7x7w7v7v7u7t7s7r7r7q7p7Y]I122@@@KKKKKKeeePPPPPPbbbOOO666JJJAAA-Q]>y8y7x7w7v7v7u7t7s7r7r7q7UYE89:FFFIII\\\///000GGG\\\oooEEEJJJ6;=7Az9Zy8y7x7w7v7v7u7t7s7r7r7KM=@AA666:::OOOOOO000GGG\\\gggHHH6=?2k{C@${8y8y7x7w7v7v7u7t7s7r7eR2@?=UUU===333HHHaaabbb[[[?IM0QZ:EDl|:#y8ny7xw7xw7xw7xw7xu7xu7xs7xs7x\N>cccSSS\\\HHHCCC???FFFFMNDW[D{F>x91w7w7w7u7u7s7j8PKERRRuuutttgggmmmVVVWWWCCCz6Zx7w7v7v7u7t7s7f8QKBOMMooouuuCCCy7]y7y7x91u6v7u7t7s7q7_9[M=ULCRMHVVVffftttCCCz7\y8y8nx7fv7v7u7t7s7r7r7q7n8:::0<<(0`   "*15 <&)PB4/(@<";49G%>2=.0DJShur||)jv`lEL:, ; CkA@?><72-|y[r[DOfe>fs?G~4%DpDBBA@>84+uie4q}S\9~c_j9&EEDCBBA;6/vUB1DKBJJSYdX\g4 LFEEDCB@:4vfS<0p|zYdRrR\)HyGFEEDC?92$~x]D<1 WbIRYdP ju/HHGFEED?9$jfRPA0OW=DYdN^#AIHHGFEEA;^dK71 q~YdLCJgIIHHGFEC=jnPH@1[d-3DK]hYdJDLaKJIHHGFE? |\^TB1 YdHIRW KKJIHHGFD$qkfTB1*/_jYdG}NXKn7%p7o7Ҟo7؝n7؜n6ؗq9MKKJIHHGF$pxfTB1]gwLUYdDz_l;q8|q7p7p7o7o7n7p8 MLKKJIHHG%xfTB1 YdBwcm6r8`r7q7q7p7p7o7o7p8 OMLKKJIHG$xfL3/al_jPYYd@t~E@s7r7r7q7q7p7p7o7p8 ONMLKKJIH%r@w~1]b50| ivYd?q)ٴu8`s7s7r7r7q7q7p7p7p8 NNNMLKKJI$ErxSSB1 Yd=o/8)u6t7s7s7r7r7q7q7p7o4'O^ONNMLKKJ$GkpgfUNMIjnFSUCCC8AB+NR[cV^8l09yu7t7t7s7s7r7r7q7q7q8VRPONNMLKJ%zrxkgopaaaYYYRRRJJJCCC;;;444,,,%%% !9NQj193 u7u7t7t7s7s7r7r7q7q7RPONNMLK$q~oooohhhdddsssyyynnn[[[;;;h3:{5v7u7u7t7t7s7s7r7r7q6p69PoPONNML#htelllrrrzzziiiZZZQQQHHHNNNYYYcccuuurrr999 [5;@w6/w7v7u7u7t7t7s7s7r7r7q7o3M!PlONNN"ZcXXXmmmmmmSSSAAAGGGJJJKKKKKKKKKJJJIIIDDDHHH\\\YYY %CH5<7w9:w7w7v7u7u7t7t7s7s7r7r7q7r7q6=r5w3w3f3f3BFfuwXXXVVVBBBFFFKKKHHHBBB???@@@>>>@@@EEEKKKIIIBBBGGGEEE#?D7<<"y9(x7w7w7v7v7u7t7t7s7s7r7r7q7q7p7p7o7o7n73IBU\]???>>>FFFKKKDDDAAAQQQVVVLLLBBB555666???GGGJJJBBB<<<2?A9=<y6x7x7w7w7v7v7u7t7t7s7s7r7r7q7q7p7p7o7o73IBBDD...AAAJJJFFF???bbbbbbWWWLLLBBB555***444@@@JJJHHH===&+,;>y7x7x7w7w7v7v7u7t7t7s7s7r7r7q7q7p7p7o73IB...888DDDKKKCCCLLLnnnlllmmmiiifffRRR---'''===GGGIII@@@&+,:?y8įy7x7x7w7w7v7v7u7t7t7s7s7r7r7q7q7p7p74IA(((???CCCJJJDDDXXXoooAAA555DDDRRRccckkk777>>>HHHIII>>>'37@?y8vy8y7x7x7w7w7v7v7u7t7t7s7s7r7r7q7q7p74HA,--CCC@@@IIIIIIkkk"""%%%444DDDRRR___mmmaaaCCCKKKFFF777/^lAAv|6!z8y8y7x7x7w7w7v7v7u7t7t7s7s7r7r7q7q71A:233QQQDDDDDDJJJkkk%%%444DDDRRR___mmmfffKKKGGG:::(59=B>){8z8y8y7x7x7w7w7v7v7u7t7t7s7s7r7r7q7-94555HHH444666LLLPPPccc+++444DDDRRR___nnnYYYEEE577(7;?CB@{8z8y8y7x7x7w7w7v7v7u7t7t7s7s7r7r786,EEE<<>>777000)))'''(((***,/04V_CGFDF(|;'y87{78v78v78v78v78v78v78v78r78r78r78r78r78sY;OLIlllKKKvvvYYYGGGIIIKKKMMMNNNPPPQQQLLLDKL>EFHCz7.w7Xw7Xw7Xw7Xt7Xt7Xt7Xt7Xt7Xr7KE>PPPCCCxxxXXXYYYMMMSSSPPPRRRTTTUUUAAA@@@w8x7w7w7v7v7u7t7t7s7s7j8HC=ONM```iiicccnnnmmmbbbsssAAA@@@y7x7x7w7w7v7v7u7t7t7s7s7n7OF???AAA@@@AAA===333{9z8y8x73w7w7v7v7u7t7t7s7s7r7r7q7q7p7q9H333@@@ {:>z8z8y9Ԯz7bx7x7w7w7v7v7u7t7t7s7s7r7r7q7q7z9z8z8y8y7x7x7w7w7v7v7u7t7t7s7s7r7r6o55{9:z8ðz8y8y7x7x7w7w7v7v7u7t7t7s6t7r5{96{8[y8zz7y7x7֬w6w7ڪv7ɩv6u7r8Nq9?~?0pp? ?(@    "(,0355530,'! &.5??G}NWVa_jcocnWbGP38i @5-%M @@>g>x==93ܮ.ɦ*%l\jdp}AKT[[C+p|coU` P6-" =B@A@??>;740,xgtEnYC.2Le{m-coOYG1& @ DCBBAA@?<851( o8e8&`kiv/nGcn)/]3& GEDDCBBAA>:62'huewM@3&(,7=alMVbn$'U0#GEEDCCBBA=841qwu[M?3& .4IRKToam"K+J-FFEEDCCBA<83(}h[I71&hsKTnt^i2#GGFFEEDCC@<73 lzueN=93& CKtKTllw5=`' HHGFFEEDC@;7( yjOHI@3&8?KTj]OY) IHHGFFEED@<7k~WY[L:1&`k>EKTh\g) IIHGGFFEEB=9yg`hT?62& KTf_j'IIIHGGFFED?: pdpUFD?3&17-3-2rKTKTe_k"IJIIHGGFFEA<hty{[SWM@3&KTc`kKJJIIHGGFFC<mxn[g[M@3&LUKTa_kKKJJIIHGGFF@ny`ph[M@3&z'+KT_al s:q8o7Ğp7Оp7Нn6Мn6Нl6hLKKJJIIHGGFEmxtwuh[M@3&7=GO #KT]cop0q7p7p7p7o7o7o7n6n6NLKKJJIIHGGEmxuh[M@3&KT[eq@r7âq7q7p7p7p7o7o7o7n6NMLKKJJIHHGFlxuh[M?2&JRZd8?KTZcpr6r7r7q7q7p7p7p7o7o7p8NMMLKKJJIHHGlwuhW>,_e0&:AYdiusKTX zv4's7r7r7r7q7q7p7p7p7o7p8NNMMLKKJJIHHlwrG0SX-TX12&5; #'KTV:t8s7s7r7r7r7q7q7p7p7p7p8NNNMMLKKJJIHkwu>ej9cgFJ@3& KTT8@t7t7s7s7r7r7r7q7q7p7p7p8OONNMLLKKJJIlw@adOg[M@3'  KTR9<w9:t7t7t7s7s7r7r7r7q7q7p7o8P}OONNMLLKKJJfpY^uh[QRVfhUUUOOOIIIDDD>>>999333+56 CGOWN99Yt8{u7t7t7t7s7s7r7r7r7q7q7p7Q>>999333---((("""##I\^:9t7u7u7t7t7t7s7s7r7r7r7q7q7v; QPOONNMLLKKS[rppqkkkeee```ZZZUUUQQQWWW```ZZZFFF444---((("""w}}::v6u7u7u7t7t7t7s7s7r7r7r7q7q8VS"QPOONNMLLKJRmooopppkkkgggyyy}}}OOO  Z^^;:v8Ӫv7u7u7u7t7t7t7s7s7r7r7r7q7ڟ@Q&PPOONNMLLBHw___ooorrryyy~~~]]]NNNEEE@@@AAABBBAAAHHHRRR___|||LLL m;;Iw7v7v7u7u7u7t7t7t7s7s7r7r7r7q7+O=QONNNMM8?Yef___pppsssiiiOOO@@@DDDHHHIIIJJJKKKKKKJJJIIIHHHEEE@@@NNNdddppp&&&&>B <;>w7w7v7v7u7u7u7t7t7t7s7s7r7r7r7q7Ġo5> 05QQQ___```QQQ???EEEIIIKKKJJJFFFDDDCCCCCCDDDFFFIIIKKKIIIFFF@@@LLL]]] 7:!<<91w7w7w7v7v7u7u7u7t7t7t7s7s7r7r7r7q7q7Ѣp7p8q6hn6hn6hn6hn6hn6h25eprQQQMMMGGG@@@GGGJJJJJJDDD@@@???DDDCCC???;;;<<==x7ȭx7w7w7w7v7v7v7u7u7t7t7t7s7s7r7r7r7q7q7q7p7p7o7o7o736MQQ'''777BBBIIIKKKCCC<<<[[[fff^^^VVVNNNFFF===444***000<<>Ux7x7x7w7w7w7v7v7v7u7u7t7t7t7s7s7r7r7r7q7q7q7p7p7o7o725788'''???EEEJJJGGG@@@IIIooofffbbbfffggg```NNN666***"""888???FFFJJJFFF???'(($fy?>z8y7x7x7w7w7w7v7v7v7u7u7t7t7t7s7s7r7r7r7q7q7q7p7p7o735,,,222???FFFKKKFFF???XXXrrr{{{mmmUUUPPPYYYgggmmmUUU!!!111>>>EEEKKKGGG???*/1+o??w7Oy8y7x7x7w7w7w7v7v7v7u7u7t7t7t7s7s7r7r7r7q7q7q7p7p725&&&777???EEEJJJHHH@@@ccc,,,+++666BBBLLLWWWaaannn]]]///???GGGKKKFFF>>>#%&<@@3 y8y8y7x7x7w7w7w7v7v7v7u7u7t7t7t7s7s7r7r7r7q7q7q7p713"""<<<@@@AAAHHHKKKEEEttt++++++666BBBLLLWWWaaakkktttAAACCCKKKIIIBBB666'7>>OOO888'''666QQQYYYwwwuuu\\\UUU^^^jjjooo\\\DDD222%*+'8=8EDDBtq9 z8z8y8y8y7x7x7w7w7w7v7v7v7u7u7t7t7t7s7s7r7r7sT/?7-666AAAEEEBBB666***###555GGGTTTccc___KKK>>>356#$$+JR=MMMwwwNNNeeeUUUGGGIIIJJJKKKLLLNNNOOOPPPRRRQQQMMMHII?@@?bnrv7y9(y9(y9(y9(y9(s9(s9(s9(s9(s9(s9(s9(r8rnW;JIGLLLEEEeeemmmGGGyyyKKKLLLNNNOOOPPPRRRSSSTTTVVVDDD===`y7w7w7w7v7v7v7u7u7t7t7t7s7s7o8JDNLJOOOPPPggg~~~KKKLLLRRRDDD===`y7y7x7x7v8ӫw6gx8`v7ͪv7v7u7u7t7t7t7s7s7r7j7rX:KE<@??MKILLLNNNPPP]]]tttXXXRRRqqqDDD===`y7y8y7x7v;p0v7v7v7u7u7t7t7t7s7s7r7r7r7i8qX:ZL;IC<>>=BA@FFFFFFHHHIIIJJJMMMAAA===`z7y8y8y7w7v7v7v7u7u7t7t7t7s7s7r7r7r7q7q7q7n7f8===T>>>k===}===???>>>>>>@@@(z8z8y8y8w3mIw6w7v7v7v7u7u7t7t7t7s7s7r7r7r7q7q7q7p6|8iz8z8y8x9y69x73w7w7w7w7v7v7v7u7u7t7t7t7s7s7r7r7r7q7q7q9-{8αz8z8y8y8y7x7x7w7w7w7v7v7v7u7u7t7t7t7s7s7r7r7r7p7y6&{8ӱz8z8y8y8y7x7x7w7w7w7v7v7v7u7u7t7t7t7s7s7r7r63 {8zz8z8y8y8y7x7x7w7w7w7v7v7v7u7u7t7t7t7s6q96UUx7Fz8y8y8y7x7x7w7w7w7v7v7v7u7ԧv7u6^x8 w3{5w5+x73u7%s3@???????`? ? ? ? ?  0008~??(       !#$%%%%%$$#!   !$&(*,-./0000/.-,*(&$!  "%(+.035689::;;::986420.+(%!  #'+/258;= E16gEMQ[U`WaXbYdYdT_MWDM7>pK=:852.+'#  #(,048<?06hOY]hbnbnbnbnbnbnbnbnbnbnbnbnbnbnZeLV06h?;840,'# > ==,=<8Q5c1_,V&MhFK^> (27159="'XPY^jbnbnbnbndp~3ADDHE6'|htbnbnbnbn_jLUO=940+&! @&@c????>>>=;:865410/-+$ nbnbnbnco+]}~~~~~}b?cobnbnamS]*0a=83.)$ BB4ApAA@@@????>><:875320.-*vcobnbn"h|h_VRUeu~~~~~vHp|bnbnbnS]L;60+% CBZBBBAAA@@@????<;975420/-hvbnbn({zU;#?b}~~}W{bnbn_kAI=82,&  DCbCCBBBBAAA@@@??><986421/,xbnbnq}~b=0)" 3j~~~q}bnbnWaG93,&  D2DDCCCBBBAAAA@@@><;96531/-rbnbnGbD=60)"-2yNX$dl]~_bnbn[fN92,% ELDDDDCCCBBBAAAA@?>;986420+ obndp|nQJD=60)"-2 NXKTKT*hp jtbn^i&*Y81+$EjEEDDDDCCCBBBAAAA>=;87531/ pbnhtbWQJD=60)"-2 7>NXKTKTKTq|bn_jM6/)" FCFEEEDDDDCCCBBBAAA><:86420bngse^WQJD=60)" NXKTKTKTvbnZe=4-& FFFFEEEDDDCCCCBBBA?><97531$cobn|ke^WQJD;40)"rNXKTKTKTgsbnPZ81)" G~FFFFEEEDDDCCCCBBB?=;96530 qbnMrke^WQH=420)" NXKTKTKTbnbn9@k4,%GGFFFFEEEDDDCCCCBA?<:9742(bngryrke^UG<8320)" NXKTKTKTLbnYd7/(  H+GGGFFFFEEEDDDCCCCA?=:8641zbnOyrkeUD@;8440)"T^NXKTKTKTdpbn-3Y1*" HWHGGGFFFEEEEDDDCCCA>=:8642gucoyrjUHD@<>=60)"]hNXKTKTKTUbnU`3+$ HeHHGGGFFFEEEEDDDCCA?<:863%bn!wylTLHDHID=60)"w&*7=NXKTKTKTbn`l4,%HoHHHGGGFFFEEEEDDDCA?=:863{bncqUPLKRQJD=60)"*/{NXKTKTKTr}bnB-%IpHHHHGGGFFFEEEEDDDA?=;863gubnz[UPT\WQJC720)"FNNXKTKTKTLbn8?`-%IpIHHHHGGGFFFEEEEDDB?=;96)bnr}aYU]e^WPC8320)" [e R\NXKTKTKT]bnHQ},%IpIIHHHGGGGFFFEEEEDB@=;:7bnRn]Ybke]NA;8320)"DLNXKTKTKTkbnKU+# JpIIIHHHGGGGFFFEEEECA?<:8{bnza]frkaMD@;8960)"NXKTKTKTlbnKT)" JpJIIIHHHGGGGFFFEEEDA?=:8 obnpahyrbMHDAAC=60)" NXKTKTKTibnKT|'  JpJJIIIHHHGGGGFFFEEEC@><9jybnee~y`PLHGOJD=60)"OYNXKTKTKTgbnKTx%JpJJIIIIHHHGGGGFFFEEDB?=;fscoe{fUPLRVQJD=60)";A5;4:4:3939AH17m{NXKTKTKTdbnKTs! KpJJJIIIIHHHGGGGFFFEEC@>;bnlwspYUR^^WQJD=60)"NXKTKTKTabnLUnKpKJJJIIIIHHHGGGGFFFEDB?9bn v{]YWfe^WQJD=60)"MVeqNXKTKTKT^bnMVh KpKKJJJIIIIHHHGGGGFFFECA;bn'ze]Zmke^WQJD=60)" #NXKTKTKT[bnNXaKpKKKJJJIIIIHHHGGGGFFFDB=bn'z|a^qrke^WQJD=60)"fr=DYcNXKTKTKTXbnQZ[ LpKKKKJJJIIIIHHHGGGFFFFD?bn&yhasyrke^WQJD=60)"NXKTKTKTUbnS]U p7p7Vo7uo7o7o7o7n7n6n6n6n6m6LpLKKKKJJJIIIIHHHGGGFFFF@bn%yemyrke^WQJD=60)" &+NXKTKTKTRbnWaMp7Lp7p7p7p7o7o7o7o7o7n7n6n6n6n6LpLLKKKKJJJIIIIHHHGGGFFFDbn%ygyrke^WQJD=60)" WagsNXKTKTKTObn\gEq7q7p7p7p7p7p7p7o7o7o7o7o7n7n6n6n6MpLLLKKKKJJJIIIIHHHGGGFFDbn$x|yrke^WQJD=60)",1NXKTKTKTMbnbn>q7=q7q7q7p7p7p7p7p7p7o7o7o7o7o7n7n6n6MpMLLLKKKKJJJIIIIHHHGGGFDbn$xyrke^WQJD=60)" NXKTKTKTJbnbn:r7-q7q7q7q7q7p7p7p7p7p7p7o7o7o7o7o7n7n6MpMMLLLKKKKJJJIIIHHHHGGGEbn#xyrke^WQJD=60)"uGO#'vNXKTKTKTGbnbn7r7r7r7q7q7q7q7q7p7p7p7p7p7p7o7o7o7o7o7n7MpMMMLLLKKKKJJJIIIHHHHGGFbn#wyrke^WQJD;30)"JRgsNXKTKTKTDbnbn4r7 r7ףr7r7r7q7q7q7q7q7p7p7p7p7p7p7o7o7o7o7o7NpMMMMLLLKKKKJJJIIIHHHHGFbn"wyrke^WL;0nu)OT.0)"}s$) NXKTKTKTAbnbn0s7|r7r7r7r7r7q7q7q7q7q7p7p7p7p7p7p7o7o7o7o7NpNMMMMLLLKKKKJJJIIIHHHHFbn"wyrkeY>.SW,PT*PT)OT.0)"tlyNXKTKTKT>bnds7s7s7r7r7r7r7r7q7q7q7q7q7p7p7p7p7p7p7o7o7o7NpNNMMMMLLLKKKJJJJIIIHHHHbn!vyrgH1UY/QU-PU,PT*PT)W]10)" }lx NXKTKTKT:bn-7s7s7s7s7r7r7r7r7r7q7q7q7q7q7p7p7p7p7p7p7o7o7OpNNNMMMMLLLKKKJJJJIIIHHHbn vyd9bg2QU0QU/QU0[`7<=60)"lxn{NXKTKTKT7bn08{t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7p7p7p7p7p7p7o7OpONNNMMMMLLLKKKJJJJIIIHHbo uZ7UX4RU2QU5`eGQJD=60)"t!% #'wNXKTKTKT4bn088!t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7p7p7p7p7p7p7OkOONNNMMMMLLLKKKJJJJIIIHcouO7RV5RU7]aN\WQJD=60)"NXKTKTKT0bn288t7Ѧt7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7p7p7p7p7p7OMOOONNNMMMMLLLKKKJJJJIIIcpuY8SV7RVDv|ge^WQJD=60)"NXKTKTKT/bn3988t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7p7p7p7p7p7P*OOOONNNMMMLLLLKKKJJJJIIdqui:SV8SVQqke^WQJD=60,.3433v~2kr4`f1`f+ip$pyyNXKTKTKT-~bn3998Bu7St7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7p7p7p7p7PPOOOONNNMMMLLLLKKKJJJJI`mpz=UX:SV`yrke^WQJHKQVceVVVSSSPPPNNNKKKHHHEEEBBB???===:::777444222///)8:SYLTFMKTKT+y]i4999u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7p7p7p7 PPOOOONNNMMMLLLLKKKJJJJ\gkuu>>======;;;;;;<<<;;;<<<<<<>>>???@@@BBBEEEHHHJJJKKKKKKIIIGGGDDDAAA???@@@LLLQQQNNN.25>===<<>><<<<<>>@@@BBBEEEIIIKKKJJJIIIGGGEEEAAA??????FFFGGG>>>047A>===<>>??????CCCFFFIIIKKKLLLHHHEEEBBB???===;;;GGGZZZ\\\XXXTTTPPPLLLHHHDDD???;;;666444777;;;===>>>BBBDDDGGGKKKKKKIIIGGGEEE@@@??????======%67:D>>===>>===???^^^ddd```\\\XXXTTTPPPLLLHHHDDD???;;;666111---111999;;;>>>AAADDDGGGKKKKKKHHHFFFCCC??????888222.22 >>===x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7p7p7p7o7o7o7o7o7n7-+6;>>>DDD***(((333???@@@DDDGGGIIIKKKJJJFFFBBB???===<<>>>== x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7p7p7p7o7o7o7o7o7-+5:>>>:::"""###>>>???BBBFFFHHHJJJLLLGGGCCC@@@===;;;^^^lllhhhddd```\\\XXXTTTPPPLLLHHHDDD???;;;666111---((($$$333:::===???CCCGGGLLLJJJIIIFFFCCC??????---"""-1?>>>>y7lx7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7p7p7p7o7o7o7o7.,69>>>...""",,,??????CCCFFFIIIKKKJJJFFFBBB>>>;;;OOOqqqlllhhhddd```\\\XXXTTTPPPLLLHHHDDD???;;;666111---(((######999:::>>>BBBDDDIIIKKKJJJHHHDDD@@@???222"""(+??>>>y7Wy7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7p7p7p7o7o7o7.,49===###"""555??????DDDGGGJJJKKKJJJEEEBBB>>>:::___qqqlllhhhdddaaammmrrrwww~~~~~~rrrdddUUU===111---(((###555:::===@@@DDDIIIKKKJJJHHHEEE@@@???777"""!#$???>>y8Ay7y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7p7p7p7o7o7.,48777"""###>>>??????DDDHHHJJJKKKIIIDDD@@@===;;;nnnqqqmmmiiiyyy|||nnnccc\\\cccmmmttt{{{~~~ddd888((($$$---:::<<<@@@DDDGGGKKKJJJHHHEEEAAA???999""""""????>y8y8y7y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7p7p7p7o7/,38222"""'''?????????DDDGGGIIIJJJIIIDDDAAA===CCCuuuqqq{{{TTT///333999???EEEJJJOOOTTTYYYdddsssyyyaaa%%%''':::<<<@@@CCCHHHLLLJJJHHHDDD@@@???555"""$04@@???y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7p7p7p7/,48---""",,,?????????DDDGGGIIIKKKJJJFFFAAA???GGGuuu}}}RRR###(((...333999???EEEJJJOOOTTTYYY___dddjjjuuubbb %%%;;;===@@@EEEHHHKKKJJJGGGDDD@@@???111"""*NY@@@??Hy8sy8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7p7p70,37((("""111?????????CCCFFFHHHKKKKKKHHHCCC???IIIMMM"""(((...333999???EEEJJJOOOTTTYYY___dddiiinnnsss___%%%;;;>>>BBBFFFJJJKKKIIIGGGCCC??????***""".hz@@@@? z8&y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7p70,27$$$"""666?????????AAAEEEHHHJJJKKKIIIDDDAAAJJJ]]]"""(((...333999???EEEJJJOOOTTTYYY___dddiiinnnsssppp444===@@@DDDHHHLLLJJJIIIFFFBBB???999""""""9A@@@z8ڰy8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p70,03""""""<<>>]]]UUU***@@@???---""""""***UUU___GGGEEEFFFVVViii((((((...333999???EEEJJJOOOTTTYYY___dddmmmvvvdddHHHGGGEEEBBB???666'''""""""+NYADDDCCCB z88z8z8z8z8y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7,("""%%%LLLhhh;;;666@@@@@@AAA666%%%""""""111]]]gggXXXGGGVVVggg888333999???EEEJJJOOOTTTZZZiiiwwwxxxaaaFFFEEEBBB@@@666)))""""""#&'2vEEDDDDCCez83z8z8z8z8y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r753(%%%JJJeeeddd(((>>>BBByyyCCCBBB@@@333###""""""&&&AAA]]]ggg^^^fff|||oooeeekkkuuu|||zzz___GGGHHHJJJ>>>...$$$"""""""##/bpAFEEEDDDDz8%z8߱z8z8z8y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7UB*,($""""""%%%111???BBBuuuBBBBBBCCCAAA///""""""""""""###;;;UUU```nnnxxx}}}|||uuukkkaaa[[[NNN;;;'''"""""""""#&'.]j@FFFFEEEDDDz8z8z8z8z8y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7b7999<<=====ZZZ???BBBZZZBBBCCCDDDDDDEEEFFFFFFGGG???333***&&&###"""""""""""""""""""""""""""$$$)))...444:::>W^ADGGGGFFFFE[z8&z8hz8y8Ӱy8߯y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7OF<===XXXAAABBBZZZBBBCCCLLLEEEEEEFFFFFFGGGHHHHHHIIIIIIIIIGGGCCCAAA===>>>AAAEEEHHHLLLQQQPPPGGG>>>=============??@^gBFGFFFnW;===BBB[[[@@@{{{ZZZBBBCCChhhHHHFFFGGGHHHHHHIIIIIIJJJKKKKKKLLLMMMMMMNNNOOOOOOPPPQQQQQQRRRRRRNNNIIICCC>>>=========?ajGLGl7@?====XXXGGGHHHZZZBBBCCCFFFGGGHHHHHHIIIIIIJJJKKKKKKLLLMMMMMMNNNOOOOOOPPPQQQQQQRRRSSSSSSTTTUUUUUUOOOEEE======r7kU:======]]]@@@MMMMMMBBBCCCFFFGGGHHHWWWTTTIIIJJJKKKKKKLLLMMMMMMNNNOOOOOOPPPQQQQQQRRRSSSSSSTTTUUUUUUVVVLLL======x7w7Hw7Hw7Hw7Hw7Hv7Hv7Hv7Hv7Hv7Hv7Hu7Hu7Hu7Hu7Hu7Ht7Ht7Ht7Ht7Ht7Hs7Hs7Hs7Hs7Hs7Hr7Ơp8IC<===HHHZZZAAABBBBBBCCCFFFGGGHHHKKKKKKLLLMMMMMMNNNOOOOOOPPPQQQQQQRRRSSSSSSTTTUUUUUUVVVLLL======x7px7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7c9======JJJZZZBBBBBBCCCFFFGGGHHHRRRKKKLLLMMMqqqRRRQQQQQQRRRSSSSSSTTTUUUUUUVVVLLL======x7qx7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7rY:======MMM^^^CCCCCCYYYFFFGGGHHHRRRKKKLLLMMMQQQQQQRRRmmmzzzmmm___UUUVVVLLL======x7qx7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7nV:======DDD```MMMDDDEEEeeewwwFFFGGGHHHRRRKKKLLLMMMQQQQQQRRRVVVLLL======x7rx7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7oV:>>=======VVV]]]HHHEEEFFFFFFGGGHHHRRRKKKLLLMMMQQQQQQRRRVVVLLL======y7sx7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7e9JC<======AAAWWW\\\OOOFFFGGGHHHjjjOOOKKKLLLMMMQQQQQQRRRVVVLLL======y8sy7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7o7fR;>>=======>>>QQQYYYZZZNNNHHHIIIIIIJJJKKKKKKLLLMMMQQQQQQRRRVVVLLL======y8ty8y7x7x7x7x7x7w7w7w7w7|w7vv7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7l8lV:EA==========???NNNVVVXXXVVVOOOKKKKKKLLLMMMXXXyyypppQQQQQQRRRVVVLLL======y8ty8y8y7x7x7x7x7x7ޭw79v7)v7Ϊv7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7p7{]9UJ=============@@@JJJPPPQQQSSSUUUTTTPPPOOOOOOPPPQQQQQQRRRssswwwVVVLLL======y8uy8y8y8y7x7x7x7x71v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7n8a9fR;IC<=====================AAAGGGLLLMMMOOOPPPQQQSSSTTTTTTTTTVVVUUUVVVLLL======z8vy8y8y8y8y7x7x7v7|v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7l8c9uZ:eQ;TI>>@@@AAABBBCCCAAA======z8vz8y8y8y8y8y7x7cw7Cv7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7k8c9qX:=============================================z8wz8z8y8y8y8y8y7Qw71w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7p7p7+++@@@===*<<<<>>>N===`<<>>======.z8sz8z8z8y8y8y8y8w7cw7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7p7z8_z8z8z8z8y8y8y8߯y8 w7w7Ȭw7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7p7L{81z8z8z8z8z8y8y8y8y8x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7q7p7{8˲z8z8z8z8z8y8y8y8Яy8Ry7x7x7Ix7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7q7q7U{8F{8z8z8z8z8z8y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7q7q7{8{8z8z8z8z8z8y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7q7ˢq7{8{8{8z8z8z8z8z8y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7r7r7 {8_{8z8z8z8z8z8y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7r7r7r7{8({8z8z8z8z8z8y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7s7s7s7.{8+z8z8z8z8z8y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7t7t7t7t7s7s7'z8 z8gz8z8z8y8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7u7u7u7u7t7ڧt7t7_t7z8z8My8y8y8y8y7x7x7x7x7x7w7w7w7w7w7v7v7v7v7v7v7u7ͩu7u7Uu7y8 y7x7)x78x7Fx7Ux7dw7rw7lw7[w7Kw7:v7)v7v7????`???pyexiv2-0.3.2/src/0000775000175000017500000000000011651315372013325 5ustar osomonosomonpyexiv2-0.3.2/src/pygtk-example.py0000775000175000017500000000500111651315372016465 0ustar osomonosomon#!/usr/bin/python # -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2007-2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import sys import gtk from pyexiv2 import ImageMetadata if __name__ == '__main__': """ Example of how to combine pygtk and pyexiv2 to display thumbnail data. Minimalistic example of how to load and display with pygtk the thumbnail data extracted from an image. The path to the image file from which the thumbnail data should be extracted should be passed as the only argument of the script. It is of course assumed that you have pygtk installed. """ if (len(sys.argv) != 2): print 'Usage: ' + sys.argv[0] + ' path/to/picture/file/containing/jpeg/thumbnail' sys.exit(1) app = gtk.Window(gtk.WINDOW_TOPLEVEL) app.connect('destroy', lambda app: gtk.main_quit()) # Load the image, read the metadata and extract the thumbnail data metadata = ImageMetadata(sys.argv[1]) metadata.read() previews = metadata.previews if not previews: print "This image doesn't contain any thumbnail." sys.exit(1) # Get the largest preview available preview = previews[-1] # Create a pixbuf loader to read the thumbnail data pbloader = gtk.gdk.PixbufLoader() pbloader.write(preview.data) # Get the resulting pixbuf and build an image to be displayed pixbuf = pbloader.get_pixbuf() pbloader.close() imgwidget = gtk.Image() imgwidget.set_from_pixbuf(pixbuf) # Show the application's main window app.add(imgwidget) imgwidget.show() app.show() sys.exit(gtk.main()) pyexiv2-0.3.2/src/pyqt-example.py0000775000175000017500000000451311651315372016333 0ustar osomonosomon#!/usr/bin/python # -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2007-2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import sys from PyQt4 import QtGui from pyexiv2 import ImageMetadata if __name__ == '__main__': """ Example of how to combine PyQt4 and pyexiv2 to display thumbnail data. Minimalistic example of how to load and display with PyQt the thumbnail data extracted from an image. The path to the image file from which the thumbnail data should be extracted should be passed as the only argument of the script. It is of course assumed that you have PyQt4 installed. """ if (len(sys.argv) != 2): print 'Usage: ' + sys.argv[0] + ' path/to/picture/file/containing/jpeg/thumbnail' sys.exit(1) app = QtGui.QApplication([]) # Load the image, read the metadata and extract the thumbnail data metadata = ImageMetadata(sys.argv[1]) metadata.read() previews = metadata.previews if not previews: print "This image doesn't contain any thumbnail." sys.exit(1) # Get the largest preview available preview = previews[-1] # Create a pixmap from the thumbnail data pixmap = QtGui.QPixmap() pixmap.loadFromData(preview.data, 'JPEG') # Create a QT label to display the pixmap label = QtGui.QLabel(None) label.setPixmap(pixmap) # Show the application's main window label.show() sys.exit(app.exec_()) pyexiv2-0.3.2/src/exiv2wrapper.cpp0000664000175000017500000011341011651315372016467 0ustar osomonosomon// ***************************************************************************** /* * Copyright (C) 2006-2011 Olivier Tilloy * * This file is part of the pyexiv2 distribution. * * pyexiv2 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. * * pyexiv2 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 pyexiv2; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. */ /* Author: Olivier Tilloy */ // ***************************************************************************** #include "exiv2wrapper.hpp" #include "boost/python/stl_iterator.hpp" #include // Custom error codes for Exiv2 exceptions #define METADATA_NOT_READ 101 #define NON_REPEATABLE 102 #define KEY_NOT_FOUND 103 #define INVALID_VALUE 104 #define EXISTING_PREFIX 105 #define BUILTIN_NS 106 #define NOT_REGISTERED 107 // Custom macros #define CHECK_METADATA_READ \ if (!_dataRead) throw Exiv2::Error(METADATA_NOT_READ); namespace exiv2wrapper { void Image::_instantiate_image() { _exifThumbnail = 0; // If an exception is thrown, it has to be done outside of the // Py_{BEGIN,END}_ALLOW_THREADS block. Exiv2::Error error(0); // Release the GIL to allow other python threads to run // while opening the file. Py_BEGIN_ALLOW_THREADS try { if (_data != 0) { _image = Exiv2::ImageFactory::open(_data, _size); } else { _image = Exiv2::ImageFactory::open(_filename); } } catch (Exiv2::Error& err) { error = err; } // Re-acquire the GIL Py_END_ALLOW_THREADS if (error.code() == 0) { assert(_image.get() != 0); _dataRead = false; } else { throw error; } } // Base constructor Image::Image(const std::string& filename) { _filename = filename; _data = 0; _instantiate_image(); } // From buffer constructor Image::Image(const std::string& buffer, unsigned long size) { // Deep copy of the data buffer _data = new Exiv2::byte[size]; for (unsigned long i = 0; i < size; ++i) { _data[i] = buffer[i]; } _size = size; _instantiate_image(); } // Copy constructor Image::Image(const Image& image) { _filename = image._filename; _instantiate_image(); } Image::~Image() { if (_data != 0) { delete[] _data; } if (_exifThumbnail != 0) { delete _exifThumbnail; } } void Image::readMetadata() { // If an exception is thrown, it has to be done outside of the // Py_{BEGIN,END}_ALLOW_THREADS block. Exiv2::Error error(0); // Release the GIL to allow other python threads to run // while reading metadata. Py_BEGIN_ALLOW_THREADS try { _image->readMetadata(); _exifData = &_image->exifData(); _iptcData = &_image->iptcData(); _xmpData = &_image->xmpData(); _dataRead = true; } catch (Exiv2::Error& err) { error = err; } // Re-acquire the GIL Py_END_ALLOW_THREADS if (error.code() != 0) { throw error; } } void Image::writeMetadata() { CHECK_METADATA_READ // If an exception is thrown, it has to be done outside of the // Py_{BEGIN,END}_ALLOW_THREADS block. Exiv2::Error error(0); // Release the GIL to allow other python threads to run // while writing metadata. Py_BEGIN_ALLOW_THREADS try { _image->writeMetadata(); } catch (Exiv2::Error& err) { error = err; } // Re-acquire the GIL Py_END_ALLOW_THREADS if (error.code() != 0) { throw error; } } unsigned int Image::pixelWidth() const { CHECK_METADATA_READ return _image->pixelWidth(); } unsigned int Image::pixelHeight() const { CHECK_METADATA_READ return _image->pixelHeight(); } std::string Image::mimeType() const { CHECK_METADATA_READ return _image->mimeType(); } boost::python::list Image::exifKeys() { CHECK_METADATA_READ boost::python::list keys; for(Exiv2::ExifMetadata::iterator i = _exifData->begin(); i != _exifData->end(); ++i) { keys.append(i->key()); } return keys; } const ExifTag Image::getExifTag(std::string key) { CHECK_METADATA_READ Exiv2::ExifKey exifKey = Exiv2::ExifKey(key); if(_exifData->findKey(exifKey) == _exifData->end()) { throw Exiv2::Error(KEY_NOT_FOUND, key); } return ExifTag(key, &(*_exifData)[key], _exifData, _image->byteOrder()); } void Image::deleteExifTag(std::string key) { CHECK_METADATA_READ Exiv2::ExifKey exifKey = Exiv2::ExifKey(key); Exiv2::ExifMetadata::iterator datum = _exifData->findKey(exifKey); if(datum == _exifData->end()) { throw Exiv2::Error(KEY_NOT_FOUND, key); } _exifData->erase(datum); } boost::python::list Image::iptcKeys() { CHECK_METADATA_READ boost::python::list keys; for(Exiv2::IptcMetadata::iterator i = _iptcData->begin(); i != _iptcData->end(); ++i) { // The key is appended to the list if and only if it is not already // present. if (keys.count(i->key()) == 0) { keys.append(i->key()); } } return keys; } const IptcTag Image::getIptcTag(std::string key) { CHECK_METADATA_READ Exiv2::IptcKey iptcKey = Exiv2::IptcKey(key); if(_iptcData->findKey(iptcKey) == _iptcData->end()) { throw Exiv2::Error(KEY_NOT_FOUND, key); } return IptcTag(key, _iptcData); } void Image::deleteIptcTag(std::string key) { CHECK_METADATA_READ Exiv2::IptcKey iptcKey = Exiv2::IptcKey(key); Exiv2::IptcMetadata::iterator dataIterator = _iptcData->findKey(iptcKey); if (dataIterator == _iptcData->end()) { throw Exiv2::Error(KEY_NOT_FOUND, key); } while (dataIterator != _iptcData->end()) { if (dataIterator->key() == key) { dataIterator = _iptcData->erase(dataIterator); } else { ++dataIterator; } } } boost::python::list Image::xmpKeys() { CHECK_METADATA_READ boost::python::list keys; for(Exiv2::XmpMetadata::iterator i = _xmpData->begin(); i != _xmpData->end(); ++i) { keys.append(i->key()); } return keys; } const XmpTag Image::getXmpTag(std::string key) { CHECK_METADATA_READ Exiv2::XmpKey xmpKey = Exiv2::XmpKey(key); if(_xmpData->findKey(xmpKey) == _xmpData->end()) { throw Exiv2::Error(KEY_NOT_FOUND, key); } return XmpTag(key, &(*_xmpData)[key]); } void Image::deleteXmpTag(std::string key) { CHECK_METADATA_READ Exiv2::XmpKey xmpKey = Exiv2::XmpKey(key); Exiv2::XmpMetadata::iterator i = _xmpData->findKey(xmpKey); if(i != _xmpData->end()) { _xmpData->erase(i); } else throw Exiv2::Error(KEY_NOT_FOUND, key); } const std::string Image::getComment() const { CHECK_METADATA_READ return _image->comment(); } void Image::setComment(const std::string& comment) { CHECK_METADATA_READ _image->setComment(comment); } void Image::clearComment() { CHECK_METADATA_READ _image->clearComment(); } boost::python::list Image::previews() { CHECK_METADATA_READ boost::python::list previews; Exiv2::PreviewManager pm(*_image); Exiv2::PreviewPropertiesList props = pm.getPreviewProperties(); for (Exiv2::PreviewPropertiesList::const_iterator i = props.begin(); i != props.end(); ++i) { previews.append(Preview(pm.getPreviewImage(*i))); } return previews; } void Image::copyMetadata(Image& other, bool exif, bool iptc, bool xmp) const { CHECK_METADATA_READ if (!other._dataRead) throw Exiv2::Error(METADATA_NOT_READ); if (exif) other._image->setExifData(*_exifData); if (iptc) other._image->setIptcData(*_iptcData); if (xmp) other._image->setXmpData(*_xmpData); } std::string Image::getDataBuffer() const { std::string buffer; // Release the GIL to allow other python threads to run // while reading the image data. Py_BEGIN_ALLOW_THREADS Exiv2::BasicIo& io = _image->io(); unsigned long size = io.size(); long pos = -1; if (io.isopen()) { // Remember the current position in the stream pos = io.tell(); // Go to the beginning of the stream io.seek(0, Exiv2::BasicIo::beg); } else { io.open(); } // Copy the data buffer in a string. Since the data buffer can contain null // characters ('\x00'), the string cannot be simply constructed like that: // _data = std::string((char*) previewImage.pData()); // because it would be truncated after the first occurence of a null // character. Therefore, it has to be copied character by character. // First allocate the memory for the whole string... buffer.resize(size, ' '); // ... then fill it with the raw data. for (unsigned long i = 0; i < size; ++i) { io.read((Exiv2::byte*) &buffer[i], 1); } if (pos == -1) { // The stream was initially closed io.close(); } else { // Reset to the initial position in the stream io.seek(pos, Exiv2::BasicIo::beg); } // Re-acquire the GIL Py_END_ALLOW_THREADS return buffer; } Exiv2::ByteOrder Image::getByteOrder() const { CHECK_METADATA_READ return _image->byteOrder(); } Exiv2::ExifThumb* Image::_getExifThumbnail() { CHECK_METADATA_READ if (_exifThumbnail == 0) { _exifThumbnail = new Exiv2::ExifThumb(*_exifData); } return _exifThumbnail; } const std::string Image::getExifThumbnailMimeType() { return std::string(_getExifThumbnail()->mimeType()); } const std::string Image::getExifThumbnailExtension() { return std::string(_getExifThumbnail()->extension()); } void Image::writeExifThumbnailToFile(const std::string& path) { _getExifThumbnail()->writeFile(path); } const std::string Image::getExifThumbnailData() { Exiv2::DataBuf buffer = _getExifThumbnail()->copy(); // Copy the data buffer in a string. Since the data buffer can contain null // characters ('\x00'), the string cannot be simply constructed like that: // data = std::string((char*) buffer.pData_); // because it would be truncated after the first occurence of a null // character. Therefore, it has to be copied character by character. // First allocate the memory for the whole string... std::string data = std::string(buffer.size_, ' '); // ... then fill it with the raw data. for(unsigned int i = 0; i < buffer.size_; ++i) { data[i] = buffer.pData_[i]; } return data; } void Image::eraseExifThumbnail() { _getExifThumbnail()->erase(); } void Image::setExifThumbnailFromFile(const std::string& path) { _getExifThumbnail()->setJpegThumbnail(path); } void Image::setExifThumbnailFromData(const std::string& data) { const Exiv2::byte* buffer = (const Exiv2::byte*) data.c_str(); _getExifThumbnail()->setJpegThumbnail(buffer, data.size()); } const std::string Image::getIptcCharset() const { CHECK_METADATA_READ const char* charset = _iptcData->detectCharset(); if (charset != 0) { return std::string(charset); } else { return std::string(); } } ExifTag::ExifTag(const std::string& key, Exiv2::Exifdatum* datum, Exiv2::ExifData* data, Exiv2::ByteOrder byteOrder): _key(key), _byteOrder(byteOrder) { if (datum != 0 && data != 0) { _datum = datum; _data = data; } else { _datum = new Exiv2::Exifdatum(_key); _data = 0; } // Conditional code, exiv2 0.21 changed APIs we need // (see https://bugs.launchpad.net/pyexiv2/+bug/684177). #if EXIV2_TEST_VERSION(0,21,0) Exiv2::ExifKey exifKey(key); _type = Exiv2::TypeInfo::typeName(exifKey.defaultTypeId()); // Where available, extract the type from the metadata, it is more reliable // than static type information. The exception is for user comments, for // which we’d rather keep the 'Comment' type instead of 'Undefined'. if ((_data != 0) && (_type != "Comment")) { _type = _datum->typeName(); } _name = exifKey.tagName(); _label = exifKey.tagLabel(); _description = exifKey.tagDesc(); _sectionName = Exiv2::ExifTags::sectionName(exifKey); // The section description is not exposed in the API any longer // (see http://dev.exiv2.org/issues/744). For want of anything better, // fall back on the section’s name. _sectionDescription = _sectionName; #else const uint16_t tag = _datum->tag(); const Exiv2::IfdId ifd = _datum->ifdId(); _type = Exiv2::TypeInfo::typeName(Exiv2::ExifTags::tagType(tag, ifd)); // Where available, extract the type from the metadata, it is more reliable // than static type information. The exception is for user comments, for // which we’d rather keep the 'Comment' type instead of 'Undefined'. if ((_data != 0) && (_type != "Comment")) { _type = _datum->typeName(); } _name = Exiv2::ExifTags::tagName(tag, ifd); _label = Exiv2::ExifTags::tagLabel(tag, ifd); _description = Exiv2::ExifTags::tagDesc(tag, ifd); _sectionName = Exiv2::ExifTags::sectionName(tag, ifd); _sectionDescription = Exiv2::ExifTags::sectionDesc(tag, ifd); #endif } ExifTag::~ExifTag() { if (_data == 0) { delete _datum; } } void ExifTag::setRawValue(const std::string& value) { int result = _datum->setValue(value); if (result != 0) { throw Exiv2::Error(INVALID_VALUE); } } void ExifTag::setParentImage(Image& image) { Exiv2::ExifData* data = image.getExifData(); if (data == _data) { // The parent image is already the one passed as a parameter. // This happens when replacing a tag by itself. In this case, don’t do // anything (see https://bugs.launchpad.net/pyexiv2/+bug/622739). return; } _data = data; std::string value = _datum->toString(); delete _datum; _datum = &(*_data)[_key.key()]; _datum->setValue(value); _byteOrder = image.getByteOrder(); } const std::string ExifTag::getKey() { return _key.key(); } const std::string ExifTag::getType() { return _type; } const std::string ExifTag::getName() { return _name; } const std::string ExifTag::getLabel() { return _label; } const std::string ExifTag::getDescription() { return _description; } const std::string ExifTag::getSectionName() { return _sectionName; } const std::string ExifTag::getSectionDescription() { return _sectionDescription; } const std::string ExifTag::getRawValue() { return _datum->toString(); } const std::string ExifTag::getHumanValue() { return _datum->print(_data); } int ExifTag::getByteOrder() { return _byteOrder; } IptcTag::IptcTag(const std::string& key, Exiv2::IptcData* data): _key(key) { _from_data = (data != 0); if (_from_data) { _data = data; } else { _data = new Exiv2::IptcData(); _data->add(Exiv2::Iptcdatum(_key)); } Exiv2::IptcMetadata::iterator iterator = _data->findKey(_key); const uint16_t tag = iterator->tag(); const uint16_t record = iterator->record(); _type = Exiv2::TypeInfo::typeName(Exiv2::IptcDataSets::dataSetType(tag, record)); _name = Exiv2::IptcDataSets::dataSetName(tag, record); _title = Exiv2::IptcDataSets::dataSetTitle(tag, record); _description = Exiv2::IptcDataSets::dataSetDesc(tag, record); // What is the photoshop name anyway? Where is it used? _photoshopName = Exiv2::IptcDataSets::dataSetPsName(tag, record); _repeatable = Exiv2::IptcDataSets::dataSetRepeatable(tag, record); _recordName = Exiv2::IptcDataSets::recordName(record); _recordDescription = Exiv2::IptcDataSets::recordDesc(record); if (_from_data) { // Check that we are not trying to assign multiple values to a tag that // is not repeatable. unsigned int nb_values = 0; for(Exiv2::IptcMetadata::iterator iterator = _data->begin(); iterator != _data->end(); ++iterator) { if (iterator->key() == key) { ++nb_values; if (!_repeatable && (nb_values > 1)) { throw Exiv2::Error(NON_REPEATABLE); } } } } } IptcTag::~IptcTag() { if (!_from_data) { delete _data; } } void IptcTag::setRawValues(const boost::python::list& values) { if (!_repeatable && (boost::python::len(values) > 1)) { // The tag is not repeatable but we are trying to assign it more than // one value. throw Exiv2::Error(NON_REPEATABLE); } unsigned int index = 0; unsigned int max = boost::python::len(values); Exiv2::IptcMetadata::iterator iterator = _data->findKey(_key); while (index < max) { std::string value = boost::python::extract(values[index++]); if (iterator != _data->end()) { // Override an existing value int result = iterator->setValue(value); if (result != 0) { throw Exiv2::Error(INVALID_VALUE); } // Jump to the next datum matching the key ++iterator; while ((iterator != _data->end()) && (iterator->key() != _key.key())) { ++iterator; } } else { // Append a new value Exiv2::Iptcdatum datum(_key); int result = datum.setValue(value); if (result != 0) { throw Exiv2::Error(INVALID_VALUE); } int state = _data->add(datum); if (state == 6) { throw Exiv2::Error(NON_REPEATABLE); } // Reset iterator that has been invalidated by appending a datum iterator = _data->end(); } } // Erase the remaining values if any while (iterator != _data->end()) { if (iterator->key() == _key.key()) { iterator = _data->erase(iterator); } else { ++iterator; } } } void IptcTag::setParentImage(Image& image) { Exiv2::IptcData* data = image.getIptcData(); if (data == _data) { // The parent image is already the one passed as a parameter. // This happens when replacing a tag by itself. In this case, don’t do // anything (see https://bugs.launchpad.net/pyexiv2/+bug/622739). return; } const boost::python::list values = getRawValues(); delete _data; _from_data = true; _data = data; setRawValues(values); } const std::string IptcTag::getKey() { return _key.key(); } const std::string IptcTag::getType() { return _type; } const std::string IptcTag::getName() { return _name; } const std::string IptcTag::getTitle() { return _title; } const std::string IptcTag::getDescription() { return _description; } const std::string IptcTag::getPhotoshopName() { return _photoshopName; } const bool IptcTag::isRepeatable() { return _repeatable; } const std::string IptcTag::getRecordName() { return _recordName; } const std::string IptcTag::getRecordDescription() { return _recordDescription; } const boost::python::list IptcTag::getRawValues() { boost::python::list values; for(Exiv2::IptcMetadata::iterator iterator = _data->begin(); iterator != _data->end(); ++iterator) { if (iterator->key() == _key.key()) { values.append(iterator->toString()); } } return values; } XmpTag::XmpTag(const std::string& key, Exiv2::Xmpdatum* datum): _key(key) { _from_datum = (datum != 0); if (_from_datum) { _datum = datum; _exiv2_type = datum->typeName(); } else { _datum = new Exiv2::Xmpdatum(_key); _exiv2_type = Exiv2::TypeInfo::typeName(Exiv2::XmpProperties::propertyType(_key)); } const char* title = Exiv2::XmpProperties::propertyTitle(_key); if (title != 0) { _title = title; } const char* description = Exiv2::XmpProperties::propertyDesc(_key); if (description != 0) { _description = description; } const Exiv2::XmpPropertyInfo* info = Exiv2::XmpProperties::propertyInfo(_key); if (info != 0) { _name = info->name_; _type = info->xmpValueType_; } } XmpTag::~XmpTag() { if (!_from_datum) { delete _datum; } } void XmpTag::setTextValue(const std::string& value) { _datum->setValue(value); } void XmpTag::setArrayValue(const boost::python::list& values) { // Reset the value _datum->setValue(0); for(boost::python::stl_input_iterator iterator(values); iterator != boost::python::stl_input_iterator(); ++iterator) { _datum->setValue(*iterator); } } void XmpTag::setLangAltValue(const boost::python::dict& values) { // Reset the value _datum->setValue(0); for(boost::python::stl_input_iterator iterator(values); iterator != boost::python::stl_input_iterator(); ++iterator) { std::string key = *iterator; std::string value = boost::python::extract(values.get(key)); _datum->setValue("lang=\"" + key + "\" " + value); } } void XmpTag::setParentImage(Image& image) { Exiv2::Xmpdatum* datum = &(*image.getXmpData())[_key.key()]; if (datum == _datum) { // The parent image is already the one passed as a parameter. // This happens when replacing a tag by itself. In this case, don’t do // anything (see https://bugs.launchpad.net/pyexiv2/+bug/622739). return; } switch (Exiv2::XmpProperties::propertyType(_key)) { case Exiv2::xmpText: { const std::string value = getTextValue(); delete _datum; _from_datum = true; _datum = &(*image.getXmpData())[_key.key()]; setTextValue(value); break; } case Exiv2::xmpAlt: case Exiv2::xmpBag: case Exiv2::xmpSeq: { const boost::python::list value = getArrayValue(); delete _datum; _from_datum = true; _datum = &(*image.getXmpData())[_key.key()]; setArrayValue(value); break; } case Exiv2::langAlt: { const boost::python::dict value = getLangAltValue(); delete _datum; _from_datum = true; _datum = &(*image.getXmpData())[_key.key()]; setLangAltValue(value); break; } default: // Should not happen, this case is here for the sake // of completeness and to avoid compiler warnings. assert(0); } } const std::string XmpTag::getKey() { return _key.key(); } const std::string XmpTag::getExiv2Type() { return _exiv2_type; } const std::string XmpTag::getType() { return _type; } const std::string XmpTag::getName() { return _name; } const std::string XmpTag::getTitle() { return _title; } const std::string XmpTag::getDescription() { return _description; } const std::string XmpTag::getTextValue() { return dynamic_cast(&_datum->value())->value_; } const boost::python::list XmpTag::getArrayValue() { std::vector value = dynamic_cast(&_datum->value())->value_; boost::python::list rvalue; for(std::vector::const_iterator i = value.begin(); i != value.end(); ++i) { rvalue.append(*i); } return rvalue; } const boost::python::dict XmpTag::getLangAltValue() { Exiv2::LangAltValue::ValueType value = dynamic_cast(&_datum->value())->value_; boost::python::dict rvalue; for (Exiv2::LangAltValue::ValueType::const_iterator i = value.begin(); i != value.end(); ++i) { rvalue[i->first] = i->second; } return rvalue; } Preview::Preview(const Exiv2::PreviewImage& previewImage) { _mimeType = previewImage.mimeType(); _extension = previewImage.extension(); _size = previewImage.size(); _dimensions = boost::python::make_tuple(previewImage.width(), previewImage.height()); // Copy the data buffer in a string. Since the data buffer can contain null // characters ('\x00'), the string cannot be simply constructed like that: // _data = std::string((char*) previewImage.pData()); // because it would be truncated after the first occurence of a null // character. Therefore, it has to be copied character by character. const Exiv2::byte* pData = previewImage.pData(); // First allocate the memory for the whole string... _data = std::string(_size, ' '); // ... then fill it with the raw data. for(unsigned int i = 0; i < _size; ++i) { _data[i] = pData[i]; } } void Preview::writeToFile(const std::string& path) const { std::string filename = path + _extension; std::ofstream fd(filename.c_str(), std::ios::out | std::ios::binary); fd << _data; fd.close(); } void translateExiv2Error(Exiv2::Error const& error) { // Use the Python 'C' API to set up an exception object const char* message = error.what(); // The type of the Python exception depends on the error code // Warning: this piece of code should be updated in case the error codes // defined by Exiv2 (file 'src/error.cpp') are changed switch (error.code()) { // Exiv2 error codes case 2: // {path}: Call to `{function}' failed: {strerror} // May be raised when reading a file PyErr_SetString(PyExc_RuntimeError, message); break; case 3: // This does not look like a {image type} image // May be raised by readMetadata() PyErr_SetString(PyExc_IOError, message); break; case 4: // Invalid dataset name `{dataset name}' // May be raised when instantiating an IptcKey from a string PyErr_SetString(PyExc_KeyError, message); break; case 5: // Invalid record name `{record name}' // May be raised when instantiating an IptcKey from a string PyErr_SetString(PyExc_KeyError, message); break; case 6: // Invalid key `{key}' // May be raised when instantiating an ExifKey, an IptcKey or an // XmpKey from a string PyErr_SetString(PyExc_KeyError, message); break; case 7: // Invalid tag name or ifdId `{tag name}', ifdId {ifdId} // May be raised when instantiating an ExifKey from a string PyErr_SetString(PyExc_KeyError, message); break; case 8: // Value not set // May be raised when calling value() on a datum PyErr_SetString(PyExc_ValueError, message); break; case 9: // {path}: Failed to open the data source: {strerror} // May be raised by readMetadata() PyErr_SetString(PyExc_IOError, message); break; case 10: // {path}: Failed to open file ({mode}): {strerror} // May be raised by writeMetadata() PyErr_SetString(PyExc_IOError, message); break; case 11: // {path}: The file contains data of an unknown image type // May be raised when opening an image PyErr_SetString(PyExc_IOError, message); break; case 12: // The memory contains data of an unknown image type // May be raised when instantiating an image from a data buffer PyErr_SetString(PyExc_IOError, message); break; case 13: // Image type {image type} is not supported // May be raised when creating a new image PyErr_SetString(PyExc_IOError, message); break; case 14: // Failed to read image data // May be raised by readMetadata() PyErr_SetString(PyExc_IOError, message); break; case 15: // This does not look like a JPEG image // May be raised by readMetadata() PyErr_SetString(PyExc_IOError, message); break; case 17: // {old path}: Failed to rename file to {new path}: {strerror} // May be raised by writeMetadata() PyErr_SetString(PyExc_IOError, message); break; case 18: // {path}: Transfer failed: {strerror} // May be raised by writeMetadata() PyErr_SetString(PyExc_IOError, message); break; case 19: // Memory transfer failed: {strerror} // May be raised by writeMetadata() PyErr_SetString(PyExc_IOError, message); break; case 20: // Failed to read input data // May be raised by writeMetadata() PyErr_SetString(PyExc_IOError, message); break; case 21: // Failed to write image // May be raised by writeMetadata() PyErr_SetString(PyExc_IOError, message); break; case 22: // Input data does not contain a valid image // May be raised by writeMetadata() PyErr_SetString(PyExc_IOError, message); break; case 23: // Invalid ifdId {ifdId} // May be raised when instantiating an ExifKey from a tag and // IFD item string PyErr_SetString(PyExc_KeyError, message); break; case 26: // Offset out of range // May be raised by writeMetadata() (TIFF) PyErr_SetString(PyExc_IOError, message); break; case 27: // Unsupported data area offset type // May be raised by writeMetadata() (TIFF) PyErr_SetString(PyExc_IOError, message); break; case 28: // Invalid charset: `{charset name}' // May be raised when instantiating a CommentValue from a string PyErr_SetString(PyExc_ValueError, message); break; case 29: // Unsupported date format // May be raised when instantiating a DateValue from a string PyErr_SetString(PyExc_ValueError, message); break; case 30: // Unsupported time format // May be raised when instantiating a TimeValue from a string PyErr_SetString(PyExc_ValueError, message); break; case 31: // Writing to {image format} images is not supported // May be raised by writeMetadata() for certain image types PyErr_SetString(PyExc_IOError, message); break; case 32: // Setting {metadata type} in {image format} images is not supported // May be raised when setting certain types of metadata for certain // image types that don't support them PyErr_SetString(PyExc_ValueError, message); break; case 33: // This does not look like a CRW image // May be raised by readMetadata() (CRW) PyErr_SetString(PyExc_IOError, message); break; case 35: // No namespace info available for XMP prefix `{prefix}' // May be raised when retrieving property info for an XmpKey PyErr_SetString(PyExc_KeyError, message); break; case 36: // No prefix registered for namespace `{namespace}', needed for // property path `{property path}' // May be raised by readMetadata() when reading the XMP data PyErr_SetString(PyExc_KeyError, message); break; case 37: // Size of {type of metadata} JPEG segment is larger than // 65535 bytes // May be raised by writeMetadata() (JPEG) PyErr_SetString(PyExc_ValueError, message); break; case 38: // Unhandled Xmpdatum {key} of type {value type} // May be raised by readMetadata() when reading the XMP data PyErr_SetString(PyExc_TypeError, message); break; case 39: // Unhandled XMP node {key} with opt={XMP Toolkit option flags} // May be raised by readMetadata() when reading the XMP data PyErr_SetString(PyExc_TypeError, message); break; case 40: // XMP Toolkit error {error id}: {error message} // May be raised by readMetadata() when reading the XMP data PyErr_SetString(PyExc_RuntimeError, message); break; case 41: // Failed to decode Lang Alt property {property path} // with opt={XMP Toolkit option flags} // May be raised by readMetadata() when reading the XMP data PyErr_SetString(PyExc_ValueError, message); break; case 42: // Failed to decode Lang Alt qualifier {qualifier path} // with opt={XMP Toolkit option flags} // May be raised by readMetadata() when reading the XMP data PyErr_SetString(PyExc_ValueError, message); break; case 43: // Failed to encode Lang Alt property {key} // May be raised by writeMetadata() PyErr_SetString(PyExc_ValueError, message); break; case 44: // Failed to determine property name from path {property path}, // namespace {namespace} // May be raised by readMetadata() when reading the XMP data PyErr_SetString(PyExc_KeyError, message); break; case 45: // Schema namespace {namespace} is not registered with // the XMP Toolkit // May be raised by readMetadata() when reading the XMP data PyErr_SetString(PyExc_ValueError, message); break; case 46: // No namespace registered for prefix `{prefix}' // May be raised when instantiating an XmpKey from a string PyErr_SetString(PyExc_KeyError, message); break; case 47: // Aliases are not supported. Please send this XMP packet // to ahuggel@gmx.net `{namespace}', `{property path}', `{value}' // May be raised by readMetadata() when reading the XMP data PyErr_SetString(PyExc_ValueError, message); break; case 48: // Invalid XmpText type `{type}' // May be raised when instantiating an XmpTextValue from a string PyErr_SetString(PyExc_TypeError, message); break; case 49: // TIFF directory {TIFF directory name} has too many entries // May be raised by writeMetadata() (TIFF) PyErr_SetString(PyExc_IOError, message); break; // Custom error codes case METADATA_NOT_READ: PyErr_SetString(PyExc_IOError, "Image metadata has not been read yet"); break; case NON_REPEATABLE: PyErr_SetString(PyExc_KeyError, "Tag is not repeatable"); break; case KEY_NOT_FOUND: PyErr_SetString(PyExc_KeyError, "Tag not set"); break; case INVALID_VALUE: PyErr_SetString(PyExc_ValueError, "Invalid value"); break; case EXISTING_PREFIX: PyErr_SetString(PyExc_KeyError, "A namespace with this prefix already exists"); break; case BUILTIN_NS: PyErr_SetString(PyExc_KeyError, "Cannot unregister a builtin namespace"); break; case NOT_REGISTERED: PyErr_SetString(PyExc_KeyError, "No namespace registered under this name"); break; // Default handler default: PyErr_SetString(PyExc_RuntimeError, message); } } void registerXmpNs(const std::string& name, const std::string& prefix) { try { const std::string& ns = Exiv2::XmpProperties::ns(prefix); } catch (Exiv2::Error& error) { // No namespace exists with the requested prefix, it is safe to // register a new one. Exiv2::XmpProperties::registerNs(name, prefix); return; } throw Exiv2::Error(EXISTING_PREFIX, prefix); } void unregisterXmpNs(const std::string& name) { const std::string& prefix = Exiv2::XmpProperties::prefix(name); if (prefix != "") { Exiv2::XmpProperties::unregisterNs(name); try { const Exiv2::XmpNsInfo* info = Exiv2::XmpProperties::nsInfo(prefix); } catch (Exiv2::Error& error) { // The namespace has been successfully unregistered. return; } // The namespace hasn’t been unregistered because it’s builtin. throw Exiv2::Error(BUILTIN_NS, name); } else { throw Exiv2::Error(NOT_REGISTERED, name); } } void unregisterAllXmpNs() { // Unregister all custom namespaces. Exiv2::XmpProperties::unregisterNs(); } } // End of namespace exiv2wrapper pyexiv2-0.3.2/src/SConscript0000664000175000017500000000420611651315372015341 0ustar osomonosomon# -*- coding: utf-8 -*- import os import site from distutils.sysconfig import get_python_inc, get_python_lib import SCons.Util env = Environment() # Take environment variables into account # (see https://bugs.launchpad.net/pyexiv2/+bug/249835) if os.environ.has_key('CXX'): env['CXX'] = os.environ['CXX'] if os.environ.has_key('CXXFLAGS'): env['CXXFLAGS'] += SCons.Util.CLVar(os.environ['CXXFLAGS']) if os.environ.has_key('LDFLAGS'): env['LINKFLAGS'] += SCons.Util.CLVar(os.environ['LDFLAGS']) # Include directories to look for 'Python.h' in env.Append(CPPPATH=[get_python_inc(plat_specific=True)]) # Libraries to link against # On some systems, boost_python is actually called boost_python-mt. # Use the BOOSTLIB argument to override the default value. # See https://bugs.launchpad.net/pyexiv2/+bug/523858. libs = [ARGUMENTS.get('BOOSTLIB', 'boost_python'), 'exiv2'] env.Append(LIBS=libs) # Build shared library libpyexiv2 cpp_sources = ['exiv2wrapper.cpp', 'exiv2wrapper_python.cpp'] libpyexiv2 = env.SharedLibrary('exiv2python', cpp_sources) env.Alias('lib', libpyexiv2) # Install the shared library and the Python modules, invoked with # 'scons install'. try: user_site = GetOption('user') except AttributeError: user_site = False if user_site: # Install in the current user site directory. # See http://www.python.org/dev/peps/pep-0370/ for reference. install_dir = site.USER_SITE else: python_lib_path = get_python_lib(plat_specific=True) # If DESTDIR is specified on the command line when invoking # scons, its value will be prepended to each installed target file. See # http://www.gnu.org/prep/standards/html_node/DESTDIR.html for reference. dest_dir = ARGUMENTS.get('DESTDIR') if dest_dir is None or not os.path.isabs(dest_dir): install_dir = python_lib_path else: install_dir = os.path.join(dest_dir, python_lib_path[1:]) env.Install(install_dir, [libpyexiv2]) modules = ['__init__', 'metadata', 'exif', 'iptc', 'xmp', 'preview', 'utils'] env.Install(os.path.join(install_dir, 'pyexiv2'), ['pyexiv2/%s.py' % module for module in modules]) env.Alias('install', install_dir) pyexiv2-0.3.2/src/examples.py0000775000175000017500000000420011651315372015514 0ustar osomonosomon#!/usr/bin/python # -*- coding: utf-8 -*- from pyexiv2 import ImageMetadata import sys, os from datetime import datetime, date def print_key_value(metadata, key): print key, '=', metadata[key] if __name__ == '__main__': # Read an image file's metadata image_file = sys.argv[1] metadata = ImageMetadata(image_file) metadata.read() # Print a list of all the keys of the EXIF tags in the image print 'EXIF keys:', metadata.exif_keys try: # Print the value of the Exif.Image.DateTime tag key = 'Exif.Image.DateTime' print_key_value(metadata, key) # Set the value of the Exif.Image.DateTime tag metadata[key] = datetime.now() print_key_value(metadata, key) except KeyError: print '[not set]' # Add a new tag key = 'Exif.Image.Orientation' metadata[key] = 1 print_key_value(metadata, key) # Print a list of all the keys of the IPTC tags in the image print os.linesep, 'IPTC keys:', metadata.iptc_keys try: # Print the value of the Iptc.Application2.DateCreated tag key = 'Iptc.Application2.DateCreated' print_key_value(metadata, key) # Set the value of the Iptc.Application2.DateCreated tag metadata[key] = [date.today()] print_key_value(metadata, key) except KeyError: print '[not set]' # Add a new tag key = 'Iptc.Application2.Keywords' keywords = ['little', 'big', 'man'] metadata[key] = keywords print_key_value(metadata, key) # Print a list of all the keys of the XMP tags in the image print os.linesep, 'XMP keys:', metadata.xmp_keys try: # Print the value of the Xmp.dc.subject tag key = 'Xmp.dc.subject' print_key_value(metadata, key) # Set the value of the Xmp.dc.subject tag metadata[key] = keywords print_key_value(metadata, key) except KeyError: print '[not set]' # Add a new tag key = 'Xmp.dc.title' metadata[key] = {'x-default': 'Sunset', 'fr': 'Coucher de soleil'} print_key_value(metadata, key) # Write back the metadata to the file metadata.write() pyexiv2-0.3.2/src/exiv2wrapper_python.cpp0000664000175000017500000001405111651315372020071 0ustar osomonosomon// ***************************************************************************** /* * Copyright (C) 2006-2010 Olivier Tilloy * * This file is part of the pyexiv2 distribution. * * pyexiv2 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. * * pyexiv2 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 pyexiv2; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. */ /* Author: Olivier Tilloy */ // ***************************************************************************** #include "exiv2wrapper.hpp" #include "exiv2/exv_conf.h" #include "exiv2/version.hpp" #include using namespace boost::python; using namespace exiv2wrapper; boost::python::tuple exiv2_version = \ boost::python::make_tuple(EXIV2_MAJOR_VERSION, EXIV2_MINOR_VERSION, EXIV2_PATCH_VERSION); BOOST_PYTHON_MODULE(libexiv2python) { scope().attr("exiv2_version_info") = exiv2_version; register_exception_translator(&translateExiv2Error); // Swallow all warnings and error messages written by libexiv2 to stderr // (if it was compiled with DEBUG or without SUPPRESS_WARNINGS). // See https://bugs.launchpad.net/pyexiv2/+bug/507620. std::cerr.rdbuf(NULL); class_("_ExifTag", init()) .def("_setRawValue", &ExifTag::setRawValue) .def("_setParentImage", &ExifTag::setParentImage) .def("_getKey", &ExifTag::getKey) .def("_getType", &ExifTag::getType) .def("_getName", &ExifTag::getName) .def("_getLabel", &ExifTag::getLabel) .def("_getDescription", &ExifTag::getDescription) .def("_getSectionName", &ExifTag::getSectionName) .def("_getSectionDescription", &ExifTag::getSectionDescription) .def("_getRawValue", &ExifTag::getRawValue) .def("_getHumanValue", &ExifTag::getHumanValue) .def("_getByteOrder", &ExifTag::getByteOrder) ; class_("_IptcTag", init()) .def("_setRawValues", &IptcTag::setRawValues) .def("_setParentImage", &IptcTag::setParentImage) .def("_getKey", &IptcTag::getKey) .def("_getType", &IptcTag::getType) .def("_getName", &IptcTag::getName) .def("_getTitle", &IptcTag::getTitle) .def("_getDescription", &IptcTag::getDescription) .def("_getPhotoshopName", &IptcTag::getPhotoshopName) .def("_isRepeatable", &IptcTag::isRepeatable) .def("_getRecordName", &IptcTag::getRecordName) .def("_getRecordDescription", &IptcTag::getRecordDescription) .def("_getRawValues", &IptcTag::getRawValues) ; class_("_XmpTag", init()) .def("_setTextValue", &XmpTag::setTextValue) .def("_setArrayValue", &XmpTag::setArrayValue) .def("_setLangAltValue", &XmpTag::setLangAltValue) .def("_setParentImage", &XmpTag::setParentImage) .def("_getKey", &XmpTag::getKey) .def("_getExiv2Type", &XmpTag::getExiv2Type) .def("_getType", &XmpTag::getType) .def("_getName", &XmpTag::getName) .def("_getTitle", &XmpTag::getTitle) .def("_getDescription", &XmpTag::getDescription) .def("_getTextValue", &XmpTag::getTextValue) .def("_getArrayValue", &XmpTag::getArrayValue) .def("_getLangAltValue", &XmpTag::getLangAltValue) ; class_("_Preview", init()) .def_readonly("mime_type", &Preview::_mimeType) .def_readonly("extension", &Preview::_extension) .def_readonly("size", &Preview::_size) .def_readonly("dimensions", &Preview::_dimensions) .def_readonly("data", &Preview::_data) .def("write_to_file", &Preview::writeToFile) ; class_("_Image", init()) .def(init()) .def("_readMetadata", &Image::readMetadata) .def("_writeMetadata", &Image::writeMetadata) .def("_getPixelWidth", &Image::pixelWidth) .def("_getPixelHeight", &Image::pixelHeight) .def("_getMimeType", &Image::mimeType) .def("_exifKeys", &Image::exifKeys) .def("_getExifTag", &Image::getExifTag) .def("_deleteExifTag", &Image::deleteExifTag) .def("_iptcKeys", &Image::iptcKeys) .def("_getIptcTag", &Image::getIptcTag) .def("_deleteIptcTag", &Image::deleteIptcTag) .def("_xmpKeys", &Image::xmpKeys) .def("_getXmpTag", &Image::getXmpTag) .def("_deleteXmpTag", &Image::deleteXmpTag) .def("_getComment", &Image::getComment) .def("_setComment", &Image::setComment) .def("_clearComment", &Image::clearComment) .def("_previews", &Image::previews) .def("_copyMetadata", &Image::copyMetadata) .def("_getDataBuffer", &Image::getDataBuffer) .def("_getExifThumbnailMimeType", &Image::getExifThumbnailMimeType) .def("_getExifThumbnailExtension", &Image::getExifThumbnailExtension) .def("_writeExifThumbnailToFile", &Image::writeExifThumbnailToFile) .def("_getExifThumbnailData", &Image::getExifThumbnailData) .def("_eraseExifThumbnail", &Image::eraseExifThumbnail) .def("_setExifThumbnailFromFile", &Image::setExifThumbnailFromFile) .def("_setExifThumbnailFromData", &Image::setExifThumbnailFromData) .def("_getIptcCharset", &Image::getIptcCharset) ; def("_registerXmpNs", registerXmpNs, args("name", "prefix")); def("_unregisterXmpNs", unregisterXmpNs, args("name")); def("_unregisterAllXmpNs", unregisterAllXmpNs); } pyexiv2-0.3.2/src/exiv2wrapper.hpp0000664000175000017500000002017511651315372016501 0ustar osomonosomon// ***************************************************************************** /* * Copyright (C) 2006-2010 Olivier Tilloy * * This file is part of the pyexiv2 distribution. * * pyexiv2 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. * * pyexiv2 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 pyexiv2; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. */ /* Author: Olivier Tilloy */ // ***************************************************************************** #ifndef __exiv2wrapper__ #define __exiv2wrapper__ #include #include "exiv2/image.hpp" #include "exiv2/preview.hpp" #include "boost/python.hpp" namespace exiv2wrapper { class Image; class ExifTag { public: // Constructor ExifTag(const std::string& key, Exiv2::Exifdatum* datum=0, Exiv2::ExifData* data=0, Exiv2::ByteOrder byteOrder=Exiv2::invalidByteOrder); ~ExifTag(); void setRawValue(const std::string& value); void setParentImage(Image& image); const std::string getKey(); const std::string getType(); const std::string getName(); const std::string getLabel(); const std::string getDescription(); const std::string getSectionName(); const std::string getSectionDescription(); const std::string getRawValue(); const std::string getHumanValue(); int getByteOrder(); private: Exiv2::ExifKey _key; Exiv2::Exifdatum* _datum; Exiv2::ExifData* _data; std::string _type; std::string _name; std::string _label; std::string _description; std::string _sectionName; std::string _sectionDescription; int _byteOrder; }; class IptcTag { public: // Constructor IptcTag(const std::string& key, Exiv2::IptcData* data=0); ~IptcTag(); void setRawValues(const boost::python::list& values); void setParentImage(Image& image); const std::string getKey(); const std::string getType(); const std::string getName(); const std::string getTitle(); const std::string getDescription(); const std::string getPhotoshopName(); const bool isRepeatable(); const std::string getRecordName(); const std::string getRecordDescription(); const boost::python::list getRawValues(); private: Exiv2::IptcKey _key; bool _from_data; // whether the tag is built from an existing IptcData Exiv2::IptcData* _data; std::string _type; std::string _name; std::string _title; std::string _description; std::string _photoshopName; bool _repeatable; std::string _recordName; std::string _recordDescription; }; class XmpTag { public: // Constructor XmpTag(const std::string& key, Exiv2::Xmpdatum* datum=0); ~XmpTag(); void setTextValue(const std::string& value); void setArrayValue(const boost::python::list& values); void setLangAltValue(const boost::python::dict& values); void setParentImage(Image& image); const std::string getKey(); const std::string getExiv2Type(); const std::string getType(); const std::string getName(); const std::string getTitle(); const std::string getDescription(); const std::string getTextValue(); const boost::python::list getArrayValue(); const boost::python::dict getLangAltValue(); private: Exiv2::XmpKey _key; bool _from_datum; // whether the tag is built from an existing Xmpdatum Exiv2::Xmpdatum* _datum; std::string _exiv2_type; std::string _type; std::string _name; std::string _title; std::string _description; }; class Preview { public: Preview(const Exiv2::PreviewImage& previewImage); void writeToFile(const std::string& path) const; std::string _mimeType; std::string _extension; unsigned int _size; boost::python::tuple _dimensions; std::string _data; }; class Image { public: // Constructors Image(const std::string& filename); Image(const std::string& buffer, unsigned long size); Image(const Image& image); ~Image(); void readMetadata(); void writeMetadata(); // Read-only access to the dimensions of the picture. unsigned int pixelWidth() const; unsigned int pixelHeight() const; // Read-only access to the MIME type of the image. std::string mimeType() const; // Read and write access to the EXIF tags. // For a complete list of the available EXIF tags, see // libexiv2's documentation (http://exiv2.org/tags.html). // Return a list of all the keys of available EXIF tags set in the // image. boost::python::list exifKeys(); // Return the required EXIF tag. // Throw an exception if the tag is not set. const ExifTag getExifTag(std::string key); // Delete the required EXIF tag. // Throw an exception if the tag was not set. void deleteExifTag(std::string key); // Read and write access to the IPTC tags. // For a complete list of the available IPTC tags, see // libexiv2's documentation (http://exiv2.org/iptc.html). // Returns a list of all the keys of available IPTC tags set in the // image. This list has no duplicates: each of its items is unique, // even if a tag is present more than once. boost::python::list iptcKeys(); // Return the required IPTC tag. // Throw an exception if the tag is not set. const IptcTag getIptcTag(std::string key); // Delete (all the repetitions of) the required IPTC tag. // Throw an exception if the tag was not set. void deleteIptcTag(std::string key); boost::python::list xmpKeys(); // Return the required XMP tag. // Throw an exception if the tag is not set. const XmpTag getXmpTag(std::string key); // Delete the required XMP tag. // Throw an exception if the tag was not set. void deleteXmpTag(std::string key); // Comment const std::string getComment() const; void setComment(const std::string& comment); void clearComment(); // Read access to the thumbnail embedded in the image. boost::python::list previews(); // Manipulate the JPEG/TIFF thumbnail embedded in the EXIF data. const std::string getExifThumbnailMimeType(); const std::string getExifThumbnailExtension(); void writeExifThumbnailToFile(const std::string& path); const std::string getExifThumbnailData(); void eraseExifThumbnail(); void setExifThumbnailFromFile(const std::string& path); void setExifThumbnailFromData(const std::string& data); // Copy the metadata to another image. void copyMetadata(Image& other, bool exif=true, bool iptc=true, bool xmp=true) const; // Return the image data buffer. std::string getDataBuffer() const; // Accessors Exiv2::ExifData* getExifData() { return _exifData; }; Exiv2::IptcData* getIptcData() { return _iptcData; }; Exiv2::XmpData* getXmpData() { return _xmpData; }; Exiv2::ByteOrder getByteOrder() const; const std::string getIptcCharset() const; private: std::string _filename; Exiv2::byte* _data; long _size; Exiv2::Image::AutoPtr _image; Exiv2::ExifData* _exifData; Exiv2::IptcData* _iptcData; Exiv2::XmpData* _xmpData; Exiv2::ExifThumb* _exifThumbnail; Exiv2::ExifThumb* _getExifThumbnail(); // true if the image's internal metadata has already been read, // false otherwise bool _dataRead; void _instantiate_image(); }; // Translate an Exiv2 generic exception into a Python exception void translateExiv2Error(Exiv2::Error const& error); // Functions to manipulate custom XMP namespaces void registerXmpNs(const std::string& name, const std::string& prefix); void unregisterXmpNs(const std::string& name); void unregisterAllXmpNs(); } // End of namespace exiv2wrapper #endif pyexiv2-0.3.2/src/pyexiv2/0000775000175000017500000000000011651316052014727 5ustar osomonosomonpyexiv2-0.3.2/src/pyexiv2/main.py0000775000175000017500000000306311651315372016236 0ustar osomonosomon#!/usr/bin/python # -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2006-2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import sys from pyexiv2.metadata import ImageMetadata if __name__ == '__main__': args = sys.argv if len(args) != 2: print 'Usage: %s image_file' % args[0] sys.exit(-1) metadata = ImageMetadata(args[1]) metadata.read() for key in metadata.exif_keys: tag = metadata[key] print '%-45s%-11s%s' % (key, tag.type, str(tag)) for key in metadata.iptc_keys: tag = metadata[key] print '%-45s%-11s%s' % (key, tag.type, str(tag)) # TODO: print XMP tags. pyexiv2-0.3.2/src/pyexiv2/xmp.py0000664000175000017500000004307711651315372016124 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2006-2011 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** """ XMP specific code. """ import libexiv2python from pyexiv2.utils import FixedOffset, is_fraction, make_fraction, \ GPSCoordinate, DateTimeFormatter import datetime import re class XmpValueError(ValueError): """ Exception raised when failing to parse the *value* of an XMP tag. :attribute value: the value that fails to be parsed :type value: string :attribute type: the XMP type of the tag :type type: string """ def __init__(self, value, type): self.value = value self.type = type def __str__(self): return 'Invalid value for XMP type [%s]: [%s]' % \ (self.type, self.value) class XmpTag(object): """ An XMP tag. Here is a correspondance table between the XMP types and the possible python types the value of a tag may take: - alt, bag, seq: list of the contained simple type - lang alt: dict of (language-code: value) - Boolean: boolean - Colorant: *[not implemented yet]* - Date: :class:`datetime.date`, :class:`datetime.datetime` - Dimensions: *[not implemented yet]* - Font: *[not implemented yet]* - GPSCoordinate: :class:`pyexiv2.utils.GPSCoordinate` - Integer: int - Locale: *[not implemented yet]* - MIMEType: 2-tuple of strings - Rational: :class:`fractions.Fraction` if available (Python ≥ 2.6) or :class:`pyexiv2.utils.Rational` - Real: *[not implemented yet]* - AgentName, ProperName, Text: unicode string - Thumbnail: *[not implemented yet]* - URI, URL: string - XPath: *[not implemented yet]* """ # FIXME: should inherit from ListenerInterface and implement observation of # changes on list/dict values. # strptime is not flexible enough to handle all valid Date formats, we use a # custom regular expression _time_zone_re = r'Z|((?P\+|-)(?P\d{2}):(?P\d{2}))' _time_re = r'(?P\d{2})(:(?P\d{2})(:(?P\d{2})(.(?P\d+))?)?(?P%s))?' % _time_zone_re _date_re = re.compile(r'(?P\d{4})(-(?P\d{2})(-(?P\d{2})(T(?P\u00B6'). attr('href', '#' + this.id). attr('title', _('Permalink to this headline')). appendTo(this); }); $('dt[id]').each(function() { $('\u00B6'). attr('href', '#' + this.id). attr('title', _('Permalink to this definition')). appendTo(this); }); }, /** * workaround a firefox stupidity */ fixFirefoxAnchorBug : function() { if (document.location.hash && $.browser.mozilla) window.setTimeout(function() { document.location.href += ''; }, 10); }, /** * highlight the search words provided in the url in the text */ highlightSearchWords : function() { var params = $.getQueryParameters(); var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; if (terms.length) { var body = $('div.body'); window.setTimeout(function() { $.each(terms, function() { body.highlightText(this.toLowerCase(), 'highlighted'); }); }, 10); $('') .appendTo($('.sidebar .this-page-menu')); } }, /** * init the domain index toggle buttons */ initIndexTable : function() { var togglers = $('img.toggler').click(function() { var src = $(this).attr('src'); var idnum = $(this).attr('id').substr(7); $('tr.cg-' + idnum).toggle(); if (src.substr(-9) == 'minus.png') $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); else $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); }).css('display', ''); if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { togglers.click(); } }, /** * helper function to hide the search marks again */ hideSearchWords : function() { $('.sidebar .this-page-menu li.highlight-link').fadeOut(300); $('span.highlighted').removeClass('highlighted'); }, /** * make the url absolute */ makeURL : function(relativeURL) { return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; }, /** * get the current relative url */ getCurrentURL : function() { var path = document.location.pathname; var parts = path.split(/\//); $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { if (this == '..') parts.pop(); }); var url = parts.join('/'); return path.substring(url.lastIndexOf('/') + 1, path.length - 1); } }; // quick alias for translations _ = Documentation.gettext; $(document).ready(function() { Documentation.init(); }); pyexiv2-0.3.2/doc/html/_static/minus.png0000664000175000017500000000016611621012362017527 0ustar osomonosomonPNG  IHDR r PLTE)))`MZ%IDATxڍ1@I,[Wc}t4?@Q IENDB`pyexiv2-0.3.2/doc/html/_static/jquery.js0000664000175000017500000071525211603136650017563 0ustar osomonosomon/*! * jQuery JavaScript Library v1.6.2 * http://jquery.com/ * * Copyright 2011, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * Includes Sizzle.js * http://sizzlejs.com/ * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * * Date: Thu Jun 30 14:16:56 2011 -0400 */ (function( window, undefined ) { // Use the correct document accordingly with window argument (sandbox) var document = window.document, navigator = window.navigator, location = window.location; var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context, rootjQuery ); }, // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$, // A central reference to the root jQuery(document) rootjQuery, // A simple way to check for HTML strings or ID strings // (both of which we optimize for) quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, // Check if a string has a non-whitespace character in it rnotwhite = /\S/, // Used for trimming whitespace trimLeft = /^\s+/, trimRight = /\s+$/, // Check for digits rdigit = /\d/, // Match a standalone tag rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, // JSON RegExp rvalidchars = /^[\],:{}\s]*$/, rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, // Useragent RegExp rwebkit = /(webkit)[ \/]([\w.]+)/, ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, rmsie = /(msie) ([\w.]+)/, rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, // Matches dashed string for camelizing rdashAlpha = /-([a-z])/ig, // Used by jQuery.camelCase as callback to replace() fcamelCase = function( all, letter ) { return letter.toUpperCase(); }, // Keep a UserAgent string for use with jQuery.browser userAgent = navigator.userAgent, // For matching the engine and version of the browser browserMatch, // The deferred used on DOM ready readyList, // The ready event handler DOMContentLoaded, // Save a reference to some core methods toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, push = Array.prototype.push, slice = Array.prototype.slice, trim = String.prototype.trim, indexOf = Array.prototype.indexOf, // [[Class]] -> type pairs class2type = {}; jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) if ( !selector ) { return this; } // Handle $(DOMElement) if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; } // The body element only exists once, optimize finding it if ( selector === "body" && !context && document.body ) { this.context = document; this[0] = document.body; this.selector = selector; this.length = 1; return this; } // Handle HTML strings if ( typeof selector === "string" ) { // Are we dealing with HTML string or an ID? if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = quickExpr.exec( selector ); } // Verify a match, and that no context was specified for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; doc = (context ? context.ownerDocument || context : document); // If a single string is passed in and it's a single tag // just do a createElement and skip the rest ret = rsingleTag.exec( selector ); if ( ret ) { if ( jQuery.isPlainObject( context ) ) { selector = [ document.createElement( ret[1] ) ]; jQuery.fn.attr.call( selector, context, true ); } else { selector = [ doc.createElement( ret[1] ) ]; } } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; } return jQuery.merge( this, selector ); // HANDLE: $("#id") } else { elem = document.getElementById( match[2] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id !== match[2] ) { return rootjQuery.find( selector ); } // Otherwise, we inject the element directly into the jQuery object this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; } // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return (context || rootjQuery).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); } // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); } if (selector.selector !== undefined) { this.selector = selector.selector; this.context = selector.context; } return jQuery.makeArray( selector, this ); }, // Start with an empty selector selector: "", // The current version of jQuery being used jquery: "1.6.2", // The default length of a jQuery object is 0 length: 0, // The number of elements contained in the matched element set size: function() { return this.length; }, toArray: function() { return slice.call( this, 0 ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { return num == null ? // Return a 'clean' array this.toArray() : // Return just the object ( num < 0 ? this[ this.length + num ] : this[ num ] ); }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set var ret = this.constructor(); if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); } else { jQuery.merge( ret, elems ); } // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; if ( name === "find" ) { ret.selector = this.selector + (this.selector ? " " : "") + selector; } else if ( name ) { ret.selector = this.selector + "." + name + "(" + selector + ")"; } // Return the newly-formed element set return ret; }, // Execute a callback for every element in the matched set. // (You can seed the arguments with an array of args, but this is // only used internally.) each: function( callback, args ) { return jQuery.each( this, callback, args ); }, ready: function( fn ) { // Attach the listeners jQuery.bindReady(); // Add the callback readyList.done( fn ); return this; }, eq: function( i ) { return i === -1 ? this.slice( i ) : this.slice( i, +i + 1 ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, slice: function() { return this.pushStack( slice.apply( this, arguments ), "slice", slice.call(arguments).join(",") ); }, map: function( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }, end: function() { return this.prevObject || this.constructor(null); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: push, sort: [].sort, splice: [].splice }; // Give the init function the jQuery prototype for later instantiation jQuery.fn.init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // extend jQuery itself if only one argument is passed if ( length === i ) { target = this; --i; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { clone = src && jQuery.isPlainObject(src) ? src : {}; } // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; }; jQuery.extend({ noConflict: function( deep ) { if ( window.$ === jQuery ) { window.$ = _$; } if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } return jQuery; }, // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, // Hold (or release) the ready event holdReady: function( hold ) { if ( hold ) { jQuery.readyWait++; } else { jQuery.ready( true ); } }, // Handle when the DOM is ready ready: function( wait ) { // Either a released hold or an DOMready/load event and not yet ready if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); } // Remember that the DOM is ready jQuery.isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } // If there are functions bound, to execute readyList.resolveWith( document, [ jQuery ] ); // Trigger any bound ready events if ( jQuery.fn.trigger ) { jQuery( document ).trigger( "ready" ).unbind( "ready" ); } } }, bindReady: function() { if ( readyList ) { return; } readyList = jQuery._Deferred(); // Catch cases where $(document).ready() is called after the // browser event has already occurred. if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready return setTimeout( jQuery.ready, 1 ); } // Mozilla, Opera and webkit nightlies currently support this event if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // A fallback to window.onload, that will always work window.addEventListener( "load", jQuery.ready, false ); // If IE event model is used } else if ( document.attachEvent ) { // ensure firing before onload, // maybe late but safe also for iframes document.attachEvent( "onreadystatechange", DOMContentLoaded ); // A fallback to window.onload, that will always work window.attachEvent( "onload", jQuery.ready ); // If IE and not a frame // continually check to see if the document is ready var toplevel = false; try { toplevel = window.frameElement == null; } catch(e) {} if ( document.documentElement.doScroll && toplevel ) { doScrollCheck(); } } }, // See test/unit/core.js for details concerning isFunction. // Since version 1.3, DOM methods and functions like alert // aren't supported. They return false on IE (#2968). isFunction: function( obj ) { return jQuery.type(obj) === "function"; }, isArray: Array.isArray || function( obj ) { return jQuery.type(obj) === "array"; }, // A crude way of determining if an object is a window isWindow: function( obj ) { return obj && typeof obj === "object" && "setInterval" in obj; }, isNaN: function( obj ) { return obj == null || !rdigit.test( obj ) || isNaN( obj ); }, type: function( obj ) { return obj == null ? String( obj ) : class2type[ toString.call(obj) ] || "object"; }, isPlainObject: function( obj ) { // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { return false; } // Not own constructor property must be Object if ( obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. var key; for ( key in obj ) {} return key === undefined || hasOwn.call( obj, key ); }, isEmptyObject: function( obj ) { for ( var name in obj ) { return false; } return true; }, error: function( msg ) { throw msg; }, parseJSON: function( data ) { if ( typeof data !== "string" || !data ) { return null; } // Make sure leading/trailing whitespace is removed (IE can't handle it) data = jQuery.trim( data ); // Attempt to parse using the native JSON parser first if ( window.JSON && window.JSON.parse ) { return window.JSON.parse( data ); } // Make sure the incoming data is actual JSON // Logic borrowed from http://json.org/json2.js if ( rvalidchars.test( data.replace( rvalidescape, "@" ) .replace( rvalidtokens, "]" ) .replace( rvalidbraces, "")) ) { return (new Function( "return " + data ))(); } jQuery.error( "Invalid JSON: " + data ); }, // Cross-browser xml parsing // (xml & tmp used internally) parseXML: function( data , xml , tmp ) { if ( window.DOMParser ) { // Standard tmp = new DOMParser(); xml = tmp.parseFromString( data , "text/xml" ); } else { // IE xml = new ActiveXObject( "Microsoft.XMLDOM" ); xml.async = "false"; xml.loadXML( data ); } tmp = xml.documentElement; if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { jQuery.error( "Invalid XML: " + data ); } return xml; }, noop: function() {}, // Evaluates a script in a global context // Workarounds based on findings by Jim Driscoll // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context globalEval: function( data ) { if ( data && rnotwhite.test( data ) ) { // We use execScript on Internet Explorer // We use an anonymous function so that context is window // rather than jQuery in Firefox ( window.execScript || function( data ) { window[ "eval" ].call( window, data ); } )( data ); } }, // Converts a dashed string to camelCased string; // Used by both the css and data modules camelCase: function( string ) { return string.replace( rdashAlpha, fcamelCase ); }, nodeName: function( elem, name ) { return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); }, // args is for internal usage only each: function( object, callback, args ) { var name, i = 0, length = object.length, isObj = length === undefined || jQuery.isFunction( object ); if ( args ) { if ( isObj ) { for ( name in object ) { if ( callback.apply( object[ name ], args ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.apply( object[ i++ ], args ) === false ) { break; } } } // A special, fast, case for the most common use of each } else { if ( isObj ) { for ( name in object ) { if ( callback.call( object[ name ], name, object[ name ] ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { break; } } } } return object; }, // Use native String.trim function wherever possible trim: trim ? function( text ) { return text == null ? "" : trim.call( text ); } : // Otherwise use our own trimming functionality function( text ) { return text == null ? "" : text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); }, // results is for internal usage only makeArray: function( array, results ) { var ret = results || []; if ( array != null ) { // The window, strings (and functions) also have 'length' // The extra typeof function check is to prevent crashes // in Safari 2 (See: #3039) // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 var type = jQuery.type( array ); if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { push.call( ret, array ); } else { jQuery.merge( ret, array ); } } return ret; }, inArray: function( elem, array ) { if ( indexOf ) { return indexOf.call( array, elem ); } for ( var i = 0, length = array.length; i < length; i++ ) { if ( array[ i ] === elem ) { return i; } } return -1; }, merge: function( first, second ) { var i = first.length, j = 0; if ( typeof second.length === "number" ) { for ( var l = second.length; j < l; j++ ) { first[ i++ ] = second[ j ]; } } else { while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; } } first.length = i; return first; }, grep: function( elems, callback, inv ) { var ret = [], retVal; inv = !!inv; // Go through the array, only saving the items // that pass the validator function for ( var i = 0, length = elems.length; i < length; i++ ) { retVal = !!callback( elems[ i ], i ); if ( inv !== retVal ) { ret.push( elems[ i ] ); } } return ret; }, // arg is for internal usage only map: function( elems, callback, arg ) { var value, key, ret = [], i = 0, length = elems.length, // jquery objects are treated as arrays isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; // Go through the array, translating each of the items to their if ( isArray ) { for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret[ ret.length ] = value; } } // Go through every key on the object, } else { for ( key in elems ) { value = callback( elems[ key ], key, arg ); if ( value != null ) { ret[ ret.length ] = value; } } } // Flatten any nested arrays return ret.concat.apply( [], ret ); }, // A global GUID counter for objects guid: 1, // Bind a function to a context, optionally partially applying any // arguments. proxy: function( fn, context ) { if ( typeof context === "string" ) { var tmp = fn[ context ]; context = fn; fn = tmp; } // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. if ( !jQuery.isFunction( fn ) ) { return undefined; } // Simulated bind var args = slice.call( arguments, 2 ), proxy = function() { return fn.apply( context, args.concat( slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; return proxy; }, // Mutifunctional method to get and set values to a collection // The value/s can optionally be executed if it's a function access: function( elems, key, value, exec, fn, pass ) { var length = elems.length; // Setting many attributes if ( typeof key === "object" ) { for ( var k in key ) { jQuery.access( elems, k, key[k], exec, fn, value ); } return elems; } // Setting one attribute if ( value !== undefined ) { // Optionally, function values get executed if exec is true exec = !pass && exec && jQuery.isFunction(value); for ( var i = 0; i < length; i++ ) { fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); } return elems; } // Getting an attribute return length ? fn( elems[0], key ) : undefined; }, now: function() { return (new Date()).getTime(); }, // Use of jQuery.browser is frowned upon. // More details: http://docs.jquery.com/Utilities/jQuery.browser uaMatch: function( ua ) { ua = ua.toLowerCase(); var match = rwebkit.exec( ua ) || ropera.exec( ua ) || rmsie.exec( ua ) || ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || []; return { browser: match[1] || "", version: match[2] || "0" }; }, sub: function() { function jQuerySub( selector, context ) { return new jQuerySub.fn.init( selector, context ); } jQuery.extend( true, jQuerySub, this ); jQuerySub.superclass = this; jQuerySub.fn = jQuerySub.prototype = this(); jQuerySub.fn.constructor = jQuerySub; jQuerySub.sub = this.sub; jQuerySub.fn.init = function init( selector, context ) { if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { context = jQuerySub( context ); } return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); }; jQuerySub.fn.init.prototype = jQuerySub.fn; var rootjQuerySub = jQuerySub(document); return jQuerySub; }, browser: {} }); // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); browserMatch = jQuery.uaMatch( userAgent ); if ( browserMatch.browser ) { jQuery.browser[ browserMatch.browser ] = true; jQuery.browser.version = browserMatch.version; } // Deprecated, use jQuery.browser.webkit instead if ( jQuery.browser.webkit ) { jQuery.browser.safari = true; } // IE doesn't match non-breaking spaces with \s if ( rnotwhite.test( "\xA0" ) ) { trimLeft = /^[\s\xA0]+/; trimRight = /[\s\xA0]+$/; } // All jQuery objects should point back to these rootjQuery = jQuery(document); // Cleanup functions for the document ready method if ( document.addEventListener ) { DOMContentLoaded = function() { document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); jQuery.ready(); }; } else if ( document.attachEvent ) { DOMContentLoaded = function() { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( document.readyState === "complete" ) { document.detachEvent( "onreadystatechange", DOMContentLoaded ); jQuery.ready(); } }; } // The DOM ready check for Internet Explorer function doScrollCheck() { if ( jQuery.isReady ) { return; } try { // If IE is used, use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch(e) { setTimeout( doScrollCheck, 1 ); return; } // and execute any waiting functions jQuery.ready(); } return jQuery; })(); var // Promise methods promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ), // Static reference to slice sliceDeferred = [].slice; jQuery.extend({ // Create a simple deferred (one callbacks list) _Deferred: function() { var // callbacks list callbacks = [], // stored [ context , args ] fired, // to avoid firing when already doing so firing, // flag to know if the deferred has been cancelled cancelled, // the deferred itself deferred = { // done( f1, f2, ...) done: function() { if ( !cancelled ) { var args = arguments, i, length, elem, type, _fired; if ( fired ) { _fired = fired; fired = 0; } for ( i = 0, length = args.length; i < length; i++ ) { elem = args[ i ]; type = jQuery.type( elem ); if ( type === "array" ) { deferred.done.apply( deferred, elem ); } else if ( type === "function" ) { callbacks.push( elem ); } } if ( _fired ) { deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); } } return this; }, // resolve with given context and args resolveWith: function( context, args ) { if ( !cancelled && !fired && !firing ) { // make sure args are available (#8421) args = args || []; firing = 1; try { while( callbacks[ 0 ] ) { callbacks.shift().apply( context, args ); } } finally { fired = [ context, args ]; firing = 0; } } return this; }, // resolve with this as context and given arguments resolve: function() { deferred.resolveWith( this, arguments ); return this; }, // Has this deferred been resolved? isResolved: function() { return !!( firing || fired ); }, // Cancel cancel: function() { cancelled = 1; callbacks = []; return this; } }; return deferred; }, // Full fledged deferred (two callbacks list) Deferred: function( func ) { var deferred = jQuery._Deferred(), failDeferred = jQuery._Deferred(), promise; // Add errorDeferred methods, then and promise jQuery.extend( deferred, { then: function( doneCallbacks, failCallbacks ) { deferred.done( doneCallbacks ).fail( failCallbacks ); return this; }, always: function() { return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments ); }, fail: failDeferred.done, rejectWith: failDeferred.resolveWith, reject: failDeferred.resolve, isRejected: failDeferred.isResolved, pipe: function( fnDone, fnFail ) { return jQuery.Deferred(function( newDefer ) { jQuery.each( { done: [ fnDone, "resolve" ], fail: [ fnFail, "reject" ] }, function( handler, data ) { var fn = data[ 0 ], action = data[ 1 ], returned; if ( jQuery.isFunction( fn ) ) { deferred[ handler ](function() { returned = fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { returned.promise().then( newDefer.resolve, newDefer.reject ); } else { newDefer[ action ]( returned ); } }); } else { deferred[ handler ]( newDefer[ action ] ); } }); }).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { if ( obj == null ) { if ( promise ) { return promise; } promise = obj = {}; } var i = promiseMethods.length; while( i-- ) { obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ]; } return obj; } }); // Make sure only one callback list will be used deferred.done( failDeferred.cancel ).fail( deferred.cancel ); // Unexpose cancel delete deferred.cancel; // Call given func if any if ( func ) { func.call( deferred, deferred ); } return deferred; }, // Deferred helper when: function( firstParam ) { var args = arguments, i = 0, length = args.length, count = length, deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? firstParam : jQuery.Deferred(); function resolveFunc( i ) { return function( value ) { args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; if ( !( --count ) ) { // Strange bug in FF4: // Values changed onto the arguments object sometimes end up as undefined values // outside the $.when method. Cloning the object into a fresh array solves the issue deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) ); } }; } if ( length > 1 ) { for( ; i < length; i++ ) { if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) { args[ i ].promise().then( resolveFunc(i), deferred.reject ); } else { --count; } } if ( !count ) { deferred.resolveWith( deferred, args ); } } else if ( deferred !== firstParam ) { deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); } return deferred.promise(); } }); jQuery.support = (function() { var div = document.createElement( "div" ), documentElement = document.documentElement, all, a, select, opt, input, marginDiv, support, fragment, body, testElementParent, testElement, testElementStyle, tds, events, eventName, i, isSupported; // Preliminary tests div.setAttribute("className", "t"); div.innerHTML = "
a"; all = div.getElementsByTagName( "*" ); a = div.getElementsByTagName( "a" )[ 0 ]; // Can't get basic test support if ( !all || !all.length || !a ) { return {}; } // First batch of supports tests select = document.createElement( "select" ); opt = select.appendChild( document.createElement("option") ); input = div.getElementsByTagName( "input" )[ 0 ]; support = { // IE strips leading whitespace when .innerHTML is used leadingWhitespace: ( div.firstChild.nodeType === 3 ), // Make sure that tbody elements aren't automatically inserted // IE will insert them into empty tables tbody: !div.getElementsByTagName( "tbody" ).length, // Make sure that link elements get serialized correctly by innerHTML // This requires a wrapper element in IE htmlSerialize: !!div.getElementsByTagName( "link" ).length, // Get the style information from getAttribute // (IE uses .cssText instead) style: /top/.test( a.getAttribute("style") ), // Make sure that URLs aren't manipulated // (IE normalizes it by default) hrefNormalized: ( a.getAttribute( "href" ) === "/a" ), // Make sure that element opacity exists // (IE uses filter instead) // Use a regex to work around a WebKit issue. See #5145 opacity: /^0.55$/.test( a.style.opacity ), // Verify style float existence // (IE uses styleFloat instead of cssFloat) cssFloat: !!a.style.cssFloat, // Make sure that if no value is specified for a checkbox // that it defaults to "on". // (WebKit defaults to "" instead) checkOn: ( input.value === "on" ), // Make sure that a selected-by-default option has a working selected property. // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) optSelected: opt.selected, // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) getSetAttribute: div.className !== "t", // Will be defined later submitBubbles: true, changeBubbles: true, focusinBubbles: false, deleteExpando: true, noCloneEvent: true, inlineBlockNeedsLayout: false, shrinkWrapBlocks: false, reliableMarginRight: true }; // Make sure checked status is properly cloned input.checked = true; support.noCloneChecked = input.cloneNode( true ).checked; // Make sure that the options inside disabled selects aren't marked as disabled // (WebKit marks them as disabled) select.disabled = true; support.optDisabled = !opt.disabled; // Test to see if it's possible to delete an expando from an element // Fails in Internet Explorer try { delete div.test; } catch( e ) { support.deleteExpando = false; } if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { div.attachEvent( "onclick", function() { // Cloning a node shouldn't copy over any // bound event handlers (IE does this) support.noCloneEvent = false; }); div.cloneNode( true ).fireEvent( "onclick" ); } // Check if a radio maintains it's value // after being appended to the DOM input = document.createElement("input"); input.value = "t"; input.setAttribute("type", "radio"); support.radioValue = input.value === "t"; input.setAttribute("checked", "checked"); div.appendChild( input ); fragment = document.createDocumentFragment(); fragment.appendChild( div.firstChild ); // WebKit doesn't clone checked state correctly in fragments support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; div.innerHTML = ""; // Figure out if the W3C box model works as expected div.style.width = div.style.paddingLeft = "1px"; body = document.getElementsByTagName( "body" )[ 0 ]; // We use our own, invisible, body unless the body is already present // in which case we use a div (#9239) testElement = document.createElement( body ? "div" : "body" ); testElementStyle = { visibility: "hidden", width: 0, height: 0, border: 0, margin: 0 }; if ( body ) { jQuery.extend( testElementStyle, { position: "absolute", left: -1000, top: -1000 }); } for ( i in testElementStyle ) { testElement.style[ i ] = testElementStyle[ i ]; } testElement.appendChild( div ); testElementParent = body || documentElement; testElementParent.insertBefore( testElement, testElementParent.firstChild ); // Check if a disconnected checkbox will retain its checked // value of true after appended to the DOM (IE6/7) support.appendChecked = input.checked; support.boxModel = div.offsetWidth === 2; if ( "zoom" in div.style ) { // Check if natively block-level elements act like inline-block // elements when setting their display to 'inline' and giving // them layout // (IE < 8 does this) div.style.display = "inline"; div.style.zoom = 1; support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); // Check if elements with layout shrink-wrap their children // (IE 6 does this) div.style.display = ""; div.innerHTML = "
"; support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); } div.innerHTML = "
t
"; tds = div.getElementsByTagName( "td" ); // Check if table cells still have offsetWidth/Height when they are set // to display:none and there are still other visible table cells in a // table row; if so, offsetWidth/Height are not reliable for use when // determining if an element has been hidden directly using // display:none (it is still safe to use offsets if a parent element is // hidden; don safety goggles and see bug #4512 for more information). // (only IE 8 fails this test) isSupported = ( tds[ 0 ].offsetHeight === 0 ); tds[ 0 ].style.display = ""; tds[ 1 ].style.display = "none"; // Check if empty table cells still have offsetWidth/Height // (IE < 8 fail this test) support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); div.innerHTML = ""; // Check if div with explicit width and no margin-right incorrectly // gets computed margin-right based on width of container. For more // info see bug #3333 // Fails in WebKit before Feb 2011 nightlies // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right if ( document.defaultView && document.defaultView.getComputedStyle ) { marginDiv = document.createElement( "div" ); marginDiv.style.width = "0"; marginDiv.style.marginRight = "0"; div.appendChild( marginDiv ); support.reliableMarginRight = ( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; } // Remove the body element we added testElement.innerHTML = ""; testElementParent.removeChild( testElement ); // Technique from Juriy Zaytsev // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ // We only care about the case where non-standard event systems // are used, namely in IE. Short-circuiting here helps us to // avoid an eval call (in setAttribute) which can cause CSP // to go haywire. See: https://developer.mozilla.org/en/Security/CSP if ( div.attachEvent ) { for( i in { submit: 1, change: 1, focusin: 1 } ) { eventName = "on" + i; isSupported = ( eventName in div ); if ( !isSupported ) { div.setAttribute( eventName, "return;" ); isSupported = ( typeof div[ eventName ] === "function" ); } support[ i + "Bubbles" ] = isSupported; } } // Null connected elements to avoid leaks in IE testElement = fragment = select = opt = body = marginDiv = div = input = null; return support; })(); // Keep track of boxModel jQuery.boxModel = jQuery.support.boxModel; var rbrace = /^(?:\{.*\}|\[.*\])$/, rmultiDash = /([a-z])([A-Z])/g; jQuery.extend({ cache: {}, // Please use with caution uuid: 0, // Unique for each copy of jQuery on the page // Non-digits removed to match rinlinejQuery expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), // The following elements throw uncatchable exceptions if you // attempt to add expando properties to them. noData: { "embed": true, // Ban all objects except for Flash (which handle expandos) "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", "applet": true }, hasData: function( elem ) { elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; return !!elem && !isEmptyDataObject( elem ); }, data: function( elem, name, data, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, // We have to handle DOM nodes and JS objects differently because IE6-7 // can't GC object references properly across the DOM-JS boundary isNode = elem.nodeType, // Only DOM nodes need the global jQuery cache; JS object data is // attached directly to the object so GC can occur automatically cache = isNode ? jQuery.cache : elem, // Only defining an ID for JS objects if its cache already exists allows // the code to shortcut on the same path as a DOM node with no cache id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; // Avoid doing any more work than we need to when trying to get data on an // object that has no data at all if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { return; } if ( !id ) { // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache if ( isNode ) { elem[ jQuery.expando ] = id = ++jQuery.uuid; } else { id = jQuery.expando; } } if ( !cache[ id ] ) { cache[ id ] = {}; // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery // metadata on plain JS objects when the object is serialized using // JSON.stringify if ( !isNode ) { cache[ id ].toJSON = jQuery.noop; } } // An object can be passed to jQuery.data instead of a key/value pair; this gets // shallow copied over onto the existing cache if ( typeof name === "object" || typeof name === "function" ) { if ( pvt ) { cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); } else { cache[ id ] = jQuery.extend(cache[ id ], name); } } thisCache = cache[ id ]; // Internal jQuery data is stored in a separate object inside the object's data // cache in order to avoid key collisions between internal data and user-defined // data if ( pvt ) { if ( !thisCache[ internalKey ] ) { thisCache[ internalKey ] = {}; } thisCache = thisCache[ internalKey ]; } if ( data !== undefined ) { thisCache[ jQuery.camelCase( name ) ] = data; } // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should // not attempt to inspect the internal events object using jQuery.data, as this // internal data object is undocumented and subject to change. if ( name === "events" && !thisCache[name] ) { return thisCache[ internalKey ] && thisCache[ internalKey ].events; } return getByName ? // Check for both converted-to-camel and non-converted data property names thisCache[ jQuery.camelCase( name ) ] || thisCache[ name ] : thisCache; }, removeData: function( elem, name, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var internalKey = jQuery.expando, isNode = elem.nodeType, // See jQuery.data for more information cache = isNode ? jQuery.cache : elem, // See jQuery.data for more information id = isNode ? elem[ jQuery.expando ] : jQuery.expando; // If there is already no cache entry for this object, there is no // purpose in continuing if ( !cache[ id ] ) { return; } if ( name ) { var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; if ( thisCache ) { delete thisCache[ name ]; // If there is no data left in the cache, we want to continue // and let the cache object itself get destroyed if ( !isEmptyDataObject(thisCache) ) { return; } } } // See jQuery.data for more information if ( pvt ) { delete cache[ id ][ internalKey ]; // Don't destroy the parent cache unless the internal data object // had been the only thing left in it if ( !isEmptyDataObject(cache[ id ]) ) { return; } } var internalCache = cache[ id ][ internalKey ]; // Browsers that fail expando deletion also refuse to delete expandos on // the window, but it will allow it on all other JS objects; other browsers // don't care if ( jQuery.support.deleteExpando || cache != window ) { delete cache[ id ]; } else { cache[ id ] = null; } // We destroyed the entire user cache at once because it's faster than // iterating through each key, but we need to continue to persist internal // data if it existed if ( internalCache ) { cache[ id ] = {}; // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery // metadata on plain JS objects when the object is serialized using // JSON.stringify if ( !isNode ) { cache[ id ].toJSON = jQuery.noop; } cache[ id ][ internalKey ] = internalCache; // Otherwise, we need to eliminate the expando on the node to avoid // false lookups in the cache for entries that no longer exist } else if ( isNode ) { // IE does not allow us to delete expando properties from nodes, // nor does it have a removeAttribute function on Document nodes; // we must handle all of these cases if ( jQuery.support.deleteExpando ) { delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); } else { elem[ jQuery.expando ] = null; } } }, // For internal use only. _data: function( elem, name, data ) { return jQuery.data( elem, name, data, true ); }, // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { if ( elem.nodeName ) { var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; if ( match ) { return !(match === true || elem.getAttribute("classid") !== match); } } return true; } }); jQuery.fn.extend({ data: function( key, value ) { var data = null; if ( typeof key === "undefined" ) { if ( this.length ) { data = jQuery.data( this[0] ); if ( this[0].nodeType === 1 ) { var attr = this[0].attributes, name; for ( var i = 0, l = attr.length; i < l; i++ ) { name = attr[i].name; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.substring(5) ); dataAttr( this[0], name, data[ name ] ); } } } } return data; } else if ( typeof key === "object" ) { return this.each(function() { jQuery.data( this, key ); }); } var parts = key.split("."); parts[1] = parts[1] ? "." + parts[1] : ""; if ( value === undefined ) { data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); // Try to fetch any internally stored data first if ( data === undefined && this.length ) { data = jQuery.data( this[0], key ); data = dataAttr( this[0], key, data ); } return data === undefined && parts[1] ? this.data( parts[0] ) : data; } else { return this.each(function() { var $this = jQuery( this ), args = [ parts[0], value ]; $this.triggerHandler( "setData" + parts[1] + "!", args ); jQuery.data( this, key, value ); $this.triggerHandler( "changeData" + parts[1] + "!", args ); }); } }, removeData: function( key ) { return this.each(function() { jQuery.removeData( this, key ); }); } }); function dataAttr( elem, key, data ) { // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { var name = "data-" + key.replace( rmultiDash, "$1-$2" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { data = data === "true" ? true : data === "false" ? false : data === "null" ? null : !jQuery.isNaN( data ) ? parseFloat( data ) : rbrace.test( data ) ? jQuery.parseJSON( data ) : data; } catch( e ) {} // Make sure we set the data so it isn't changed later jQuery.data( elem, key, data ); } else { data = undefined; } } return data; } // TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON // property to be considered empty objects; this property always exists in // order to make sure JSON.stringify does not expose internal metadata function isEmptyDataObject( obj ) { for ( var name in obj ) { if ( name !== "toJSON" ) { return false; } } return true; } function handleQueueMarkDefer( elem, type, src ) { var deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", defer = jQuery.data( elem, deferDataKey, undefined, true ); if ( defer && ( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) && ( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) { // Give room for hard-coded callbacks to fire first // and eventually mark/queue something else on the element setTimeout( function() { if ( !jQuery.data( elem, queueDataKey, undefined, true ) && !jQuery.data( elem, markDataKey, undefined, true ) ) { jQuery.removeData( elem, deferDataKey, true ); defer.resolve(); } }, 0 ); } } jQuery.extend({ _mark: function( elem, type ) { if ( elem ) { type = (type || "fx") + "mark"; jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true ); } }, _unmark: function( force, elem, type ) { if ( force !== true ) { type = elem; elem = force; force = false; } if ( elem ) { type = type || "fx"; var key = type + "mark", count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 ); if ( count ) { jQuery.data( elem, key, count, true ); } else { jQuery.removeData( elem, key, true ); handleQueueMarkDefer( elem, type, "mark" ); } } }, queue: function( elem, type, data ) { if ( elem ) { type = (type || "fx") + "queue"; var q = jQuery.data( elem, type, undefined, true ); // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { if ( !q || jQuery.isArray(data) ) { q = jQuery.data( elem, type, jQuery.makeArray(data), true ); } else { q.push( data ); } } return q || []; } }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), fn = queue.shift(), defer; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { fn = queue.shift(); } if ( fn ) { // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { queue.unshift("inprogress"); } fn.call(elem, function() { jQuery.dequeue(elem, type); }); } if ( !queue.length ) { jQuery.removeData( elem, type + "queue", true ); handleQueueMarkDefer( elem, type, "queue" ); } } }); jQuery.fn.extend({ queue: function( type, data ) { if ( typeof type !== "string" ) { data = type; type = "fx"; } if ( data === undefined ) { return jQuery.queue( this[0], type ); } return this.each(function() { var queue = jQuery.queue( this, type, data ); if ( type === "fx" && queue[0] !== "inprogress" ) { jQuery.dequeue( this, type ); } }); }, dequeue: function( type ) { return this.each(function() { jQuery.dequeue( this, type ); }); }, // Based off of the plugin by Clint Helfers, with permission. // http://blindsignals.com/index.php/2009/07/jquery-delay/ delay: function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; type = type || "fx"; return this.queue( type, function() { var elem = this; setTimeout(function() { jQuery.dequeue( elem, type ); }, time ); }); }, clearQueue: function( type ) { return this.queue( type || "fx", [] ); }, // Get a promise resolved when queues of a certain type // are emptied (fx is the type by default) promise: function( type, object ) { if ( typeof type !== "string" ) { object = type; type = undefined; } type = type || "fx"; var defer = jQuery.Deferred(), elements = this, i = elements.length, count = 1, deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", tmp; function resolve() { if ( !( --count ) ) { defer.resolveWith( elements, [ elements ] ); } } while( i-- ) { if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) { count++; tmp.done( resolve ); } } resolve(); return defer.promise(); } }); var rclass = /[\n\t\r]/g, rspace = /\s+/, rreturn = /\r/g, rtype = /^(?:button|input)$/i, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, rinvalidChar = /\:|^on/, formHook, boolHook; jQuery.fn.extend({ attr: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.attr ); }, removeAttr: function( name ) { return this.each(function() { jQuery.removeAttr( this, name ); }); }, prop: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.prop ); }, removeProp: function( name ) { name = jQuery.propFix[ name ] || name; return this.each(function() { // try/catch handles cases where IE balks (such as removing a property on window) try { this[ name ] = undefined; delete this[ name ]; } catch( e ) {} }); }, addClass: function( value ) { var classNames, i, l, elem, setClass, c, cl; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).addClass( value.call(this, j, this.className) ); }); } if ( value && typeof value === "string" ) { classNames = value.split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; if ( elem.nodeType === 1 ) { if ( !elem.className && classNames.length === 1 ) { elem.className = value; } else { setClass = " " + elem.className + " "; for ( c = 0, cl = classNames.length; c < cl; c++ ) { if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { setClass += classNames[ c ] + " "; } } elem.className = jQuery.trim( setClass ); } } } } return this; }, removeClass: function( value ) { var classNames, i, l, elem, className, c, cl; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).removeClass( value.call(this, j, this.className) ); }); } if ( (value && typeof value === "string") || value === undefined ) { classNames = (value || "").split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; if ( elem.nodeType === 1 && elem.className ) { if ( value ) { className = (" " + elem.className + " ").replace( rclass, " " ); for ( c = 0, cl = classNames.length; c < cl; c++ ) { className = className.replace(" " + classNames[ c ] + " ", " "); } elem.className = jQuery.trim( className ); } else { elem.className = ""; } } } } return this; }, toggleClass: function( value, stateVal ) { var type = typeof value, isBool = typeof stateVal === "boolean"; if ( jQuery.isFunction( value ) ) { return this.each(function( i ) { jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); }); } return this.each(function() { if ( type === "string" ) { // toggle individual class names var className, i = 0, self = jQuery( this ), state = stateVal, classNames = value.split( rspace ); while ( (className = classNames[ i++ ]) ) { // check each className given, space seperated list state = isBool ? state : !self.hasClass( className ); self[ state ? "addClass" : "removeClass" ]( className ); } } else if ( type === "undefined" || type === "boolean" ) { if ( this.className ) { // store className if set jQuery._data( this, "__className__", this.className ); } // toggle whole className this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; } }); }, hasClass: function( selector ) { var className = " " + selector + " "; for ( var i = 0, l = this.length; i < l; i++ ) { if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { return true; } } return false; }, val: function( value ) { var hooks, ret, elem = this[0]; if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { return ret; } ret = elem.value; return typeof ret === "string" ? // handle most common string cases ret.replace(rreturn, "") : // handle cases where value is null/undef or number ret == null ? "" : ret; } return undefined; } var isFunction = jQuery.isFunction( value ); return this.each(function( i ) { var self = jQuery(this), val; if ( this.nodeType !== 1 ) { return; } if ( isFunction ) { val = value.call( this, i, self.val() ); } else { val = value; } // Treat null/undefined as ""; convert numbers to string if ( val == null ) { val = ""; } else if ( typeof val === "number" ) { val += ""; } else if ( jQuery.isArray( val ) ) { val = jQuery.map(val, function ( value ) { return value == null ? "" : value + ""; }); } hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; // If set returns undefined, fall back to normal setting if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { this.value = val; } }); } }); jQuery.extend({ valHooks: { option: { get: function( elem ) { // attributes.value is undefined in Blackberry 4.7 but // uses .value. See #6932 var val = elem.attributes.value; return !val || val.specified ? elem.value : elem.text; } }, select: { get: function( elem ) { var value, index = elem.selectedIndex, values = [], options = elem.options, one = elem.type === "select-one"; // Nothing was selected if ( index < 0 ) { return null; } // Loop through all the selected options for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { var option = options[ i ]; // Don't return options that are disabled or in a disabled optgroup if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { // Get the specific value for the option value = jQuery( option ).val(); // We don't need an array for one selects if ( one ) { return value; } // Multi-Selects return an array values.push( value ); } } // Fixes Bug #2551 -- select.val() broken in IE after form.reset() if ( one && !values.length && options.length ) { return jQuery( options[ index ] ).val(); } return values; }, set: function( elem, value ) { var values = jQuery.makeArray( value ); jQuery(elem).find("option").each(function() { this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; }); if ( !values.length ) { elem.selectedIndex = -1; } return values; } } }, attrFn: { val: true, css: true, html: true, text: true, data: true, width: true, height: true, offset: true }, attrFix: { // Always normalize to ensure hook usage tabindex: "tabIndex" }, attr: function( elem, name, value, pass ) { var nType = elem.nodeType; // don't get/set attributes on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return undefined; } if ( pass && name in jQuery.attrFn ) { return jQuery( elem )[ name ]( value ); } // Fallback to prop when attributes are not supported if ( !("getAttribute" in elem) ) { return jQuery.prop( elem, name, value ); } var ret, hooks, notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); // Normalize the name if needed if ( notxml ) { name = jQuery.attrFix[ name ] || name; hooks = jQuery.attrHooks[ name ]; if ( !hooks ) { // Use boolHook for boolean attributes if ( rboolean.test( name ) ) { hooks = boolHook; // Use formHook for forms and if the name contains certain characters } else if ( formHook && name !== "className" && (jQuery.nodeName( elem, "form" ) || rinvalidChar.test( name )) ) { hooks = formHook; } } } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); return undefined; } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { elem.setAttribute( name, "" + value ); return value; } } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { ret = elem.getAttribute( name ); // Non-existent attributes return null, we normalize to undefined return ret === null ? undefined : ret; } }, removeAttr: function( elem, name ) { var propName; if ( elem.nodeType === 1 ) { name = jQuery.attrFix[ name ] || name; if ( jQuery.support.getSetAttribute ) { // Use removeAttribute in browsers that support it elem.removeAttribute( name ); } else { jQuery.attr( elem, name, "" ); elem.removeAttributeNode( elem.getAttributeNode( name ) ); } // Set corresponding property to false for boolean attributes if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) { elem[ propName ] = false; } } }, attrHooks: { type: { set: function( elem, value ) { // We can't allow the type property to be changed (since it causes problems in IE) if ( rtype.test( elem.nodeName ) && elem.parentNode ) { jQuery.error( "type property can't be changed" ); } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { // Setting the type on a radio button after the value resets the value in IE6-9 // Reset value to it's default in case type is set after value // This is for element creation var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { elem.value = val; } return value; } } }, tabIndex: { get: function( elem ) { // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ var attributeNode = elem.getAttributeNode("tabIndex"); return attributeNode && attributeNode.specified ? parseInt( attributeNode.value, 10 ) : rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? 0 : undefined; } }, // Use the value property for back compat // Use the formHook for button elements in IE6/7 (#1954) value: { get: function( elem, name ) { if ( formHook && jQuery.nodeName( elem, "button" ) ) { return formHook.get( elem, name ); } return name in elem ? elem.value : null; }, set: function( elem, value, name ) { if ( formHook && jQuery.nodeName( elem, "button" ) ) { return formHook.set( elem, value, name ); } // Does not return so that setAttribute is also used elem.value = value; } } }, propFix: { tabindex: "tabIndex", readonly: "readOnly", "for": "htmlFor", "class": "className", maxlength: "maxLength", cellspacing: "cellSpacing", cellpadding: "cellPadding", rowspan: "rowSpan", colspan: "colSpan", usemap: "useMap", frameborder: "frameBorder", contenteditable: "contentEditable" }, prop: function( elem, name, value ) { var nType = elem.nodeType; // don't get/set properties on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return undefined; } var ret, hooks, notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); if ( notxml ) { // Fix name and attach hooks name = jQuery.propFix[ name ] || name; hooks = jQuery.propHooks[ name ]; } if ( value !== undefined ) { if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { return (elem[ name ] = value); } } else { if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== undefined ) { return ret; } else { return elem[ name ]; } } }, propHooks: {} }); // Hook for boolean attributes boolHook = { get: function( elem, name ) { // Align boolean attributes with corresponding properties return jQuery.prop( elem, name ) ? name.toLowerCase() : undefined; }, set: function( elem, value, name ) { var propName; if ( value === false ) { // Remove boolean attributes when set to false jQuery.removeAttr( elem, name ); } else { // value is true since we know at this point it's type boolean and not false // Set boolean attributes to the same name and set the DOM property propName = jQuery.propFix[ name ] || name; if ( propName in elem ) { // Only set the IDL specifically if it already exists on the element elem[ propName ] = true; } elem.setAttribute( name, name.toLowerCase() ); } return name; } }; // IE6/7 do not support getting/setting some attributes with get/setAttribute if ( !jQuery.support.getSetAttribute ) { // propFix is more comprehensive and contains all fixes jQuery.attrFix = jQuery.propFix; // Use this for any attribute on a form in IE6/7 formHook = jQuery.attrHooks.name = jQuery.attrHooks.title = jQuery.valHooks.button = { get: function( elem, name ) { var ret; ret = elem.getAttributeNode( name ); // Return undefined if nodeValue is empty string return ret && ret.nodeValue !== "" ? ret.nodeValue : undefined; }, set: function( elem, value, name ) { // Check form objects in IE (multiple bugs related) // Only use nodeValue if the attribute node exists on the form var ret = elem.getAttributeNode( name ); if ( ret ) { ret.nodeValue = value; return value; } } }; // Set width and height to auto instead of 0 on empty string( Bug #8150 ) // This is for removals jQuery.each([ "width", "height" ], function( i, name ) { jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { set: function( elem, value ) { if ( value === "" ) { elem.setAttribute( name, "auto" ); return value; } } }); }); } // Some attributes require a special call on IE if ( !jQuery.support.hrefNormalized ) { jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { get: function( elem ) { var ret = elem.getAttribute( name, 2 ); return ret === null ? undefined : ret; } }); }); } if ( !jQuery.support.style ) { jQuery.attrHooks.style = { get: function( elem ) { // Return undefined in the case of empty string // Normalize to lowercase since IE uppercases css property names return elem.style.cssText.toLowerCase() || undefined; }, set: function( elem, value ) { return (elem.style.cssText = "" + value); } }; } // Safari mis-reports the default selected property of an option // Accessing the parent's selectedIndex property fixes it if ( !jQuery.support.optSelected ) { jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { get: function( elem ) { var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; // Make sure that it also works with optgroups, see #5701 if ( parent.parentNode ) { parent.parentNode.selectedIndex; } } } }); } // Radios and checkboxes getter/setter if ( !jQuery.support.checkOn ) { jQuery.each([ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = { get: function( elem ) { // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified return elem.getAttribute("value") === null ? "on" : elem.value; } }; }); } jQuery.each([ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { set: function( elem, value ) { if ( jQuery.isArray( value ) ) { return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0); } } }); }); var rnamespaces = /\.(.*)$/, rformElems = /^(?:textarea|input|select)$/i, rperiod = /\./g, rspaces = / /g, rescape = /[^\w\s.|`]/g, fcleanup = function( nm ) { return nm.replace(rescape, "\\$&"); }; /* * A number of helper functions used for managing events. * Many of the ideas behind this code originated from * Dean Edwards' addEvent library. */ jQuery.event = { // Bind an event to an element // Original by Dean Edwards add: function( elem, types, handler, data ) { if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } if ( handler === false ) { handler = returnFalse; } else if ( !handler ) { // Fixes bug #7229. Fix recommended by jdalton return; } var handleObjIn, handleObj; if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; } // Make sure that the function being executed has a unique ID if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element's event structure var elemData = jQuery._data( elem ); // If no elemData is found then we must be trying to bind to one of the // banned noData elements if ( !elemData ) { return; } var events = elemData.events, eventHandle = elemData.handle; if ( !events ) { elemData.events = events = {}; } if ( !eventHandle ) { elemData.handle = eventHandle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.handle.apply( eventHandle.elem, arguments ) : undefined; }; } // Add elem as a property of the handle function // This is to prevent a memory leak with non-native events in IE. eventHandle.elem = elem; // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); types = types.split(" "); var type, i = 0, namespaces; while ( (type = types[ i++ ]) ) { handleObj = handleObjIn ? jQuery.extend({}, handleObjIn) : { handler: handler, data: data }; // Namespaced event handlers if ( type.indexOf(".") > -1 ) { namespaces = type.split("."); type = namespaces.shift(); handleObj.namespace = namespaces.slice(0).sort().join("."); } else { namespaces = []; handleObj.namespace = ""; } handleObj.type = type; if ( !handleObj.guid ) { handleObj.guid = handler.guid; } // Get the current list of functions bound to this event var handlers = events[ type ], special = jQuery.event.special[ type ] || {}; // Init the event handler queue if ( !handlers ) { handlers = events[ type ] = []; // Check for a special event handler // Only use addEventListener/attachEvent if the special // events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // Bind the global event handler to the element if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add the function to the element's handler list handlers.push( handleObj ); // Keep track of which events have been used, for event optimization jQuery.event.global[ type ] = true; } // Nullify elem to prevent memory leaks in IE elem = null; }, global: {}, // Detach an event or set of events from an element remove: function( elem, types, handler, pos ) { // don't do events on text and comment nodes if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } if ( handler === false ) { handler = returnFalse; } var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, elemData = jQuery.hasData( elem ) && jQuery._data( elem ), events = elemData && elemData.events; if ( !elemData || !events ) { return; } // types is actually an event object here if ( types && types.type ) { handler = types.handler; types = types.type; } // Unbind all events for the element if ( !types || typeof types === "string" && types.charAt(0) === "." ) { types = types || ""; for ( type in events ) { jQuery.event.remove( elem, type + types ); } return; } // Handle multiple events separated by a space // jQuery(...).unbind("mouseover mouseout", fn); types = types.split(" "); while ( (type = types[ i++ ]) ) { origType = type; handleObj = null; all = type.indexOf(".") < 0; namespaces = []; if ( !all ) { // Namespaced event handlers namespaces = type.split("."); type = namespaces.shift(); namespace = new RegExp("(^|\\.)" + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); } eventType = events[ type ]; if ( !eventType ) { continue; } if ( !handler ) { for ( j = 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; if ( all || namespace.test( handleObj.namespace ) ) { jQuery.event.remove( elem, origType, handleObj.handler, j ); eventType.splice( j--, 1 ); } } continue; } special = jQuery.event.special[ type ] || {}; for ( j = pos || 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; if ( handler.guid === handleObj.guid ) { // remove the given handler for the given type if ( all || namespace.test( handleObj.namespace ) ) { if ( pos == null ) { eventType.splice( j--, 1 ); } if ( special.remove ) { special.remove.call( elem, handleObj ); } } if ( pos != null ) { break; } } } // remove generic event handler if no more handlers exist if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } ret = null; delete events[ type ]; } } // Remove the expando if it's no longer used if ( jQuery.isEmptyObject( events ) ) { var handle = elemData.handle; if ( handle ) { handle.elem = null; } delete elemData.events; delete elemData.handle; if ( jQuery.isEmptyObject( elemData ) ) { jQuery.removeData( elem, undefined, true ); } } }, // Events that are safe to short-circuit if no handlers are attached. // Native DOM events should not be added, they may have inline handlers. customEvent: { "getData": true, "setData": true, "changeData": true }, trigger: function( event, data, elem, onlyHandlers ) { // Event object or event type var type = event.type || event, namespaces = [], exclusive; if ( type.indexOf("!") >= 0 ) { // Exclusive events trigger only for the exact event (no namespaces) type = type.slice(0, -1); exclusive = true; } if ( type.indexOf(".") >= 0 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split("."); type = namespaces.shift(); namespaces.sort(); } if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { // No jQuery handlers for this event type, and it can't have inline handlers return; } // Caller can pass in an Event, Object, or just an event type string event = typeof event === "object" ? // jQuery.Event object event[ jQuery.expando ] ? event : // Object literal new jQuery.Event( type, event ) : // Just the event type (string) new jQuery.Event( type ); event.type = type; event.exclusive = exclusive; event.namespace = namespaces.join("."); event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)"); // triggerHandler() and global events don't bubble or run the default action if ( onlyHandlers || !elem ) { event.preventDefault(); event.stopPropagation(); } // Handle a global trigger if ( !elem ) { // TODO: Stop taunting the data cache; remove global events and always attach to document jQuery.each( jQuery.cache, function() { // internalKey variable is just used to make it easier to find // and potentially change this stuff later; currently it just // points to jQuery.expando var internalKey = jQuery.expando, internalCache = this[ internalKey ]; if ( internalCache && internalCache.events && internalCache.events[ type ] ) { jQuery.event.trigger( event, data, internalCache.handle.elem ); } }); return; } // Don't do events on text and comment nodes if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } // Clean up the event in case it is being reused event.result = undefined; event.target = elem; // Clone any incoming data and prepend the event, creating the handler arg list data = data != null ? jQuery.makeArray( data ) : []; data.unshift( event ); var cur = elem, // IE doesn't like method names with a colon (#3533, #8272) ontype = type.indexOf(":") < 0 ? "on" + type : ""; // Fire event on the current element, then bubble up the DOM tree do { var handle = jQuery._data( cur, "handle" ); event.currentTarget = cur; if ( handle ) { handle.apply( cur, data ); } // Trigger an inline bound script if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) { event.result = false; event.preventDefault(); } // Bubble up to document, then to window cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window; } while ( cur && !event.isPropagationStopped() ); // If nobody prevented the default action, do it now if ( !event.isDefaultPrevented() ) { var old, special = jQuery.event.special[ type ] || {}; if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) && !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { // Call a native DOM method on the target with the same name name as the event. // Can't use an .isFunction)() check here because IE6/7 fails that test. // IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch. try { if ( ontype && elem[ type ] ) { // Don't re-trigger an onFOO event when we call its FOO() method old = elem[ ontype ]; if ( old ) { elem[ ontype ] = null; } jQuery.event.triggered = type; elem[ type ](); } } catch ( ieError ) {} if ( old ) { elem[ ontype ] = old; } jQuery.event.triggered = undefined; } } return event.result; }, handle: function( event ) { event = jQuery.event.fix( event || window.event ); // Snapshot the handlers list since a called handler may add/remove events. var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0), run_all = !event.exclusive && !event.namespace, args = Array.prototype.slice.call( arguments, 0 ); // Use the fix-ed Event rather than the (read-only) native event args[0] = event; event.currentTarget = this; for ( var j = 0, l = handlers.length; j < l; j++ ) { var handleObj = handlers[ j ]; // Triggered event must 1) be non-exclusive and have no namespace, or // 2) have namespace(s) a subset or equal to those in the bound event. if ( run_all || event.namespace_re.test( handleObj.namespace ) ) { // Pass in a reference to the handler function itself // So that we can later remove it event.handler = handleObj.handler; event.data = handleObj.data; event.handleObj = handleObj; var ret = handleObj.handler.apply( this, args ); if ( ret !== undefined ) { event.result = ret; if ( ret === false ) { event.preventDefault(); event.stopPropagation(); } } if ( event.isImmediatePropagationStopped() ) { break; } } } return event.result; }, props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), fix: function( event ) { if ( event[ jQuery.expando ] ) { return event; } // store a copy of the original event object // and "clone" to set read-only properties var originalEvent = event; event = jQuery.Event( originalEvent ); for ( var i = this.props.length, prop; i; ) { prop = this.props[ --i ]; event[ prop ] = originalEvent[ prop ]; } // Fix target property, if necessary if ( !event.target ) { // Fixes #1925 where srcElement might not be defined either event.target = event.srcElement || document; } // check if target is a textnode (safari) if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } // Add relatedTarget, if necessary if ( !event.relatedTarget && event.fromElement ) { event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; } // Calculate pageX/Y if missing and clientX/Y available if ( event.pageX == null && event.clientX != null ) { var eventDocument = event.target.ownerDocument || document, doc = eventDocument.documentElement, body = eventDocument.body; event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); } // Add which for key events if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { event.which = event.charCode != null ? event.charCode : event.keyCode; } // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) if ( !event.metaKey && event.ctrlKey ) { event.metaKey = event.ctrlKey; } // Add which for click: 1 === left; 2 === middle; 3 === right // Note: button is not normalized, so don't use it if ( !event.which && event.button !== undefined ) { event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); } return event; }, // Deprecated, use jQuery.guid instead guid: 1E8, // Deprecated, use jQuery.proxy instead proxy: jQuery.proxy, special: { ready: { // Make sure the ready event is setup setup: jQuery.bindReady, teardown: jQuery.noop }, live: { add: function( handleObj ) { jQuery.event.add( this, liveConvert( handleObj.origType, handleObj.selector ), jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); }, remove: function( handleObj ) { jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); } }, beforeunload: { setup: function( data, namespaces, eventHandle ) { // We only want to do this special case on windows if ( jQuery.isWindow( this ) ) { this.onbeforeunload = eventHandle; } }, teardown: function( namespaces, eventHandle ) { if ( this.onbeforeunload === eventHandle ) { this.onbeforeunload = null; } } } } }; jQuery.removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { elem.removeEventListener( type, handle, false ); } } : function( elem, type, handle ) { if ( elem.detachEvent ) { elem.detachEvent( "on" + type, handle ); } }; jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword if ( !this.preventDefault ) { return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) { this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; // Event type } else { this.type = src; } // Put explicitly provided properties onto the event object if ( props ) { jQuery.extend( this, props ); } // timeStamp is buggy for some events on Firefox(#3843) // So we won't rely on the native value this.timeStamp = jQuery.now(); // Mark it as fixed this[ jQuery.expando ] = true; }; function returnFalse() { return false; } function returnTrue() { return true; } // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html jQuery.Event.prototype = { preventDefault: function() { this.isDefaultPrevented = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if preventDefault exists run it on the original event if ( e.preventDefault ) { e.preventDefault(); // otherwise set the returnValue property of the original event to false (IE) } else { e.returnValue = false; } }, stopPropagation: function() { this.isPropagationStopped = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if stopPropagation exists run it on the original event if ( e.stopPropagation ) { e.stopPropagation(); } // otherwise set the cancelBubble property of the original event to true (IE) e.cancelBubble = true; }, stopImmediatePropagation: function() { this.isImmediatePropagationStopped = returnTrue; this.stopPropagation(); }, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse }; // Checks if an event happened on an element within another element // Used in jQuery.event.special.mouseenter and mouseleave handlers var withinElement = function( event ) { // Check if mouse(over|out) are still within the same parent element var related = event.relatedTarget, inside = false, eventType = event.type; event.type = event.data; if ( related !== this ) { if ( related ) { inside = jQuery.contains( this, related ); } if ( !inside ) { jQuery.event.handle.apply( this, arguments ); event.type = eventType; } } }, // In case of event delegation, we only need to rename the event.type, // liveHandler will take care of the rest. delegate = function( event ) { event.type = event.data; jQuery.event.handle.apply( this, arguments ); }; // Create mouseenter and mouseleave events jQuery.each({ mouseenter: "mouseover", mouseleave: "mouseout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { setup: function( data ) { jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); }, teardown: function( data ) { jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); } }; }); // submit delegation if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { setup: function( data, namespaces ) { if ( !jQuery.nodeName( this, "form" ) ) { jQuery.event.add(this, "click.specialSubmit", function( e ) { var elem = e.target, type = elem.type; if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { trigger( "submit", this, arguments ); } }); jQuery.event.add(this, "keypress.specialSubmit", function( e ) { var elem = e.target, type = elem.type; if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { trigger( "submit", this, arguments ); } }); } else { return false; } }, teardown: function( namespaces ) { jQuery.event.remove( this, ".specialSubmit" ); } }; } // change delegation, happens here so we have bind. if ( !jQuery.support.changeBubbles ) { var changeFilters, getVal = function( elem ) { var type = elem.type, val = elem.value; if ( type === "radio" || type === "checkbox" ) { val = elem.checked; } else if ( type === "select-multiple" ) { val = elem.selectedIndex > -1 ? jQuery.map( elem.options, function( elem ) { return elem.selected; }).join("-") : ""; } else if ( jQuery.nodeName( elem, "select" ) ) { val = elem.selectedIndex; } return val; }, testChange = function testChange( e ) { var elem = e.target, data, val; if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { return; } data = jQuery._data( elem, "_change_data" ); val = getVal(elem); // the current data will be also retrieved by beforeactivate if ( e.type !== "focusout" || elem.type !== "radio" ) { jQuery._data( elem, "_change_data", val ); } if ( data === undefined || val === data ) { return; } if ( data != null || val ) { e.type = "change"; e.liveFired = undefined; jQuery.event.trigger( e, arguments[1], elem ); } }; jQuery.event.special.change = { filters: { focusout: testChange, beforedeactivate: testChange, click: function( e ) { var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) { testChange.call( this, e ); } }, // Change has to be called before submit // Keydown will be called before keypress, which is used in submit-event delegation keydown: function( e ) { var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) || (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || type === "select-multiple" ) { testChange.call( this, e ); } }, // Beforeactivate happens also before the previous element is blurred // with this event you can't trigger a change event, but you can store // information beforeactivate: function( e ) { var elem = e.target; jQuery._data( elem, "_change_data", getVal(elem) ); } }, setup: function( data, namespaces ) { if ( this.type === "file" ) { return false; } for ( var type in changeFilters ) { jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); } return rformElems.test( this.nodeName ); }, teardown: function( namespaces ) { jQuery.event.remove( this, ".specialChange" ); return rformElems.test( this.nodeName ); } }; changeFilters = jQuery.event.special.change.filters; // Handle when the input is .focus()'d changeFilters.focus = changeFilters.beforeactivate; } function trigger( type, elem, args ) { // Piggyback on a donor event to simulate a different one. // Fake originalEvent to avoid donor's stopPropagation, but if the // simulated event prevents default then we do the same on the donor. // Don't pass args or remember liveFired; they apply to the donor event. var event = jQuery.extend( {}, args[ 0 ] ); event.type = type; event.originalEvent = {}; event.liveFired = undefined; jQuery.event.handle.call( elem, event ); if ( event.isDefaultPrevented() ) { args[ 0 ].preventDefault(); } } // Create "bubbling" focus and blur events if ( !jQuery.support.focusinBubbles ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { // Attach a single capturing handler while someone wants focusin/focusout var attaches = 0; jQuery.event.special[ fix ] = { setup: function() { if ( attaches++ === 0 ) { document.addEventListener( orig, handler, true ); } }, teardown: function() { if ( --attaches === 0 ) { document.removeEventListener( orig, handler, true ); } } }; function handler( donor ) { // Donor event is always a native one; fix it and switch its type. // Let focusin/out handler cancel the donor focus/blur event. var e = jQuery.event.fix( donor ); e.type = fix; e.originalEvent = {}; jQuery.event.trigger( e, null, e.target ); if ( e.isDefaultPrevented() ) { donor.preventDefault(); } } }); } jQuery.each(["bind", "one"], function( i, name ) { jQuery.fn[ name ] = function( type, data, fn ) { var handler; // Handle object literals if ( typeof type === "object" ) { for ( var key in type ) { this[ name ](key, data, type[key], fn); } return this; } if ( arguments.length === 2 || data === false ) { fn = data; data = undefined; } if ( name === "one" ) { handler = function( event ) { jQuery( this ).unbind( event, handler ); return fn.apply( this, arguments ); }; handler.guid = fn.guid || jQuery.guid++; } else { handler = fn; } if ( type === "unload" && name !== "one" ) { this.one( type, data, fn ); } else { for ( var i = 0, l = this.length; i < l; i++ ) { jQuery.event.add( this[i], type, handler, data ); } } return this; }; }); jQuery.fn.extend({ unbind: function( type, fn ) { // Handle object literals if ( typeof type === "object" && !type.preventDefault ) { for ( var key in type ) { this.unbind(key, type[key]); } } else { for ( var i = 0, l = this.length; i < l; i++ ) { jQuery.event.remove( this[i], type, fn ); } } return this; }, delegate: function( selector, types, data, fn ) { return this.live( types, data, fn, selector ); }, undelegate: function( selector, types, fn ) { if ( arguments.length === 0 ) { return this.unbind( "live" ); } else { return this.die( types, null, fn, selector ); } }, trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, triggerHandler: function( type, data ) { if ( this[0] ) { return jQuery.event.trigger( type, data, this[0], true ); } }, toggle: function( fn ) { // Save reference to arguments for access in closure var args = arguments, guid = fn.guid || jQuery.guid++, i = 0, toggler = function( event ) { // Figure out which function to execute var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); // Make sure that clicks stop event.preventDefault(); // and execute the function return args[ lastToggle ].apply( this, arguments ) || false; }; // link all the functions, so any of them can unbind this click handler toggler.guid = guid; while ( i < args.length ) { args[ i++ ].guid = guid; } return this.click( toggler ); }, hover: function( fnOver, fnOut ) { return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } }); var liveMap = { focus: "focusin", blur: "focusout", mouseenter: "mouseover", mouseleave: "mouseout" }; jQuery.each(["live", "die"], function( i, name ) { jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { var type, i = 0, match, namespaces, preType, selector = origSelector || this.selector, context = origSelector ? this : jQuery( this.context ); if ( typeof types === "object" && !types.preventDefault ) { for ( var key in types ) { context[ name ]( key, data, types[key], selector ); } return this; } if ( name === "die" && !types && origSelector && origSelector.charAt(0) === "." ) { context.unbind( origSelector ); return this; } if ( data === false || jQuery.isFunction( data ) ) { fn = data || returnFalse; data = undefined; } types = (types || "").split(" "); while ( (type = types[ i++ ]) != null ) { match = rnamespaces.exec( type ); namespaces = ""; if ( match ) { namespaces = match[0]; type = type.replace( rnamespaces, "" ); } if ( type === "hover" ) { types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); continue; } preType = type; if ( liveMap[ type ] ) { types.push( liveMap[ type ] + namespaces ); type = type + namespaces; } else { type = (liveMap[ type ] || type) + namespaces; } if ( name === "live" ) { // bind live handler for ( var j = 0, l = context.length; j < l; j++ ) { jQuery.event.add( context[j], "live." + liveConvert( type, selector ), { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); } } else { // unbind live handler context.unbind( "live." + liveConvert( type, selector ), fn ); } } return this; }; }); function liveHandler( event ) { var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, elems = [], selectors = [], events = jQuery._data( this, "events" ); // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { return; } if ( event.namespace ) { namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); } event.liveFired = this; var live = events.live.slice(0); for ( j = 0; j < live.length; j++ ) { handleObj = live[j]; if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { selectors.push( handleObj.selector ); } else { live.splice( j--, 1 ); } } match = jQuery( event.target ).closest( selectors, event.currentTarget ); for ( i = 0, l = match.length; i < l; i++ ) { close = match[i]; for ( j = 0; j < live.length; j++ ) { handleObj = live[j]; if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) { elem = close.elem; related = null; // Those two events require additional checking if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { event.type = handleObj.preType; related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; // Make sure not to accidentally match a child element with the same selector if ( related && jQuery.contains( elem, related ) ) { related = elem; } } if ( !related || related !== elem ) { elems.push({ elem: elem, handleObj: handleObj, level: close.level }); } } } } for ( i = 0, l = elems.length; i < l; i++ ) { match = elems[i]; if ( maxLevel && match.level > maxLevel ) { break; } event.currentTarget = match.elem; event.data = match.handleObj.data; event.handleObj = match.handleObj; ret = match.handleObj.origHandler.apply( match.elem, arguments ); if ( ret === false || event.isPropagationStopped() ) { maxLevel = match.level; if ( ret === false ) { stop = false; } if ( event.isImmediatePropagationStopped() ) { break; } } } return stop; } function liveConvert( type, selector ) { return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&"); } jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { // Handle event binding jQuery.fn[ name ] = function( data, fn ) { if ( fn == null ) { fn = data; data = null; } return arguments.length > 0 ? this.bind( name, data, fn ) : this.trigger( name ); }; if ( jQuery.attrFn ) { jQuery.attrFn[ name ] = true; } }); /*! * Sizzle CSS Selector Engine * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, done = 0, toString = Object.prototype.toString, hasDuplicate = false, baseHasDuplicate = true, rBackslash = /\\/g, rNonWord = /\W/; // Here we check if the JavaScript engine is using some sort of // optimization where it does not always call our comparision // function. If that is the case, discard the hasDuplicate value. // Thus far that includes Google Chrome. [0, 0].sort(function() { baseHasDuplicate = false; return 0; }); var Sizzle = function( selector, context, results, seed ) { results = results || []; context = context || document; var origContext = context; if ( context.nodeType !== 1 && context.nodeType !== 9 ) { return []; } if ( !selector || typeof selector !== "string" ) { return results; } var m, set, checkSet, extra, ret, cur, pop, i, prune = true, contextXML = Sizzle.isXML( context ), parts = [], soFar = selector; // Reset the position of the chunker regexp (start from head) do { chunker.exec( "" ); m = chunker.exec( soFar ); if ( m ) { soFar = m[3]; parts.push( m[1] ); if ( m[2] ) { extra = m[3]; break; } } } while ( m ); if ( parts.length > 1 && origPOS.exec( selector ) ) { if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { set = posProcess( parts[0] + parts[1], context ); } else { set = Expr.relative[ parts[0] ] ? [ context ] : Sizzle( parts.shift(), context ); while ( parts.length ) { selector = parts.shift(); if ( Expr.relative[ selector ] ) { selector += parts.shift(); } set = posProcess( selector, set ); } } } else { // Take a shortcut and set the context if the root selector is an ID // (but not if it'll be faster if the inner selector is an ID) if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { ret = Sizzle.find( parts.shift(), context, contextXML ); context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; } if ( context ) { ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; if ( parts.length > 0 ) { checkSet = makeArray( set ); } else { prune = false; } while ( parts.length ) { cur = parts.pop(); pop = cur; if ( !Expr.relative[ cur ] ) { cur = ""; } else { pop = parts.pop(); } if ( pop == null ) { pop = context; } Expr.relative[ cur ]( checkSet, pop, contextXML ); } } else { checkSet = parts = []; } } if ( !checkSet ) { checkSet = set; } if ( !checkSet ) { Sizzle.error( cur || selector ); } if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); } else if ( context && context.nodeType === 1 ) { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { results.push( set[i] ); } } } else { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) { results.push( set[i] ); } } } } else { makeArray( checkSet, results ); } if ( extra ) { Sizzle( extra, origContext, results, seed ); Sizzle.uniqueSort( results ); } return results; }; Sizzle.uniqueSort = function( results ) { if ( sortOrder ) { hasDuplicate = baseHasDuplicate; results.sort( sortOrder ); if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) { if ( results[i] === results[ i - 1 ] ) { results.splice( i--, 1 ); } } } } return results; }; Sizzle.matches = function( expr, set ) { return Sizzle( expr, null, null, set ); }; Sizzle.matchesSelector = function( node, expr ) { return Sizzle( expr, null, null, [node] ).length > 0; }; Sizzle.find = function( expr, context, isXML ) { var set; if ( !expr ) { return []; } for ( var i = 0, l = Expr.order.length; i < l; i++ ) { var match, type = Expr.order[i]; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { var left = match[1]; match.splice( 1, 1 ); if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace( rBackslash, "" ); set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; } } } } if ( !set ) { set = typeof context.getElementsByTagName !== "undefined" ? context.getElementsByTagName( "*" ) : []; } return { set: set, expr: expr }; }; Sizzle.filter = function( expr, set, inplace, not ) { var match, anyFound, old = expr, result = [], curLoop = set, isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); while ( expr && set.length ) { for ( var type in Expr.filter ) { if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { var found, item, filter = Expr.filter[ type ], left = match[1]; anyFound = false; match.splice(1,1); if ( left.substr( left.length - 1 ) === "\\" ) { continue; } if ( curLoop === result ) { result = []; } if ( Expr.preFilter[ type ] ) { match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); if ( !match ) { anyFound = found = true; } else if ( match === true ) { continue; } } if ( match ) { for ( var i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { found = filter( item, match, i, curLoop ); var pass = not ^ !!found; if ( inplace && found != null ) { if ( pass ) { anyFound = true; } else { curLoop[i] = false; } } else if ( pass ) { result.push( item ); anyFound = true; } } } } if ( found !== undefined ) { if ( !inplace ) { curLoop = result; } expr = expr.replace( Expr.match[ type ], "" ); if ( !anyFound ) { return []; } break; } } } // Improper expression if ( expr === old ) { if ( anyFound == null ) { Sizzle.error( expr ); } else { break; } } old = expr; } return curLoop; }; Sizzle.error = function( msg ) { throw "Syntax error, unrecognized expression: " + msg; }; var Expr = Sizzle.selectors = { order: [ "ID", "NAME", "TAG" ], match: { ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, leftMatch: {}, attrMap: { "class": "className", "for": "htmlFor" }, attrHandle: { href: function( elem ) { return elem.getAttribute( "href" ); }, type: function( elem ) { return elem.getAttribute( "type" ); } }, relative: { "+": function(checkSet, part){ var isPartStr = typeof part === "string", isTag = isPartStr && !rNonWord.test( part ), isPartStrNotTag = isPartStr && !isTag; if ( isTag ) { part = part.toLowerCase(); } for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { if ( (elem = checkSet[i]) ) { while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? elem || false : elem === part; } } if ( isPartStrNotTag ) { Sizzle.filter( part, checkSet, true ); } }, ">": function( checkSet, part ) { var elem, isPartStr = typeof part === "string", i = 0, l = checkSet.length; if ( isPartStr && !rNonWord.test( part ) ) { part = part.toLowerCase(); for ( ; i < l; i++ ) { elem = checkSet[i]; if ( elem ) { var parent = elem.parentNode; checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } } } else { for ( ; i < l; i++ ) { elem = checkSet[i]; if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : elem.parentNode === part; } } if ( isPartStr ) { Sizzle.filter( part, checkSet, true ); } } }, "": function(checkSet, part, isXML){ var nodeCheck, doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); }, "~": function( checkSet, part, isXML ) { var nodeCheck, doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); } }, find: { ID: function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 return m && m.parentNode ? [m] : []; } }, NAME: function( match, context ) { if ( typeof context.getElementsByName !== "undefined" ) { var ret = [], results = context.getElementsByName( match[1] ); for ( var i = 0, l = results.length; i < l; i++ ) { if ( results[i].getAttribute("name") === match[1] ) { ret.push( results[i] ); } } return ret.length === 0 ? null : ret; } }, TAG: function( match, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( match[1] ); } } }, preFilter: { CLASS: function( match, curLoop, inplace, result, not, isXML ) { match = " " + match[1].replace( rBackslash, "" ) + " "; if ( isXML ) { return match; } for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { if ( !inplace ) { result.push( elem ); } } else if ( inplace ) { curLoop[i] = false; } } } return false; }, ID: function( match ) { return match[1].replace( rBackslash, "" ); }, TAG: function( match, curLoop ) { return match[1].replace( rBackslash, "" ).toLowerCase(); }, CHILD: function( match ) { if ( match[1] === "nth" ) { if ( !match[2] ) { Sizzle.error( match[0] ); } match[2] = match[2].replace(/^\+|\s*/g, ''); // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); // calculate the numbers (first)n+(last) including if they are negative match[2] = (test[1] + (test[2] || 1)) - 0; match[3] = test[3] - 0; } else if ( match[2] ) { Sizzle.error( match[0] ); } // TODO: Move to normal caching system match[0] = done++; return match; }, ATTR: function( match, curLoop, inplace, result, not, isXML ) { var name = match[1] = match[1].replace( rBackslash, "" ); if ( !isXML && Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; } // Handle if an un-quoted value was used match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); if ( match[2] === "~=" ) { match[4] = " " + match[4] + " "; } return match; }, PSEUDO: function( match, curLoop, inplace, result, not ) { if ( match[1] === "not" ) { // If we're dealing with a complex expression, or a simple one if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); if ( !inplace ) { result.push.apply( result, ret ); } return false; } } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { return true; } return match; }, POS: function( match ) { match.unshift( true ); return match; } }, filters: { enabled: function( elem ) { return elem.disabled === false && elem.type !== "hidden"; }, disabled: function( elem ) { return elem.disabled === true; }, checked: function( elem ) { return elem.checked === true; }, selected: function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { elem.parentNode.selectedIndex; } return elem.selected === true; }, parent: function( elem ) { return !!elem.firstChild; }, empty: function( elem ) { return !elem.firstChild; }, has: function( elem, i, match ) { return !!Sizzle( match[3], elem ).length; }, header: function( elem ) { return (/h\d/i).test( elem.nodeName ); }, text: function( elem ) { var attr = elem.getAttribute( "type" ), type = elem.type; // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) // use getAttribute instead to test this case return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); }, radio: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; }, checkbox: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; }, file: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; }, password: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; }, submit: function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && "submit" === elem.type; }, image: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; }, reset: function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && "reset" === elem.type; }, button: function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && "button" === elem.type || name === "button"; }, input: function( elem ) { return (/input|select|textarea|button/i).test( elem.nodeName ); }, focus: function( elem ) { return elem === elem.ownerDocument.activeElement; } }, setFilters: { first: function( elem, i ) { return i === 0; }, last: function( elem, i, match, array ) { return i === array.length - 1; }, even: function( elem, i ) { return i % 2 === 0; }, odd: function( elem, i ) { return i % 2 === 1; }, lt: function( elem, i, match ) { return i < match[3] - 0; }, gt: function( elem, i, match ) { return i > match[3] - 0; }, nth: function( elem, i, match ) { return match[3] - 0 === i; }, eq: function( elem, i, match ) { return match[3] - 0 === i; } }, filter: { PSEUDO: function( elem, match, i, array ) { var name = match[1], filter = Expr.filters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } else if ( name === "contains" ) { return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; } else if ( name === "not" ) { var not = match[3]; for ( var j = 0, l = not.length; j < l; j++ ) { if ( not[j] === elem ) { return false; } } return true; } else { Sizzle.error( name ); } }, CHILD: function( elem, match ) { var type = match[1], node = elem; switch ( type ) { case "only": case "first": while ( (node = node.previousSibling) ) { if ( node.nodeType === 1 ) { return false; } } if ( type === "first" ) { return true; } node = elem; case "last": while ( (node = node.nextSibling) ) { if ( node.nodeType === 1 ) { return false; } } return true; case "nth": var first = match[2], last = match[3]; if ( first === 1 && last === 0 ) { return true; } var doneName = match[0], parent = elem.parentNode; if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { var count = 0; for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { node.nodeIndex = ++count; } } parent.sizcache = doneName; } var diff = elem.nodeIndex - last; if ( first === 0 ) { return diff === 0; } else { return ( diff % first === 0 && diff / first >= 0 ); } } }, ID: function( elem, match ) { return elem.nodeType === 1 && elem.getAttribute("id") === match; }, TAG: function( elem, match ) { return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; }, CLASS: function( elem, match ) { return (" " + (elem.className || elem.getAttribute("class")) + " ") .indexOf( match ) > -1; }, ATTR: function( elem, match ) { var name = match[1], result = Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : elem[ name ] != null ? elem[ name ] : elem.getAttribute( name ), value = result + "", type = match[2], check = match[4]; return result == null ? type === "!=" : type === "=" ? value === check : type === "*=" ? value.indexOf(check) >= 0 : type === "~=" ? (" " + value + " ").indexOf(check) >= 0 : !check ? value && result !== false : type === "!=" ? value !== check : type === "^=" ? value.indexOf(check) === 0 : type === "$=" ? value.substr(value.length - check.length) === check : type === "|=" ? value === check || value.substr(0, check.length + 1) === check + "-" : false; }, POS: function( elem, match, i, array ) { var name = match[2], filter = Expr.setFilters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } } } }; var origPOS = Expr.match.POS, fescape = function(all, num){ return "\\" + (num - 0 + 1); }; for ( var type in Expr.match ) { Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); } var makeArray = function( array, results ) { array = Array.prototype.slice.call( array, 0 ); if ( results ) { results.push.apply( results, array ); return results; } return array; }; // Perform a simple check to determine if the browser is capable of // converting a NodeList to an array using builtin methods. // Also verifies that the returned array holds DOM nodes // (which is not the case in the Blackberry browser) try { Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; // Provide a fallback method if it does not work } catch( e ) { makeArray = function( array, results ) { var i = 0, ret = results || []; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); } else { if ( typeof array.length === "number" ) { for ( var l = array.length; i < l; i++ ) { ret.push( array[i] ); } } else { for ( ; array[i]; i++ ) { ret.push( array[i] ); } } } return ret; }; } var sortOrder, siblingCheck; if ( document.documentElement.compareDocumentPosition ) { sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; return 0; } if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { return a.compareDocumentPosition ? -1 : 1; } return a.compareDocumentPosition(b) & 4 ? -1 : 1; }; } else { sortOrder = function( a, b ) { // The nodes are identical, we can exit early if ( a === b ) { hasDuplicate = true; return 0; // Fallback to using sourceIndex (in IE) if it's available on both nodes } else if ( a.sourceIndex && b.sourceIndex ) { return a.sourceIndex - b.sourceIndex; } var al, bl, ap = [], bp = [], aup = a.parentNode, bup = b.parentNode, cur = aup; // If the nodes are siblings (or identical) we can do a quick check if ( aup === bup ) { return siblingCheck( a, b ); // If no parents were found then the nodes are disconnected } else if ( !aup ) { return -1; } else if ( !bup ) { return 1; } // Otherwise they're somewhere else in the tree so we need // to build up a full list of the parentNodes for comparison while ( cur ) { ap.unshift( cur ); cur = cur.parentNode; } cur = bup; while ( cur ) { bp.unshift( cur ); cur = cur.parentNode; } al = ap.length; bl = bp.length; // Start walking down the tree looking for a discrepancy for ( var i = 0; i < al && i < bl; i++ ) { if ( ap[i] !== bp[i] ) { return siblingCheck( ap[i], bp[i] ); } } // We ended someplace up the tree so do a sibling check return i === al ? siblingCheck( a, bp[i], -1 ) : siblingCheck( ap[i], b, 1 ); }; siblingCheck = function( a, b, ret ) { if ( a === b ) { return ret; } var cur = a.nextSibling; while ( cur ) { if ( cur === b ) { return -1; } cur = cur.nextSibling; } return 1; }; } // Utility function for retreiving the text value of an array of DOM nodes Sizzle.getText = function( elems ) { var ret = "", elem; for ( var i = 0; elems[i]; i++ ) { elem = elems[i]; // Get the text from text nodes and CDATA nodes if ( elem.nodeType === 3 || elem.nodeType === 4 ) { ret += elem.nodeValue; // Traverse everything else, except comment nodes } else if ( elem.nodeType !== 8 ) { ret += Sizzle.getText( elem.childNodes ); } } return ret; }; // Check to see if the browser returns elements by name when // querying by getElementById (and provide a workaround) (function(){ // We're going to inject a fake input element with a specified name var form = document.createElement("div"), id = "script" + (new Date()).getTime(), root = document.documentElement; form.innerHTML = ""; // Inject it into the root element, check its status, and remove it quickly root.insertBefore( form, root.firstChild ); // The workaround has to do additional checks after a getElementById // Which slows things down for other browsers (hence the branching) if ( document.getElementById( id ) ) { Expr.find.ID = function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; } }; Expr.filter.ID = function( elem, match ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return elem.nodeType === 1 && node && node.nodeValue === match; }; } root.removeChild( form ); // release memory in IE root = form = null; })(); (function(){ // Check to see if the browser returns only elements // when doing getElementsByTagName("*") // Create a fake element var div = document.createElement("div"); div.appendChild( document.createComment("") ); // Make sure no comments are found if ( div.getElementsByTagName("*").length > 0 ) { Expr.find.TAG = function( match, context ) { var results = context.getElementsByTagName( match[1] ); // Filter out possible comments if ( match[1] === "*" ) { var tmp = []; for ( var i = 0; results[i]; i++ ) { if ( results[i].nodeType === 1 ) { tmp.push( results[i] ); } } results = tmp; } return results; }; } // Check to see if an attribute returns normalized href attributes div.innerHTML = ""; if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && div.firstChild.getAttribute("href") !== "#" ) { Expr.attrHandle.href = function( elem ) { return elem.getAttribute( "href", 2 ); }; } // release memory in IE div = null; })(); if ( document.querySelectorAll ) { (function(){ var oldSizzle = Sizzle, div = document.createElement("div"), id = "__sizzle__"; div.innerHTML = "

"; // Safari can't handle uppercase or unicode characters when // in quirks mode. if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { return; } Sizzle = function( query, context, extra, seed ) { context = context || document; // Only use querySelectorAll on non-XML documents // (ID selectors don't work in non-HTML documents) if ( !seed && !Sizzle.isXML(context) ) { // See if we find a selector to speed up var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { // Speed-up: Sizzle("TAG") if ( match[1] ) { return makeArray( context.getElementsByTagName( query ), extra ); // Speed-up: Sizzle(".CLASS") } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { return makeArray( context.getElementsByClassName( match[2] ), extra ); } } if ( context.nodeType === 9 ) { // Speed-up: Sizzle("body") // The body element only exists once, optimize finding it if ( query === "body" && context.body ) { return makeArray( [ context.body ], extra ); // Speed-up: Sizzle("#ID") } else if ( match && match[3] ) { var elem = context.getElementById( match[3] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id === match[3] ) { return makeArray( [ elem ], extra ); } } else { return makeArray( [], extra ); } } try { return makeArray( context.querySelectorAll(query), extra ); } catch(qsaError) {} // qSA works strangely on Element-rooted queries // We can work around this by specifying an extra ID on the root // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { var oldContext = context, old = context.getAttribute( "id" ), nid = old || id, hasParent = context.parentNode, relativeHierarchySelector = /^\s*[+~]/.test( query ); if ( !old ) { context.setAttribute( "id", nid ); } else { nid = nid.replace( /'/g, "\\$&" ); } if ( relativeHierarchySelector && hasParent ) { context = context.parentNode; } try { if ( !relativeHierarchySelector || hasParent ) { return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); } } catch(pseudoError) { } finally { if ( !old ) { oldContext.removeAttribute( "id" ); } } } } return oldSizzle(query, context, extra, seed); }; for ( var prop in oldSizzle ) { Sizzle[ prop ] = oldSizzle[ prop ]; } // release memory in IE div = null; })(); } (function(){ var html = document.documentElement, matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; if ( matches ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9 fails this) var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), pseudoWorks = false; try { // This should fail with an exception // Gecko does not error, returns false instead matches.call( document.documentElement, "[test!='']:sizzle" ); } catch( pseudoError ) { pseudoWorks = true; } Sizzle.matchesSelector = function( node, expr ) { // Make sure that attribute selectors are quoted expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); if ( !Sizzle.isXML( node ) ) { try { if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { var ret = matches.call( node, expr ); // IE 9's matchesSelector returns false on disconnected nodes if ( ret || !disconnectedMatch || // As well, disconnected nodes are said to be in a document // fragment in IE 9, so check for that node.document && node.document.nodeType !== 11 ) { return ret; } } } catch(e) {} } return Sizzle(expr, null, null, [node]).length > 0; }; } })(); (function(){ var div = document.createElement("div"); div.innerHTML = "
"; // Opera can't find a second classname (in 9.6) // Also, make sure that getElementsByClassName actually exists if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { return; } // Safari caches class attributes, doesn't catch changes (in 3.2) div.lastChild.className = "e"; if ( div.getElementsByClassName("e").length === 1 ) { return; } Expr.order.splice(1, 0, "CLASS"); Expr.find.CLASS = function( match, context, isXML ) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; // release memory in IE div = null; })(); function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { var match = false; elem = elem[dir]; while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 && !isXML ){ elem.sizcache = doneName; elem.sizset = i; } if ( elem.nodeName.toLowerCase() === cur ) { match = elem; break; } elem = elem[dir]; } checkSet[i] = match; } } } function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { var match = false; elem = elem[dir]; while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 ) { if ( !isXML ) { elem.sizcache = doneName; elem.sizset = i; } if ( typeof cur !== "string" ) { if ( elem === cur ) { match = true; break; } } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { match = elem; break; } } elem = elem[dir]; } checkSet[i] = match; } } } if ( document.documentElement.contains ) { Sizzle.contains = function( a, b ) { return a !== b && (a.contains ? a.contains(b) : true); }; } else if ( document.documentElement.compareDocumentPosition ) { Sizzle.contains = function( a, b ) { return !!(a.compareDocumentPosition(b) & 16); }; } else { Sizzle.contains = function() { return false; }; } Sizzle.isXML = function( elem ) { // documentElement is verified for cases where it doesn't yet exist // (such as loading iframes in IE - #4833) var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }; var posProcess = function( selector, context ) { var match, tmpSet = [], later = "", root = context.nodeType ? [context] : context; // Position selectors must be done after the filter // And so must :not(positional) so we move all PSEUDOs to the end while ( (match = Expr.match.PSEUDO.exec( selector )) ) { later += match[0]; selector = selector.replace( Expr.match.PSEUDO, "" ); } selector = Expr.relative[selector] ? selector + "*" : selector; for ( var i = 0, l = root.length; i < l; i++ ) { Sizzle( selector, root[i], tmpSet ); } return Sizzle.filter( later, tmpSet ); }; // EXPOSE jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; jQuery.expr[":"] = jQuery.expr.filters; jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains; })(); var runtil = /Until$/, rparentsprev = /^(?:parents|prevUntil|prevAll)/, // Note: This RegExp should be improved, or likely pulled from Sizzle rmultiselector = /,/, isSimple = /^.[^:#\[\.,]*$/, slice = Array.prototype.slice, POS = jQuery.expr.match.POS, // methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { children: true, contents: true, next: true, prev: true }; jQuery.fn.extend({ find: function( selector ) { var self = this, i, l; if ( typeof selector !== "string" ) { return jQuery( selector ).filter(function() { for ( i = 0, l = self.length; i < l; i++ ) { if ( jQuery.contains( self[ i ], this ) ) { return true; } } }); } var ret = this.pushStack( "", "find", selector ), length, n, r; for ( i = 0, l = this.length; i < l; i++ ) { length = ret.length; jQuery.find( selector, this[i], ret ); if ( i > 0 ) { // Make sure that the results are unique for ( n = length; n < ret.length; n++ ) { for ( r = 0; r < length; r++ ) { if ( ret[r] === ret[n] ) { ret.splice(n--, 1); break; } } } } } return ret; }, has: function( target ) { var targets = jQuery( target ); return this.filter(function() { for ( var i = 0, l = targets.length; i < l; i++ ) { if ( jQuery.contains( this, targets[i] ) ) { return true; } } }); }, not: function( selector ) { return this.pushStack( winnow(this, selector, false), "not", selector); }, filter: function( selector ) { return this.pushStack( winnow(this, selector, true), "filter", selector ); }, is: function( selector ) { return !!selector && ( typeof selector === "string" ? jQuery.filter( selector, this ).length > 0 : this.filter( selector ).length > 0 ); }, closest: function( selectors, context ) { var ret = [], i, l, cur = this[0]; // Array if ( jQuery.isArray( selectors ) ) { var match, selector, matches = {}, level = 1; if ( cur && selectors.length ) { for ( i = 0, l = selectors.length; i < l; i++ ) { selector = selectors[i]; if ( !matches[ selector ] ) { matches[ selector ] = POS.test( selector ) ? jQuery( selector, context || this.context ) : selector; } } while ( cur && cur.ownerDocument && cur !== context ) { for ( selector in matches ) { match = matches[ selector ]; if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) { ret.push({ selector: selector, elem: cur, level: level }); } } cur = cur.parentNode; level++; } } return ret; } // String var pos = POS.test( selectors ) || typeof selectors !== "string" ? jQuery( selectors, context || this.context ) : 0; for ( i = 0, l = this.length; i < l; i++ ) { cur = this[i]; while ( cur ) { if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { ret.push( cur ); break; } else { cur = cur.parentNode; if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { break; } } } } ret = ret.length > 1 ? jQuery.unique( ret ) : ret; return this.pushStack( ret, "closest", selectors ); }, // Determine the position of an element within // the matched set of elements index: function( elem ) { if ( !elem || typeof elem === "string" ) { return jQuery.inArray( this[0], // If it receives a string, the selector is used // If it receives nothing, the siblings are used elem ? jQuery( elem ) : this.parent().children() ); } // Locate the position of the desired element return jQuery.inArray( // If it receives a jQuery object, the first element is used elem.jquery ? elem[0] : elem, this ); }, add: function( selector, context ) { var set = typeof selector === "string" ? jQuery( selector, context ) : jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), all = jQuery.merge( this.get(), set ); return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? all : jQuery.unique( all ) ); }, andSelf: function() { return this.add( this.prevObject ); } }); // A painfully simple check to see if an element is disconnected // from a document (should be improved, where feasible). function isDisconnected( node ) { return !node || !node.parentNode || node.parentNode.nodeType === 11; } jQuery.each({ parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { return jQuery.dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { return jQuery.dir( elem, "parentNode", until ); }, next: function( elem ) { return jQuery.nth( elem, 2, "nextSibling" ); }, prev: function( elem ) { return jQuery.nth( elem, 2, "previousSibling" ); }, nextAll: function( elem ) { return jQuery.dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return jQuery.dir( elem, "previousSibling" ); }, nextUntil: function( elem, i, until ) { return jQuery.dir( elem, "nextSibling", until ); }, prevUntil: function( elem, i, until ) { return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return jQuery.sibling( elem.parentNode.firstChild, elem ); }, children: function( elem ) { return jQuery.sibling( elem.firstChild ); }, contents: function( elem ) { return jQuery.nodeName( elem, "iframe" ) ? elem.contentDocument || elem.contentWindow.document : jQuery.makeArray( elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { var ret = jQuery.map( this, fn, until ), // The variable 'args' was introduced in // https://github.com/jquery/jquery/commit/52a0238 // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. // http://code.google.com/p/v8/issues/detail?id=1050 args = slice.call(arguments); if ( !runtil.test( name ) ) { selector = until; } if ( selector && typeof selector === "string" ) { ret = jQuery.filter( selector, ret ); } ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { ret = ret.reverse(); } return this.pushStack( ret, name, args.join(",") ); }; }); jQuery.extend({ filter: function( expr, elems, not ) { if ( not ) { expr = ":not(" + expr + ")"; } return elems.length === 1 ? jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : jQuery.find.matches(expr, elems); }, dir: function( elem, dir, until ) { var matched = [], cur = elem[ dir ]; while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { if ( cur.nodeType === 1 ) { matched.push( cur ); } cur = cur[dir]; } return matched; }, nth: function( cur, result, dir, elem ) { result = result || 1; var num = 0; for ( ; cur; cur = cur[dir] ) { if ( cur.nodeType === 1 && ++num === result ) { break; } } return cur; }, sibling: function( n, elem ) { var r = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { r.push( n ); } } return r; } }); // Implement the identical functionality for filter and not function winnow( elements, qualifier, keep ) { // Can't pass null or undefined to indexOf in Firefox 4 // Set to 0 to skip string check qualifier = qualifier || 0; if ( jQuery.isFunction( qualifier ) ) { return jQuery.grep(elements, function( elem, i ) { var retVal = !!qualifier.call( elem, i, elem ); return retVal === keep; }); } else if ( qualifier.nodeType ) { return jQuery.grep(elements, function( elem, i ) { return (elem === qualifier) === keep; }); } else if ( typeof qualifier === "string" ) { var filtered = jQuery.grep(elements, function( elem ) { return elem.nodeType === 1; }); if ( isSimple.test( qualifier ) ) { return jQuery.filter(qualifier, filtered, !keep); } else { qualifier = jQuery.filter( qualifier, filtered ); } } return jQuery.grep(elements, function( elem, i ) { return (jQuery.inArray( elem, qualifier ) >= 0) === keep; }); } var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, rleadingWhitespace = /^\s+/, rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, rtagName = /<([\w:]+)/, rtbody = /", "" ], legend: [ 1, "
", "
" ], thead: [ 1, "", "
" ], tr: [ 2, "", "
" ], td: [ 3, "", "
" ], col: [ 2, "", "
" ], area: [ 1, "", "" ], _default: [ 0, "", "" ] }; wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; // IE can't serialize and

Index

_ | B | C | D | E | F | G | H | I | K | L | M | N | P | R | S | T | U | V | W | X

_

__delitem__() (pyexiv2.metadata.ImageMetadata method)
__eq__() (pyexiv2.utils.GPSCoordinate method)
(pyexiv2.utils.Rational method)
__exiv2_version__ (in module pyexiv2)
__getitem__() (pyexiv2.metadata.ImageMetadata method)
__repr__() (pyexiv2.utils.Rational method)
__setitem__() (pyexiv2.metadata.ImageMetadata method)
__str__() (pyexiv2.utils.GPSCoordinate method)
(pyexiv2.utils.Rational method)
__version__ (in module pyexiv2)

B

buffer (pyexiv2.metadata.ImageMetadata attribute)

C

comment (pyexiv2.metadata.ImageMetadata attribute)
copy() (pyexiv2.metadata.ImageMetadata method)

D

data (pyexiv2.exif.ExifThumbnail attribute)
(pyexiv2.preview.Preview attribute)
degrees (pyexiv2.utils.GPSCoordinate attribute)
denominator (pyexiv2.utils.Rational attribute)
description (pyexiv2.exif.ExifTag attribute)
(pyexiv2.iptc.IptcTag attribute)
(pyexiv2.xmp.XmpTag attribute)
dimensions (pyexiv2.metadata.ImageMetadata attribute)
(pyexiv2.preview.Preview attribute)
direction (pyexiv2.utils.GPSCoordinate attribute)

E

erase() (pyexiv2.exif.ExifThumbnail method)
exif_keys (pyexiv2.metadata.ImageMetadata attribute)
ExifTag (class in pyexiv2.exif)
ExifThumbnail (class in pyexiv2.exif)
ExifValueError
exiv2_version_info (in module pyexiv2)
extension (pyexiv2.exif.ExifThumbnail attribute)
(pyexiv2.preview.Preview attribute)

F

from_buffer() (pyexiv2.metadata.ImageMetadata class method)
from_string() (pyexiv2.utils.GPSCoordinate static method)
(pyexiv2.utils.Rational static method)

G

GPSCoordinate (class in pyexiv2.utils)

H

human_value (pyexiv2.exif.ExifTag attribute)

I

ImageMetadata (class in pyexiv2.metadata)
iptc_charset (pyexiv2.metadata.ImageMetadata attribute)
iptc_keys (pyexiv2.metadata.ImageMetadata attribute)
IptcTag (class in pyexiv2.iptc)
IptcValueError

K

key (pyexiv2.exif.ExifTag attribute)
(pyexiv2.iptc.IptcTag attribute)
(pyexiv2.xmp.XmpTag attribute)

L

label (pyexiv2.exif.ExifTag attribute)

M

make_fraction() (in module pyexiv2.utils)
mime_type (pyexiv2.exif.ExifThumbnail attribute)
(pyexiv2.metadata.ImageMetadata attribute)
(pyexiv2.preview.Preview attribute)
minutes (pyexiv2.utils.GPSCoordinate attribute)

N

name (pyexiv2.exif.ExifTag attribute)
(pyexiv2.iptc.IptcTag attribute)
(pyexiv2.xmp.XmpTag attribute)
numerator (pyexiv2.utils.Rational attribute)

P

photoshop_name (pyexiv2.iptc.IptcTag attribute)
Preview (class in pyexiv2.preview)
previews (pyexiv2.metadata.ImageMetadata attribute)
pyexiv2 (module)
pyexiv2.exif (module)
pyexiv2.iptc (module)
pyexiv2.metadata (module)
pyexiv2.preview (module)
pyexiv2.utils (module)
pyexiv2.xmp (module)

R

Rational (class in pyexiv2.utils)
raw_value (pyexiv2.exif.ExifTag attribute)
(pyexiv2.iptc.IptcTag attribute)
(pyexiv2.xmp.XmpTag attribute)
read() (pyexiv2.metadata.ImageMetadata method)
record_description (pyexiv2.iptc.IptcTag attribute)
record_name (pyexiv2.iptc.IptcTag attribute)
register_namespace() (in module pyexiv2.xmp)
repeatable (pyexiv2.iptc.IptcTag attribute)

S

seconds (pyexiv2.utils.GPSCoordinate attribute)
section_description (pyexiv2.exif.ExifTag attribute)
section_name (pyexiv2.exif.ExifTag attribute)
set_from_file() (pyexiv2.exif.ExifThumbnail method)
size (pyexiv2.preview.Preview attribute)
string_to_undefined() (in module pyexiv2.utils)

T

title (pyexiv2.iptc.IptcTag attribute)
(pyexiv2.xmp.XmpTag attribute)
to_float() (pyexiv2.utils.Rational method)
type (pyexiv2.exif.ExifTag attribute)
(pyexiv2.iptc.IptcTag attribute)
(pyexiv2.xmp.XmpTag attribute)

U

undefined_to_string() (in module pyexiv2.utils)
unregister_namespace() (in module pyexiv2.xmp)
unregister_namespaces() (in module pyexiv2.xmp)

V

value (pyexiv2.exif.ExifTag attribute)
(pyexiv2.iptc.IptcTag attribute)
(pyexiv2.xmp.XmpTag attribute)
version_info (in module pyexiv2)

W

write() (pyexiv2.metadata.ImageMetadata method)
write_to_file() (pyexiv2.exif.ExifThumbnail method)
(pyexiv2.preview.Preview method)

X

xmp_keys (pyexiv2.metadata.ImageMetadata attribute)
XmpTag (class in pyexiv2.xmp)
XmpValueError
pyexiv2-0.3.2/doc/html/release_procedure.html0000664000175000017500000002377411651316054020640 0ustar osomonosomon pyexiv2 release procedure — pyexiv2 v0.3.2 documentation

pyexiv2 release procedure

Release branch

Set the version number to release:

export VERSION=0.3.1
export D=pyexiv2-$VERSION

Branch to release:

bzr branch lp:pyexiv2 $D
cd $D

Bump the version number: change the value of version_info in src/pyexiv2/__init__.py.

Bump the version number in the Windows installer script: change the value of PYEXIV2_VERSION in win32-installer.nsi.

Commit the changes:

bzr commit -m "Bumped version number to $VERSION."

Update the NEWS file with the changes since the last release:

  • dependencies
  • changes
  • bugs fixed
  • contributors

Commit and push the changes:

bzr commit -m "Updated NEWS."
bzr push lp:~osomon/pyexiv2/$D

Source tarball

Build pyexiv2:

scons

Build the HTML documentation:

scons doc
mv doc/_build doc/html

Create the tarball and sign it:

cd ..
tar cvvjf $D.tar.bz2 $D --exclude-vcs \
    --exclude=build --exclude=.doctrees --exclude=.buildinfo \
    --exclude=objects.inv --exclude=.sconsign.dblite --exclude=*.pyc
gpg --armor --sign --detach-sig $D.tar.bz2
cd $D

Windows installer

Cross-compile:

./cross-compile.sh

Build the installer and sign it:

makensis win32-installer.nsi
gpg --armor --sign --detach-sig pyexiv2-$VERSION-setup.exe

Publication

Communication

Web site

The branch for the website is at lp:~osomon/pyexiv2/website.

  • Update the download page with the new release
  • Update the online documentation

Final

Merge back the release branch in the master branch and tag it:

cd <local/path/to/master/>
bzr merge lp:~osomon/pyexiv2/$D
bzr commit -m "Merge the $VERSION release."
bzr tag release-$VERSION
bzr push

Table Of Contents

This Page

pyexiv2-0.3.2/doc/html/searchindex.js0000664000175000017500000002347711651316054017115 0ustar osomonosomonSearch.setIndex({objects:{"":{pyexiv2:[1,0,1]},"pyexiv2.exif.ExifTag":{raw_value:[1,2,1],name:[1,2,1],description:[1,2,1],value:[1,2,1],label:[1,2,1],human_value:[1,2,1],section_name:[1,2,1],key:[1,2,1],type:[1,2,1],section_description:[1,2,1]},"pyexiv2.preview":{Preview:[1,5,1]},"pyexiv2.exif.ExifThumbnail":{extension:[1,2,1],set_from_file:[1,1,1],write_to_file:[1,1,1],erase:[1,1,1],data:[1,2,1],mime_type:[1,2,1]},pyexiv2:{"__version__":[1,6,1],exif:[1,0,1],version_info:[1,6,1],utils:[1,0,1],"__exiv2_version__":[1,6,1],iptc:[1,0,1],exiv2_version_info:[1,6,1],preview:[1,0,1],xmp:[1,0,1],metadata:[1,0,1]},"pyexiv2.xmp":{unregister_namespaces:[1,3,1],register_namespace:[1,3,1],XmpValueError:[1,4,1],unregister_namespace:[1,3,1],XmpTag:[1,5,1]},"pyexiv2.utils":{string_to_undefined:[1,3,1],undefined_to_string:[1,3,1],Rational:[1,5,1],make_fraction:[1,3,1],GPSCoordinate:[1,5,1]},"pyexiv2.metadata.ImageMetadata":{comment:[1,2,1],"__delitem__":[1,1,1],iptc_keys:[1,2,1],dimensions:[1,2,1],xmp_keys:[1,2,1],buffer:[1,2,1],iptc_charset:[1,2,1],from_buffer:[1,7,1],"__getitem__":[1,1,1],previews:[1,2,1],write:[1,1,1],read:[1,1,1],"__setitem__":[1,1,1],exif_keys:[1,2,1],copy:[1,1,1],mime_type:[1,2,1]},"pyexiv2.iptc":{IptcTag:[1,5,1],IptcValueError:[1,4,1]},"pyexiv2.utils.Rational":{to_float:[1,1,1],"__str__":[1,1,1],denominator:[1,2,1],numerator:[1,2,1],"__repr__":[1,1,1],from_string:[1,8,1],"__eq__":[1,1,1]},"pyexiv2.iptc.IptcTag":{raw_value:[1,2,1],record_description:[1,2,1],record_name:[1,2,1],name:[1,2,1],title:[1,2,1],type:[1,2,1],value:[1,2,1],repeatable:[1,2,1],key:[1,2,1],photoshop_name:[1,2,1],description:[1,2,1]},"pyexiv2.xmp.XmpTag":{raw_value:[1,2,1],name:[1,2,1],title:[1,2,1],value:[1,2,1],key:[1,2,1],type:[1,2,1],description:[1,2,1]},"pyexiv2.preview.Preview":{dimensions:[1,2,1],extension:[1,2,1],write_to_file:[1,1,1],data:[1,2,1],mime_type:[1,2,1],size:[1,2,1]},"pyexiv2.exif":{ExifValueError:[1,4,1],ExifTag:[1,5,1],ExifThumbnail:[1,5,1]},"pyexiv2.metadata":{ImageMetadata:[1,5,1]},"pyexiv2.utils.GPSCoordinate":{direction:[1,2,1],seconds:[1,2,1],minutes:[1,2,1],"__str__":[1,1,1],degrees:[1,2,1],from_string:[1,8,1],"__eq__":[1,1,1]}},terms:{represent:1,all:[1,2,3,4],code:[0,1,3],forget:3,string_to_undefin:1,skip:3,prefix:1,follow:[3,4],alt:1,disk:[1,4],content:[0,4],typeerror:1,depend:[0,1,2,3,4],appdata:3,flash:4,readabl:1,send:2,present:[3,4],under:[0,3],provincest:4,resolutionunit:4,sourc:[1,2,3,4],dblite:2,string:[1,4],exifthumbnail:1,fals:[1,4],mime_typ:[1,4],util:[0,1],slong:1,tagnam:1,foo:4,imagemetadata:[1,4],administr:3,level:[0,3],iptc_charset:[1,4],list:[1,2,3,4],upload:2,"try":1,team:3,quick:4,ddd:1,dimens:[1,4],"44z":4,characterset:4,record_nam:1,direct:1,exif_kei:[1,4],sign:2,second:1,design:0,pass:1,download:2,append:1,even:4,index:[0,3],what:4,lazili:4,compar:1,preserv:1,section:1,testsrunn:3,access:[0,1,4],delet:[1,4],version:[0,1,2,3],"new":[2,4],net:2,boost:3,"public":2,metadata:[0,1,4],full:3,bylin:4,gener:3,here:[1,3],modif:1,trunk:3,path:[1,2],modifi:[1,3,4],sinc:2,valu:[1,2,3,4],convert:[1,4],copyright:4,forum:2,datetim:[1,4],from_buff:1,implement:[1,4],regardless:4,mmk:1,modul:[0,1,4],filenam:1,"boolean":1,famili:1,instal:[0,3,2,4],unit:[0,3],from:[1,3,4],zip:3,commun:2,regist:[1,4],two:[1,4],todai:4,websit:2,makensi:[3,2],call:[1,3,4],usr:3,python27:3,preview:[0,1,4],type:[0,1,4],until:1,minor:1,more:4,sort:[1,4],xmptag:[1,4],accept:1,particular:1,known:1,hold:4,must:1,application2:4,none:[1,4],retriev:[3,4],xmp:[0,1,4],setup:[3,2],work:[1,3,4],focu:3,dev:3,tag_nam:3,del:4,can:[1,3,4],p7zip:3,fedora:2,overrid:1,overwritten:[1,4],tar:2,give:[3,4],process:3,share:3,indic:[0,1],sshort:1,high:0,tag:[0,1,2,3,4],want:3,tarbal:2,onlin:2,alwai:[1,4],osomon:[3,2],end:1,rather:4,anoth:1,charset:4,georg:4,write:[0,1,2,3,4],how:4,anyon:3,sever:[1,3],instead:1,simpl:[1,3,4],updat:[0,2],overridden:1,befor:1,beauti:4,mai:[1,4],data:[0,1,4],welcom:0,"short":1,attempt:1,third:1,classmethod:1,bind:0,favorit:3,correspond:[1,3],exclud:2,issu:3,inform:[1,3],"switch":3,allow:[0,1,4],photoshop:1,yresolut:4,feedback:3,nsi:[3,2],offici:1,becaus:4,jpeg:[0,1,4],increas:[1,4],privileg:3,exif_thumbnail:4,iptc:[0,1,4],paramet:1,thumb:4,scon:[3,2],fix:2,ifd1:1,window:[3,2],html:[3,2],"__exiv2_version__":1,mail:[3,2],main:3,pixel:1,exiv2wrapp:3,"return":1,thei:4,python:[0,1,2,3,4],timestamp:1,handi:[1,4],propernam:1,detach:2,discuss:3,oper:4,from_str:1,"__delitem__":1,document:[0,1,2,3,4],name:[1,3],xmp_kei:[1,4],artist:4,separ:1,micro:1,alreadi:[1,4],each:[3,4],fulli:1,unicod:1,purl:1,mean:3,compil:[3,2],bump:2,procedur:2,redistribut:3,"static":1,expect:1,undefined_to_str:1,happen:4,patch:3,space:1,armor:2,open:3,exiv2wrapper_python:3,categori:4,manipul:[0,1,4],envelop:4,differ:4,free:3,standard:[0,1],datecr:4,"__setitem__":1,mime:1,dictionari:4,releas:[3,2],org:[1,4],"byte":[1,4],"13t21":4,preserve_timestamp:1,mingw32:3,created:4,omit:1,caption:4,mimetyp:1,licens:0,south:1,first:4,origin:1,softwar:[3,4],major:1,directli:1,onc:1,number:[1,2],sometim:3,photoshop_nam:1,date:[1,4],mingw:3,done:3,"long":1,section_descript:1,size:[1,4],gpl:0,set_from_fil:[1,4],given:[1,4],script:[0,3,2],unknown:1,top:3,gpg:2,system:3,doctre:2,citi:4,master:2,similarli:4,termin:3,white:1,john:4,"final":2,store:4,shell:3,option:[1,2,3,4],namespac:[1,4],juli:4,copi:[1,3,4],specifi:3,part:1,pars:1,sconsign:2,sbyte:1,than:4,wide:3,conveni:[1,4],target:3,keyword:4,provid:[0,1,4],remov:1,"_tag":1,charact:[1,4],project:3,jpeginterchangeformat:1,minut:1,latitud:1,"function":[0,1],comput:4,beforehand:1,argument:1,raw:[1,4],manner:4,have:[1,3,4],tabl:[0,1],need:[3,4],requisit:3,element:1,buildinfo:2,built:0,lib:3,self:1,note:[3,4],also:[1,3,4],builtin:1,denomin:1,take:1,which:[1,3,4],tupl:1,singl:[1,4],unless:1,distribut:[0,3,2],exiv2:[0,2,4],buffer:[1,4],object:[1,2,4],compress:1,xmpvalueerror:[1,4],plai:4,pair:4,pyc:2,segment:1,"class":[1,4],"_build":[3,2],pyexiv2:[0,1,2,3,4],bzr:[3,2],url:1,doc:[3,2],cover:4,uri:1,doe:1,tag_or_valu:1,width:1,dot:1,raw_valu:[1,4],text:[1,4],"__str__":1,font:1,find:3,current:[3,2,4],onli:[1,4],explicitli:[1,4],launchpad:[3,2],pretti:4,writer:4,enough:1,should:3,dict:1,photo:4,folder:3,local:[1,2],libboost:3,meant:4,contribut:[0,3],variou:4,get:[0,1,3,4],express:1,libexiv2:[0,1,3],ssk:1,report:3,sration:1,requir:3,bar:4,emb:4,baz:4,method:1,bag:1,imagedescript:4,contain:[1,3],through:4,where:1,conform:1,set:[1,2,4],seq:1,creator:4,see:4,mandatori:1,result:3,arg:1,fail:1,subject:4,mileston:2,statu:2,correctli:4,someth:1,dll:3,ringo:4,written:4,won:1,groupnam:1,"import":4,"19t13":4,thumbnail:[0,1,4],attribut:[1,4],altern:3,agentnam:1,accord:[1,4],kei:[1,4],numer:1,exampl:4,extens:1,pyexiv2_vers:2,len:4,otherwis:1,come:3,last:[0,2],equal:1,against:3,gpscoordin:1,modifyd:4,tutori:[0,4],let:4,strftime:4,comment:[1,3,4],pre:3,point:1,instanti:1,overview:4,ubuntu:[3,2],height:1,header:3,linux:3,batteri:3,tuesdai:4,assign:4,"_metadata":1,addition:3,west:1,three:1,been:[1,4],mark:2,compon:1,much:4,interest:3,subscrib:3,valueerror:1,hesit:3,octob:0,imag:[0,1,3,4],search:0,addreleas:2,ani:1,iptc_kei:[1,4],coordin:1,"__eq__":1,those:4,"case":4,cvvjf:2,east:1,packag:[3,2],properti:[1,4],defin:4,invok:3,error:1,write_to_fil:[1,4],readi:3,them:[3,4],jpg:[1,4],destin:1,eras:[1,4],revis:3,ascii:[1,4],"__init__":2,develop:[0,3,2],"__getitem__":1,minim:3,make:1,cross:[3,2],same:4,proce:3,instanc:1,decod:1,largest:4,"__version__":1,infer:4,complet:4,http:[1,2,4],version_info:[1,2],effect:4,moment:4,rais:[1,4],user:3,improv:3,typic:[1,3],bz2:2,pixelydimens:4,unregister_namespac:[1,4],without:1,contact:4,command:3,thi:[0,1,3,4],undefin:1,latest:3,countrynam:4,load:4,identifi:[1,3],just:[3,4],familynam:1,obtain:3,human:1,paul:4,yet:[1,4],languag:1,section_nam:1,web:2,now:4,exiftag:[1,4],easi:[0,4],restructuredtext:3,except:1,shortcut:[1,4],blog:2,color:1,add:4,valid:[1,4],save:4,approxim:1,match:1,build:[0,3,2],real:1,jpeginterchangeformatlength:1,format:[1,4],read:[0,1,3,4],iptcvalueerror:[1,4],know:[3,4],associ:[1,3],python2:3,like:[3,4],specif:[1,3,4],to_float:1,integ:1,granuja:3,api:[0,1,4],necessari:1,either:[1,4],page:[0,2],right:4,interact:4,some:[3,4],back:[1,2,3,4],destdir:3,"export":2,librari:[0,1,3],tmp:4,win32:[3,2],exifvalueerror:[1,4],exif:[0,1,4],easili:4,fpic:3,track:3,exiv2_version_info:1,record_descript:1,reproduc:3,refer:[3,4],encourag:3,run:3,ration:1,broken:4,host:3,lboost_python:3,found:3,bazaar:3,xresolut:4,between:1,src:[3,2],about:2,actual:3,most:3,sig:2,label:[1,4],degre:1,commit:2,other:[1,4],restrict:1,subset:4,"__repr__":1,tiff:[0,1,4],objectnam:4,"float":1,encod:[1,4],automat:[1,4],unregist:[1,4],contributor:2,chang:2,announc:2,your:[0,3],merg:2,iptctag:[1,4],wai:[3,4],pictur:4,pyd:3,execut:3,support:4,lexiv2:3,custom:[1,4],avail:[1,3,4],start:4,interfac:0,includ:3,fraction:1,suit:3,xpath:1,reduc:1,north:1,form:1,offer:[0,4],make_fract:1,basic:4,keyerror:1,don:3,line:3,"true":1,bug:[3,2],succe:1,utf:4,possibl:[1,4],whether:[1,4],record:1,limit:4,usercom:4,site:[3,2],sampl:3,embed:[0,1,4],featur:3,creat:[2,4],"int":1,request:3,inv:2,doesn:[1,4],repres:[1,4],register_namespac:[1,4],exist:1,file:[0,1,2,3,4],adddownloadfil:2,check:4,titl:[1,4],when:[1,3],invalid:[1,4],prepend:3,libexiv2python:3,branch:[3,2],test:[0,3,4],you:[3,4],repeat:[1,4],sequenc:1,sconscript:3,cpp:3,queri:4,previous:1,debian:[3,2],longitud:1,lang:1,sphinx:3,lead:1,directori:3,reliabl:3,descript:[1,4],time:1,push:2,pixelxdimens:4,human_valu:1},objtypes:{"0":"py:module","1":"py:method","2":"py:attribute","3":"py:function","4":"py:exception","5":"py:class","6":"py:data","7":"py:classmethod","8":"py:staticmethod"},titles:["pyexiv2 documentation","API documentation","pyexiv2 release procedure","Developers","Tutorial"],objnames:{"0":"Python module","1":"Python method","2":"Python attribute","3":"Python function","4":"Python exception","5":"Python class","6":"Python data","7":"Python class method","8":"Python static method"},filenames:["index","api","release_procedure","developers","tutorial"]})pyexiv2-0.3.2/doc/html/developers.html0000664000175000017500000003334711651316054017315 0ustar osomonosomon Developers — pyexiv2 v0.3.2 documentation

Developers

If you are a developer and use pyexiv2 in your project, you will find here useful information.

Getting the code

pyexiv2’s source code is versioned with bazaar, and all the branches, including the main development focus (sometimes referred to as trunk), are hosted on Launchpad.

To get a working copy of the latest revision of the development branch, just issue the following command in a terminal:

bzr branch lp:pyexiv2

If you need to get a specific revision identified by a tag (all releases of pyexiv2 are tagged), use the following command:

bzr branch -r tag:tag_name lp:pyexiv2

A list of all the available tags can be obtained using the bzr tags command:

osomon@granuja:~/dev/pyexiv2$ bzr tags
release-0.1          60
release-0.1.1        73
release-0.1.2        99
release-0.1.3        99.1.6
release-0.2.0        296
release-0.2.1        306
release-0.2.2        318
release-0.3.0        349

Dependencies

You will need the following dependencies installed on your system to build and use pyexiv2:

For Python, boost.python and libexiv2, the development files are needed (-dev packages). A typical list of packages to install on a Debian/Ubuntu system is:

python-all-dev libboost-python-dev libexiv2-dev scons

Some unit tests have a dependency on python-tz. This dependency is optional: the corresponding tests will be skipped if it is not present on the system.

Additionally, if you want to cross-compile pyexiv2 for Windows and generate a Windows installer, you will need the following dependencies:

A typical list of packages to install on a Debian/Ubuntu system is:

mingw32 p7zip-full nsis

Building and installing

Linux

Building pyexiv2 is as simple as invoking scons in the top-level directory:

osomon@granuja:~/dev/pyexiv2$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o build/exiv2wrapper.os -c -fPIC -I/usr/include/python2.7 src/exiv2wrapper.cpp
g++ -o build/exiv2wrapper_python.os -c -fPIC -I/usr/include/python2.7 src/exiv2wrapper_python.cpp
g++ -o build/libexiv2python.so -shared build/exiv2wrapper.os build/exiv2wrapper_python.os -lboost_python -lexiv2
scons: done building targets.

The result of the build process is a shared library, libexiv2python.so, in the build directory:

osomon@granuja:~/dev/pyexiv2$ ls build/
exiv2wrapper.os  exiv2wrapper_python.os  libexiv2python.so

To install pyexiv2 system-wide, just invoke scons install. You will most likely need administrative privileges to proceed. The --user switch will install pyexiv2 in the current user site directory (Python ≥ 2.6 is required).

Note to packagers: if DESTDIR is specified on the command line when invoking scons install, its value will be prepended to each installed target file.

Windows

The top-level directory of the development branch contains a shell script named cross-compile.sh that retrieves all the dependencies required and cross-compiles pyexiv2 for Windows on a Linux host. Read the comments in the header of the script to know the pre-requisites.

The result of the compilation is a DLL, libexiv2python.pyd, in the build directory. This file and the pyexiv2 folder in src should be copied to the system-wide site directory of a Python 2.7 setup (typically C:\Python27\Lib\site-packages\) or to the user site directory (%APPDATA%\Python\Python27\site-packages\).

The top-level directory of the branch also contains an NSIS installer script named win32-installer.nsi. From the top-level directory of the branch, run the following command:

makensis win32-installer.nsi

This will generate a ready-to-distribute installer executable named pyexiv2-0.3-setup.exe.

Documentation

The present documentation is generated using Sphinx from reStructuredText sources found in the doc/ directory. Invoke scons doc to (re)build the HTML documentation. Alternatively, you can issue the following command:

sphinx-build -b html doc/ doc/_build/

The index of the documentation will then be found under doc/_build/index.html.

Unit tests

pyexiv2’s source comes with a battery of unit tests, in the test/ directory. To run them, invoke scons test. Alternatively, you can execute the TestsRunner.py script.

Contributing

pyexiv2 is Free Software, meaning that you are encouraged to use it, modify it to suit your needs, contribute back improvements, and redistribute it.

Bugs are tracked on Launchpad. There is a team called pyexiv2-developers open to anyone interested in following development on pyexiv2. Don’t hesitate to subscribe to the team (you don’t need to actually contribute!) and to the associated mailing list.

There are several ways in which you can contribute to improve pyexiv2:

  • Use it;
  • Give your feedback and discuss issues and feature requests on the mailing list;
  • Report bugs, write patches;
  • Package it for your favorite distribution/OS.

When reporting a bug, don’t forget to include the following information in the report:

  • version of pyexiv2
  • version of libexiv2 it was compiled against
  • a minimal script that reliably reproduces the issue
  • a sample image file with which the bug can reliably be reproduced

Table Of Contents

Previous topic

API documentation

This Page

pyexiv2-0.3.2/doc/html/index.html0000664000175000017500000001741211651316054016247 0ustar osomonosomon pyexiv2 documentation — pyexiv2 v0.3.2 documentation

pyexiv2 documentation

Welcome! This is the documentation for pyexiv2 0.3.2, last updated October 24, 2011.

pyexiv2 is a python binding to exiv2, the C++ library for manipulation of EXIF, IPTC and XMP image metadata. It is a python module that allows your python scripts to read and write metadata (EXIF, IPTC, XMP, thumbnails) embedded in image files (JPEG, TIFF, ...).

It is designed as a high-level interface to the functionalities offered by libexiv2. Using python’s built-in data types and standard modules, it provides easy manipulation of image metadata.

pyexiv2 is distributed under the GPL version 2 license.

Contents:

Indices and tables

Table Of Contents

Next topic

Tutorial

This Page

pyexiv2-0.3.2/doc/html/search.html0000664000175000017500000000676411651316054016415 0ustar osomonosomon Search — pyexiv2 v0.3.2 documentation

Search

Please activate JavaScript to enable the search functionality.

From here you can search these documents. Enter your search words into the box below and click "search". Note that the search function will automatically search for all of the words. Pages containing fewer words won't appear in the result list.

pyexiv2-0.3.2/doc/html/tutorial.html0000664000175000017500000007124611651316054017010 0ustar osomonosomon Tutorial — pyexiv2 v0.3.2 documentation

Tutorial

This tutorial is meant to give you a quick overview of what pyexiv2 allows you to do. You can just read it through or follow it interactively, in which case you will need to have pyexiv2 installed. It doesn’t cover all the possibilities offered by pyexiv2, only a basic subset of them. For complete reference, see the API documentation.

Let’s get started!

First of all, we import the pyexiv2 module:

>>> import pyexiv2

We then load an image file and read its metadata:

>>> metadata = pyexiv2.ImageMetadata('test.jpg')
>>> metadata.read()

Reading and writing EXIF tags

Let’s retrieve a list of all the available EXIF tags available in the image:

>>> metadata.exif_keys
['Exif.Image.ImageDescription',
 'Exif.Image.XResolution',
 'Exif.Image.YResolution',
 'Exif.Image.ResolutionUnit',
 'Exif.Image.Software',
 'Exif.Image.DateTime',
 'Exif.Image.Artist',
 'Exif.Image.Copyright',
 'Exif.Image.ExifTag',
 'Exif.Photo.Flash',
 'Exif.Photo.PixelXDimension',
 'Exif.Photo.PixelYDimension']

Each of those tags can be accessed with the [] operator on the metadata, much like a python dictionary:

>>> tag = metadata['Exif.Image.DateTime']

The value of an ExifTag object can be accessed in two different ways: with the raw_value and with the value attributes:

>>> tag.raw_value
'2004-07-13T21:23:44Z'

>>> tag.value
datetime.datetime(2004, 7, 13, 21, 23, 44)

The raw value is always a byte string, this is how the value is stored in the file. The value is lazily computed from the raw value depending on the EXIF type of the tag, and is represented as a convenient python object to allow easy manipulation.

Note that querying the value of a tag may raise an ExifValueError if the format of the raw value is invalid according to the EXIF specification (may happen if it was written by other software that implements the specification in a broken manner), or if pyexiv2 doesn’t know how to convert it to a convenient python object.

Accessing the value of a tag as a python object allows easy manipulation and formatting:

>>> tag.value.strftime('%A %d %B %Y, %H:%M:%S')
'Tuesday 13 July 2004, 21:23:44'

Now let’s modify the value of the tag and write it back to the file:

>>> import datetime
>>> tag.value = datetime.datetime.today()

>>> metadata.write()

Similarly to reading the value of a tag, one can set either the raw_value or the value (which will be automatically converted to a correctly formatted byte string by pyexiv2).

You can also add new tags to the metadata by providing a valid key and value pair (see exiv2’s documentation for a list of valid EXIF tags):

>>> key = 'Exif.Photo.UserComment'
>>> value = 'This is a useful comment.'
>>> metadata[key] = pyexiv2.ExifTag(key, value)

As a handy shortcut, you can always assign a value for a given key regardless of whether it’s already present in the metadata. If a tag was present, its value is overwritten. If the tag was not present, one is created and its value is set:

>>> metadata[key] = value

The EXIF data may optionally embed a thumbnail in the JPEG or TIFF format. The thumbnail can be accessed, set from a JPEG file or buffer, saved to disk and erased:

>>> thumb = metadata.exif_thumbnail
>>> thumb.set_from_file('/tmp/thumbnail.jpg')
>>> thumb.write_to_file('/tmp/copy')
>>> thumb.erase()
>>> metadata.write()

Reading and writing IPTC tags

Reading and writing IPTC tags works pretty much the same way as with EXIF tags. Let’s retrieve the list of all available IPTC tags in the image:

>>> metadata.iptc_keys
['Iptc.Application2.Caption',
 'Iptc.Application2.Writer',
 'Iptc.Application2.Byline',
 'Iptc.Application2.ObjectName',
 'Iptc.Application2.DateCreated',
 'Iptc.Application2.City',
 'Iptc.Application2.ProvinceState',
 'Iptc.Application2.CountryName',
 'Iptc.Application2.Category',
 'Iptc.Application2.Keywords',
 'Iptc.Application2.Copyright']

Each of those tags can be accessed with the [] operator on the metadata:

>>> tag = metadata['Iptc.Application2.DateCreated']

An IPTC tag always has a list of values rather than a single value. This is because some tags have a repeatable character. Tags that are not repeatable only hold one value in their list of values.

Check the repeatable attribute to know whether a tag can hold more than one value:

>>> tag.repeatable
False

As with EXIF tags, the values of an IptcTag object can be accessed in two different ways: with the raw_value and with the value attributes:

>>> tag.raw_value
['2004-07-13']

>>> tag.value
[datetime.date(2004, 7, 13)]

Note that querying the values of a tag may raise an IptcValueError if the format of the raw values is invalid according to the IPTC specification (may happen if it was written by other software that implements the specification in a broken manner), or if pyexiv2 doesn’t know how to convert it to a convenient python object.

Now let’s modify the values of the tag and write it back to the file:

>>> tag.value = [datetime.date.today()]

>>> metadata.write()

Similarly to reading the values of a tag, one can set either the raw_value or the value (which will be automatically converted to correctly formatted byte strings by pyexiv2).

You can also add new tags to the metadata by providing a valid key and values pair (see exiv2’s documentation for a list of valid IPTC tags):

>>> key = 'Iptc.Application2.Contact'
>>> values = ['John', 'Paul', 'Ringo', 'George']
>>> metadata[key] = pyexiv2.IptcTag(key, values)

As a handy shortcut, you can always assign values for a given key regardless of whether it’s already present in the metadata. If a tag was present, its values are overwritten. If the tag was not present, one is created and its values are set:

>>> metadata[key] = values

The IPTC metadata in an image may embed an optional character set for its encoding. This is defined by the Iptc.Envelope.CharacterSet tag. The ImageMetadata class has an iptc_charset property that allows to easily get, set and delete this value:

>>> metadata.iptc_charset
'ascii'

>>> metadata.iptc_charset = 'utf-8'

>>> del metadata.iptc_charset

Note that at the moment, the only supported charset that can be assigned to the property is utf-8. Also note that even if the charset is not explicitly set, its value may be inferred from the contents of the image. If not, it will be None.

Reading and writing XMP tags

Reading and writing XMP tags works pretty much the same way as with EXIF tags. Let’s retrieve the list of all available XMP tags in the image:

>>> metadata.xmp_keys
['Xmp.dc.creator',
 'Xmp.dc.description',
 'Xmp.dc.rights',
 'Xmp.dc.source',
 'Xmp.dc.subject',
 'Xmp.dc.title',
 'Xmp.xmp.CreateDate',
 'Xmp.xmp.ModifyDate']

Each of those tags can be accessed with the [] operator on the metadata:

>>> tag = metadata['Xmp.xmp.ModifyDate']

As with EXIF tags, the value of an XmpTag object can be accessed in two different ways: with the raw_value and with the value attributes:

>>> tag.raw_value
'2002-07-19T13:28:10'

>>> tag.value
datetime.datetime(2002, 7, 19, 13, 28, 10)

Note that querying the value of a tag may raise an XmpValueError if the format of the raw value is invalid according to the XMP specification (may happen if it was written by other software that implements the specification in a broken manner), or if pyexiv2 doesn’t know how to convert it to a convenient python object.

Now let’s modify the value of the tag and write it back to the file:

>>> tag.value = datetime.datetime.today()

>>> metadata.write()

Similarly to reading the value of a tag, one can set either the raw_value or the value (which will be automatically converted to a correctly formatted byte string by pyexiv2).

You can also add new tags to the metadata by providing a valid key and value pair (see exiv2’s documentation for a list of valid XMP tags):

>>> key = 'Xmp.xmp.Label'
>>> value = 'A beautiful picture.'
>>> metadata[key] = pyexiv2.XmpTag(key, value)

As a handy shortcut, you can always assign a value for a given key regardless of whether it’s already present in the metadata. If a tag was present, its value is overwritten. If the tag was not present, one is created and its value is set:

>>> metadata[key] = value

If you need to write custom metadata, you can register a custom XMP namespace:

>>> pyexiv2.xmp.register_namespace('http://example.org/foo/', 'foo')
>>> metadata['Xmp.foo.bar'] = 'baz'

Note that a limitation of the current implementation is that only simple text values can be written to tags in a custom namespace.

A custom namespace can be unregistered. This has the effect of invalidating all tags in this namespace for images that have not been written back yet:

>>> pyexiv2.xmp.unregister_namespace('http://example.org/foo/')

Accessing embedded previews

Images may embed previews (also called thumbnails) of various sizes in their metadata. pyexiv2 allows to easily access them:

>>> previews = metadata.previews

>>> len(previews)
2

They are sorted by increasing size. Let’s play with the largest one:

>>> largest = previews[-1]

>>> largest.dimensions
(320, 240)

>>> largest.mime_type
'image/jpeg'

>>> largest.write_to_file('largest')

Table Of Contents

Previous topic

pyexiv2 documentation

Next topic

API documentation

This Page

pyexiv2-0.3.2/doc/html/api.html0000664000175000017500000016351611651316054015720 0ustar osomonosomon API documentation — pyexiv2 v0.3.2 documentation

API documentation

pyexiv2

pyexiv2.version_info

A tuple containing the three components of the version number: major, minor, micro.

pyexiv2.__version__

The version of the module as a string (major.minor.micro).

pyexiv2.exiv2_version_info

A tuple containing the three components of the version number of libexiv2: major, minor, micro.

pyexiv2.__exiv2_version__

The version of libexiv2 as a string (major.minor.micro).

pyexiv2.metadata

class pyexiv2.metadata.ImageMetadata(filename)

A container for all the metadata embedded in an image.

It provides convenient methods for the manipulation of EXIF, IPTC and XMP metadata embedded in image files such as JPEG and TIFF files, using Python types. It also provides access to the previews embedded in an image.

classmethod from_buffer(buffer)

Instantiate an image container from an image buffer.

Parameters:buffer (string) – a buffer containing image data
read()

Read the metadata embedded in the associated image. It is necessary to call this method once before attempting to access the metadata (an exception will be raised if trying to access metadata before calling this method).

write(preserve_timestamps=False)

Write the metadata back to the image.

Parameters:preserve_timestamps (boolean) – whether to preserve the file’s original timestamps (access time and modification time)
dimensions

A tuple containing the width and height of the image, expressed in pixels.

mime_type

The mime type of the image, as a string.

exif_keys

List of the keys of the available EXIF tags.

iptc_keys

List of the keys of the available IPTC tags.

iptc_charset

An optional character set the IPTC data is encoded in.

xmp_keys

List of the keys of the available XMP tags.

__getitem__(key)

Get a metadata tag for a given key.

Parameters:key (string) – metadata key in the dotted form familyName.groupName.tagName where familyName may be one of exif, iptc or xmp.
Raises KeyError:
 if the tag doesn’t exist
__setitem__(key, tag_or_value)

Set a metadata tag for a given key. If the tag was previously set, it is overwritten. As a handy shortcut, a value may be passed instead of a fully formed tag. The corresponding tag object will be instantiated.

Parameters:
  • key (string) – metadata key in the dotted form familyName.groupName.tagName where familyName may be one of exif, iptc or xmp.
  • tag_or_value (pyexiv2.exif.ExifTag or pyexiv2.iptc.IptcTag or pyexiv2.xmp.XmpTag or any valid value type) – an instance of the corresponding family of metadata tag, or a value
Raises KeyError:
 

if the key is invalid

__delitem__(key)

Delete a metadata tag for a given key.

Parameters:key (string) – metadata key in the dotted form familyName.groupName.tagName where familyName may be one of exif, iptc or xmp.
Raises KeyError:
 if the tag with the given key doesn’t exist
comment

The image comment.

previews

List of the previews available in the image, sorted by increasing size.

copy(other, exif=True, iptc=True, xmp=True, comment=True)

Copy the metadata to another image. The metadata in the destination is overridden. In particular, if the destination contains e.g. EXIF data and the source doesn’t, it will be erased in the destination, unless explicitly omitted.

Parameters:
  • other (pyexiv2.metadata.ImageMetadata) – the destination metadata to copy to (it must have been read() beforehand)
  • exif (boolean) – whether to copy the EXIF metadata
  • iptc (boolean) – whether to copy the IPTC metadata
  • xmp (boolean) – whether to copy the XMP metadata
  • comment (boolean) – whether to copy the image comment
buffer

The image buffer as a string. If metadata has been modified, the data won’t be up-to-date until write() has been called.

pyexiv2.exif

exception pyexiv2.exif.ExifValueError(value, type)

Exception raised when failing to parse the value of an EXIF tag.

Attribute value:
 the value that fails to be parsed
Attribute type:the EXIF type of the tag
class pyexiv2.exif.ExifTag(key, value=None, _tag=None)

An EXIF tag.

Here is a correspondance table between the EXIF types and the possible python types the value of a tag may take:

  • Ascii: datetime.datetime, datetime.date, string
  • Byte, SByte: string
  • Comment: string
  • Long, SLong: [list of] long
  • Short, SShort: [list of] int
  • Rational, SRational: [list of] fractions.Fraction if available (Python ≥ 2.6) or pyexiv2.utils.Rational
  • Undefined: string
key

The key of the tag in the dotted form familyName.groupName.tagName where familyName = exif.

type

The EXIF type of the tag (one of Ascii, Byte, SByte, Comment, Short, SShort, Long, SLong, Rational, SRational, Undefined).

name

The name of the tag (this is also the third part of the key).

label

The title (label) of the tag.

description

The description of the tag.

section_name

The name of the tag’s section.

section_description

The description of the tag’s section.

raw_value

The raw value of the tag as a string.

value

The value of the tag as a python object.

human_value

A (read-only) human-readable representation of the value of the tag.

class pyexiv2.exif.ExifThumbnail(_metadata)

A thumbnail image optionally embedded in the IFD1 segment of the EXIF data.

The image is either a TIFF or a JPEG image.

mime_type

The mime type of the preview image (e.g. image/jpeg).

extension

The file extension of the preview image with a leading dot (e.g. .jpg).

data

The raw thumbnail data. Setting it is restricted to a buffer in the JPEG format.

set_from_file(path)

Set the EXIF thumbnail to the JPEG image path. This sets only the Compression, JPEGInterchangeFormat and JPEGInterchangeFormatLength tags, which is not all the thumbnail EXIF information mandatory according to the EXIF standard (but it is enough to work with the thumbnail).

Parameters:path (string) – path to a JPEG file to set the thumbnail to
write_to_file(path)

Write the thumbnail image to a file on disk. The file extension will be automatically appended to the path.

Parameters:path (string) – path to write the thumbnail to (without an extension)
erase()

Delete the thumbnail from the EXIF data. Removes all Exif.Thumbnail.*, i.e. Exif IFD1 tags.

pyexiv2.iptc

exception pyexiv2.iptc.IptcValueError(value, type)

Exception raised when failing to parse the value of an IPTC tag.

Attribute value:
 the value that fails to be parsed
Attribute type:the IPTC type of the tag
class pyexiv2.iptc.IptcTag(key, values=None, _tag=None)

An IPTC tag.

This tag can have several values (tags that have the repeatable property).

Here is a correspondance table between the IPTC types and the possible python types the value of a tag may take:

  • Short: int
  • String: string
  • Date: datetime.date
  • Time: datetime.time
  • Undefined: string
key

The key of the tag in the dotted form familyName.groupName.tagName where familyName = iptc.

type

The IPTC type of the tag (one of Short, String, Date, Time, Undefined).

name

The name of the tag (this is also the third part of the key).

title

The title (label) of the tag.

description

The description of the tag.

photoshop_name

The Photoshop name of the tag.

repeatable

Whether the tag is repeatable (accepts several values).

record_name

The name of the tag’s record.

record_description

The description of the tag’s record.

raw_value

The raw values of the tag as a list of strings.

value

The values of the tag as a list of python objects.

pyexiv2.xmp

pyexiv2.xmp.register_namespace(name, prefix)

Register a custom XMP namespace.

Overriding the prefix of a known or previously registered namespace is not allowed.

Parameters:
  • name (string) – the name of the custom namespace (ending with a /), typically a URL (e.g. http://purl.org/dc/elements/1.1/)
  • prefix (string) – the prefix for the custom namespace (keys in this namespace will be in the form Xmp.{prefix}.{something})
Raises:
  • ValueError – if the name doesn’t end with a /
  • KeyError – if a namespace already exist with this prefix
pyexiv2.xmp.unregister_namespace(name)

Unregister a custom XMP namespace.

A custom namespace is identified by its name, not by its prefix.

Attempting to unregister an unknown namespace raises an error, as does attempting to unregister a builtin namespace.

Parameters:

name (string) – the name of the custom namespace (ending with a /), typically a URL (e.g. http://purl.org/dc/elements/1.1/)

Raises:
  • ValueError – if the name doesn’t end with a /
  • KeyError – if the namespace is unknown or a builtin namespace
pyexiv2.xmp.unregister_namespaces()

Unregister all custom XMP namespaces.

Builtin namespaces are not unregistered.

This function always succeeds.

exception pyexiv2.xmp.XmpValueError(value, type)

Exception raised when failing to parse the value of an XMP tag.

Attribute value:
 the value that fails to be parsed
Attribute type:the XMP type of the tag
class pyexiv2.xmp.XmpTag(key, value=None, _tag=None)

An XMP tag.

Here is a correspondance table between the XMP types and the possible python types the value of a tag may take:

  • alt, bag, seq: list of the contained simple type
  • lang alt: dict of (language-code: value)
  • Boolean: boolean
  • Colorant: [not implemented yet]
  • Date: datetime.date, datetime.datetime
  • Dimensions: [not implemented yet]
  • Font: [not implemented yet]
  • GPSCoordinate: pyexiv2.utils.GPSCoordinate
  • Integer: int
  • Locale: [not implemented yet]
  • MIMEType: 2-tuple of strings
  • Rational: fractions.Fraction if available (Python ≥ 2.6) or pyexiv2.utils.Rational
  • Real: [not implemented yet]
  • AgentName, ProperName, Text: unicode string
  • Thumbnail: [not implemented yet]
  • URI, URL: string
  • XPath: [not implemented yet]
key

The key of the tag in the dotted form familyName.groupName.tagName where familyName = xmp.

type

The XMP type of the tag.

name

The name of the tag (this is also the third part of the key).

title

The title (label) of the tag.

description

The description of the tag.

raw_value

The raw value of the tag as a [list of] string(s).

value

The value of the tag as a [list of] python object(s).

pyexiv2.preview

class pyexiv2.preview.Preview(preview)

A preview image (properties and data buffer) embedded in image metadata.

mime_type

The mime type of the preview image (e.g. image/jpeg).

extension

The file extension of the preview image with a leading dot (e.g. .jpg).

size

The size of the preview image in bytes.

dimensions

A tuple containing the width and height of the preview image in pixels.

data

The preview image data buffer.

write_to_file(path)

Write the preview image to a file on disk. The file extension will be automatically appended to the path.

Parameters:path (string) – path to write the preview to (without an extension)

pyexiv2.utils

pyexiv2.utils.undefined_to_string(undefined)

Convert an undefined string into its corresponding sequence of bytes. The undefined string must contain the ascii codes of a sequence of bytes, separated by white spaces (e.g. “48 50 50 49” will be converted into “0221”). The Undefined type is part of the EXIF specification.

Parameters:undefined (string) – an undefined string
Returns:the corresponding decoded string
Return type:string
pyexiv2.utils.string_to_undefined(sequence)

Convert a string into its undefined form. The undefined form contains a sequence of ascii codes separated by white spaces (e.g. “0221” will be converted into “48 50 50 49”). The Undefined type is part of the EXIF specification.

Parameters:sequence (string) – a sequence of bytes
Returns:the corresponding undefined string
Return type:string
pyexiv2.utils.make_fraction(*args)

Make a fraction.

The type of the returned object depends on the availability of the fractions module in the standard library (Python ≥ 2.6).

Raises TypeError:
 if the arguments do not match the expected format for a fraction
class pyexiv2.utils.Rational(numerator, denominator)

A class representing a rational number.

Its numerator and denominator are read-only properties.

Do not use this class directly to instantiate a rational number. Instead, use make_fraction().

numerator

The numerator of the rational number.

denominator

The denominator of the rational number.

static from_string(string)

Instantiate a Rational from a string formatted as [-]numerator/denominator.

Parameters:string (string) – a string representation of a rational number
Returns:the rational number parsed
Return type:Rational
Raises ValueError:
 if the format of the string is invalid
to_float()
Returns:a floating point number approximation of the value
Return type:float
__eq__(other)

Compare two rational numbers for equality.

Two rational numbers are equal if their reduced forms are equal.

Parameters:other (Rational) – the rational number to compare to self for equality
Returns:True if equal, False otherwise
Return type:boolean
__str__()
Returns:a string representation of the rational number
Return type:string
__repr__()
Returns:the official string representation of the object
Return type:string
class pyexiv2.utils.GPSCoordinate(degrees, minutes, seconds, direction)

A class representing GPS coordinates (e.g. a latitude or a longitude).

Its attributes (degrees, minutes, seconds, direction) are read-only properties.

degrees

The degrees component of the coordinate.

minutes

The minutes component of the coordinate.

seconds

The seconds component of the coordinate.

direction

The direction component of the coordinate.

static from_string(string)

Instantiate a GPSCoordinate from a string formatted as DDD,MM,SSk or DDD,MM.mmk where DDD is a number of degrees, MM is a number of minutes, SS is a number of seconds, mm is a fraction of minutes, and k is a single character N, S, E, W indicating a direction (north, south, east, west).

Parameters:string (string) – a string representation of a GPS coordinate
Returns:the GPS coordinate parsed
Return type:GPSCoordinate
Raises ValueError:
 if the format of the string is invalid
__eq__(other)

Compare two GPS coordinates for equality.

Two coordinates are equal if and only if all their components are equal.

Parameters:other (GPSCoordinate) – the GPS coordinate to compare to self for equality
Returns:True if equal, False otherwise
Return type:boolean
__str__()
Returns:a string representation of the GPS coordinate conforming to the XMP specification
Return type:string

Table Of Contents

Previous topic

Tutorial

Next topic

Developers

This Page

pyexiv2-0.3.2/doc/html/py-modindex.html0000664000175000017500000001147411651316054017377 0ustar osomonosomon Python Module Index — pyexiv2 v0.3.2 documentation

Python Module Index

p
 
p
pyexiv2
    pyexiv2.exif
    pyexiv2.iptc
    pyexiv2.metadata
    pyexiv2.preview
    pyexiv2.utils
    pyexiv2.xmp
pyexiv2-0.3.2/doc/tutorial.rst0000664000175000017500000002301411651315372015700 0ustar osomonosomonTutorial ======== This tutorial is meant to give you a quick overview of what pyexiv2 allows you to do. You can just read it through or follow it interactively, in which case you will need to have pyexiv2 installed. It doesn't cover all the possibilities offered by pyexiv2, only a basic subset of them. For complete reference, see the :doc:`api`. Let's get started! First of all, we import the pyexiv2 module:: >>> import pyexiv2 We then load an image file and read its metadata:: >>> metadata = pyexiv2.ImageMetadata('test.jpg') >>> metadata.read() Reading and writing EXIF tags ############################# Let's retrieve a list of all the available EXIF tags available in the image:: >>> metadata.exif_keys ['Exif.Image.ImageDescription', 'Exif.Image.XResolution', 'Exif.Image.YResolution', 'Exif.Image.ResolutionUnit', 'Exif.Image.Software', 'Exif.Image.DateTime', 'Exif.Image.Artist', 'Exif.Image.Copyright', 'Exif.Image.ExifTag', 'Exif.Photo.Flash', 'Exif.Photo.PixelXDimension', 'Exif.Photo.PixelYDimension'] Each of those tags can be accessed with the ``[]`` operator on the metadata, much like a python dictionary:: >>> tag = metadata['Exif.Image.DateTime'] The value of an :class:`ExifTag` object can be accessed in two different ways: with the :attr:`raw_value` and with the :attr:`value` attributes:: >>> tag.raw_value '2004-07-13T21:23:44Z' >>> tag.value datetime.datetime(2004, 7, 13, 21, 23, 44) The raw value is always a byte string, this is how the value is stored in the file. The value is lazily computed from the raw value depending on the EXIF type of the tag, and is represented as a convenient python object to allow easy manipulation. Note that querying the value of a tag may raise an :exc:`ExifValueError` if the format of the raw value is invalid according to the EXIF specification (may happen if it was written by other software that implements the specification in a broken manner), or if pyexiv2 doesn't know how to convert it to a convenient python object. Accessing the value of a tag as a python object allows easy manipulation and formatting:: >>> tag.value.strftime('%A %d %B %Y, %H:%M:%S') 'Tuesday 13 July 2004, 21:23:44' Now let's modify the value of the tag and write it back to the file:: >>> import datetime >>> tag.value = datetime.datetime.today() >>> metadata.write() Similarly to reading the value of a tag, one can set either the :attr:`raw_value` or the :attr:`value` (which will be automatically converted to a correctly formatted byte string by pyexiv2). You can also add new tags to the metadata by providing a valid key and value pair (see exiv2's documentation for a list of valid `EXIF tags `_):: >>> key = 'Exif.Photo.UserComment' >>> value = 'This is a useful comment.' >>> metadata[key] = pyexiv2.ExifTag(key, value) As a handy shortcut, you can always assign a value for a given key regardless of whether it's already present in the metadata. If a tag was present, its value is overwritten. If the tag was not present, one is created and its value is set:: >>> metadata[key] = value The EXIF data may optionally embed a thumbnail in the JPEG or TIFF format. The thumbnail can be accessed, set from a JPEG file or buffer, saved to disk and erased:: >>> thumb = metadata.exif_thumbnail >>> thumb.set_from_file('/tmp/thumbnail.jpg') >>> thumb.write_to_file('/tmp/copy') >>> thumb.erase() >>> metadata.write() Reading and writing IPTC tags ############################# Reading and writing IPTC tags works pretty much the same way as with EXIF tags. Let's retrieve the list of all available IPTC tags in the image:: >>> metadata.iptc_keys ['Iptc.Application2.Caption', 'Iptc.Application2.Writer', 'Iptc.Application2.Byline', 'Iptc.Application2.ObjectName', 'Iptc.Application2.DateCreated', 'Iptc.Application2.City', 'Iptc.Application2.ProvinceState', 'Iptc.Application2.CountryName', 'Iptc.Application2.Category', 'Iptc.Application2.Keywords', 'Iptc.Application2.Copyright'] Each of those tags can be accessed with the ``[]`` operator on the metadata:: >>> tag = metadata['Iptc.Application2.DateCreated'] An IPTC tag always has a list of values rather than a single value. This is because some tags have a repeatable character. Tags that are not repeatable only hold one value in their list of values. Check the :attr:`repeatable` attribute to know whether a tag can hold more than one value:: >>> tag.repeatable False As with EXIF tags, the values of an :class:`IptcTag` object can be accessed in two different ways: with the :attr:`raw_value` and with the :attr:`value` attributes:: >>> tag.raw_value ['2004-07-13'] >>> tag.value [datetime.date(2004, 7, 13)] Note that querying the values of a tag may raise an :exc:`IptcValueError` if the format of the raw values is invalid according to the IPTC specification (may happen if it was written by other software that implements the specification in a broken manner), or if pyexiv2 doesn't know how to convert it to a convenient python object. Now let's modify the values of the tag and write it back to the file:: >>> tag.value = [datetime.date.today()] >>> metadata.write() Similarly to reading the values of a tag, one can set either the :attr:`raw_value` or the :attr:`value` (which will be automatically converted to correctly formatted byte strings by pyexiv2). You can also add new tags to the metadata by providing a valid key and values pair (see exiv2's documentation for a list of valid `IPTC tags `_):: >>> key = 'Iptc.Application2.Contact' >>> values = ['John', 'Paul', 'Ringo', 'George'] >>> metadata[key] = pyexiv2.IptcTag(key, values) As a handy shortcut, you can always assign values for a given key regardless of whether it's already present in the metadata. If a tag was present, its values are overwritten. If the tag was not present, one is created and its values are set:: >>> metadata[key] = values The IPTC metadata in an image may embed an optional character set for its encoding. This is defined by the ``Iptc.Envelope.CharacterSet`` tag. The :class:`ImageMetadata` class has an :attr:`iptc_charset` property that allows to easily get, set and delete this value:: >>> metadata.iptc_charset 'ascii' >>> metadata.iptc_charset = 'utf-8' >>> del metadata.iptc_charset Note that at the moment, the only supported charset that can be assigned to the property is ``utf-8``. Also note that even if the charset is not explicitly set, its value may be inferred from the contents of the image. If not, it will be ``None``. Reading and writing XMP tags ############################ Reading and writing XMP tags works pretty much the same way as with EXIF tags. Let's retrieve the list of all available XMP tags in the image:: >>> metadata.xmp_keys ['Xmp.dc.creator', 'Xmp.dc.description', 'Xmp.dc.rights', 'Xmp.dc.source', 'Xmp.dc.subject', 'Xmp.dc.title', 'Xmp.xmp.CreateDate', 'Xmp.xmp.ModifyDate'] Each of those tags can be accessed with the ``[]`` operator on the metadata:: >>> tag = metadata['Xmp.xmp.ModifyDate'] As with EXIF tags, the value of an :class:`XmpTag` object can be accessed in two different ways: with the :attr:`raw_value` and with the :attr:`value` attributes:: >>> tag.raw_value '2002-07-19T13:28:10' >>> tag.value datetime.datetime(2002, 7, 19, 13, 28, 10) Note that querying the value of a tag may raise an :exc:`XmpValueError` if the format of the raw value is invalid according to the XMP specification (may happen if it was written by other software that implements the specification in a broken manner), or if pyexiv2 doesn't know how to convert it to a convenient python object. Now let's modify the value of the tag and write it back to the file:: >>> tag.value = datetime.datetime.today() >>> metadata.write() Similarly to reading the value of a tag, one can set either the :attr:`raw_value` or the :attr:`value` (which will be automatically converted to a correctly formatted byte string by pyexiv2). You can also add new tags to the metadata by providing a valid key and value pair (see exiv2's documentation for a list of valid `XMP tags `_):: >>> key = 'Xmp.xmp.Label' >>> value = 'A beautiful picture.' >>> metadata[key] = pyexiv2.XmpTag(key, value) As a handy shortcut, you can always assign a value for a given key regardless of whether it's already present in the metadata. If a tag was present, its value is overwritten. If the tag was not present, one is created and its value is set:: >>> metadata[key] = value If you need to write custom metadata, you can register a custom XMP namespace:: >>> pyexiv2.xmp.register_namespace('http://example.org/foo/', 'foo') >>> metadata['Xmp.foo.bar'] = 'baz' Note that a limitation of the current implementation is that only simple text values can be written to tags in a custom namespace. A custom namespace can be unregistered. This has the effect of invalidating all tags in this namespace for images that have not been written back yet:: >>> pyexiv2.xmp.unregister_namespace('http://example.org/foo/') Accessing embedded previews ########################### Images may embed previews (also called thumbnails) of various sizes in their metadata. pyexiv2 allows to easily access them:: >>> previews = metadata.previews >>> len(previews) 2 They are sorted by increasing size. Let's play with the largest one:: >>> largest = previews[-1] >>> largest.dimensions (320, 240) >>> largest.mime_type 'image/jpeg' >>> largest.write_to_file('largest') pyexiv2-0.3.2/doc/api.rst0000664000175000017500000000361211651315372014610 0ustar osomonosomonAPI documentation ================= pyexiv2 ####### .. module:: pyexiv2 .. autodata:: version_info .. autodata:: __version__ .. autodata:: exiv2_version_info .. autodata:: __exiv2_version__ pyexiv2.metadata ################ .. module:: pyexiv2.metadata .. autoclass:: ImageMetadata :members: from_buffer, read, write, dimensions, mime_type, exif_keys, iptc_keys, iptc_charset, xmp_keys, __getitem__, __setitem__, __delitem__, comment, previews, copy, buffer pyexiv2.exif ############ .. module:: pyexiv2.exif .. autoexception:: ExifValueError .. autoclass:: ExifTag :members: key, type, name, label, description, section_name, section_description, raw_value, value, human_value .. autoclass:: ExifThumbnail :members: mime_type, extension, data, set_from_file, write_to_file, erase pyexiv2.iptc ############ .. module:: pyexiv2.iptc .. autoexception:: IptcValueError .. autoclass:: IptcTag :members: key, type, name, title, description, photoshop_name, repeatable, record_name, record_description, raw_value, value pyexiv2.xmp ########### .. module:: pyexiv2.xmp .. autofunction:: register_namespace .. autofunction:: unregister_namespace .. autofunction:: unregister_namespaces .. autoexception:: XmpValueError .. autoclass:: XmpTag :members: key, type, name, title, description, raw_value, value pyexiv2.preview ############### .. module:: pyexiv2.preview .. autoclass:: Preview :members: mime_type, extension, size, dimensions, data, write_to_file pyexiv2.utils ############# .. module:: pyexiv2.utils .. autofunction:: undefined_to_string .. autofunction:: string_to_undefined .. autofunction:: make_fraction .. autoclass:: Rational :members: numerator, denominator, from_string, to_float, __eq__, __str__, __repr__ .. autoclass:: GPSCoordinate :members: degrees, minutes, seconds, direction, from_string, __eq__, __str__ pyexiv2-0.3.2/doc/SConscript0000664000175000017500000000144311651315372015317 0ustar osomonosomon# -*- coding: utf-8 -*- import inspect import os.path import sys from sphinx.application import Sphinx # Build HTML documentation using sphinx script = inspect.currentframe().f_code.co_filename srcdir = os.path.dirname(script) confdir = srcdir outdir = os.path.join(srcdir, '_build') doctreedir = os.path.join(outdir, '.doctrees') sphinx = Sphinx(srcdir, confdir, outdir, doctreedir, 'html', {}, sys.stdout) sources = [os.path.join(srcdir, doc + '.rst') for doc in sphinx.builder.get_outdated_docs()] output = [os.path.join(outdir, doc + '.html') for doc in sphinx.builder.get_outdated_docs()] def build_doc(target, source, env): sphinx.build(False, []) return sphinx.statuscode env = Environment() doc = env.Command(output, sources, build_doc) env.Alias('doc', doc) pyexiv2-0.3.2/doc/developers.rst0000664000175000017500000001361111651315372016207 0ustar osomonosomonDevelopers ========== If you are a developer and use pyexiv2 in your project, you will find here useful information. Getting the code ################ pyexiv2's source code is versioned with `bazaar `_, and all the branches, including the main development focus (sometimes referred to as *trunk*), are hosted on `Launchpad `_. To get a working copy of the latest revision of the development branch, just issue the following command in a terminal:: bzr branch lp:pyexiv2 If you need to get a specific revision identified by a tag (all releases of pyexiv2 are tagged), use the following command:: bzr branch -r tag:tag_name lp:pyexiv2 A list of all the available tags can be obtained using the ``bzr tags`` command:: osomon@granuja:~/dev/pyexiv2$ bzr tags release-0.1 60 release-0.1.1 73 release-0.1.2 99 release-0.1.3 99.1.6 release-0.2.0 296 release-0.2.1 306 release-0.2.2 318 release-0.3.0 349 Dependencies ############ You will need the following dependencies installed on your system to build and use pyexiv2: * `Python `_ ≥ 2.6 * `boost.python `_ ≥ 1.35 * `libexiv2 `_ ≥ 0.19 * `SCons `_ For Python, boost.python and libexiv2, the development files are needed (-dev packages). A typical list of packages to install on a Debian/Ubuntu system is:: python-all-dev libboost-python-dev libexiv2-dev scons Some unit tests have a dependency on `python-tz `_. This dependency is optional: the corresponding tests will be skipped if it is not present on the system. Additionally, if you want to cross-compile pyexiv2 for Windows and generate a Windows installer, you will need the following dependencies: * `MinGW `_ * `7-Zip `_ * `NSIS `_ A typical list of packages to install on a Debian/Ubuntu system is:: mingw32 p7zip-full nsis Building and installing ####################### Linux +++++ Building pyexiv2 is as simple as invoking ``scons`` in the top-level directory:: osomon@granuja:~/dev/pyexiv2$ scons scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... g++ -o build/exiv2wrapper.os -c -fPIC -I/usr/include/python2.7 src/exiv2wrapper.cpp g++ -o build/exiv2wrapper_python.os -c -fPIC -I/usr/include/python2.7 src/exiv2wrapper_python.cpp g++ -o build/libexiv2python.so -shared build/exiv2wrapper.os build/exiv2wrapper_python.os -lboost_python -lexiv2 scons: done building targets. The result of the build process is a shared library, ``libexiv2python.so``, in the build directory:: osomon@granuja:~/dev/pyexiv2$ ls build/ exiv2wrapper.os exiv2wrapper_python.os libexiv2python.so To install pyexiv2 system-wide, just invoke ``scons install``. You will most likely need administrative privileges to proceed. The ``--user`` switch will install pyexiv2 in the current `user site directory `_ (Python ≥ 2.6 is required). Note to packagers: if `DESTDIR `_ is specified on the command line when invoking ``scons install``, its value will be prepended to each installed target file. Windows +++++++ The top-level directory of the development branch contains a shell script named ``cross-compile.sh`` that retrieves all the dependencies required and cross-compiles pyexiv2 for Windows on a Linux host. Read the comments in the header of the script to know the pre-requisites. The result of the compilation is a DLL, ``libexiv2python.pyd``, in the build directory. This file and the ``pyexiv2`` folder in ``src`` should be copied to the system-wide site directory of a Python 2.7 setup (typically ``C:\Python27\Lib\site-packages\``) or to the user site directory (``%APPDATA%\Python\Python27\site-packages\``). The top-level directory of the branch also contains an NSIS installer script named ``win32-installer.nsi``. From the top-level directory of the branch, run the following command:: makensis win32-installer.nsi This will generate a ready-to-distribute installer executable named ``pyexiv2-0.3-setup.exe``. Documentation ############# The present documentation is generated using `Sphinx `_ from reStructuredText sources found in the doc/ directory. Invoke ``scons doc`` to (re)build the HTML documentation. Alternatively, you can issue the following command:: sphinx-build -b html doc/ doc/_build/ The index of the documentation will then be found under doc/_build/index.html. Unit tests ########## pyexiv2's source comes with a battery of unit tests, in the test/ directory. To run them, invoke ``scons test``. Alternatively, you can execute the ``TestsRunner.py`` script. Contributing ############ pyexiv2 is Free Software, meaning that you are encouraged to use it, modify it to suit your needs, contribute back improvements, and redistribute it. `Bugs `_ are tracked on Launchpad. There is a team called `pyexiv2-developers `_ open to anyone interested in following development on pyexiv2. Don't hesitate to subscribe to the team (you don't need to actually contribute!) and to the associated mailing list. There are several ways in which you can contribute to improve pyexiv2: * Use it; * Give your feedback and discuss issues and feature requests on the mailing list; * Report bugs, write patches; * Package it for your favorite distribution/OS. When reporting a bug, don't forget to include the following information in the report: * version of pyexiv2 * version of libexiv2 it was compiled against * a minimal script that reliably reproduces the issue * a sample image file with which the bug can reliably be reproduced pyexiv2-0.3.2/test/0000775000175000017500000000000011651315372013515 5ustar osomonosomonpyexiv2-0.3.2/test/gps_coordinate.py0000664000175000017500000000657411651315372017103 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import unittest from pyexiv2.utils import GPSCoordinate class TestGPSCoordinate(unittest.TestCase): def test_constructor(self): r = GPSCoordinate(62, 58, 2, 'W') self.assertEqual(r.degrees, 62) self.assertEqual(r.minutes, 58) self.assertEqual(r.seconds, 2) self.assertEqual(r.direction, 'W') r = GPSCoordinate(92, 58, 2, 'W') self.assertEqual(r.degrees, 92) self.assertEqual(r.minutes, 58) self.assertEqual(r.seconds, 2) self.assertEqual(r.direction, 'W') self.assertRaises(ValueError, GPSCoordinate, -23, 58, 2, 'W') self.assertRaises(ValueError, GPSCoordinate, 91, 58, 2, 'S') self.assertRaises(ValueError, GPSCoordinate, 62, -23, 2, 'W') self.assertRaises(ValueError, GPSCoordinate, 62, 61, 2, 'W') self.assertRaises(ValueError, GPSCoordinate, 62, 58, -23, 'W') self.assertRaises(ValueError, GPSCoordinate, 62, 58, 61, 'W') self.assertRaises(ValueError, GPSCoordinate, 62, 58, 2, 'A') def test_read_only(self): r = GPSCoordinate(62, 58, 2, 'W') try: r.degrees = 5 except AttributeError: pass else: self.fail('Degrees is not read-only.') try: r.minutes = 5 except AttributeError: pass else: self.fail('Minutes is not read-only.') try: r.seconds = 5 except AttributeError: pass else: self.fail('Seconds is not read-only.') try: r.direction = 'S' except AttributeError: pass else: self.fail('Direction is not read-only.') def test_from_string(self): self.assertEqual(GPSCoordinate.from_string('54,59.380000N'), GPSCoordinate(54, 59, 23, 'N')) self.assertEqual(GPSCoordinate.from_string('1,54.850000W'), GPSCoordinate(1, 54, 51, 'W')) self.assertRaises(ValueError, GPSCoordinate.from_string, '51N') self.assertRaises(ValueError, GPSCoordinate.from_string, '48 24 3 S') self.assertRaises(ValueError, GPSCoordinate.from_string, '48°24\'3"S') self.assertRaises(ValueError, GPSCoordinate.from_string, 'invalid') def test_to_string(self): self.assertEqual(str(GPSCoordinate(54, 59, 23, 'N')), '54,59,23N') pyexiv2-0.3.2/test/xmp.py0000664000175000017500000006224111651315372014700 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2009-2011 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import unittest from pyexiv2.xmp import XmpTag, XmpValueError, register_namespace, \ unregister_namespace, unregister_namespaces from pyexiv2.utils import FixedOffset, make_fraction from pyexiv2.metadata import ImageMetadata import datetime from testutils import EMPTY_JPG_DATA # Optional dependency on python-tz, more tests can be run if it is installed try: import pytz except ImportError: pytz = None class TestXmpTag(unittest.TestCase): def test_convert_to_python_bag(self): # Valid values tag = XmpTag('Xmp.dc.subject') self.assertEqual(tag.type, 'bag Text') self.assertEqual(tag._convert_to_python('', 'Text'), u'') self.assertEqual(tag._convert_to_python('One value only', 'Text'), u'One value only') def test_convert_to_string_bag(self): # Valid values tag = XmpTag('Xmp.dc.subject') self.assertEqual(tag.type, 'bag Text') self.assertEqual(tag._convert_to_string(u'', 'Text'), '') self.assertEqual(tag._convert_to_string('One value only', 'Text'), 'One value only') self.assertEqual(tag._convert_to_string(u'One value only', 'Text'), 'One value only') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, [1, 2, 3], 'Text') def test_convert_to_python_boolean(self): # Valid values tag = XmpTag('Xmp.xmpRights.Marked') self.assertEqual(tag.type, 'Boolean') self.assertEqual(tag._convert_to_python('True', 'Boolean'), True) self.assertEqual(tag._convert_to_python('False', 'Boolean'), False) # Invalid values: not converted self.failUnlessRaises(XmpValueError, tag._convert_to_python, 'invalid', 'Boolean') self.failUnlessRaises(XmpValueError, tag._convert_to_python, None, 'Boolean') def test_convert_to_string_boolean(self): # Valid values tag = XmpTag('Xmp.xmpRights.Marked') self.assertEqual(tag.type, 'Boolean') self.assertEqual(tag._convert_to_string(True, 'Boolean'), 'True') self.assertEqual(tag._convert_to_string(False, 'Boolean'), 'False') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, 'invalid', 'Boolean') self.failUnlessRaises(XmpValueError, tag._convert_to_string, None, 'Boolean') def test_convert_to_python_date(self): # Valid values tag = XmpTag('Xmp.xmp.CreateDate') self.assertEqual(tag.type, 'Date') self.assertEqual(tag._convert_to_python('1999', 'Date'), datetime.date(1999, 1, 1)) self.assertEqual(tag._convert_to_python('1999-10', 'Date'), datetime.date(1999, 10, 1)) self.assertEqual(tag._convert_to_python('1999-10-13', 'Date'), datetime.date(1999, 10, 13)) self.assertEqual(tag._convert_to_python('1999-10-13T05:03Z', 'Date') - \ datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset()), datetime.timedelta(0)) self.assertEqual(tag._convert_to_python('1999-10-13T05:03+06:00', 'Date') - \ datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset('+', 6, 0)), datetime.timedelta(0)) self.assertEqual(tag._convert_to_python('1999-10-13T05:03-06:00', 'Date') - \ datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset('-', 6, 0)), datetime.timedelta(0)) self.assertEqual(tag._convert_to_python('1999-10-13T05:03:54Z', 'Date') - \ datetime.datetime(1999, 10, 13, 5, 3, 54, tzinfo=FixedOffset()), datetime.timedelta(0)) self.assertEqual(tag._convert_to_python('1999-10-13T05:03:54+06:00', 'Date') - \ datetime.datetime(1999, 10, 13, 5, 3, 54, tzinfo=FixedOffset('+', 6, 0)), datetime.timedelta(0)) self.assertEqual(tag._convert_to_python('1999-10-13T05:03:54-06:00', 'Date') - \ datetime.datetime(1999, 10, 13, 5, 3, 54, tzinfo=FixedOffset('-', 6, 0)), datetime.timedelta(0)) self.assertEqual(tag._convert_to_python('1999-10-13T05:03:54.721Z', 'Date') - \ datetime.datetime(1999, 10, 13, 5, 3, 54, 721000, tzinfo=FixedOffset()), datetime.timedelta(0)) self.assertEqual(tag._convert_to_python('1999-10-13T05:03:54.721+06:00', 'Date') - \ datetime.datetime(1999, 10, 13, 5, 3, 54, 721000, tzinfo=FixedOffset('+', 6, 0)), datetime.timedelta(0)) self.assertEqual(tag._convert_to_python('1999-10-13T05:03:54.721-06:00', 'Date') - \ datetime.datetime(1999, 10, 13, 5, 3, 54, 721000, tzinfo=FixedOffset('-', 6, 0)), datetime.timedelta(0)) # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_python, 'invalid', 'Date') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '11/10/1983', 'Date') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '-1000', 'Date') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '2009-13', 'Date') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '2009-10-32', 'Date') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '2009-10-30T25:12Z', 'Date') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '2009-10-30T23:67Z', 'Date') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '2009-01-22T21', 'Date') def test_convert_to_string_date(self): # Valid values tag = XmpTag('Xmp.xmp.CreateDate') self.assertEqual(tag.type, 'Date') self.assertEqual(tag._convert_to_string(datetime.date(2009, 2, 4), 'Date'), '2009-02-04') self.assertEqual(tag._convert_to_string(datetime.date(1899, 12, 31), 'Date'), '1899-12-31') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13), 'Date'), '1999-10-13') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3), 'Date'), '1999-10-13T05:03Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59), 'Date'), '1899-12-31T23:59Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset()), 'Date'), '1999-10-13T05:03Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, tzinfo=FixedOffset()), 'Date'), '1899-12-31T23:59Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset('+', 5, 30)), 'Date'), '1999-10-13T05:03+05:30') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, tzinfo=FixedOffset('-', 11, 30)), 'Date'), '1999-10-13T05:03-11:30') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, tzinfo=FixedOffset('+', 5, 30)), 'Date'), '1899-12-31T23:59+05:30') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27), 'Date'), '1999-10-13T05:03:27Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59), 'Date'), '1899-12-31T23:59:59Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, tzinfo=FixedOffset()), 'Date'), '1999-10-13T05:03:27Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset()), 'Date'), '1899-12-31T23:59:59Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, tzinfo=FixedOffset('+', 5, 30)), 'Date'), '1999-10-13T05:03:27+05:30') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, tzinfo=FixedOffset('-', 11, 30)), 'Date'), '1999-10-13T05:03:27-11:30') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset('+', 5, 30)), 'Date'), '1899-12-31T23:59:59+05:30') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, 124300), 'Date'), '1999-10-13T05:03:27.1243Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59, 124300), 'Date'), '1899-12-31T23:59:59.1243Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, 124300, tzinfo=FixedOffset()), 'Date'), '1999-10-13T05:03:27.1243Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59, 124300, tzinfo=FixedOffset()), 'Date'), '1899-12-31T23:59:59.1243Z') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, 124300, tzinfo=FixedOffset('+', 5, 30)), 'Date'), '1999-10-13T05:03:27.1243+05:30') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13, 5, 3, 27, 124300, tzinfo=FixedOffset('-', 11, 30)), 'Date'), '1999-10-13T05:03:27.1243-11:30') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59, 124300, tzinfo=FixedOffset('+', 5, 30)), 'Date'), '1899-12-31T23:59:59.1243+05:30') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, 'invalid', 'Date') self.failUnlessRaises(XmpValueError, tag._convert_to_string, None, 'Date') def test_convert_to_string_date_with_real_timezones(self): if pytz is None: # Poor man’s test skipping. Starting with Python 2.7, decorators are # available to implement this in a cleaner fashion # (http://docs.python.org/library/unittest.html#unittest-skipping). print 'Install python-tz to run this test. Skipping.' return tag = XmpTag('Xmp.xmp.CreateDate') self.assertEqual(tag.type, 'Date') t = pytz.timezone('UTC').localize(datetime.datetime(2011, 2, 2, 10, 52, 4)) self.assertEqual(tag._convert_to_string(t, 'Date'), '2011-02-02T10:52:04Z') t = pytz.timezone('CET').localize(datetime.datetime(2011, 2, 2, 10, 52, 4)) self.assertEqual(tag._convert_to_string(t, 'Date'), '2011-02-02T10:52:04+01:00') def test_convert_to_python_integer(self): # Valid values tag = XmpTag('Xmp.xmpMM.SaveID') self.assertEqual(tag.type, 'Integer') self.assertEqual(tag._convert_to_python('23', 'Integer'), 23) self.assertEqual(tag._convert_to_python('+5628', 'Integer'), 5628) self.assertEqual(tag._convert_to_python('-4', 'Integer'), -4) # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_python, 'abc', 'Integer') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '5,64', 'Integer') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '47.0001', 'Integer') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '1E3', 'Integer') def test_convert_to_string_integer(self): # Valid values tag = XmpTag('Xmp.xmpMM.SaveID') self.assertEqual(tag.type, 'Integer') self.assertEqual(tag._convert_to_string(123, 'Integer'), '123') self.assertEqual(tag._convert_to_string(-57, 'Integer'), '-57') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, 'invalid', 'Integer') self.failUnlessRaises(XmpValueError, tag._convert_to_string, 3.14, 'Integer') def test_convert_to_python_mimetype(self): # Valid values tag = XmpTag('Xmp.dc.format') self.assertEqual(tag.type, 'MIMEType') self.assertEqual(tag._convert_to_python('image/jpeg', 'MIMEType'), ('image', 'jpeg')) self.assertEqual(tag._convert_to_python('video/ogg', 'MIMEType'), ('video', 'ogg')) # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_python, 'invalid', 'MIMEType') self.failUnlessRaises(XmpValueError, tag._convert_to_python, 'image-jpeg', 'MIMEType') def test_convert_to_string_mimetype(self): # Valid values tag = XmpTag('Xmp.dc.format') self.assertEqual(tag.type, 'MIMEType') self.assertEqual(tag._convert_to_string(('image', 'jpeg'), 'MIMEType'), 'image/jpeg') self.assertEqual(tag._convert_to_string(('video', 'ogg'), 'MIMEType'), 'video/ogg') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, 'invalid', 'MIMEType') self.failUnlessRaises(XmpValueError, tag._convert_to_string, ('image',), 'MIMEType') def test_convert_to_python_propername(self): # Valid values tag = XmpTag('Xmp.photoshop.CaptionWriter') self.assertEqual(tag.type, 'ProperName') self.assertEqual(tag._convert_to_python('Gérard', 'ProperName'), u'Gérard') self.assertEqual(tag._convert_to_python('Python Software Foundation', 'ProperName'), u'Python Software Foundation') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_python, None, 'ProperName') def test_convert_to_string_propername(self): # Valid values tag = XmpTag('Xmp.photoshop.CaptionWriter') self.assertEqual(tag.type, 'ProperName') self.assertEqual(tag._convert_to_string('Gérard', 'ProperName'), 'Gérard') self.assertEqual(tag._convert_to_string(u'Gérard', 'ProperName'), 'Gérard') self.assertEqual(tag._convert_to_string(u'Python Software Foundation', 'ProperName'), 'Python Software Foundation') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, None, 'ProperName') def test_convert_to_python_text(self): # Valid values tag = XmpTag('Xmp.dc.source') self.assertEqual(tag.type, 'Text') self.assertEqual(tag._convert_to_python('Some text.', 'Text'), u'Some text.') self.assertEqual(tag._convert_to_python('Some text with exotic chàräctérʐ.', 'Text'), u'Some text with exotic chàräctérʐ.') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_python, None, 'Text') def test_convert_to_string_text(self): # Valid values tag = XmpTag('Xmp.dc.source') self.assertEqual(tag.type, 'Text') self.assertEqual(tag._convert_to_string(u'Some text', 'Text'), 'Some text') self.assertEqual(tag._convert_to_string(u'Some text with exotic chàräctérʐ.', 'Text'), 'Some text with exotic chàräctérʐ.') self.assertEqual(tag._convert_to_string('Some text with exotic chàräctérʐ.', 'Text'), 'Some text with exotic chàräctérʐ.') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, None, 'Text') def test_convert_to_python_uri(self): # Valid values tag = XmpTag('Xmp.xmpMM.DocumentID') self.assertEqual(tag.type, 'URI') self.assertEqual(tag._convert_to_python('http://example.com', 'URI'), 'http://example.com') self.assertEqual(tag._convert_to_python('https://example.com', 'URI'), 'https://example.com') self.assertEqual(tag._convert_to_python('http://localhost:8000/resource', 'URI'), 'http://localhost:8000/resource') self.assertEqual(tag._convert_to_python('uuid:9A3B7F52214211DAB6308A7391270C13', 'URI'), 'uuid:9A3B7F52214211DAB6308A7391270C13') def test_convert_to_string_uri(self): # Valid values tag = XmpTag('Xmp.xmpMM.DocumentID') self.assertEqual(tag.type, 'URI') self.assertEqual(tag._convert_to_string('http://example.com', 'URI'), 'http://example.com') self.assertEqual(tag._convert_to_string(u'http://example.com', 'URI'), 'http://example.com') self.assertEqual(tag._convert_to_string('https://example.com', 'URI'), 'https://example.com') self.assertEqual(tag._convert_to_string('http://localhost:8000/resource', 'URI'), 'http://localhost:8000/resource') self.assertEqual(tag._convert_to_string('uuid:9A3B7F52214211DAB6308A7391270C13', 'URI'), 'uuid:9A3B7F52214211DAB6308A7391270C13') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, None, 'URI') def test_convert_to_python_url(self): # Valid values tag = XmpTag('Xmp.xmp.BaseURL') self.assertEqual(tag.type, 'URL') self.assertEqual(tag._convert_to_python('http://example.com', 'URL'), 'http://example.com') self.assertEqual(tag._convert_to_python('https://example.com', 'URL'), 'https://example.com') self.assertEqual(tag._convert_to_python('http://localhost:8000/resource', 'URL'), 'http://localhost:8000/resource') def test_convert_to_string_url(self): # Valid values tag = XmpTag('Xmp.xmp.BaseURL') self.assertEqual(tag.type, 'URL') self.assertEqual(tag._convert_to_string('http://example.com', 'URL'), 'http://example.com') self.assertEqual(tag._convert_to_string(u'http://example.com', 'URL'), 'http://example.com') self.assertEqual(tag._convert_to_string('https://example.com', 'URL'), 'https://example.com') self.assertEqual(tag._convert_to_string('http://localhost:8000/resource', 'URL'), 'http://localhost:8000/resource') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, None, 'URL') def test_convert_to_python_rational(self): # Valid values tag = XmpTag('Xmp.xmpDM.videoPixelAspectRatio') self.assertEqual(tag.type, 'Rational') self.assertEqual(tag._convert_to_python('5/3', 'Rational'), make_fraction(5, 3)) self.assertEqual(tag._convert_to_python('-5/3', 'Rational'), make_fraction(-5, 3)) # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_python, 'invalid', 'Rational') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '5 / 3', 'Rational') self.failUnlessRaises(XmpValueError, tag._convert_to_python, '5/-3', 'Rational') def test_convert_to_string_rational(self): # Valid values tag = XmpTag('Xmp.xmpDM.videoPixelAspectRatio') self.assertEqual(tag.type, 'Rational') self.assertEqual(tag._convert_to_string(make_fraction(5, 3), 'Rational'), '5/3') self.assertEqual(tag._convert_to_string(make_fraction(-5, 3), 'Rational'), '-5/3') # Invalid values self.failUnlessRaises(XmpValueError, tag._convert_to_string, 'invalid', 'Rational') # TODO: other types def test_set_value(self): tag = XmpTag('Xmp.xmp.ModifyDate', datetime.datetime(2005, 9, 7, 15, 9, 51, tzinfo=FixedOffset('-', 7, 0))) old_value = tag.value tag.value = datetime.datetime(2009, 4, 22, 8, 30, 27, tzinfo=FixedOffset()) self.failIfEqual(tag.value, old_value) def test_set_value_empty(self): tag = XmpTag('Xmp.dc.creator') self.failUnlessEqual(tag.type, 'seq ProperName') self.failUnlessRaises(ValueError, setattr, tag, 'value', []) tag = XmpTag('Xmp.dc.title') self.failUnlessEqual(tag.type, 'Lang Alt') self.failUnlessRaises(ValueError, setattr, tag, 'value', {}) def test_set_value_incorrect_type(self): # Expecting a list of values tag = XmpTag('Xmp.dc.publisher') self.failUnlessEqual(tag.type, 'bag ProperName') self.failUnlessRaises(TypeError, setattr, tag, 'value', None) self.failUnlessRaises(TypeError, setattr, tag, 'value', 'bleh') # Expecting a dictionary mapping language codes to values tag = XmpTag('Xmp.dc.description') self.failUnlessEqual(tag.type, 'Lang Alt') self.failUnlessRaises(TypeError, setattr, tag, 'value', None) self.failUnlessRaises(TypeError, setattr, tag, 'value', ['bleh']) def test_set_value_basestring_for_langalt(self): tag = XmpTag('Xmp.dc.description') self.failUnlessEqual(tag.type, 'Lang Alt') tag.value = 'bleh' self.failUnlessEqual(tag.value, {'x-default': 'bleh'}) class TestXmpNamespaces(unittest.TestCase): def setUp(self): self.metadata = ImageMetadata.from_buffer(EMPTY_JPG_DATA) self.metadata.read() def test_not_registered(self): self.assertEqual(len(self.metadata.xmp_keys), 0) key = 'Xmp.foo.bar' value = 'foobar' self.assertRaises(KeyError, self.metadata.__setitem__, key, value) def test_name_must_end_with_slash(self): self.assertRaises(ValueError, register_namespace, 'foobar', 'foo') self.assertRaises(ValueError, unregister_namespace, 'foobar') def test_cannot_register_builtin(self): self.assertRaises(KeyError, register_namespace, 'foobar/', 'dc') def test_cannot_register_twice(self): name = 'foobar/' prefix = 'boo' register_namespace(name, prefix) self.assertRaises(KeyError, register_namespace, name, prefix) def test_register_and_set(self): register_namespace('foobar/', 'bar') key = 'Xmp.bar.foo' value = 'foobar' self.metadata[key] = value self.assert_(key in self.metadata.xmp_keys) def test_can_only_set_text_values(self): # At the moment custom namespaces only support setting simple text # values. register_namespace('foobar/', 'far') key = 'Xmp.far.foo' value = datetime.date.today() self.assertRaises(XmpValueError, self.metadata.__setitem__, key, value) value = datetime.datetime.now() self.assertRaises(XmpValueError, self.metadata.__setitem__, key, value) value = ['foo', 'bar'] self.assertRaises(XmpValueError, self.metadata.__setitem__, key, value) value = {'x-default': 'foo', 'fr-FR': 'bar'} self.assertRaises(XmpValueError, self.metadata.__setitem__, key, value) value = 'simple text value' self.metadata[key] = value def test_cannot_unregister_builtin(self): name = 'http://purl.org/dc/elements/1.1/' # DC builtin namespace self.assertRaises(KeyError, unregister_namespace, name) def test_cannot_unregister_inexistent(self): name = 'boofar/' self.assertRaises(KeyError, unregister_namespace, name) def test_cannot_unregister_twice(self): name = 'bleh/' prefix = 'ble' register_namespace(name, prefix) unregister_namespace(name) self.assertRaises(KeyError, unregister_namespace, name) def test_unregister(self): name = 'blah/' prefix = 'bla' register_namespace(name, prefix) unregister_namespace(name) def test_unregister_invalidates_keys_in_ns(self): name = 'blih/' prefix = 'bli' register_namespace(name, prefix) key = 'Xmp.%s.blu' % prefix self.metadata[key] = 'foobar' self.assert_(key in self.metadata.xmp_keys) unregister_namespace(name) self.assertRaises(KeyError, self.metadata.write) def test_unregister_all_ns(self): # Unregistering all custom namespaces will always succeed, even if there # are no custom namespaces registered. unregister_namespaces() name = 'blop/' prefix = 'blo' register_namespace(name, prefix) self.metadata['Xmp.%s.bar' % prefix] = 'foobar' name2 = 'blup/' prefix2 = 'blu' register_namespace(name2, prefix2) self.metadata['Xmp.%s.bar' % prefix2] = 'foobar' unregister_namespaces() self.assertRaises(KeyError, self.metadata.__setitem__, 'Xmp.%s.baz' % prefix, 'foobaz') self.assertRaises(KeyError, self.metadata.__setitem__, 'Xmp.%s.baz' % prefix2, 'foobaz') pyexiv2-0.3.2/test/metadata.py0000664000175000017500000010606011651315372015652 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2009-2011 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** from pyexiv2.metadata import ImageMetadata from pyexiv2.exif import ExifTag from pyexiv2.iptc import IptcTag from pyexiv2.xmp import XmpTag from pyexiv2.utils import FixedOffset, make_fraction import datetime import os import tempfile import time import unittest from testutils import EMPTY_JPG_DATA class TestImageMetadata(unittest.TestCase): def setUp(self): # Create an empty image file fd, self.pathname = tempfile.mkstemp(suffix='.jpg') os.write(fd, EMPTY_JPG_DATA) os.close(fd) # Write some metadata m = ImageMetadata(self.pathname) m.read() m['Exif.Image.Make'] = 'EASTMAN KODAK COMPANY' m['Exif.Image.DateTime'] = datetime.datetime(2009, 2, 9, 13, 33, 20) m['Iptc.Application2.Caption'] = ['blabla'] m['Iptc.Application2.DateCreated'] = [datetime.date(2004, 7, 13)] m['Xmp.dc.format'] = ('image', 'jpeg') m['Xmp.dc.subject'] = ['image', 'test', 'pyexiv2'] m.comment = 'Hello World!' m.write() self.metadata = ImageMetadata(self.pathname) def tearDown(self): os.remove(self.pathname) ###################### # Test general methods ###################### def test_not_read_raises(self): # http://bugs.launchpad.net/pyexiv2/+bug/687373 self.assertRaises(IOError, self.metadata.write) self.assertRaises(IOError, getattr, self.metadata, 'dimensions') self.assertRaises(IOError, getattr, self.metadata, 'mime_type') self.assertRaises(IOError, getattr, self.metadata, 'exif_keys') self.assertRaises(IOError, getattr, self.metadata, 'iptc_keys') self.assertRaises(IOError, getattr, self.metadata, 'xmp_keys') self.assertRaises(IOError, self.metadata._get_exif_tag, 'Exif.Image.Make') self.assertRaises(IOError, self.metadata._get_iptc_tag, 'Iptc.Application2.Caption') self.assertRaises(IOError, self.metadata._get_xmp_tag, 'Xmp.dc.format') self.assertRaises(IOError, self.metadata._set_exif_tag, 'Exif.Image.Make', 'foobar') self.assertRaises(IOError, self.metadata._set_iptc_tag, 'Iptc.Application2.Caption', ['foobar']) self.assertRaises(IOError, self.metadata._set_xmp_tag, 'Xmp.dc.format', ('foo', 'bar')) self.assertRaises(IOError, self.metadata._delete_exif_tag, 'Exif.Image.Make') self.assertRaises(IOError, self.metadata._delete_iptc_tag, 'Iptc.Application2.Caption') self.assertRaises(IOError, self.metadata._delete_xmp_tag, 'Xmp.dc.format') self.assertRaises(IOError, getattr, self.metadata, 'comment') self.assertRaises(IOError, setattr, self.metadata, 'comment', 'foobar') self.assertRaises(IOError, delattr, self.metadata, 'comment') self.assertRaises(IOError, getattr, self.metadata, 'previews') other = ImageMetadata(self.pathname) self.assertRaises(IOError, self.metadata.copy, other) self.assertRaises(IOError, getattr, self.metadata, 'buffer') thumb = self.metadata.exif_thumbnail self.assertRaises(IOError, getattr, thumb, 'mime_type') self.assertRaises(IOError, getattr, thumb, 'extension') self.assertRaises(IOError, thumb.write_to_file, '/tmp/foobar.jpg') self.assertRaises(IOError, thumb.erase) self.assertRaises(IOError, thumb.set_from_file, '/tmp/foobar.jpg') self.assertRaises(IOError, getattr, thumb, 'data') self.assertRaises(IOError, setattr, thumb, 'data', EMPTY_JPG_DATA) self.assertRaises(IOError, getattr, self.metadata, 'iptc_charset') def test_read(self): self.assertRaises(IOError, getattr, self.metadata, '_image') self.metadata.read() self.failIfEqual(self.metadata._image, None) def test_read_nonexistent_file(self): metadata = ImageMetadata('idontexist') self.failUnlessRaises(IOError, metadata.read) def test_write_preserve_timestamps(self): stat = os.stat(self.pathname) atime = round(stat.st_atime) mtime = round(stat.st_mtime) metadata = ImageMetadata(self.pathname) metadata.read() metadata.comment = 'Yellow Submarine' time.sleep(1.1) metadata.write(preserve_timestamps=True) stat2 = os.stat(self.pathname) atime2 = round(stat2.st_atime) mtime2 = round(stat2.st_mtime) self.failUnlessEqual(atime2, atime) self.failUnlessEqual(mtime2, mtime) def test_write_dont_preserve_timestamps(self): stat = os.stat(self.pathname) atime = round(stat.st_atime) mtime = round(stat.st_mtime) metadata = ImageMetadata(self.pathname) metadata.read() metadata.comment = 'Yellow Submarine' time.sleep(1.1) metadata.write() stat2 = os.stat(self.pathname) atime2 = round(stat2.st_atime) mtime2 = round(stat2.st_mtime) # It is not safe to assume that atime will have been modified when the # file has been read, as it may depend on mount options (e.g. noatime, # relatime). # See discussion at http://bugs.launchpad.net/pyexiv2/+bug/624999. #self.failIfEqual(atime2, atime) self.failIfEqual(mtime2, mtime) metadata.comment = 'Yesterday' time.sleep(1.1) metadata.write(preserve_timestamps=True) stat3 = os.stat(self.pathname) atime3 = round(stat3.st_atime) mtime3 = round(stat3.st_mtime) self.failUnlessEqual(atime3, atime2) self.failUnlessEqual(mtime3, mtime2) ########################### # Test EXIF-related methods ########################### def test_exif_keys(self): self.metadata.read() self.assertEqual(self.metadata._keys['exif'], None) keys = self.metadata.exif_keys self.assertEqual(len(keys), 2) self.assertEqual(self.metadata._keys['exif'], keys) def test_get_exif_tag(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Get an existing tag key = 'Exif.Image.Make' tag = self.metadata._get_exif_tag(key) self.assert_(isinstance(tag, ExifTag)) self.assertEqual(self.metadata._tags['exif'][key], tag) # Try to get an nonexistent tag key = 'Exif.Photo.Sharpness' self.failUnlessRaises(KeyError, self.metadata._get_exif_tag, key) def test_set_exif_tag_wrong(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Try to set a tag with wrong type tag = 'Not an exif tag' self.failUnlessRaises(TypeError, self.metadata._set_exif_tag, tag) self.assertEqual(self.metadata._tags['exif'], {}) def test_set_exif_tag_create(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Create a new tag tag = ExifTag('Exif.Thumbnail.Orientation', 1) self.assert_(tag.key not in self.metadata.exif_keys) self.metadata._set_exif_tag(tag.key, tag) self.assert_(tag.key in self.metadata.exif_keys) self.assertEqual(self.metadata._tags['exif'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._exifKeys()) self.assertEqual(self.metadata._image._getExifTag(tag.key)._getRawValue(), tag.raw_value) def test_set_exif_tag_overwrite(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Overwrite an existing tag tag = ExifTag('Exif.Image.DateTime', datetime.datetime(2009, 3, 20, 20, 32, 0)) self.metadata._set_exif_tag(tag.key, tag) self.assertEqual(self.metadata._tags['exif'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._exifKeys()) self.assertEqual(self.metadata._image._getExifTag(tag.key)._getRawValue(), tag.raw_value) def test_set_exif_tag_overwrite_already_cached(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Overwrite an existing tag already cached key = 'Exif.Image.Make' tag = self.metadata._get_exif_tag(key) self.assertEqual(self.metadata._tags['exif'][key], tag) new_tag = ExifTag(key, 'World Company') self.metadata._set_exif_tag(key, new_tag) self.assertEqual(self.metadata._tags['exif'], {key: new_tag}) self.assert_(key in self.metadata._image._exifKeys()) self.assertEqual(self.metadata._image._getExifTag(key)._getRawValue(), new_tag.raw_value) def test_set_exif_tag_direct_value_assignment(self): self.metadata.read() self.assertEqual(self.metadata._tags['exif'], {}) # Direct value assignment: pass a value instead of a fully-formed tag key = 'Exif.Thumbnail.Orientation' value = 1 self.metadata._set_exif_tag(key, value) self.assert_(key in self.metadata.exif_keys) self.assert_(key in self.metadata._image._exifKeys()) tag = self.metadata._get_exif_tag(key) self.assertEqual(tag.value, value) self.assertEqual(self.metadata._tags['exif'], {key: tag}) self.assertEqual(self.metadata._image._getExifTag(key)._getRawValue(), tag.raw_value) def test_delete_exif_tag_inexistent(self): self.metadata.read() key = 'Exif.Image.Artist' self.failUnlessRaises(KeyError, self.metadata._delete_exif_tag, key) def test_delete_exif_tag_not_cached(self): self.metadata.read() key = 'Exif.Image.DateTime' self.assertEqual(self.metadata._tags['exif'], {}) self.assert_(key in self.metadata.exif_keys) self.metadata._delete_exif_tag(key) self.assertEqual(self.metadata._tags['exif'], {}) self.failIf(key in self.metadata.exif_keys) def test_delete_exif_tag_cached(self): self.metadata.read() key = 'Exif.Image.DateTime' self.assert_(key in self.metadata.exif_keys) tag = self.metadata._get_exif_tag(key) self.assertEqual(self.metadata._tags['exif'][key], tag) self.metadata._delete_exif_tag(key) self.assertEqual(self.metadata._tags['exif'], {}) self.failIf(key in self.metadata.exif_keys) ########################### # Test IPTC-related methods ########################### def test_iptc_keys(self): self.metadata.read() self.assertEqual(self.metadata._keys['iptc'], None) keys = self.metadata.iptc_keys self.assertEqual(len(keys), 2) self.assertEqual(self.metadata._keys['iptc'], keys) def test_get_iptc_tag(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Get an existing tag key = 'Iptc.Application2.DateCreated' tag = self.metadata._get_iptc_tag(key) self.assert_(isinstance(tag, IptcTag)) self.assertEqual(self.metadata._tags['iptc'][key], tag) # Try to get an nonexistent tag key = 'Iptc.Application2.Copyright' self.failUnlessRaises(KeyError, self.metadata._get_iptc_tag, key) def test_set_iptc_tag_wrong(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Try to set a tag with wrong type tag = 'Not an iptc tag' self.failUnlessRaises(TypeError, self.metadata._set_iptc_tag, tag) self.assertEqual(self.metadata._tags['iptc'], {}) def test_set_iptc_tag_create(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Create a new tag tag = IptcTag('Iptc.Application2.Writer', ['Nobody']) self.assert_(tag.key not in self.metadata.iptc_keys) self.metadata._set_iptc_tag(tag.key, tag) self.assert_(tag.key in self.metadata.iptc_keys) self.assertEqual(self.metadata._tags['iptc'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._iptcKeys()) self.assertEqual(self.metadata._image._getIptcTag(tag.key)._getRawValues(), tag.raw_value) def test_set_iptc_tag_overwrite(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Overwrite an existing tag tag = IptcTag('Iptc.Application2.Caption', ['A picture.']) self.metadata._set_iptc_tag(tag.key, tag) self.assertEqual(self.metadata._tags['iptc'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._iptcKeys()) self.assertEqual(self.metadata._image._getIptcTag(tag.key)._getRawValues(), tag.raw_value) def test_set_iptc_tag_overwrite_already_cached(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Overwrite an existing tag already cached key = 'Iptc.Application2.Caption' tag = self.metadata._get_iptc_tag(key) self.assertEqual(self.metadata._tags['iptc'][key], tag) new_tag = IptcTag(key, ['A picture.']) self.metadata._set_iptc_tag(key, new_tag) self.assertEqual(self.metadata._tags['iptc'], {key: new_tag}) self.assert_(key in self.metadata._image._iptcKeys()) self.assertEqual(self.metadata._image._getIptcTag(key)._getRawValues(), new_tag.raw_value) def test_set_iptc_tag_direct_value_assignment(self): self.metadata.read() self.assertEqual(self.metadata._tags['iptc'], {}) # Direct value assignment: pass a value instead of a fully-formed tag key = 'Iptc.Application2.Writer' values = ['Nobody'] self.metadata._set_iptc_tag(key, values) self.assert_(key in self.metadata.iptc_keys) self.assert_(key in self.metadata._image._iptcKeys()) tag = self.metadata._get_iptc_tag(key) self.assertEqual(tag.value, values) self.assertEqual(self.metadata._tags['iptc'], {key: tag}) self.assertEqual(self.metadata._image._getIptcTag(key)._getRawValues(), tag.raw_value) def test_delete_iptc_tag_inexistent(self): self.metadata.read() key = 'Iptc.Application2.LocationCode' self.failUnlessRaises(KeyError, self.metadata._delete_iptc_tag, key) def test_delete_iptc_tag_not_cached(self): self.metadata.read() key = 'Iptc.Application2.Caption' self.assertEqual(self.metadata._tags['iptc'], {}) self.assert_(key in self.metadata.iptc_keys) self.metadata._delete_iptc_tag(key) self.assertEqual(self.metadata._tags['iptc'], {}) self.failIf(key in self.metadata.iptc_keys) def test_delete_iptc_tag_cached(self): self.metadata.read() key = 'Iptc.Application2.Caption' self.assert_(key in self.metadata.iptc_keys) tag = self.metadata._get_iptc_tag(key) self.assertEqual(self.metadata._tags['iptc'][key], tag) self.metadata._delete_iptc_tag(key) self.assertEqual(self.metadata._tags['iptc'], {}) self.failIf(key in self.metadata.iptc_keys) ########################## # Test XMP-related methods ########################## def test_xmp_keys(self): self.metadata.read() self.assertEqual(self.metadata._keys['xmp'], None) keys = self.metadata.xmp_keys self.assertEqual(len(keys), 2) self.assertEqual(self.metadata._keys['xmp'], keys) def test_get_xmp_tag(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Get an existing tag key = 'Xmp.dc.subject' tag = self.metadata._get_xmp_tag(key) self.assert_(isinstance(tag, XmpTag)) self.assertEqual(self.metadata._tags['xmp'][key], tag) # Try to get an nonexistent tag key = 'Xmp.xmp.Label' self.failUnlessRaises(KeyError, self.metadata._get_xmp_tag, key) def test_set_xmp_tag_wrong(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Try to set a tag with wrong type tag = 'Not an xmp tag' self.failUnlessRaises(TypeError, self.metadata._set_xmp_tag, tag) self.assertEqual(self.metadata._tags['xmp'], {}) def test_set_xmp_tag_create(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Create a new tag tag = XmpTag('Xmp.dc.title', {'x-default': 'This is not a title', 'fr-FR': "Ceci n'est pas un titre"}) self.assert_(tag.key not in self.metadata.xmp_keys) self.metadata._set_xmp_tag(tag.key, tag) self.assert_(tag.key in self.metadata.xmp_keys) self.assertEqual(self.metadata._tags['xmp'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._xmpKeys()) self.assertEqual(self.metadata._image._getXmpTag(tag.key)._getLangAltValue(), tag.raw_value) def test_set_xmp_tag_overwrite(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Overwrite an existing tag tag = XmpTag('Xmp.dc.format', ('image', 'png')) self.metadata._set_xmp_tag(tag.key, tag) self.assertEqual(self.metadata._tags['xmp'], {tag.key: tag}) self.assert_(tag.key in self.metadata._image._xmpKeys()) self.assertEqual(self.metadata._image._getXmpTag(tag.key)._getTextValue(), tag.raw_value) def test_set_xmp_tag_overwrite_already_cached(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Overwrite an existing tag already cached key = 'Xmp.dc.subject' tag = self.metadata._get_xmp_tag(key) self.assertEqual(self.metadata._tags['xmp'][key], tag) new_tag = XmpTag(key, ['hello', 'world']) self.metadata._set_xmp_tag(key, new_tag) self.assertEqual(self.metadata._tags['xmp'], {key: new_tag}) self.assert_(key in self.metadata._image._xmpKeys()) self.assertEqual(self.metadata._image._getXmpTag(key)._getArrayValue(), new_tag.raw_value) def test_set_xmp_tag_direct_value_assignment(self): self.metadata.read() self.assertEqual(self.metadata._tags['xmp'], {}) # Direct value assignment: pass a value instead of a fully-formed tag key = 'Xmp.dc.title' value = {'x-default': 'This is not a title', 'fr-FR': "Ceci n'est pas un titre"} self.metadata._set_xmp_tag(key, value) self.assert_(key in self.metadata.xmp_keys) self.assert_(key in self.metadata._image._xmpKeys()) tag = self.metadata._get_xmp_tag(key) self.assertEqual(tag.value, value) self.assertEqual(self.metadata._tags['xmp'], {key: tag}) self.assertEqual(self.metadata._image._getXmpTag(key)._getLangAltValue(), tag.raw_value) def test_delete_xmp_tag_inexistent(self): self.metadata.read() key = 'Xmp.xmp.CreatorTool' self.failUnlessRaises(KeyError, self.metadata._delete_xmp_tag, key) def test_delete_xmp_tag_not_cached(self): self.metadata.read() key = 'Xmp.dc.subject' self.assertEqual(self.metadata._tags['xmp'], {}) self.assert_(key in self.metadata.xmp_keys) self.metadata._delete_xmp_tag(key) self.assertEqual(self.metadata._tags['xmp'], {}) self.failIf(key in self.metadata.xmp_keys) def test_delete_xmp_tag_cached(self): self.metadata.read() key = 'Xmp.dc.subject' self.assert_(key in self.metadata.xmp_keys) tag = self.metadata._get_xmp_tag(key) self.assertEqual(self.metadata._tags['xmp'][key], tag) self.metadata._delete_xmp_tag(key) self.assertEqual(self.metadata._tags['xmp'], {}) self.failIf(key in self.metadata.xmp_keys) ########################### # Test dictionary interface ########################### def test_getitem(self): self.metadata.read() # Get existing tags key = 'Exif.Image.DateTime' tag = self.metadata[key] self.assert_(isinstance(tag, ExifTag)) key = 'Iptc.Application2.Caption' tag = self.metadata[key] self.assert_(isinstance(tag, IptcTag)) key = 'Xmp.dc.format' tag = self.metadata[key] self.assert_(isinstance(tag, XmpTag)) # Try to get nonexistent tags keys = ('Exif.Image.SamplesPerPixel', 'Iptc.Application2.FixtureId', 'Xmp.xmp.Rating', 'Wrong.Noluck.Raise') for key in keys: self.failUnlessRaises(KeyError, self.metadata.__getitem__, key) def test_setitem(self): self.metadata.read() # Set new tags key = 'Exif.Photo.ExposureBiasValue' tag = ExifTag(key, make_fraction(0, 3)) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['exif']) self.failUnlessEqual(self.metadata._tags['exif'][key], tag) key = 'Iptc.Application2.City' tag = IptcTag(key, ['Barcelona']) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['iptc']) self.failUnlessEqual(self.metadata._tags['iptc'][key], tag) key = 'Xmp.dc.description' tag = XmpTag(key, {'x-default': 'Sunset picture.'}) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['xmp']) self.failUnlessEqual(self.metadata._tags['xmp'][key], tag) # Replace existing tags key = 'Exif.Photo.ExifVersion' tag = ExifTag(key, '0220') self.metadata[key] = tag self.failUnless(key in self.metadata._tags['exif']) self.failUnlessEqual(self.metadata._tags['exif'][key], tag) key = 'Iptc.Application2.Caption' tag = IptcTag(key, ['Sunset on Barcelona.']) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['iptc']) self.failUnlessEqual(self.metadata._tags['iptc'][key], tag) key = 'Xmp.dc.subject' tag = XmpTag(key, ['sunset', 'Barcelona', 'beautiful', 'beach']) self.metadata[key] = tag self.failUnless(key in self.metadata._tags['xmp']) self.failUnlessEqual(self.metadata._tags['xmp'][key], tag) def test_delitem(self): self.metadata.read() # Delete existing tags key = 'Exif.Image.Make' del self.metadata[key] self.failIf(key in self.metadata._keys['exif']) self.failIf(key in self.metadata._tags['exif']) key = 'Iptc.Application2.Caption' del self.metadata[key] self.failIf(key in self.metadata._keys['iptc']) self.failIf(key in self.metadata._tags['iptc']) key = 'Xmp.dc.subject' del self.metadata[key] self.failIf(key in self.metadata._keys['xmp']) self.failIf(key in self.metadata._tags['xmp']) # Try to delete nonexistent tags keys = ('Exif.Image.SamplesPerPixel', 'Iptc.Application2.FixtureId', 'Xmp.xmp.Rating', 'Wrong.Noluck.Raise') for key in keys: self.failUnlessRaises(KeyError, self.metadata.__delitem__, key) def test_replace_tag_by_itself(self): # Test that replacing an existing tag by itself # doesn’t result in an ugly segmentation fault # (see https://bugs.launchpad.net/pyexiv2/+bug/622739). self.metadata.read() keys = self.metadata.exif_keys + \ self.metadata.iptc_keys + \ self.metadata.xmp_keys for key in keys: self.metadata[key] = self.metadata[key] def test_nonexistent_tag_family(self): self.metadata.read() key = 'Bleh.Image.DateTime' self.failUnlessRaises(KeyError, self.metadata.__getitem__, key) self.failUnlessRaises(KeyError, self.metadata.__setitem__, key, datetime.date.today()) self.failUnlessRaises(KeyError, self.metadata.__delitem__, key) ########################## # Test the image comment # ########################## def test_get_comment(self): self.metadata.read() self.failUnlessEqual(self.metadata.comment, 'Hello World!') def test_set_comment(self): self.metadata.read() comment = 'Welcome to the real world.' self.metadata.comment = comment self.failUnlessEqual(self.metadata.comment, comment) self.metadata.comment = None self.failUnlessEqual(self.metadata.comment, '') def test_delete_comment(self): self.metadata.read() del self.metadata.comment self.failUnlessEqual(self.metadata.comment, '') #################### # Test metadata copy #################### def _set_up_other(self): self.other = ImageMetadata.from_buffer(EMPTY_JPG_DATA) def test_copy_metadata(self): self.metadata.read() self._set_up_other() self.other.read() families = ('exif', 'iptc', 'xmp') for family in families: self.failUnlessEqual(getattr(self.other, '%s_keys' % family), []) self.metadata.copy(self.other) for family in ('exif', 'iptc', 'xmp'): self.failUnlessEqual(self.other._keys[family], None) self.failUnlessEqual(self.other._tags[family], {}) keys = getattr(self.metadata, '%s_keys' % family) self.failUnlessEqual(getattr(self.other._image, '_%sKeys' % family)(), keys) self.failUnlessEqual(getattr(self.other, '%s_keys' % family), keys) for key in self.metadata.exif_keys: self.failUnlessEqual(self.metadata[key].value, self.other[key].value) for key in self.metadata.iptc_keys: self.failUnlessEqual(self.metadata[key].value, self.other[key].value) for key in self.metadata.xmp_keys: self.failUnlessEqual(self.metadata[key].value, self.other[key].value) self.failUnlessEqual(self.metadata.comment, self.other.comment) ############################# # Test MutableMapping methods ############################# def _set_up_clean(self): self.clean = ImageMetadata.from_buffer(EMPTY_JPG_DATA) def test_mutablemapping(self): self._set_up_clean() self.clean.read() self.assertEqual(len(self.clean), 0) self.assertTrue('Exif.Image.DateTimeOriginal' not in self.clean) key = 'Exif.Image.DateTimeOriginal' correctDate = datetime.datetime(2007,03,11) incorrectDate = datetime.datetime(2009,03,25) tag_date = ExifTag(key,correctDate) false_tag_date = ExifTag(key,incorrectDate) self.clean[key] = tag_date self.assertEqual(len(self.clean), 1) self.assertTrue('Exif.Image.DateTimeOriginal' in self.clean) self.assertEqual(self.clean.get('Exif.Image.DateTimeOriginal', false_tag_date), tag_date) self.assertEqual(self.clean.get('Exif.Image.DateTime', tag_date), tag_date) key = 'Exif.Photo.UserComment' tag = ExifTag(key,'UserComment') self.clean[key] = tag key = 'Iptc.Application2.Caption' tag = IptcTag(key,['Caption']) self.clean[key] = tag key = 'Xmp.dc.subject' tag = XmpTag(key, ['subject', 'values']) self.clean[key] = tag self.assertTrue('Exif.Photo.UserComment' in self.clean) self.assertTrue('Iptc.Application2.Caption' in self.clean) self.assertTrue('Xmp.dc.subject' in self.clean) self.clean.clear() self.assertEqual(len(self.clean), 0) self.assertTrue('Exif.Photo.UserComment' not in self.clean) self.assertTrue('Iptc.Application2.Caption' not in self.clean) self.assertTrue('Xmp.dc.subject' not in self.clean) ########################### # Test the EXIF thumbnail # ########################### def _test_thumbnail_tags(self, there): keys = ('Exif.Thumbnail.Compression', 'Exif.Thumbnail.JPEGInterchangeFormat', 'Exif.Thumbnail.JPEGInterchangeFormatLength') for key in keys: self.assertEqual(key in self.metadata.exif_keys, there) def test_no_exif_thumbnail(self): self.metadata.read() thumb = self.metadata.exif_thumbnail self.assertEqual(thumb.mime_type, '') self.assertEqual(thumb.extension, '') self.assertEqual(thumb.data, '') self._test_thumbnail_tags(False) def test_set_exif_thumbnail_from_data(self): self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail thumb.data = EMPTY_JPG_DATA self.assertEqual(thumb.mime_type, 'image/jpeg') self.assertEqual(thumb.extension, '.jpg') self.assertEqual(thumb.data, EMPTY_JPG_DATA) self._test_thumbnail_tags(True) def test_set_exif_thumbnail_from_file(self): fd, pathname = tempfile.mkstemp(suffix='.jpg') os.write(fd, EMPTY_JPG_DATA) os.close(fd) self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail thumb.set_from_file(pathname) os.remove(pathname) self.assertEqual(thumb.mime_type, 'image/jpeg') self.assertEqual(thumb.extension, '.jpg') self.assertEqual(thumb.data, EMPTY_JPG_DATA) self._test_thumbnail_tags(True) def test_write_exif_thumbnail_to_file(self): self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail thumb.data = EMPTY_JPG_DATA fd, pathname = tempfile.mkstemp() os.close(fd) os.remove(pathname) thumb.write_to_file(pathname) pathname = pathname + thumb.extension fd = open(pathname, 'rb') self.assertEqual(fd.read(), EMPTY_JPG_DATA) fd.close() os.remove(pathname) def test_erase_exif_thumbnail(self): self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail thumb.data = EMPTY_JPG_DATA self.assertEqual(thumb.mime_type, 'image/jpeg') self.assertEqual(thumb.extension, '.jpg') self.assertEqual(thumb.data, EMPTY_JPG_DATA) self._test_thumbnail_tags(True) thumb.erase() self.assertEqual(thumb.mime_type, '') self.assertEqual(thumb.extension, '') self.assertEqual(thumb.data, '') self._test_thumbnail_tags(False) def test_set_exif_thumbnail_from_invalid_data(self): # No check on the format of the buffer is performed, therefore it will # always work. self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail thumb.data = 'invalid' self.assertEqual(thumb.mime_type, 'image/jpeg') self._test_thumbnail_tags(True) def test_set_exif_thumbnail_from_inexistent_file(self): self.metadata.read() self._test_thumbnail_tags(False) thumb = self.metadata.exif_thumbnail fd, pathname = tempfile.mkstemp() os.close(fd) os.remove(pathname) self.failUnlessRaises(IOError, thumb.set_from_file, pathname) self._test_thumbnail_tags(False) def test_exif_thumbnail_is_preview(self): self.metadata.read() self._test_thumbnail_tags(False) self.assertEqual(len(self.metadata.previews), 0) thumb = self.metadata.exif_thumbnail thumb.data = EMPTY_JPG_DATA self._test_thumbnail_tags(True) self.assertEqual(len(self.metadata.previews), 1) preview = self.metadata.previews[0] self.assertEqual(thumb.mime_type, preview.mime_type) self.assertEqual(thumb.extension, preview.extension) self.assertEqual(thumb.data, preview.data) ######################### # Test the IPTC charset # ######################### def test_guess_iptc_charset(self): # If no charset is defined, exiv2 guesses it from the encoding of the # metadata. self.metadata.read() self.assertEqual(self.metadata.iptc_charset, 'ascii') self.metadata['Iptc.Application2.City'] = [u'Córdoba'] self.assertEqual(self.metadata.iptc_charset, 'utf-8') def test_set_iptc_charset_utf8(self): self.metadata.read() self.assert_('Iptc.Envelope.CharacterSet' not in self.metadata.iptc_keys) self.assertEqual(self.metadata.iptc_charset, 'ascii') values = ('utf-8', 'utf8', 'u8', 'utf', 'utf_8') for value in values: self.metadata.iptc_charset = value self.assertEqual(self.metadata.iptc_charset, 'utf-8') self.metadata.iptc_charset = value.upper() self.assertEqual(self.metadata.iptc_charset, 'utf-8') def test_set_invalid_iptc_charset(self): self.metadata.read() self.assert_('Iptc.Envelope.CharacterSet' not in self.metadata.iptc_keys) values = ('invalid', 'utf-9', '3.14') for value in values: self.assertRaises(ValueError, self.metadata.__setattr__, 'iptc_charset', value) def test_set_unhandled_iptc_charset(self): # At the moment, the only charset handled is UTF-8. self.metadata.read() self.assert_('Iptc.Envelope.CharacterSet' not in self.metadata.iptc_keys) values = ('ascii', 'iso8859_15', 'shift_jis') for value in values: self.assertRaises(ValueError, self.metadata.__setattr__, 'iptc_charset', value) def test_delete_iptc_charset(self): self.metadata.read() key = 'Iptc.Envelope.CharacterSet' self.assertEqual(self.metadata.iptc_charset, 'ascii') self.assert_(key not in self.metadata.iptc_keys) del self.metadata.iptc_charset self.assertEqual(self.metadata.iptc_charset, 'ascii') self.assert_(key not in self.metadata.iptc_keys) self.metadata.iptc_charset = 'utf-8' self.assertEqual(self.metadata.iptc_charset, 'utf-8') self.assert_(key in self.metadata.iptc_keys) del self.metadata.iptc_charset self.assertEqual(self.metadata.iptc_charset, 'ascii') self.assert_(key not in self.metadata.iptc_keys) self.metadata.iptc_charset = 'utf-8' self.assertEqual(self.metadata.iptc_charset, 'utf-8') self.assert_(key in self.metadata.iptc_keys) self.metadata.iptc_charset = None self.assertEqual(self.metadata.iptc_charset, 'ascii') self.assert_(key not in self.metadata.iptc_keys) pyexiv2-0.3.2/test/iptc.py0000664000175000017500000003145111651315372015032 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2009-2011 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import unittest from pyexiv2.iptc import IptcTag, IptcValueError from pyexiv2.utils import FixedOffset import datetime import warnings # Optional dependency on python-tz, more tests can be run if it is installed try: import pytz except ImportError: pytz = None class TestIptcTag(unittest.TestCase): def test_convert_to_python_short(self): # Valid values tag = IptcTag('Iptc.Envelope.FileFormat') self.assertEqual(tag.type, 'Short') self.assertEqual(tag._convert_to_python('23'), 23) self.assertEqual(tag._convert_to_python('+5628'), 5628) self.assertEqual(tag._convert_to_python('-4'), -4) # Invalid values self.failUnlessRaises(IptcValueError, tag._convert_to_python, 'abc') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '5,64') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '47.0001') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '1E3') def test_convert_to_string_short(self): # Valid values tag = IptcTag('Iptc.Envelope.FileFormat') self.assertEqual(tag.type, 'Short') self.assertEqual(tag._convert_to_string(123), '123') self.assertEqual(tag._convert_to_string(-57), '-57') # Invalid values self.failUnlessRaises(IptcValueError, tag._convert_to_string, 'invalid') self.failUnlessRaises(IptcValueError, tag._convert_to_string, 3.14) def test_convert_to_python_string(self): # Valid values tag = IptcTag('Iptc.Application2.Subject') self.assertEqual(tag.type, 'String') self.assertEqual(tag._convert_to_python('Some text.'), 'Some text.') self.assertEqual(tag._convert_to_python('Some text with exotic chàräctérʐ.'), 'Some text with exotic chàräctérʐ.') def test_convert_to_string_string(self): # Valid values tag = IptcTag('Iptc.Application2.Subject') self.assertEqual(tag.type, 'String') self.assertEqual(tag._convert_to_string(u'Some text'), 'Some text') self.assertEqual(tag._convert_to_string(u'Some text with exotic chàräctérʐ.'), 'Some text with exotic chàräctérʐ.') self.assertEqual(tag._convert_to_string('Some text with exotic chàräctérʐ.'), 'Some text with exotic chàräctérʐ.') # Invalid values self.failUnlessRaises(IptcValueError, tag._convert_to_string, None) def test_convert_to_python_date(self): # Valid values tag = IptcTag('Iptc.Envelope.DateSent') self.assertEqual(tag.type, 'Date') self.assertEqual(tag._convert_to_python('1999-10-13'), datetime.date(1999, 10, 13)) # Invalid values self.failUnlessRaises(IptcValueError, tag._convert_to_python, 'invalid') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '11/10/1983') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '-1000') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '2009-02') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '2009-10-32') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '2009-02-24T22:12:54') def test_convert_to_string_date(self): # Valid values tag = IptcTag('Iptc.Envelope.DateSent') self.assertEqual(tag.type, 'Date') self.assertEqual(tag._convert_to_string(datetime.date(2009, 2, 4)), '2009-02-04') self.assertEqual(tag._convert_to_string(datetime.date(1899, 12, 31)), '1899-12-31') self.assertEqual(tag._convert_to_string(datetime.datetime(1999, 10, 13)), '1999-10-13') self.assertEqual(tag._convert_to_string(datetime.datetime(2009, 2, 4)), '2009-02-04') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31)), '1899-12-31') self.assertEqual(tag._convert_to_string(datetime.datetime(2009, 2, 4, 10, 52, 37)), '2009-02-04') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59)), '1899-12-31') # Invalid values self.failUnlessRaises(IptcValueError, tag._convert_to_string, 'invalid') self.failUnlessRaises(IptcValueError, tag._convert_to_string, None) def test_convert_to_python_time(self): # Valid values tag = IptcTag('Iptc.Envelope.TimeSent') self.assertEqual(tag.type, 'Time') self.assertEqual(tag._convert_to_python('05:03:54+00:00'), datetime.time(5, 3, 54, tzinfo=FixedOffset())) self.assertEqual(tag._convert_to_python('05:03:54+06:00'), datetime.time(5, 3, 54, tzinfo=FixedOffset('+', 6, 0))) self.assertEqual(tag._convert_to_python('05:03:54-10:30'), datetime.time(5, 3, 54, tzinfo=FixedOffset('-', 10, 30))) # Invalid values self.failUnlessRaises(IptcValueError, tag._convert_to_python, 'invalid') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '23:12:42') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '25:12:42+00:00') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '21:77:42+00:00') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '21:12:98+00:00') self.failUnlessRaises(IptcValueError, tag._convert_to_python, '081242+0000') def test_convert_to_string_time(self): # Valid values tag = IptcTag('Iptc.Envelope.TimeSent') self.assertEqual(tag.type, 'Time') self.assertEqual(tag._convert_to_string(datetime.time(10, 52, 4)), '10:52:04+00:00') self.assertEqual(tag._convert_to_string(datetime.time(10, 52, 4, 574)), '10:52:04+00:00') self.assertEqual(tag._convert_to_string(datetime.time(10, 52, 4, tzinfo=FixedOffset())), '10:52:04+00:00') self.assertEqual(tag._convert_to_string(datetime.time(10, 52, 4, tzinfo=FixedOffset('+', 5, 30))), '10:52:04+05:30') self.assertEqual(tag._convert_to_string(datetime.time(10, 52, 4, tzinfo=FixedOffset('-', 4, 0))), '10:52:04-04:00') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59)), '23:59:59+00:00') self.assertEqual(tag._convert_to_string(datetime.datetime(2007, 2, 7, 10, 52, 4)), '10:52:04+00:00') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59, 999)), '23:59:59+00:00') self.assertEqual(tag._convert_to_string(datetime.datetime(2007, 2, 7, 10, 52, 4, 478)), '10:52:04+00:00') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset())), '23:59:59+00:00') self.assertEqual(tag._convert_to_string(datetime.datetime(2007, 2, 7, 10, 52, 4, tzinfo=FixedOffset())), '10:52:04+00:00') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset('+', 5, 30))), '23:59:59+05:30') self.assertEqual(tag._convert_to_string(datetime.datetime(2007, 2, 7, 10, 52, 4, tzinfo=FixedOffset('+', 5, 30))), '10:52:04+05:30') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset('-', 4, 0))), '23:59:59-04:00') self.assertEqual(tag._convert_to_string(datetime.datetime(2007, 2, 7, 10, 52, 4, tzinfo=FixedOffset('-', 4, 0))), '10:52:04-04:00') # Invalid values self.failUnlessRaises(IptcValueError, tag._convert_to_string, 'invalid') def test_convert_to_string_time_with_real_timezones(self): if pytz is None: # Poor man’s test skipping. Starting with Python 2.7, decorators are # available to implement this in a cleaner fashion # (http://docs.python.org/library/unittest.html#unittest-skipping). print 'Install python-tz to run this test. Skipping.' return tag = IptcTag('Iptc.Envelope.TimeSent') self.assertEqual(tag.type, 'Time') t = pytz.timezone('UTC').localize(datetime.datetime(2011, 2, 2, 10, 52, 4)) self.assertEqual(tag._convert_to_string(t), '10:52:04+00:00') t = pytz.timezone('CET').localize(datetime.datetime(2011, 2, 2, 10, 52, 4)) self.assertEqual(tag._convert_to_string(t), '10:52:04+01:00') def test_convert_to_python_undefined(self): # Valid values tag = IptcTag('Iptc.Application2.Preview') self.assertEqual(tag.type, 'Undefined') self.assertEqual(tag._convert_to_python('Some binary data.'), 'Some binary data.') self.assertEqual(tag._convert_to_python('�lj1�eEϟ�u����ᒻ;C(�SpI]���QI�}'), '�lj1�eEϟ�u����ᒻ;C(�SpI]���QI�}') def test_convert_to_string_undefined(self): # Valid values tag = IptcTag('Iptc.Application2.Preview') self.assertEqual(tag.type, 'Undefined') self.assertEqual(tag._convert_to_string('Some binary data.'), 'Some binary data.') self.assertEqual(tag._convert_to_string('�lj1�eEϟ�u����ᒻ;C(�SpI]���QI�}'), '�lj1�eEϟ�u����ᒻ;C(�SpI]���QI�}') # Invalid values self.failUnlessRaises(IptcValueError, tag._convert_to_string, None) def test_set_single_value_raises(self): tag = IptcTag('Iptc.Application2.City', ['Seattle']) self.failUnlessRaises(TypeError, setattr, tag, 'value', 'Barcelona') def test_set_value(self): tag = IptcTag('Iptc.Application2.City', ['Seattle']) old_value = tag.value tag.value = ['Barcelona'] self.failIfEqual(tag.value, old_value) def test_set_raw_value_invalid(self): tag = IptcTag('Iptc.Envelope.DateSent') value = ['foo'] self.failUnlessRaises(ValueError, setattr, tag, 'raw_value', value) def test_set_value_non_repeatable(self): tag = IptcTag('Iptc.Application2.ReleaseDate') value = [datetime.date.today(), datetime.date.today()] self.failUnlessRaises(KeyError, setattr, tag, 'value', value) def test_deprecated_properties(self): # The .raw_values and .values properties are deprecated in favour of # .raw_value and .value. Check that they still work for backward # compatibility and that they issue a deprecation warning. # See https://launchpad.net/bugs/617557. tag = IptcTag('Iptc.Application2.City', ['Barcelona']) raw_value = tag.raw_value value = tag.value with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter('always') self.assertEqual(tag.raw_values, raw_value) self.assertEqual(len(w), 1) self.assert_(issubclass(w[-1].category, DeprecationWarning)) self.assertEqual(tag.values, value) self.assertEqual(len(w), 2) self.assert_(issubclass(w[-1].category, DeprecationWarning)) tag.raw_values = ['Madrid'] self.assertEqual(len(w), 3) self.assert_(issubclass(w[-1].category, DeprecationWarning)) tag.values = ['Madrid'] self.assertEqual(len(w), 4) self.assert_(issubclass(w[-1].category, DeprecationWarning)) pyexiv2-0.3.2/test/ReadMetadataTestCase.py0000664000175000017500000002460011651315372020041 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2008-2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Authors: Olivier Tilloy # Mark Lee # # ****************************************************************************** import pyexiv2 from pyexiv2.utils import is_fraction, make_fraction import unittest import os.path import datetime import testutils FRACTION = 'fraction' class ReadMetadataTestCase(unittest.TestCase): """ Test case on reading the metadata contained in a file. """ def check_type_and_value(self, tag, etype, evalue): if etype == FRACTION: self.assert_(is_fraction(tag.value)) else: self.assert_(isinstance(tag.value, etype)) self.assertEqual(tag.value, evalue) def check_type_and_values(self, tag, etype, evalues): for value in tag.value: self.assert_(isinstance(value, etype)) self.assertEqual(tag.value, evalues) def assertCorrectFile(self, filename, md5sum): """ Ensure that the filename and the MD5 checksum match up. """ self.assert_(testutils.CheckFileSum(filename, md5sum)) def testReadMetadata(self): """ Perform various tests on reading the metadata contained in a file. """ # Check that the reference file is not corrupted filename = os.path.join('data', 'smiley1.jpg') filepath = testutils.get_absolute_file_path(filename) md5sum = 'c066958457c685853293058f9bf129c1' self.assertCorrectFile(filepath, md5sum) # Read the image metadata image = pyexiv2.ImageMetadata(filepath) image.read() # Exhaustive tests on the values of EXIF metadata exifTags = [('Exif.Image.ImageDescription', str, 'Well it is a smiley that happens to be green'), ('Exif.Image.XResolution', FRACTION, make_fraction(72, 1)), ('Exif.Image.YResolution', FRACTION, make_fraction(72, 1)), ('Exif.Image.ResolutionUnit', int, 2), ('Exif.Image.Software', str, 'ImageReady'), ('Exif.Image.DateTime', datetime.datetime, datetime.datetime(2004, 7, 13, 21, 23, 44)), ('Exif.Image.Artist', str, 'No one'), ('Exif.Image.Copyright', str, ''), ('Exif.Image.ExifTag', long, 226L), ('Exif.Photo.Flash', int, 80), ('Exif.Photo.PixelXDimension', long, 167L), ('Exif.Photo.PixelYDimension', long, 140L)] self.assertEqual(image.exif_keys, [tag[0] for tag in exifTags]) for key, ktype, value in exifTags: self.check_type_and_value(image[key], ktype, value) # Exhaustive tests on the values of IPTC metadata iptcTags = [('Iptc.Application2.Caption', str, ['yelimS green faced dude (iptc caption)']), ('Iptc.Application2.Writer', str, ['Nobody']), ('Iptc.Application2.Byline', str, ['Its me']), ('Iptc.Application2.ObjectName', str, ['GreeenDude']), ('Iptc.Application2.DateCreated', datetime.date, [datetime.date(2004, 7, 13)]), ('Iptc.Application2.City', str, ['Seattle']), ('Iptc.Application2.ProvinceState', str, ['WA']), ('Iptc.Application2.CountryName', str, ['USA']), ('Iptc.Application2.Category', str, ['Things']), ('Iptc.Application2.Keywords', str, ['Green', 'Smiley', 'Dude']), ('Iptc.Application2.Copyright', str, ['\xa9 2004 Nobody'])] self.assertEqual(image.iptc_keys, [tag[0] for tag in iptcTags]) for key, ktype, values in iptcTags: self.check_type_and_values(image[key], ktype, values) def testReadMetadataXMP(self): filename = os.path.join('data', 'exiv2-bug540.jpg') filepath = testutils.get_absolute_file_path(filename) md5sum = '64d4b7eab1e78f1f6bfb3c966e99eef2' self.assertCorrectFile(filepath, md5sum) # Read the image metadata image = pyexiv2.ImageMetadata(filepath) image.read() xmpTags = [('Xmp.dc.creator', list, [u'Ian Britton']), ('Xmp.dc.description', dict, {u'x-default': u'Communications'}), ('Xmp.dc.rights', dict, {u'x-default': u'ian Britton - FreeFoto.com'}), ('Xmp.dc.source', unicode, u'FreeFoto.com'), ('Xmp.dc.subject', list, [u'Communications']), ('Xmp.dc.title', dict, {u'x-default': u'Communications'}), ('Xmp.exif.ApertureValue', FRACTION, make_fraction(8, 1)), ('Xmp.exif.BrightnessValue', FRACTION, make_fraction(333, 1280)), ('Xmp.exif.ColorSpace', int, 1), ('Xmp.exif.DateTimeOriginal', datetime.datetime, datetime.datetime(2002, 7, 13, 15, 58, 28, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.exif.ExifVersion', unicode, u'0200'), ('Xmp.exif.ExposureBiasValue', FRACTION, make_fraction(-13, 20)), ('Xmp.exif.ExposureProgram', int, 4), ('Xmp.exif.FNumber', FRACTION, make_fraction(3, 5)), ('Xmp.exif.FileSource', int, 0), ('Xmp.exif.FlashpixVersion', unicode, u'0100'), ('Xmp.exif.FocalLength', FRACTION, make_fraction(0, 1)), ('Xmp.exif.FocalPlaneResolutionUnit', int, 2), ('Xmp.exif.FocalPlaneXResolution', FRACTION, make_fraction(3085, 256)), ('Xmp.exif.FocalPlaneYResolution', FRACTION, make_fraction(3085, 256)), ('Xmp.exif.GPSLatitude', pyexiv2.utils.GPSCoordinate, pyexiv2.utils.GPSCoordinate.from_string('54,59.380000N')), ('Xmp.exif.GPSLongitude', pyexiv2.utils.GPSCoordinate, pyexiv2.utils.GPSCoordinate.from_string('1,54.850000W')), ('Xmp.exif.GPSMapDatum', unicode, u'WGS84'), ('Xmp.exif.GPSTimeStamp', datetime.datetime, datetime.datetime(2002, 7, 13, 14, 58, 24, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.exif.GPSVersionID', unicode, u'2.0.0.0'), ('Xmp.exif.ISOSpeedRatings', list, [0]), ('Xmp.exif.MeteringMode', int, 5), ('Xmp.exif.PixelXDimension', int, 2400), ('Xmp.exif.PixelYDimension', int, 1600), ('Xmp.exif.SceneType', int, 0), ('Xmp.exif.SensingMethod', int, 2), ('Xmp.exif.ShutterSpeedValue', FRACTION, make_fraction(30827, 3245)), ('Xmp.pdf.Keywords', unicode, u'Communications'), ('Xmp.photoshop.AuthorsPosition', unicode, u'Photographer'), ('Xmp.photoshop.CaptionWriter', unicode, u'Ian Britton'), ('Xmp.photoshop.Category', unicode, u'BUS'), ('Xmp.photoshop.City', unicode, u' '), ('Xmp.photoshop.Country', unicode, u'Ubited Kingdom'), ('Xmp.photoshop.Credit', unicode, u'Ian Britton'), ('Xmp.photoshop.DateCreated', datetime.date, datetime.date(2002, 6, 20)), ('Xmp.photoshop.Headline', unicode, u'Communications'), ('Xmp.photoshop.State', unicode, u' '), ('Xmp.photoshop.SupplementalCategories', list, [u'Communications']), ('Xmp.photoshop.Urgency', int, 5), ('Xmp.tiff.Artist', unicode, u'Ian Britton'), ('Xmp.tiff.BitsPerSample', list, [8]), ('Xmp.tiff.Compression', int, 6), ('Xmp.tiff.Copyright', dict, {u'x-default': u'ian Britton - FreeFoto.com'}), ('Xmp.tiff.ImageDescription', dict, {u'x-default': u'Communications'}), ('Xmp.tiff.ImageLength', int, 400), ('Xmp.tiff.ImageWidth', int, 600), ('Xmp.tiff.Make', unicode, u'FUJIFILM'), ('Xmp.tiff.Model', unicode, u'FinePixS1Pro'), ('Xmp.tiff.Orientation', int, 1), ('Xmp.tiff.ResolutionUnit', int, 2), ('Xmp.tiff.Software', unicode, u'Adobe Photoshop 7.0'), ('Xmp.tiff.XResolution', FRACTION, make_fraction(300, 1)), ('Xmp.tiff.YCbCrPositioning', int, 2), ('Xmp.tiff.YResolution', FRACTION, make_fraction(300, 1)), ('Xmp.xmp.CreateDate', datetime.datetime, datetime.datetime(2002, 7, 13, 15, 58, 28, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.xmp.ModifyDate', datetime.datetime, datetime.datetime(2002, 7, 19, 13, 28, 10, tzinfo=pyexiv2.utils.FixedOffset())), ('Xmp.xmpBJ.JobRef', list, []), ('Xmp.xmpBJ.JobRef[1]', str, ''), ('Xmp.xmpBJ.JobRef[1]/stJob:name', str, 'Photographer'), ('Xmp.xmpMM.DocumentID', str, 'adobe:docid:photoshop:84d4dba8-9b11-11d6-895d-c4d063a70fb0'), ('Xmp.xmpRights.Marked', bool, True), ('Xmp.xmpRights.WebStatement', str, 'www.freefoto.com')] self.assertEqual(image.xmp_keys, [tag[0] for tag in xmpTags]) for key, ktype, value in xmpTags: self.check_type_and_value(image[key], ktype, value) pyexiv2-0.3.2/test/datetimeformatter.py0000664000175000017500000002256011651315372017614 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2011 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import unittest from pyexiv2.utils import DateTimeFormatter, FixedOffset import datetime class TestDateTimeFormatter(unittest.TestCase): def test_timedelta_to_offset(self): # positive deltas t = datetime.timedelta(hours=5) self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '+05:00') t = datetime.timedelta(minutes=300) self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '+05:00') t = datetime.timedelta(hours=5, minutes=12) self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '+05:12') t = datetime.timedelta(seconds=10800) self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '+03:00') # negative deltas t = datetime.timedelta(hours=-4) self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '-04:00') t = datetime.timedelta(minutes=-258) self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '-04:18') t = datetime.timedelta(hours=-2, minutes=-12) self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '-02:12') t = datetime.timedelta(seconds=-10000) self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '-02:46') def test_exif(self): # datetime d = datetime.datetime(1899, 12, 31) self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 00:00:00') d = datetime.datetime(1899, 12, 31, 23) self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:00:00') d = datetime.datetime(1899, 12, 31, 23, 59) self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:59:00') d = datetime.datetime(1899, 12, 31, 23, 59, 59) self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:59:59') d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999) self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:59:59') d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset()) self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:59:59') d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset(hours=5)) self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:59:59') d = datetime.datetime(2011, 8, 8, 19, 3, 37) self.assertEqual(DateTimeFormatter.exif(d), '2011:08:08 19:03:37') # date d = datetime.date(1899, 12, 31) self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31') d = datetime.date(2011, 8, 8) self.assertEqual(DateTimeFormatter.exif(d), '2011:08:08') # invalid type self.assertRaises(TypeError, DateTimeFormatter.exif, None) self.assertRaises(TypeError, DateTimeFormatter.exif, 3.14) def test_iptc_date(self): # datetime d = datetime.datetime(1899, 12, 31) self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31') d = datetime.datetime(1899, 12, 31, 23) self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31') d = datetime.datetime(1899, 12, 31, 23, 59) self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31') d = datetime.datetime(1899, 12, 31, 23, 59, 59) self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31') d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999) self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31') d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset()) self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31') d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset(hours=5)) self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31') d = datetime.datetime(2011, 8, 8, 19, 3, 37) self.assertEqual(DateTimeFormatter.iptc_date(d), '2011-08-08') # date d = datetime.date(1899, 12, 31) self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31') d = datetime.date(2011, 8, 8) self.assertEqual(DateTimeFormatter.iptc_date(d), '2011-08-08') # invalid type self.assertRaises(TypeError, DateTimeFormatter.iptc_date, None) self.assertRaises(TypeError, DateTimeFormatter.iptc_date, 3.14) def test_iptc_time(self): # datetime d = datetime.datetime(1899, 12, 31) self.assertEqual(DateTimeFormatter.iptc_time(d), '00:00:00+00:00') d = datetime.datetime(1899, 12, 31, 23) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:00:00+00:00') d = datetime.datetime(1899, 12, 31, 23, 59) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:00+00:00') d = datetime.datetime(1899, 12, 31, 23, 59, 59) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00') d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00') d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset()) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00') d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset(hours=5)) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+05:00') d = datetime.datetime(2011, 8, 8, 19, 3, 37) self.assertEqual(DateTimeFormatter.iptc_time(d), '19:03:37+00:00') # time d = datetime.time(23) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:00:00+00:00') d = datetime.time(23, 59) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:00+00:00') d = datetime.time(23, 59, 59) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00') d = datetime.time(23, 59, 59, 999999) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00') d = datetime.time(23, 59, 59, tzinfo=FixedOffset()) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00') d = datetime.time(23, 59, 59, tzinfo=FixedOffset(hours=5)) self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+05:00') d = datetime.time(19, 3, 37) self.assertEqual(DateTimeFormatter.iptc_time(d), '19:03:37+00:00') # invalid type self.assertRaises(TypeError, DateTimeFormatter.iptc_time, None) self.assertRaises(TypeError, DateTimeFormatter.iptc_time, 3.14) def test_xmp(self): # datetime d = datetime.datetime(1899, 12, 31) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31') d = datetime.datetime(1899, 12, 31, tzinfo=FixedOffset()) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31') d = datetime.datetime(1899, 12, 31, 23, 59) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59Z') d = datetime.datetime(1899, 12, 31, 23, 59, tzinfo=FixedOffset()) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59Z') d = datetime.datetime(1899, 12, 31, 23, 59, tzinfo=FixedOffset(hours=3)) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59+03:00') d = datetime.datetime(1899, 12, 31, 23, 59, 59) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59Z') d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset()) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59Z') d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset(hours=3)) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59+03:00') d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59.999999Z') d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999, tzinfo=FixedOffset()) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59.999999Z') d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999, tzinfo=FixedOffset(hours=3)) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59.999999+03:00') d = datetime.datetime(2011, 8, 11, 9, 23, 44) self.assertEqual(DateTimeFormatter.xmp(d), '2011-08-11T09:23:44Z') # date d = datetime.date(1899, 12, 31) self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31') d = datetime.date(2011, 8, 8) self.assertEqual(DateTimeFormatter.xmp(d), '2011-08-08') # invalid type self.assertRaises(TypeError, DateTimeFormatter.xmp, None) self.assertRaises(TypeError, DateTimeFormatter.xmp, 3.14) pyexiv2-0.3.2/test/exif.py0000664000175000017500000004004511651315372015025 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2009-2011 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import unittest from pyexiv2.exif import ExifTag, ExifValueError from pyexiv2.metadata import ImageMetadata from pyexiv2.utils import make_fraction import testutils import datetime import os.path class TestExifTag(unittest.TestCase): def test_convert_to_python_ascii(self): # Valid values: datetimes tag = ExifTag('Exif.Image.DateTime') self.assertEqual(tag.type, 'Ascii') self.assertEqual(tag._convert_to_python('2009-03-01 12:46:51'), datetime.datetime(2009, 03, 01, 12, 46, 51)) self.assertEqual(tag._convert_to_python('2009:03:01 12:46:51'), datetime.datetime(2009, 03, 01, 12, 46, 51)) self.assertEqual(tag._convert_to_python('2009-03-01T12:46:51Z'), datetime.datetime(2009, 03, 01, 12, 46, 51)) # Valid values: dates tag = ExifTag('Exif.GPSInfo.GPSDateStamp') self.assertEqual(tag.type, 'Ascii') self.assertEqual(tag._convert_to_python('2009:08:04'), datetime.date(2009, 8, 4)) # Valid values: strings tag = ExifTag('Exif.Image.Copyright') self.assertEqual(tag.type, 'Ascii') self.assertEqual(tag._convert_to_python('Some text.'), 'Some text.') self.assertEqual(tag._convert_to_python(u'Some text with exotic chàräctérʐ.'), u'Some text with exotic chàräctérʐ.') # Invalid values: datetimes tag = ExifTag('Exif.Image.DateTime') self.assertEqual(tag.type, 'Ascii') self.assertEqual(tag._convert_to_python('2009-13-01 12:46:51'), '2009-13-01 12:46:51') self.assertEqual(tag._convert_to_python('2009-12-01'), '2009-12-01') # Invalid values: dates tag = ExifTag('Exif.GPSInfo.GPSDateStamp') self.assertEqual(tag.type, 'Ascii') self.assertEqual(tag._convert_to_python('2009:13:01'), '2009:13:01') self.assertEqual(tag._convert_to_python('2009-12-01'), '2009-12-01') def test_convert_to_string_ascii(self): # Valid values: datetimes tag = ExifTag('Exif.Image.DateTime') self.assertEqual(tag.type, 'Ascii') self.assertEqual(tag._convert_to_string(datetime.datetime(2009, 03, 01, 12, 54, 28)), '2009:03:01 12:54:28') self.assertEqual(tag._convert_to_string(datetime.date(2009, 03, 01)), '2009:03:01 00:00:00') self.assertEqual(tag._convert_to_string(datetime.datetime(1899, 12, 31, 23, 59, 59)), '1899:12:31 23:59:59') self.assertEqual(tag._convert_to_string(datetime.date(1899, 12, 31)), '1899:12:31 00:00:00') # Valid values: dates tag = ExifTag('Exif.GPSInfo.GPSDateStamp') self.assertEqual(tag.type, 'Ascii') self.assertEqual(tag._convert_to_string(datetime.date(2009, 03, 01)), '2009:03:01') self.assertEqual(tag._convert_to_string(datetime.date(1899, 12, 31)), '1899:12:31') # Valid values: strings tag = ExifTag('Exif.Image.Copyright') self.assertEqual(tag.type, 'Ascii') self.assertEqual(tag._convert_to_string(u'Some text'), 'Some text') self.assertEqual(tag._convert_to_string(u'Some text with exotic chàräctérʐ.'), 'Some text with exotic chàräctérʐ.') self.assertEqual(tag._convert_to_string('Some text with exotic chàräctérʐ.'), 'Some text with exotic chàräctérʐ.') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, None) def test_convert_to_python_byte(self): # Valid values tag = ExifTag('Exif.GPSInfo.GPSVersionID') self.assertEqual(tag.type, 'Byte') self.assertEqual(tag._convert_to_python('D'), 'D') def test_convert_to_string_byte(self): # Valid values tag = ExifTag('Exif.GPSInfo.GPSVersionID') self.assertEqual(tag.type, 'Byte') self.assertEqual(tag._convert_to_string('Some text'), 'Some text') self.assertEqual(tag._convert_to_string(u'Some text'), 'Some text') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, None) def test_convert_to_python_sbyte(self): # Valid values tag = ExifTag('Exif.Pentax.Temperature') self.assertEqual(tag.type, 'SByte') self.assertEqual(tag._convert_to_python('15'), '15') def test_convert_to_string_sbyte(self): # Valid values tag = ExifTag('Exif.Pentax.Temperature') self.assertEqual(tag.type, 'SByte') self.assertEqual(tag._convert_to_string('13'), '13') self.assertEqual(tag._convert_to_string(u'13'), '13') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, None) def test_convert_to_python_comment(self): # Valid values tag = ExifTag('Exif.Photo.UserComment') self.assertEqual(tag.type, 'Comment') self.assertEqual(tag._convert_to_python('A comment'), 'A comment') for charset in ('Ascii', 'Jis', 'Unicode', 'Undefined', 'InvalidCharsetId'): self.assertEqual(tag._convert_to_python('charset="%s" A comment' % charset), 'A comment') for charset in ('Ascii', 'Jis', 'Undefined', 'InvalidCharsetId'): self.failIfEqual(tag._convert_to_python('charset="%s" déjà vu' % charset), u'déjà vu') def test_convert_to_string_comment(self): # Valid values tag = ExifTag('Exif.Photo.UserComment') self.assertEqual(tag.type, 'Comment') self.assertEqual(tag._convert_to_string('A comment'), 'A comment') self.assertEqual(tag._convert_to_string(u'A comment'), 'A comment') charsets = ('Ascii', 'Jis', 'Unicode', 'Undefined') for charset in charsets: tag.raw_value = 'charset="%s" foo' % charset self.assertEqual(tag._convert_to_string('A comment'), 'charset="%s" A comment' % charset) self.assertEqual(tag._convert_to_string('déjà vu'), 'déjà vu') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, None) def test_convert_to_python_short(self): # Valid values tag = ExifTag('Exif.Image.BitsPerSample') self.assertEqual(tag.type, 'Short') self.assertEqual(tag._convert_to_python('8'), 8) self.assertEqual(tag._convert_to_python('+5628'), 5628) # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_python, 'abc') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5,64') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '47.0001') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '1E3') def test_convert_to_string_short(self): # Valid values tag = ExifTag('Exif.Image.BitsPerSample') self.assertEqual(tag.type, 'Short') self.assertEqual(tag._convert_to_string(123), '123') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, -57) self.failUnlessRaises(ExifValueError, tag._convert_to_string, 'invalid') self.failUnlessRaises(ExifValueError, tag._convert_to_string, 3.14) def test_convert_to_python_sshort(self): # Valid values tag = ExifTag('Exif.Image.TimeZoneOffset') self.assertEqual(tag.type, 'SShort') self.assertEqual(tag._convert_to_python('8'), 8) self.assertEqual(tag._convert_to_python('+5'), 5) self.assertEqual(tag._convert_to_python('-6'), -6) # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_python, 'abc') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5,64') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '47.0001') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '1E3') def test_convert_to_string_sshort(self): # Valid values tag = ExifTag('Exif.Image.TimeZoneOffset') self.assertEqual(tag.type, 'SShort') self.assertEqual(tag._convert_to_string(12), '12') self.assertEqual(tag._convert_to_string(-3), '-3') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, 'invalid') self.failUnlessRaises(ExifValueError, tag._convert_to_string, 3.14) def test_convert_to_python_long(self): # Valid values tag = ExifTag('Exif.Image.ImageWidth') self.assertEqual(tag.type, 'Long') self.assertEqual(tag._convert_to_python('8'), 8) self.assertEqual(tag._convert_to_python('+5628'), 5628) # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_python, 'abc') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5,64') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '47.0001') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '1E3') def test_convert_to_string_long(self): # Valid values tag = ExifTag('Exif.Image.ImageWidth') self.assertEqual(tag.type, 'Long') self.assertEqual(tag._convert_to_string(123), '123') self.assertEqual(tag._convert_to_string(678024), '678024') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, -57) self.failUnlessRaises(ExifValueError, tag._convert_to_string, 'invalid') self.failUnlessRaises(ExifValueError, tag._convert_to_string, 3.14) def test_convert_to_python_slong(self): # Valid values tag = ExifTag('Exif.OlympusCs.ManometerReading') self.assertEqual(tag.type, 'SLong') self.assertEqual(tag._convert_to_python('23'), 23) self.assertEqual(tag._convert_to_python('+5628'), 5628) self.assertEqual(tag._convert_to_python('-437'), -437) # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_python, 'abc') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5,64') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '47.0001') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '1E3') def test_convert_to_string_slong(self): # Valid values tag = ExifTag('Exif.OlympusCs.ManometerReading') self.assertEqual(tag.type, 'SLong') self.assertEqual(tag._convert_to_string(123), '123') self.assertEqual(tag._convert_to_string(678024), '678024') self.assertEqual(tag._convert_to_string(-437), '-437') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, 'invalid') self.failUnlessRaises(ExifValueError, tag._convert_to_string, 3.14) def test_convert_to_python_rational(self): # Valid values tag = ExifTag('Exif.Image.XResolution') self.assertEqual(tag.type, 'Rational') self.assertEqual(tag._convert_to_python('5/3'), make_fraction(5, 3)) # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_python, 'invalid') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '-5/3') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5 / 3') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5/-3') def test_convert_to_string_rational(self): # Valid values tag = ExifTag('Exif.Image.XResolution') self.assertEqual(tag.type, 'Rational') self.assertEqual(tag._convert_to_string(make_fraction(5, 3)), '5/3') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, 'invalid') self.failUnlessRaises(ExifValueError, tag._convert_to_string, make_fraction(-5, 3)) def test_convert_to_python_srational(self): # Valid values tag = ExifTag('Exif.Image.BaselineExposure') self.assertEqual(tag.type, 'SRational') self.assertEqual(tag._convert_to_python('5/3'), make_fraction(5, 3)) self.assertEqual(tag._convert_to_python('-5/3'), make_fraction(-5, 3)) # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_python, 'invalid') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5 / 3') self.failUnlessRaises(ExifValueError, tag._convert_to_python, '5/-3') def test_convert_to_string_srational(self): # Valid values tag = ExifTag('Exif.Image.BaselineExposure') self.assertEqual(tag.type, 'SRational') self.assertEqual(tag._convert_to_string(make_fraction(5, 3)), '5/3') self.assertEqual(tag._convert_to_string(make_fraction(-5, 3)), '-5/3') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, 'invalid') def test_convert_to_python_undefined(self): # Valid values tag = ExifTag('Exif.Photo.ExifVersion') self.assertEqual(tag.type, 'Undefined') self.assertEqual(tag._convert_to_python('48 49 48 48'), '0100') def test_convert_to_string_undefined(self): # Valid values tag = ExifTag('Exif.Photo.ExifVersion') self.assertEqual(tag.type, 'Undefined') self.assertEqual(tag._convert_to_string('0100'), '48 49 48 48') self.assertEqual(tag._convert_to_string(u'0100'), '48 49 48 48') # Invalid values self.failUnlessRaises(ExifValueError, tag._convert_to_string, 3) def test_set_value(self): tag = ExifTag('Exif.Thumbnail.Orientation', 1) # top, left old_value = tag.value tag.value = 2 self.failIfEqual(tag.value, old_value) def test_set_raw_value_invalid(self): tag = ExifTag('Exif.GPSInfo.GPSVersionID') value = '2 0 0 foo' self.failUnlessRaises(ValueError, setattr, tag, 'raw_value', value) def test_makernote_types(self): # Makernote tags not attached to an image have an Undefined type by # default. When read from an existing image though, their type should be # correctly set (see https://bugs.launchpad.net/pyexiv2/+bug/781464). tag1 = ExifTag('Exif.Pentax.PreviewResolution') tag1.raw_value = '640 480' self.assertEqual(tag1.type, 'Undefined') self.failUnlessRaises(ValueError, getattr, tag1, 'value') tag2 = ExifTag('Exif.Pentax.CameraInfo') tag2.raw_value = '76830 20070527 2 1 4228109' self.assertEqual(tag2.type, 'Undefined') self.failUnlessRaises(ValueError, getattr, tag2, 'value') filepath = testutils.get_absolute_file_path(os.path.join('data', 'pentax-makernote.jpg')) checksum = '646804b309a4a2d31feafe9bffc5d7f0' self.assert_(testutils.CheckFileSum(filepath, checksum)) metadata = ImageMetadata(filepath) metadata.read() tag1 = metadata[tag1.key] self.assertEqual(tag1.type, 'Short') self.assertEqual(tag1.value, [640, 480]) tag2 = metadata[tag2.key] self.assertEqual(tag2.type, 'Long') self.assertEqual(tag2.value, [76830L, 20070527L, 2L, 1L, 4228109L]) pyexiv2-0.3.2/test/testutils.py0000664000175000017500000000561211651315372016133 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2008-2011 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import os.path import hashlib EMPTY_JPG_DATA = \ '\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xdb' \ '\x00C\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' \ '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' \ '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' \ '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\x0b\x08' \ '\x00\x01\x00\x01\x01\x01\x11\x00\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01' \ '\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06' \ '\x07\x08\t\n\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05' \ '\x05\x04\x04\x00\x00\x01}\x01\x02\x03\x00\x04\x11\x05\x12!1A\x06\x13Qa' \ '\x07"q\x142\x81\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\t\n\x16\x17' \ "\x18\x19\x1a%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85" \ '\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5' \ '\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5' \ '\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4' \ '\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda' \ '\x00\x08\x01\x01\x00\x00?\x00\x92\xbf\xff\xd9' def get_absolute_file_path(filepath): """ Return the absolute file path for the file path given in argument, considering it is relative to the caller script's directory. """ return os.path.join(os.path.dirname(os.path.abspath(__file__)), filepath) def CheckFileSum(filename, md5sum): """ Test the MD5 sum of a given file against the expected value. Keyword arguments: filename -- the name of the file to test md5sum -- the expected value of the MD5 sum of the file """ f = open(filename, 'rb') return (hashlib.md5(f.read()).hexdigest() == md5sum) pyexiv2-0.3.2/test/__init__.py0000664000175000017500000000000011651315372015614 0ustar osomonosomonpyexiv2-0.3.2/test/usercomment.py0000664000175000017500000001237311651315372016436 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** from pyexiv2.metadata import ImageMetadata import unittest import testutils import os import tempfile from testutils import EMPTY_JPG_DATA class TestUserCommentReadWrite(unittest.TestCase): checksums = { 'usercomment-ascii.jpg': 'ad29ac65fb6f63c8361aaed6cb02f8c7', 'usercomment-unicode-ii.jpg': '13b7cc09129a8677f2cf18634f5abd3c', 'usercomment-unicode-mm.jpg': '7addfed7823c556ba489cd4ab2037200', } def _read_image(self, filename): filepath = testutils.get_absolute_file_path(os.path.join('data', filename)) self.assert_(testutils.CheckFileSum(filepath, self.checksums[filename])) m = ImageMetadata(filepath) m.read() return m def _expected_raw_value(self, endianness, value): from pyexiv2 import __exiv2_version__ if __exiv2_version__ >= '0.20': return value else: encodings = {'ii': 'utf-16le', 'mm': 'utf-16be'} return value.decode('utf-8').encode(encodings[endianness]) def test_read_ascii(self): m = self._read_image('usercomment-ascii.jpg') tag = m['Exif.Photo.UserComment'] self.assertEqual(tag.type, 'Comment') self.assertEqual(tag.raw_value, 'charset="Ascii" deja vu') self.assertEqual(tag.value, u'deja vu') def test_read_unicode_little_endian(self): m = self._read_image('usercomment-unicode-ii.jpg') tag = m['Exif.Photo.UserComment'] self.assertEqual(tag.type, 'Comment') self.assertEqual(tag.raw_value, 'charset="Unicode" %s' % self._expected_raw_value('ii', 'déjà vu')) self.assertEqual(tag.value, u'déjà vu') def test_read_unicode_big_endian(self): m = self._read_image('usercomment-unicode-mm.jpg') tag = m['Exif.Photo.UserComment'] self.assertEqual(tag.type, 'Comment') self.assertEqual(tag.raw_value, 'charset="Unicode" %s' % self._expected_raw_value('mm', 'déjà vu')) self.assertEqual(tag.value, u'déjà vu') def test_write_ascii(self): m = self._read_image('usercomment-ascii.jpg') tag = m['Exif.Photo.UserComment'] self.assertEqual(tag.type, 'Comment') tag.value = 'foo bar' self.assertEqual(tag.raw_value, 'charset="Ascii" foo bar') self.assertEqual(tag.value, u'foo bar') def test_write_unicode_over_ascii(self): m = self._read_image('usercomment-ascii.jpg') tag = m['Exif.Photo.UserComment'] self.assertEqual(tag.type, 'Comment') tag.value = u'déjà vu' self.assertEqual(tag.raw_value, 'déjà vu') self.assertEqual(tag.value, u'déjà vu') def test_write_unicode_little_endian(self): m = self._read_image('usercomment-unicode-ii.jpg') tag = m['Exif.Photo.UserComment'] self.assertEqual(tag.type, 'Comment') tag.value = u'DÉJÀ VU' self.assertEqual(tag.raw_value, 'charset="Unicode" %s' % self._expected_raw_value('ii', 'DÉJÀ VU')) self.assertEqual(tag.value, u'DÉJÀ VU') def test_write_unicode_big_endian(self): m = self._read_image('usercomment-unicode-mm.jpg') tag = m['Exif.Photo.UserComment'] self.assertEqual(tag.type, 'Comment') tag.value = u'DÉJÀ VU' self.assertEqual(tag.raw_value, 'charset="Unicode" %s' % self._expected_raw_value('mm', 'DÉJÀ VU')) self.assertEqual(tag.value, u'DÉJÀ VU') class TestUserCommentAdd(unittest.TestCase): def setUp(self): # Create an empty image file fd, self.pathname = tempfile.mkstemp(suffix='.jpg') os.write(fd, EMPTY_JPG_DATA) os.close(fd) def tearDown(self): os.remove(self.pathname) def _test_add_comment(self, value): metadata = ImageMetadata(self.pathname) metadata.read() key = 'Exif.Photo.UserComment' metadata[key] = value metadata.write() metadata = ImageMetadata(self.pathname) metadata.read() self.assert_(key in metadata.exif_keys) tag = metadata[key] self.assertEqual(tag.type, 'Comment') self.assertEqual(tag.value, value) def test_add_comment_ascii(self): self._test_add_comment('deja vu') def test_add_comment_unicode(self): self._test_add_comment(u'déjà vu') pyexiv2-0.3.2/test/rational.py0000664000175000017500000000724611651315372015711 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2008-2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import unittest from pyexiv2.utils import Rational class TestRational(unittest.TestCase): def test_constructor(self): r = Rational(2, 1) self.assertEqual(r.numerator, 2) self.assertEqual(r.denominator, 1) self.assertRaises(ZeroDivisionError, Rational, 1, 0) def test_read_only(self): r = Rational(3, 4) try: r.numerator = 5 except AttributeError: pass else: self.fail('Numerator is not read-only.') try: r.denominator = 5 except AttributeError: pass else: self.fail('Denominator is not read-only.') def test_match_string(self): self.assertEqual(Rational.match_string('4/3'), (4, 3)) self.assertEqual(Rational.match_string('-4/3'), (-4, 3)) self.assertEqual(Rational.match_string('0/3'), (0, 3)) self.assertEqual(Rational.match_string('0/0'), (0, 0)) self.assertRaises(ValueError, Rational.match_string, '+3/5') self.assertRaises(ValueError, Rational.match_string, '3 / 5') self.assertRaises(ValueError, Rational.match_string, '3/-5') self.assertRaises(ValueError, Rational.match_string, 'invalid') def test_from_string(self): self.assertEqual(Rational.from_string('4/3'), Rational(4, 3)) self.assertEqual(Rational.from_string('-4/3'), Rational(-4, 3)) self.assertRaises(ValueError, Rational.from_string, '+3/5') self.assertRaises(ValueError, Rational.from_string, '3 / 5') self.assertRaises(ValueError, Rational.from_string, '3/-5') self.assertRaises(ValueError, Rational.from_string, 'invalid') self.assertRaises(ZeroDivisionError, Rational.from_string, '1/0') self.assertRaises(ZeroDivisionError, Rational.from_string, '0/0') def test_to_string(self): self.assertEqual(str(Rational(3, 5)), '3/5') self.assertEqual(str(Rational(-3, 5)), '-3/5') def test_repr(self): self.assertEqual(repr(Rational(3, 5)), 'Rational(3, 5)') self.assertEqual(repr(Rational(-3, 5)), 'Rational(-3, 5)') self.assertEqual(repr(Rational(0, 3)), 'Rational(0, 3)') def test_to_float(self): self.assertEqual(Rational(3, 6).to_float(), 0.5) self.assertEqual(Rational(11, 11).to_float(), 1.0) self.assertEqual(Rational(-2, 8).to_float(), -0.25) self.assertEqual(Rational(0, 3).to_float(), 0.0) def test_equality(self): r1 = Rational(2, 1) r2 = Rational(2, 1) r3 = Rational(8, 4) r4 = Rational(3, 2) self.assertEqual(r1, r2) self.assertEqual(r1, r3) self.assertNotEqual(r1, r4) pyexiv2-0.3.2/test/pickling.py0000664000175000017500000001250311651315372015670 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** from pyexiv2.exif import ExifTag from pyexiv2.iptc import IptcTag from pyexiv2.xmp import XmpTag from pyexiv2.utils import make_fraction, FixedOffset import unittest import pickle import datetime class TestPicklingTags(unittest.TestCase): def test_pickle_exif_tag(self): tags = [] tags.append(ExifTag('Exif.Image.DateTime', datetime.datetime(2010, 12, 22, 19, 21, 0))) tags.append(ExifTag('Exif.GPSInfo.GPSDateStamp', datetime.date.today())) tags.append(ExifTag('Exif.Image.Copyright', '(C) 2010 Santa Claus')) tags.append(ExifTag('Exif.GPSInfo.GPSVersionID', '0')) tags.append(ExifTag('Exif.Pentax.Temperature', '14')) tags.append(ExifTag('Exif.Photo.UserComment', 'foo bar baz')) tags.append(ExifTag('Exif.Image.BitsPerSample', 8)) tags.append(ExifTag('Exif.Image.TimeZoneOffset', 7)) tags.append(ExifTag('Exif.Image.ImageWidth', 7492)) tags.append(ExifTag('Exif.OlympusCs.ManometerReading', 29)) tags.append(ExifTag('Exif.Image.XResolution', make_fraction(7, 3))) tags.append(ExifTag('Exif.Image.BaselineExposure', make_fraction(-7, 3))) tags.append(ExifTag('Exif.Photo.ExifVersion', '0100')) for tag in tags: s = pickle.dumps(tag) t = pickle.loads(s) self.assert_(isinstance(t, ExifTag)) self.assertEqual(t.key, tag.key) self.assertEqual(t.type, tag.type) self.assertEqual(t.name, tag.name) self.assertEqual(t.label, tag.label) self.assertEqual(t.description, tag.description) self.assertEqual(t.section_name, tag.section_name) self.assertEqual(t.section_description, tag.section_description) self.assertEqual(t.raw_value, tag.raw_value) self.assertEqual(t.value, tag.value) self.assertEqual(t.human_value, tag.human_value) def test_pickle_iptc_tag(self): tags = [] tags.append(IptcTag('Iptc.Envelope.FileFormat', [23])) tags.append(IptcTag('Iptc.Application2.Subject', ['foo', 'bar', 'baz'])) tags.append(IptcTag('Iptc.Envelope.DateSent', [datetime.date.today()])) tags.append(IptcTag('Iptc.Envelope.TimeSent', [datetime.time(23, 37, 4, tzinfo=FixedOffset('+', 6, 0))])) tags.append(IptcTag('Iptc.Application2.Preview', ['01001101'])) for tag in tags: s = pickle.dumps(tag) t = pickle.loads(s) self.assert_(isinstance(t, IptcTag)) self.assertEqual(t.key, tag.key) self.assertEqual(t.type, tag.type) self.assertEqual(t.name, tag.name) self.assertEqual(t.title, tag.title) self.assertEqual(t.description, tag.description) self.assertEqual(t.photoshop_name, tag.photoshop_name) self.assertEqual(t.repeatable, tag.repeatable) self.assertEqual(t.record_name, tag.record_name) self.assertEqual(t.record_description, tag.record_description) self.assertEqual(t.raw_value, tag.raw_value) self.assertEqual(t.value, tag.value) def test_pickle_xmp_tag(self): tags = [] tags.append(XmpTag('Xmp.dc.subject', ['foo', 'bar', 'baz'])) tags.append(XmpTag('Xmp.xmpRights.Marked', True)) tags.append(XmpTag('Xmp.xmp.CreateDate', datetime.date.today())) tags.append(XmpTag('Xmp.xmpMM.SaveID', 34)) tags.append(XmpTag('Xmp.dc.format', ('image', 'jpeg'))) tags.append(XmpTag('Xmp.photoshop.CaptionWriter', 'John Doe')) tags.append(XmpTag('Xmp.dc.source', 'bleh')) tags.append(XmpTag('Xmp.xmpMM.DocumentID', 'http://example.com')) tags.append(XmpTag('Xmp.xmp.BaseURL', 'http://example.com')) tags.append(XmpTag('Xmp.xmpDM.videoPixelAspectRatio', make_fraction(5, 3))) for tag in tags: s = pickle.dumps(tag) t = pickle.loads(s) self.assert_(isinstance(t, XmpTag)) self.assertEqual(t.key, tag.key) self.assertEqual(t.type, tag.type) self.assertEqual(t.name, tag.name) self.assertEqual(t.title, tag.title) self.assertEqual(t.description, tag.description) self.assertEqual(t.raw_value, tag.raw_value) self.assertEqual(t.value, tag.value) pyexiv2-0.3.2/test/data/0000775000175000017500000000000011651315372014426 5ustar osomonosomonpyexiv2-0.3.2/test/data/usercomment-unicode-ii.jpg0000664000175000017500000000101011651315372021504 0ustar osomonosomonJFIFHHExifII*JR(iZHH02200100UNICODEdj vuC  }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz?pyexiv2-0.3.2/test/data/empty.jpg0000664000175000017500000000051411651315372016266 0ustar osomonosomonJFIFHHC  }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz?pyexiv2-0.3.2/test/data/smiley1.jpg0000664000175000017500000000563111651315372016520 0ustar osomonosomon2This is a jpeg comment, about the green smiley.Photoshop 3.08BIMx&yelimS green faced dude (iptc caption)zNobodyPIts me GreeenDude720040713ZSeattle_WAeUSAThingsGreenSmileyDudet 2004 NobodyExifMM* -z(1 2;ڂiWell it is a smiley that happens to be greenHHImageReady2004-07-13T21:23:44ZNo one PqDucky\,Well it is a smiley that happens to be greenAdobed   #%'%#//33//@@@@@@@@@@@@@@@&&0##0+.'''.+550055@@?@@@@@@@@@@@@"!1AQaq"2Rbr#BS3!1AQaq32#"R ?:ワzݷ5r]v~+%ª9?:~wY/zx޾\>#̲eoɝKz2TqjgֻE1]R(pcjIʯY;~%>R+I{隹Ҏu 9?ǪknEL.۲De7/}EqiuprqR#ےmp }0A<_-uʸ^̵ )%8Kekav霥UǏ'V689ǖ4^Uk.elZ\4,]WLk.R~3e/G-e!OrWXu艺-1mvqZEz0lU8Fb$;Z[rl}˒-ڲÂ\KLOHv]=eޚ?D|P'(vGNomM>A/W֍:8F׭S|-8}a\WcIj?~fIڣw,mS}4֫|'s^StTiTM%@1KQu8pgꍄbZ%G'$jk˛TxWUhf>U}%_iD.U1_40~)9W<9Z^'%_ȼ|k EihSZ$t6W'ߧ5WÎ矄kܢ9E^=|=WMf>mƟ})Iv3,m\'-iYP44GNmZߝ-{_ۗ|r^t^_ds_n_W"WYga]Џ/_,n6َ7º9z>/`oߊK-yJK>w+,1mGm9FMsZY5X3㧱h@R^h^gcnȎ|c%Ev&y.y9cv+#JIKT44gKD^5[^QRd%>$w*ylZ2IɗFSrU&_~V}Emc{Z֧5vo߂u̼MVEKYfT?Jo膤p>,^J7>jSlMp(zEL.ڰڔ),\N?936d~pDR_f#aN50(Ub^?@2AGHIM OP\_$] bnv~E   ( 8@HPX`hpx " @5 !  |w @mw+@aa@_p(Fftx{8;P cG z ?Sgb]]abeghjjjnnW\ 5 ="? 8 f QC "8 \' 3 /@5 !,@2 @A0c+ d} !H 3 )`))" nV$ $  )'$+6[;6226oOTB[tt}ѱŝ}ꍯ'))606k;;k! }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?â ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (<(qs@ J(JRb RPh(YE.ь‣(R#L tjH>9P֐< q@ Ъs@أZ]{PBzP_JQddq@ģ8#<"xixjX PlJU{ )0iU#"bR##a aM01#Iv84 íN1ӭ!ԻҘTzPT ҌJr9Ji\Zv(=(UG P҂: MҀ4 ړh=v#B()1@┊8qA@  (J3@j88`i™@(ifu0=Z@%9kL(w=Z HbNtx8`Jbހ'=N€hz@?J/D '4) A P@>NM1 >S+ (:@;!N~ zCb GAHˎ?:n9mߚhZ@;8MNI4) >! c;(ziҎz 4(F1@ ޗڀ`&(Ry /Qi@Z@7J84fmbPToPqs@n( )($^H4qޘySGv0oSO'&-P}if'mPtRJ0z46QXzsߝ13P9GA5NAҀ(zE7=Q <3}h$&x֓ biP}hRi=x \phbEh)4J1@y!8Ɛ)s@sM$?"q@)4I8#iHN=@ (p}(H=5H119~0(^ Nu1H8ptM0Ni{?ցמ!6N?*mM&6{<1OS;b#"H,{t9d\1 h9<Ӂq'< Gn@h8*r3Ha4xޔ t4bh`ʓ@^ z=9 0h(@hpMƀbKG 9PPi)KH `3"ȥ? @ 4gON<J18iꢚG4I7}Fi00 0Jcr4NM&r;g4V1J;11q@ ׊ZQJG4Zq4ӎh<)H玴)?v538 G!㩦 # # cN#ٱօg0H3M4wHh)ɠIi@)͏Z1RizU{ BJBM 9Lv6柍.})hG?i=i LJ N=o)6;w"4)t@}Pn8s9@GcB E9BwcրNM)@E)tOQPsI4t&.N'4}ӞhQBE0g<1qғJbϦ( XLlȡFއ4>qJqߥ( ^ #=:Ӊ@ކp=)<u☀)q֐敎M(-'\PmGQրsLrhQAh@/xzr Ih (`}h\@fHA O珟L8c&GLRqր~~;ֆt1#) ⁀SIC?>`Θ94~T p^!@(:p}>2y#GH<Jhu@:晎yb3;SE?Rsi{R!x&XS@j`#+MG x`ӛ'44 *xh' )FAt3PHj#;dNTs@\ҕC1H 0:8@`G= å")G9'~4 @2N(hRu1@ dd~T»^Oor)s@ý9N!qЊk z}:!1HsLOE9 H #=0Ab0<(X4׿4Oր#{ 2;cNNH.2pzPo^\ɤO޽tPmL{U>~}!Oj!z)!\sQH>v4tN48җ; )9J@s1@ Nqɠ@HM(SǧjLn`0:SE;+qM<8LGZbjC#<) 84.֛tǵ#))4ߥnq@ ( 884\@<w(z7n>Idc`@ rI6::{JҀzсhisڐiM1 cB @!րR)cL`ҊbtHa^8(4:!@\`֐)朿0րzhNI!y19@@2s)_sޙH;~V*128s prPMj@r}a@8s@}h^;P0#N #='@ 搷ETz6iZ: vԆ(G=M'zBsln} iݸ4!'drhFB4r3S{ `3H#b8@9J {ҐA48c!90m7= .rx4COA 04=)!l_Z@3@cFJ1q2:fAGQրV٠c ixBd4n%J2N$BJF=p"0zSc7` Ni@!x#vǯF3C9~z{H&2#>xH19 P8%{Sq@ ֗ 4ƀ#luހ.~is@GEӎ@?֤;Bt\PYxȠऌ1PJzPdJ^zӳ sE1O4g~9!FGˊL0"` -ԷIQ@ s!tڀ`)I=Hi65HӁ  jTH sJ1P1 J^}s@ ⚽pz0li~ 9#)E hT;{q@ `K3@ sR$d~Tg jDM>9◁;bH\` !ޜ }iTmLg~Ro84 P8p8(Lp| Lari #zPrzRc^(@֜84QiOg4P LaKS۸7rO(q/PcV8 7w  ;9=w2).x" =p(ǡccq@Aݚ0`҄'`;c99=)a?JUnS׎(#9摲ނ `\U jq &HP(/#q b .M+Ҁy֚B88i~L}( FB)O>40qJcPcS׭n:w pE *oy0nFO)}(Mq@ )6PҀz@.iʠZ`)v`x:֜p4ӞÊP1J7sy sH8=)iuhRq@=1NfR'<>z@ }iF(@lwht(;N;dglԙ,( sLʘ P9(a=@9@A=h6wgj7`@A1X(.K>Ñ{b>朸K qbW)`($}G1@Q"9@nGG'PE.8  1@?7zOJgj^@9(<(8=_z(4d@M&pM P)ŗ#h<s)HPE+|Fϭ)|gڀ2F@㯥0w<<RH1ϥ8#MnzAb=8 s#cz晸4reO9|ăց C;GZbiF} CJ\)+`61r6Ra9@ds<Q@pti#ӱn2"n?cgJE;c+g)-۩xFp:t@ cӊ76i< 0'#11@ 9ӥ 88rH搮 8!szP>_ڟ qԻJ09د8!8e徔6:ҁ%C}i_J.59\ke<܎=!=)]0= /8)'ހ rFM#cwsLc94ܐpG 9#Q#4Pp~Z^sZ@!?/GALئ83Ն@1֘ lys@`rM!hLqHJ=0 ӒFNw?j6~h8 8\cҎ~vA& !ҀH#ǥ4`8GZC(c<ޑzb )Kb tP_ȦcځM)Oqڞ!f `֤ڿ{xUQzUOzf08Jhzf9< 8i9.6}i8"~T:S`L 1sO(83Sq@sҝڔNHJCG p1!sS1{P <(##ZSzP!'-?USSUe 14])xa$`.) 78YJq;9(1j2eW\ FpI}88zt1#U; q@Gsi 8=)`o*MF;u 9zdրӜڀlSJ:GGn)~[ (ۛX2E$q] )[ڍALi;zN?QPA=qv @LJdf'E89! FhUrlRm΀WL-1hq*!{~ s@g pE$\u"LsHPPǡr7P*f玦7^EsLBqL < @b[H0@'zS *x'(c֚{yj@Fr)1!ȸI#S9ǵlRp*9 x1xp3dpGNN L(;O֘Hr`_zg`zM8*=~1zbژu*99ϵ#$}\dJN0sk)?QHc#ڀ A(%H9:cP9) 2I<y3SrzsQ@q@ASr3'8 'OjiH'Z@<>o)`LrE $[; 9N$!i28xƥd>Ԁ  FTLzQsҜ)\csJ@8h$P (ҞrG=CTcp'Sߔr{g",dm$ހ8=h[PCzTHAҨ;` QHZ\Oco>I 8=i 9r:R&,HS8Hr 1'U LB>R'qڕ!&Pr#y 03=\Jv0}E3hs}) nKc s)v㴐 PAS@MHc;W?zhC})8 ◩@A$q@ zL' {nA@*O9 ѷi~tTOQ`!zsO\v2)"JB1L҆b\t04)Ҭ9n(GznI[<QԌG7645Hb9`1Ґ!pG c E7뎧ޑ r3LCB* lO\ZXdg88<Фd1*>iIAL@֌'JV0z4)@J?3kFsKץ 6xGJLz)0NG d qHHF:iŃzA)c@ @9x?/jt zMFOiUsh6TDG~'緭Id/j62zHaIө(c *F3`vF?CmR00os4@€`zӜPXF4t(R69R>8ܸ qN~Zp wCz?4Iq)03Bm@ @99+#Br[ǥ'Jr1$`7q8Zy@!F0;N`RqTHJ8PF, .Iǥ m=2mn ;&$@(6cP1;hҟ!h@rH=ih^H]O_lS:6 ?Z).rGC҄R7@ ╔U y2JF\ǑOn1Hc Lc98FƐFZO ݽzH͸&rOA'+a;s@.AxqڑTހ'@$ )U&b;ۊ7ӏZC_zG!GC@䓐)K62LM hr@O4(9 99#edQ@!ϩ"AHW{n.0jiOz}Q4:w9Ҥ'oS`yn8phdFǭeh s@` dI9ށ8;Roc4V*rp;P6Ǿi7 cqZ4vb3Tڀ$8g?) ]Iւy ǡ r[*`( PcTI L`tuW}N}hҝ0s5##tϽ9@Ǩ-O0; Ix#9LCG9QiF ғbԊFP9Z) wZB@@8z縣' c84Ny{x\z{q{R\v`8?/S)' ah˞x~bhR=0y 6:JzI#ހ0/ ]{8n(UNads1%r(@89 c֐z:@H>?zN@̝p9(Rӑۿ)sC4u`$)G"䑓8w8_j44:Ӷhry8Iϥn)3P''#4 R:知"G(Ib!N杻9fyN(" 9Ҝϵ!e4|qE(akr@a#v8VbHN BWE0cn}(;259&y8Θ-ǵ!{c!AH'9@Ǒz\|ژq0GI@ *HA<{=# a$})BUs_bǽDA-40{P{Ә) ?M,4!T6 tlO8"rI ϥ4utEÌVbRnҀ S4c8 D<˅@Ґ 1ޔՏ`"GjQӎzi4 kS v4~TIg#ލ@ pzF~hP/nϭ)4\PNP ɥ x $8@'b?(=P:jB I$(!H9Hhڠs1lvh88)Cn4v Ҝ&>s@s FO&@j*1֘ہ)1!>cwrfۓۥIr(;QK "xɤznCSʟ€Fw)Y!WP!1h[2BC€Z0y@ 4w77yd0PP'/ ʨqCíUz4 Pޗ?/oƁpOaޙ< JA@%Jg<"( @d)l-w X;<ӂ0z!78ۏƀǥIփZ" =iA9>r'ҐF݃Rqڀ#sOyepHҔSRs@)jar di9( ny/9aST=hO3Sw3@ 2qih 9'a94@U&G:ЗzPPM0 q9 R$A؟jMÎ|@` d _3Xd1 9$'։8P#.x@ '3HzqHcbF8LC|ۇ'+9d <.ӎP!,@ni2d֠iiq:P 89eҐ v柁;(@!>ZrGL4}ܹǮhwd ;RM+c#JMsA1Jsj@嗜Spz{qA\ 0>;K@4"M9Q4SdC"D2.I M1 P<΀ aҀ$ q׵.p0i Px4RA)GFcR0P@l.Htl[p'2I94q1H >%r=hݎ٠b f$LC';4sD5m-` Oph,'=)1ڀ9e03@1j3)w=h1N<ʀ@cc61y8ҙހdXڣe E`r}h::tb`7;O3ڜ_:$>MgTHۃB2í8r2AOLzЄ :rM |vOSJbr\ rq@ J\9nm1䞔aU'zd SviPvk  .T 9恑ّӁd40lt7OQF =0E ZQSIdtҚ@\dg#0$g4 `)G1FM s@8R8ӣ8086ҀSޖ,/'y !v2GҴc<)и' t?78+dJ$vc1qC:GI p=Uw8bIV#9rI'pی#zD`ė)Hfbz-ҕX1N(fEF`@[@9Dyu0[sL 3=(|PxE$sHvx< jfz"WnGzrxRI E7  94ާNHҕؿh@p\R}3Ntw)ҰBIJeߟN )ן1@x`EQ:wn4;ҋiג1҃<]444Ѐwdئ(2,~Hr( .@;")Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@R980100 http://ns.adobe.com/xap/1.0/ C     C   " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?lb7稩.F.:gב[H.; a o ]3$dzJͱ֖C3G)(۹KDi,,IJZ; pY1!\db1ʩX4>vyzqǯPqأe'!}UPWV޸?}K5'1;1)tO;L3g-RLn*R(cW(d+ O4 kPBtrX`tb\5%ݎOV7=:Md@q]nܤGV7 gIUȕee5kcp s`z8EUo&+)#RwS;E>|b)L˸6B~i%>ӴآHQaԶr{dS_7q86z)RI,{}}xkm5̱H6:dc9x)&s&qi c 0Fj絴H.> V:f|$v v`ҦxO߅n<א%3Kyf*T(/JgG@ݝ/CTSˍ$"6GCڦygǰjIg[ivZ6U˔V%pMlihk3\?ZOfHTOl hQ´k!ŽK Uwdrb$X,4P=od~~Of0ƲIN;~Ghb&/n 5uY-UޘN55ܯllG *K`x>ՉNܲ܂2Sd6:FV ) 0=sʬi d1Ŵy z}i}B+-H/@֪]G#7;aGp{n9gF4t"q\Qi}L}kOߺ7P5HrAl_s:Ƥ[<!aPI_^WeEpx5-݊Z&7À$W7:yoȭ'O'V7]sL_a!Fl0ѯ$ʌģ>IJ68ht..-b6dҷR-*ն"|O$kDʳ*1mF"flaI# [[ aIGI23zoQ5y'+kj08'9&jib(Cd~dYGctܔ8*W$62\#)MG1z~Ti Be$p3ڷwoAlN՗Uˋ,ʧ'לz4J..>rJg VB|Ռy7(Uմ Y7NI޳n$Ʊ :tX{mWdoǓbzwK彻1bx^327va{֢++'j2aD0 ҪNs9r\B"N$ݑ`{G-n$aG }=6zh=J@c`9q.v)'MX2lJ9`wu>+yI`NK0;΍"HĥB?5Zં/JWDg#Q(V*qZ%Ս;3^<@oAcZuQ],RFx+{sҨ:Q5pCNL{"ag?s*A e~Я&5IH ;G=ib!(W<\U⺚͠Z;VSddJխ +:GZv{Zަ"|D 4|aӰP"†2:T;&9e^9>`km*UVFʂBADYR{nme($Lq8Uc1 rީ /,~ҮYC;T1$zsQ[A"J?x30Nj=I4R+(+|A?[̑}iU360sA'_RogIRUc%`8@MiYbEX8'qҋ}<Rg*Oϧ]ջJyFRO$z:֎` $~_~*彼7@Y@9=r3ҥZ'3tXX^$s϶?(LwB?1s+h5f ͤہfn:efiFUl qZڥ\9xtۘb >@ 9둊|k fDԫ{mͧL %sx pzvgl1 XQ_ig\8<pGO4&ؒGwd8grALdupP`;t"Hm?zm`Kc0;u=升~cy2A<ӽTI_Tg-$SƑ,J˒Gns}V ,R"(C~S{#Siosm#I+0y-dNlm=ђm?1ZdRT(x<6ozhDyۿ^62k(fP i_~:U'Θ$8ϵZVІG5vﵘ"H9NKP4pcA,75fZES9<\]@$>V$n:xJbM\&FӣueiF/#?*I~AF]hsB{8E!eC&Y9#1Y%Ui̛ 0}L#9S$`'Ӑ=nDiJy gF.IQNWlk y0 6vJNڳ8+脶bPR3cTYK(\p"8㞧R%fY^0Xg{{+@9')xEi%wʅ#0yg{6xUAi3~σNF?G޴!bȒݢpnzv=c1ۥrE 2S9'iD[o8U84{II7&ɩmQf]\ >YXm5mլ{@utTNN<<$^K̿i ԼvVgGc\~5P*h5 sU$OTTGZPSҺ @$',`1#=pR~83[9hhɶ&Hn#˵v\})u4c-HYwE>TiP{焣DGO'> -$!7|͸U$zm\q%d䪜OOKB=B8bSy'9%5cizgjxxs>㎝JX\^$HfVbS׸f5힣M$j퍕H;fUhV6Y(lds4,h1)~Ib{Ԃ{z#s eawTL㹝Yq$),-I !A$Ld~>/2KmmO ~ ] $oҨ2$Ű!1VPr[U%їZk+{h jAۓ^Ņ˚6p:`\iR]Mj[`6Fvÿ= g\H@a%nFqM 85mk=FX-²<;OPyb2#\ۗY{zig ź1pTI׽l2]B)<;|?.x3yVoR]ҹH\$\| H'FbF˶7ORx㷥^eSw$O'~fݒQ,iX3a); p Oq,nBiڝJ I26ݫGRz?]) vft9];fnȥݍ[R H:57VIَɼ,|#sו>P` ٦1 #q8kS8#,nKw n=-w(/IkZj]VγUhC${3 Ħa\wJ{dOj֙fG̛Wh2;{ fehO1b>cק#sVWJ_Eap|vN;vfVC6d=A'tR cufy0r9}j7]]meG !Ѥ*h/V9 :TYs AZKvm`szMKEF%'!ycR63u+ ̀Av֠o-AYdTv!p"qq'>*ā\,W؅Gc@($fTRRofbU?QZHDUP@^2InsY2] )+yP:{qWR_=U9FWVfROҟh-OrQʢK1bB*H#z7Xcb oLwY=[#pQESkyY㙢򀗙FxӑNMFHc-X.3'gj-YPK 噎z=zwԖۚ-nV 2é3J֊uE %l+wazޙ{~}Ħ&iDR[c2~piOϼ'h秧b9RvQ^,׷xYh¾Hn<í:o//EXOs}uoCSd6đv8<AZ:c$J&c(cZ)ipz&6E1{ nU 8OeO5RpG<0P=Q֤餂vs9joiw] kВ&AZѕio#H"D\wg+B }!qR~!VLop8b[n50Zlg'2sM-42mi-oa+\z95b=)ewqa19>ӡ 2qn|m*3цzQC:iJۥ@6O}i[]#u)crp1wX9l@t'>ާU/ % SM,;g=3H/.n]Id$Ovyzzٍf}Z6#* G|sg=d)>Y8}@ "9H`K[ˇGU;s{ߥR{ˁp+`o뚯X?Lw3C!`)^iZ=wO!ab7DJ&8s5RM|!}#)s4iYnpV<*JypG^$BG7Ϟq)n/"E wÓ{~ջnQ TkjmW'Ձ6RcL-?y2F~T!Ƅ)$d3{Vp0XAJP")cvG1ϟȠq۵Rb-ZfM$[ 'GRw'|ilHTݞ{q>V]<ˍ\a_zĜU;-Oɼ+xrW g?^vF]#if NH:_¥p+#0HI\t?4+:IqhPǦBA׷|V 8@@s;L`>IY-Ec`丵W3-NT%TssU.vClm$gsZBht"F-a"l.sVrn-76Ac:d-Z+JJ9lrskMu+[mq{3\\B2 aIUqp?Oylcr{ K, 7^z;tm󮶆rI(遑zU#m'3m֪/jKWz|zͷۗI1 dIsRkmBZhL*dr29 [kz|neש9<8v#>⢃Fk(ɋ핷7GKWqYls{n0G^xx )S0ppA >pqk;$] Q;VFTD>XPj@>s8'}܋k0cv߅Dp3?. [p39<5diPڅ;N.JՋ 9幼`%sIW(|y9*A'u(MN *㧱qE}vDCr'h+{7u5/.h713gj|:e#m"&q3=M_ #]f_#ˎQylHUss춺r!f..&38ϧn3 3 VO0ݰ NqG9>ޕN]JKB<{~[$=V ANK cnne$R p88=qO N-ȿ$07qзwk&Lrڼwk{sk,s$ M#3 <Ȟ}J7mNa.' I*'o闪ʲ!:8=T/I5Y[x,H$pr+gLcs銵Tɴ_)0 G>@[~n<|Zk,R1DPx#˨C'{K ,_N9{],JVKG&6,w\{J-+ȫZ4IQ[kS*&#`'i]V]B&Q2H'w簨u.IL nVF%ٳ̵C 9N]f64o0ɲH Y(8 @]Ek{sƾSy1Gb@Ͽl˰I3:9i!h7;2㊥-ډ<ȵ1$'t4+J/AΦK+K[dJKP3ڛ*hE2Eط}g 8m, ]ݯ級E\ |9Uҧkiyoy*ўJ;#kIkY(#Ȼ\~xk\5ˈMU{Qm*̌I tSZ=[Dѕ zgZ;FX]nV9$==+2s#q(>br7E2~^^kKSg#ə-|bI$qm澘~^ FO>WQP`&X,Ӡ5&cljW";RT9Zճ |m3;3x>b9<`k $% lߵYϳCynp'< *ȹqi[9[W \ViiVYL&YpCg=W{..[ȉlnݓRM^VWHCm76Sz̷LQ*2$E(4DOƤHd&cR\[aq)̬~F$3)p;VOF5]̂e4P68^#׌sSZ]'έ0h[9;tV ˶xӓS[[FwgR02r+SfvK.BvHepǯ9)&f[gD\O:y H]39JYNkYd30zr;fcXְq编Nq=9wRIJ'`C%rF*k\Z[]yf*3Hx?+RW{kha޻c\`dΓZQ;#RVӤra$R,"!c F=sگ;ˍF]mܑ!^~I d?ϔX ~y`|#=1R \j0329VI=n6,K2@v,a둂;rqW:WRFL.O u#+:צ8<>bFzc׿5-^ W V &/%hQ 03XRCj t 1wJ1d*΍-R޼{ifgE[.$'<9ϵfFډ-t$Eа++5'Ҫ~n+0nws{ZLk41dbq>*ŝwV 63?AywV*jڸF'B6v[U p|{VMѺI.-TX~e 4@>$B GjYLI03ҪDYnRZ,#SػlşT)";+ brdQlE㷂x}MiKKpp[fAG5RY!ned1)R^XdϚvkV>QgY EyIA\H9M5͜b+z 5ңlßVΤ<3 cwH>Ψ Db]qzw{X{ck*fdBI ?QZjMfX^EpAY?'mDՋW14m.m'X=5M1,ȂI9|}{V<)+Xiw͂sNG >z6"dx=N?E {ճhw!ӌ`ɪŹ/l#7lEy\c= ky< x9c3TQxXg?2gFpA\[lʏ_IUfK+c#zi6 [Mj{iNG>/Kbub7|1\ 4I( 'AI920bfFdκ8b@A:RKyͣ,T:i#D8\d:VN/5[$e`+וzNڤ-S2hz$϶N}}s2-l2G `,2s>.sh2 Yx><`; Eg "eC*=~_n֪Ͼk4D#or{ct/:N;V!bYTzW:r3Z%QwT3JP3856$jbSx8GZrz"Jcb9\:.z;z>91M+8i#Rqk}un,\ŀJVb'Sqo+I2DZn$u;@ 9?:]t4Mq !G1Mh˞Ia׃1ǽ`YlZEhfam"繊;ltAm+ &t:ϱ% F1վ-0A xexOn#\Eye4E(`΋quk;3)E'tKl݁ϧ}_HhR9@9N9=xknLLXRAB%2T^y#?͒N2P\tǠIGd -A($dy$t-gHfUd?*K3x@9<>' 9onc(@PJ7iãO.L/32F잃5go{ vi9@Ϸ3A^ZD-=c>Bܖ$HjS[oBrB:{{JinimWvW,6fm`Fy`r)l/㴇rNR5Z d};Ku6ӜOZmJiEQ9lp+Nm42qrqrnmvpz;{x9 8U!F*;[L貦An޽yo5kFʫyڬԲ!vd8/ϾkAѠJqI_Kssmncc*Ǧ5\͡(&9&g {GX/v T'me2][m$ɓАp}Udo50Gm#9u? [hQ[k W .}N}.H%W"(9nBI!-{PZGn&#JQr{JӚĨ{{ 9?*`X^D^=1# $vё+HsЊl'.!XT lv cЎN*mjdg4֭Z]vpU,-$rrK/m\ZI&BZ ylzºi6A4,ɱ\5=zh2\,$ЊN-NȡX- U]LO8 ,aʸN3jLvr"gĨ 83EdmZ3%s ҬOD2m7STw0%Cl&9r+*twq[p[y1cvIuEPI8|Gjt"K0C+;L8ςc"A[il@$TWeC(ى򪎊s]k]8AŽEL{2GQrGi$\Ƕr22z#TꚵwUkY G${89k dy X;u'N^J$~yMEu٘y`;s]@,e,Os#OjX2A<"l<\ Um2Xt# S97NRbC `OjZmKpq̉Jyx$H@֫M$ڕZE)uXy)*:6ۛpd;BA rzfAGJʼnPdPi_F`IY%T|G(98,8Y3;1_!3ª `s}EXK)rU!c#3mOО33 ݾ7o3I0بB`d1HJR՘c~0{7$($ELGV'ō.n|1rsOQ׿`pcqqɬ+YH5[EGOxSC #`nUwr' }kiE_Rbƥ{ lֺIV1 <1~+OeyQ{g֯iUYeHCL(9,G~~WZC:*yz~??U.l2[\esI$rgi3畎/ζú"78%3'ad3\K(B[p0\\r ,W[A rZēe]I%ԃ}'w lg'>-2K2K2~psqfWF_mCAcˠc߭b=\޲Mq,*yj#)Jf]rdPDsIзin^DyM'v1Ri%ˡsZ۪0DIP :>]F. m5H,6Gy5/u9դCd\B:uvhmi0wBC}39'r(m.I4Gm`GRWi'q;xKatxL/%zp?U Jl/<\ v^w}v:]?HHWco8߻yznXÈeR7J!P㑅=Ꝯgki!Io209r:V}ö)$PJŒdjTJoSfKǿ; ʇd {s|N;a6ۭ0w q&=;y- n|;Aqj4іyH=4s=?nm^b=zw8UKշIPYychIϧRtҍMي*lR avƞc\<9 i9jKKNk\0K+mHf9"R\uԗr.m< *d%\ $F+jRѡb7 @j QIn|NGLsSʞ\f;|eQgv;{zWe}a v6^R'8ԐwL`PkbO |``l=I?@}7u \Hsӧ\ěPHUŶ [Or R݅tܮnwr0pNN@?ĽA #srI>I_pۛ+[X088R㲙2adǥV!KS)q̧@ Ian Britton Communications ian Britton - FreeFoto.com FreeFoto.com Communications Communications 8/1 333/1280 1 2002-07-13T15:58:28Z 0200 -13/20 4 3/5 0 0100 0/1 2 3085/256 3085/256 54,59.380000N 1,54.850000W WGS84 14:58:24Z 2.0.0.0 0 5 2400 1600 0 2 30827/3245 Communications Photographer Ian Britton BUS Ubited Kingdom Ian Britton 2002-06-20 Communications Communications 5 Ian Britton 8 6 ian Britton - FreeFoto.com Communications 400 600 FUJIFILM FinePixS1Pro 1 2 Adobe Photoshop 7.0 300/1 2 300/1 2002-07-13T15:58:28Z 2002-07-19T13:28:10Z Photographer adobe:docid:photoshop:84d4dba8-9b11-11d6-895d-c4d063a70fb0 True www.freefoto.com C   %# , #&')*)-0-(0%()(C   (((((((((((((((((((((((((((((((((((((((((((((((((((["<!1AQa"q#2BR$3br&c%!1A"2Qaq ?cao1TOɅCvPcT穟HPV'GbDס[EtQ:T4 ~!F夺s*΄A 9 mW2HIF: .I=DZg( ,н&J|z` <'GK 駨ua*2ӔOzX/9 =m$ku{I2r.cTy}?QSAH"{03rܾ*-fPYMVy0%3OpO]VPYspz,VpGTYbMO|y%XCCq^#r`˘L.Tk|Lg͗Z̓(! 2 M8Z(Q.c ; n[OG,tn\`1K}c2l'~sDg`n#kIfmjQq.W0(]APo|.Ȇ[Xyr5c$a5_ydy(& vMȈF/}Ohj{ eZM 0d3W1WM6 ouK(sTqI]}OG( H _GmD!JJMz#V]pf6Eg]{g':ڇ0Zʍ"{gWù.{ܣ'Uo;fy\)tφ& a9U<2C5[V JmG@a3OSMKmtIsjEEJ;rS*>OIMX=͸K&55U$fouO:Z.?N,I&3cI毖J'ceQ@cEGɊݷ;q7QUqcO3#E"ƍnjD Tj7 $peu41JʏrᘝƁļ?UeTqSy 춰:؁C>s={qc8((h^KM=YR^C傉uıD+ubжKF3NPE=W#^P[򪙲ȉչ~e^-\}1>B؇/PQsY+-r/۩7W2n5jkDgH$0GSR¦Iv{OzE2%oc׷Nv}!;nd UR]M!%}&짩 2z\$l ROb~;;ja9cQ$9Eeh؏Of3RZWJ?(HȢGSq$n\:N=pS;4YRpi;|z|Ɔ3`rxEO/B<757?\Pœ'*|4UJT+I _UjPuqxkAų@#L72Wc醑h9n.n8#".2 3J|AQSGD"&r䒽lm E 5"/8 }t|Xu/@V03kgr8ibʩ1k{_sǠ%ck( Z{s<<1{37 QYkϚf=Tv,U8ݪao3ZםݾNQ4qBҹ OADM7 -'z 'V9 k^8ҞdDy-k`FUõ"4QR۳1{ភicYeqO >tIBn84¯bMؑpÜ/Nbm8fQ*)D݊bgR CVf/z/꩏r) I%_aUVBg`z#[XöCGc 4H޷E )Tm%CRGPItüɗC}7` ̬s)D=]-M1Ҭֹg(1EAT2"Jo4?̑JKaMoѫdsrp!c$y(HY4}*$Hmu>d(+W- yP 6>]>x`rukZVɤJ!]#|eұ9AE$y(Z|m$HCxvU.L*N`^`wQnhvDh58!>(r;aIs4{a]:;s`V,ǂ*4%:3+ORi1ӦlHrW'b'(d.!oӾrFP&U3&F3JQJe7<,1@52 F:׹4*d2HXSЏ c3& # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import unittest from pyexiv2.utils import undefined_to_string, string_to_undefined, \ Rational, Fraction, \ is_fraction, make_fraction, fraction_to_string class TestConversions(unittest.TestCase): def test_undefined_to_string(self): self.assertEqual(undefined_to_string("48 50 50 49"), "0221") self.assertEqual(undefined_to_string("48 50 50 49 "), "0221") self.assertRaises(ValueError, undefined_to_string, "") self.assertRaises(ValueError, undefined_to_string, "foo") self.assertRaises(ValueError, undefined_to_string, "48 50 50 49") def test_string_to_undefined(self): self.assertEqual(string_to_undefined("0221"), "48 50 50 49") self.assertEqual(string_to_undefined(""), "") def test_identity(self): value = "0221" self.assertEqual(undefined_to_string(string_to_undefined(value)), value) value = "48 50 50 49" self.assertEqual(string_to_undefined(undefined_to_string(value)), value) class TestFractions(unittest.TestCase): def test_is_fraction(self): if Fraction is not None: self.failUnless(is_fraction(Fraction())) self.failUnless(is_fraction(Fraction(3, 5))) self.failUnless(is_fraction(Fraction(Fraction(4, 5)))) self.failUnless(is_fraction(Fraction('3/2'))) self.failUnless(is_fraction(Fraction('-4/5'))) self.failUnless(is_fraction(Rational(3, 5))) self.failUnless(is_fraction(Rational(-4, 5))) self.failUnless(is_fraction(Rational.from_string("3/5"))) self.failUnless(is_fraction(Rational.from_string("-4/5"))) self.failIf(is_fraction(3 / 5)) self.failIf(is_fraction('3/5')) self.failIf(is_fraction('2.7')) self.failIf(is_fraction(2.7)) self.failIf(is_fraction('notafraction')) self.failIf(is_fraction(None)) def test_make_fraction(self): if Fraction is not None: self.assertEqual(make_fraction(3, 5), Fraction(3, 5)) self.assertEqual(make_fraction(-3, 5), Fraction(-3, 5)) self.assertEqual(make_fraction('3/2'), Fraction(3, 2)) self.assertEqual(make_fraction('-3/4'), Fraction(-3, 4)) self.assertEqual(make_fraction('0/0'), Fraction(0, 1)) else: self.assertEqual(make_fraction(3, 5), Rational(3, 5)) self.assertEqual(make_fraction(-3, 5), Rational(-3, 5)) self.assertEqual(make_fraction('3/2'), Rational(3, 2)) self.assertEqual(make_fraction('-3/4'), Rational(-3, 4)) self.assertEqual(make_fraction('0/0'), Rational(0, 1)) self.assertRaises(ZeroDivisionError, make_fraction, 3, 0) self.assertRaises(ZeroDivisionError, make_fraction, '3/0') self.assertRaises(TypeError, make_fraction, 5, 3, 2) self.assertRaises(TypeError, make_fraction, None) def test_fraction_to_string(self): self.assertEqual(fraction_to_string(make_fraction(3, 5)), '3/5') self.assertEqual(fraction_to_string(make_fraction(-3, 5)), '-3/5') self.assertEqual(fraction_to_string(make_fraction(0, 1)), '0/1') self.assertRaises(TypeError, fraction_to_string, None) self.assertRaises(TypeError, fraction_to_string, 'invalid') pyexiv2-0.3.2/test/encoding.py0000664000175000017500000000676511651315372015673 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import unittest import os import sys import binascii import locale from tempfile import gettempdir from pyexiv2.metadata import ImageMetadata _HEXDATA = """ ff d8 ff e0 00 10 4a 46 49 46 00 01 01 01 00 48 00 48 00 00 ff e1 00 36 45 78 69 66 00 00 49 49 2a 00 08 00 00 00 01 00 32 01 02 00 14 00 00 00 1a 00 00 00 00 00 00 00 32 30 31 30 3a 30 33 3a 31 38 20 31 33 3a 33 39 3a 35 38 00 ff db 00 43 00 05 03 04 04 04 03 05 04 04 04 05 05 05 06 07 0c 08 07 07 07 07 0f 0b 0b 09 0c 11 0f 12 12 11 0f 11 11 13 16 1c 17 13 14 1a 15 11 11 18 21 18 1a 1d 1d 1f 1f 1f 13 17 22 24 22 1e 24 1c 1e 1f 1e ff db 00 43 01 05 05 05 07 06 07 0e 08 08 0e 1e 14 11 14 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e ff c0 00 11 08 00 01 00 01 03 01 22 00 02 11 01 03 11 01 ff c4 00 15 00 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 ff c4 00 14 10 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff c4 00 14 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff c4 00 14 11 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff da 00 0c 03 01 00 02 11 03 11 00 3f 00 b2 c0 07 ff d9 """ _BINDATA = binascii.unhexlify(''.join(_HEXDATA.split())) class TestEncodings(unittest.TestCase): def setUp(self): self._cwd = os.getcwd() os.chdir(gettempdir()) try: locale.setlocale(locale.LC_ALL, '') except locale.Error: self.encoding = None else: lc, self.encoding = locale.getlocale() def tearDown(self): os.chdir(self._cwd) def _create_file(self, filename): try: os.remove(filename) except OSError: pass fd = open(filename, 'wb') fd.write(_BINDATA) fd.close() def _test_filename(self, filename): self._create_file(filename) m = ImageMetadata(filename) m.read() os.remove(filename) def test_ascii(self): self._test_filename('test.jpg') def test_latin1(self): self._test_filename('tést.jpg') def test_latin1_escaped(self): self._test_filename('t\xc3\xa9st.jpg') def test_unicode_ascii(self): if self.encoding is not None: self._test_filename(u'test.jpg') def test_unicode_latin1(self): if self.encoding is not None: self._test_filename(u'tést.jpg') def test_unicode_latin1_escaped(self): if self.encoding is not None: self._test_filename(u't\xe9st.jpg') pyexiv2-0.3.2/test/TestsRunner.py0000775000175000017500000000650411651315372016373 0ustar osomonosomon#!/usr/bin/python # -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2008-2011 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import unittest # Test cases to run from ReadMetadataTestCase import ReadMetadataTestCase from rational import TestRational from gps_coordinate import TestGPSCoordinate from notifying_list import TestNotifyingList from exif import TestExifTag from iptc import TestIptcTag from xmp import TestXmpTag, TestXmpNamespaces from metadata import TestImageMetadata from buffer import TestBuffer from encoding import TestEncodings from utils import TestConversions, TestFractions from usercomment import TestUserCommentReadWrite, TestUserCommentAdd from pickling import TestPicklingTags from datetimeformatter import TestDateTimeFormatter def run_unit_tests(): # Instantiate a test suite containing all the test cases suite = unittest.TestSuite() suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(ReadMetadataTestCase)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestRational)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestGPSCoordinate)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestNotifyingList)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestExifTag)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestIptcTag)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestXmpTag)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestXmpNamespaces)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestImageMetadata)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestBuffer)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestEncodings)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestConversions)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestFractions)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestUserCommentReadWrite)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestUserCommentAdd)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestPicklingTags)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestDateTimeFormatter)) # Run the test suite return unittest.TextTestRunner(verbosity=2).run(suite) if __name__ == '__main__': run_unit_tests() pyexiv2-0.3.2/test/buffer.py0000664000175000017500000000612511651315372015344 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import unittest import os.path import hashlib from datetime import datetime from pyexiv2.metadata import ImageMetadata import testutils class TestBuffer(unittest.TestCase): def setUp(self): filename = os.path.join('data', 'smiley1.jpg') self.filepath = testutils.get_absolute_file_path(filename) self.md5sum = 'c066958457c685853293058f9bf129c1' self.assert_(testutils.CheckFileSum(self.filepath, self.md5sum)) def _metadata_from_buffer(self): fd = open(self.filepath, 'rb') data = fd.read() fd.close() return ImageMetadata.from_buffer(data) def test_from_file_and_from_buffer(self): # from file m1 = ImageMetadata(self.filepath) m1.read() self.assertEqual(hashlib.md5(m1.buffer).hexdigest(), self.md5sum) # from buffer m2 = self._metadata_from_buffer() self.assertEqual(hashlib.md5(m2.buffer).hexdigest(), self.md5sum) def test_buffer_not_updated_until_write_called(self): m = self._metadata_from_buffer() m.read() self.assertEqual(hashlib.md5(m.buffer).hexdigest(), self.md5sum) # Modify the value of an EXIF tag m['Exif.Image.DateTime'].value = datetime.today() # Check that the buffer is unchanged until write() is called self.assertEqual(hashlib.md5(m.buffer).hexdigest(), self.md5sum) # Write back the changes m.write() # Check that the buffer has changed self.failIfEqual(hashlib.md5(m.buffer).hexdigest(), self.md5sum) def test_from_original_buffer(self): m1 = self._metadata_from_buffer() m2 = ImageMetadata.from_buffer(m1.buffer) self.assertEqual(hashlib.md5(m2.buffer).hexdigest(), self.md5sum) def test_from_modified_buffer(self): m1 = self._metadata_from_buffer() m1.read() key = 'Exif.Image.ImageDescription' value = 'my kingdom for a semiquaver' m1[key] = value m1.write() m2 = ImageMetadata.from_buffer(m1.buffer) m2.read() self.assertEqual(m2[key].value, value) pyexiv2-0.3.2/test/notifying_list.py0000664000175000017500000003006011651315372017127 0ustar osomonosomon# -*- coding: utf-8 -*- # ****************************************************************************** # # Copyright (C) 2009-2010 Olivier Tilloy # # This file is part of the pyexiv2 distribution. # # pyexiv2 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. # # pyexiv2 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 pyexiv2; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. # # Author: Olivier Tilloy # # ****************************************************************************** import unittest from pyexiv2.utils import ListenerInterface, NotifyingList import random class SimpleListener(ListenerInterface): def __init__(self): self.changes = 0 def contents_changed(self): self.changes += 1 class TestNotifyingList(unittest.TestCase): def setUp(self): self.values = NotifyingList([5, 7, 9, 14, 57, 3, 2]) def test_no_listener(self): # No listener is registered, nothing should happen. self.values[3] = 13 del self.values[5] self.values.append(17) self.values.extend([11, 22]) self.values.insert(4, 24) self.values.pop() self.values.remove(9) self.values.reverse() self.values.sort() self.values += [8, 4] self.values *= 3 self.values[3:4] = [8, 4] del self.values[3:5] def test_listener_interface(self): self.values.register_listener(ListenerInterface()) self.failUnlessRaises(NotImplementedError, self.values.__setitem__, 3, 13) self.failUnlessRaises(NotImplementedError, self.values.__delitem__, 5) self.failUnlessRaises(NotImplementedError, self.values.append, 17) self.failUnlessRaises(NotImplementedError, self.values.extend, [11, 22]) self.failUnlessRaises(NotImplementedError, self.values.insert, 4, 24) self.failUnlessRaises(NotImplementedError, self.values.pop) self.failUnlessRaises(NotImplementedError, self.values.remove, 9) self.failUnlessRaises(NotImplementedError, self.values.reverse) self.failUnlessRaises(NotImplementedError, self.values.sort) self.failUnlessRaises(NotImplementedError, self.values.__iadd__, [8, 4]) self.failUnlessRaises(NotImplementedError, self.values.__imul__, 3) self.failUnlessRaises(NotImplementedError, self.values.__setslice__, 3, 4, [8, 4]) self.failUnlessRaises(NotImplementedError, self.values.__delslice__, 3, 5) def _register_listeners(self): # Register a random number of listeners listeners = [SimpleListener() for i in xrange(random.randint(3, 20))] for listener in listeners: self.values.register_listener(listener) return listeners def test_setitem(self): listeners = self._register_listeners() self.values[3] = 13 self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) self.failUnlessRaises(IndexError, self.values.__setitem__, 9, 27) self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) def test_delitem(self): listeners = self._register_listeners() del self.values[5] self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) self.failUnlessRaises(IndexError, self.values.__delitem__, 9) self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) def test_append(self): listeners = self._register_listeners() self.values.append(17) self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 17]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) def test_extend(self): listeners = self._register_listeners() self.values.extend([11, 22]) self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 11, 22]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) self.failUnlessRaises(TypeError, self.values.extend, 26) self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 11, 22]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) def test_insert(self): listeners = self._register_listeners() self.values.insert(4, 24) self.failUnlessEqual(self.values, [5, 7, 9, 14, 24, 57, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) def test_pop(self): listeners = self._register_listeners() self.values.pop() self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) self.values.pop(4) self.failUnlessEqual(self.values, [5, 7, 9, 14, 3]) for listener in listeners: self.failUnlessEqual(listener.changes, 2) self.values.pop(-2) self.failUnlessEqual(self.values, [5, 7, 9, 3]) for listener in listeners: self.failUnlessEqual(listener.changes, 3) self.failUnlessRaises(IndexError, self.values.pop, 33) self.failUnlessEqual(self.values, [5, 7, 9, 3]) for listener in listeners: self.failUnlessEqual(listener.changes, 3) def test_remove(self): listeners = self._register_listeners() self.values.remove(9) self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) self.failUnlessRaises(ValueError, self.values.remove, 33) self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) def test_reverse(self): listeners = self._register_listeners() self.values.reverse() self.failUnlessEqual(self.values, [2, 3, 57, 14, 9, 7, 5]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) def test_sort(self): listeners = self._register_listeners() self.values.sort() self.failUnlessEqual(self.values, [2, 3, 5, 7, 9, 14, 57]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) self.values.sort(cmp=lambda x, y: y - x) self.failUnlessEqual(self.values, [57, 14, 9, 7, 5, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 2) self.values.sort(key=lambda x: x * x) self.failUnlessEqual(self.values, [2, 3, 5, 7, 9, 14, 57]) for listener in listeners: self.failUnlessEqual(listener.changes, 3) self.values.sort(reverse=True) self.failUnlessEqual(self.values, [57, 14, 9, 7, 5, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 4) def test_iadd(self): listeners = self._register_listeners() self.values += [44, 31, 19] self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 44, 31, 19]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) def test_imul(self): listeners = self._register_listeners() self.values *= 3 self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 5, 7, 9, 14, 57, 3, 2, 5, 7, 9, 14, 57, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) def test_setslice(self): listeners = self._register_listeners() # Basic slicing (of the form [i:j]): implemented as __setslice__. self.values[2:4] = [3, 4] self.failUnlessEqual(self.values, [5, 7, 3, 4, 57, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) self.values[3:5] = [77, 8, 12] self.failUnlessEqual(self.values, [5, 7, 3, 77, 8, 12, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 2) self.values[2:5] = [1, 0] self.failUnlessEqual(self.values, [5, 7, 1, 0, 12, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 3) self.values[0:2] = [] self.failUnlessEqual(self.values, [1, 0, 12, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 4) self.values[2:2] = [7, 5] self.failUnlessEqual(self.values, [1, 0, 7, 5, 12, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 5) # With negatives indexes self.values[4:-2] = [9] self.failUnlessEqual(self.values, [1, 0, 7, 5, 9, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 6) self.values[-2:1] = [6, 4] self.failUnlessEqual(self.values, [1, 0, 7, 5, 9, 6, 4, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 7) self.values[-5:-2] = [8] self.failUnlessEqual(self.values, [1, 0, 7, 5, 8, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 8) # With missing (implicit) indexes self.values[:2] = [4] self.failUnlessEqual(self.values, [4, 7, 5, 8, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 9) self.values[4:] = [1] self.failUnlessEqual(self.values, [4, 7, 5, 8, 1]) for listener in listeners: self.failUnlessEqual(listener.changes, 10) self.values[:] = [5, 7, 9, 14, 57, 3, 2] self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 11) def test_delslice(self): listeners = self._register_listeners() del self.values[2:3] self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) del self.values[2:2] self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 1) # With negatives indexes del self.values[4:-1] self.failUnlessEqual(self.values, [5, 7, 14, 57, 2]) for listener in listeners: self.failUnlessEqual(listener.changes, 2) del self.values[-1:5] self.failUnlessEqual(self.values, [5, 7, 14, 57]) for listener in listeners: self.failUnlessEqual(listener.changes, 3) del self.values[-2:-1] self.failUnlessEqual(self.values, [5, 7, 57]) for listener in listeners: self.failUnlessEqual(listener.changes, 4) # With missing (implicit) indexes del self.values[:1] self.failUnlessEqual(self.values, [7, 57]) for listener in listeners: self.failUnlessEqual(listener.changes, 5) del self.values[1:] self.failUnlessEqual(self.values, [7]) for listener in listeners: self.failUnlessEqual(listener.changes, 6) del self.values[:] self.failUnlessEqual(self.values, []) for listener in listeners: self.failUnlessEqual(listener.changes, 7)