pax_global_header00006660000000000000000000000064131602566130014515gustar00rootroot0000000000000052 comment=ce68cd179e253e73659ec1d44647b438a10ec311 mini_exiftool-2.9.0/000077500000000000000000000000001316025661300143725ustar00rootroot00000000000000mini_exiftool-2.9.0/.aspell.pws000066400000000000000000000016541316025661300164710ustar00rootroot00000000000000personal_ws-1.1 en 114 CodedCharacterSet Stjernstrom iptc README releating Gemfile Friedrich echoe encodings stderr Cypriano Fixnum JPEGs Gitorious neccessary jpg Soika rdoc ccoenen StandardError gemspec Kulander exif refactorings FNumber dir popen io JPEG yaml mfo coord md MiniExiftool Tempfile warings Gaelan atomar Atomar Regexes mie mluts commandline robotmay params Soutar metadata filename OO rb SupplementalCategories stdout exiftool ExifTool prerelease str Henning Krogmann timestamps encod customizable bundler CaptionAbstract Picturelife cgat Hinrich DYNAX gras yardopts Barushev aiff pdf MacRoman DateTime TestEscapeFilename ptore ws icc Gieseler UTF Wil shioyama Joshfindit Dungan Romahn uwe DateTimeOriginal datetimeoriginal filenames POSIX darwin SubjectLocation GPS quicktime pstore PStore shellwords Rau photoshop png charset rubyforge github vorbis Refactoring Bugfix xmp Jrome Horrocks acki Salzberg Hoy regtest json mini_exiftool-2.9.0/.gitignore000066400000000000000000000000711316025661300163600ustar00rootroot00000000000000*~ .*.swp doc pkg .yardoc .regtestrc .rimrc Gemfile.lock mini_exiftool-2.9.0/.travis.yml000066400000000000000000000006151316025661300165050ustar00rootroot00000000000000language: ruby before_install: - sudo apt-get install wget - wget http://www.sno.phy.queensu.ca/~phil/exiftool/Image-ExifTool-9.76.tar.gz - tar -xzf Image-ExifTool-9.76.tar.gz - cd Image-ExifTool-9.76 - perl Makefile.PL - make - sudo make install - cd .. - exiftool -ver rvm: - 2.0.0 - 2.1.9 - 2.2.5 - 2.3.1 - ruby-head notifications: email: - janfri26@gmail.com mini_exiftool-2.9.0/.yardopts000066400000000000000000000000441316025661300162360ustar00rootroot00000000000000--files Tutorial.rdoc,examples/*.rb mini_exiftool-2.9.0/COPYING000066400000000000000000000634751316025661300154440ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! mini_exiftool-2.9.0/Changelog000066400000000000000000000174411316025661300162130ustar00rootroot000000000000002.9.0 Use rim/regtest instead of regtest/task to include regtest files into gem. Update to rim 2.15 to include Gemfile and other stuff into gem file. 2.8.2 - Upgrade to rim 2.10.0 to unfreeze gemspec. 2.8.1 - Improve documentation and use Integer instead of Fixnum because it's deprecated in Ruby 2.4.0. 2.8.0 - MiniExiftool doesn't close any longer a given IO to MiniExiftool.new respectively MiniExiftool#load. You are responsible to do that. Thanks to mfo for the suggestion and implementing it. 2.7.6 - Add Gemfile (for those who use bundler). 2.7.5 - Add gem description and fix typo in summary. 2.7.4 - Print messages at $stdout when generating the PStore file with ExifTool tag names. Thanks to Joshfindit for the idea. 2.7.3 - Update docs. 2.7.2 - Include example files in gem. 2.7.1 - Improve test for fast options: Ignore other messages from stderr. - Fix typo in license identifier. - Improve example show_speedup_with_fast_option. 2.7.0 - Use duck typing to determine if filename_or_io is a filename or an IO instance. - New option :fast2. - Add example show_speedup_with_fast_option. - Update docs. 2.6.0 - Support reading from IO instances. Thanks to Gaelan for the idea. - New option :fast to increase speed when extracting information from JPEG images which are piped across a slow network connection. Thanks to Felipe Cypriano for the idea. - Refactoring: Use Open3 for all command-line calls. 2.5.1 - Add gemspec. 2.5.0 - Make the pstore dir customizable: MiniExiftool.pstore_dir and MiniExiftool.pstore_dir= Thanks to Shawn Pyle for the idea and a first approach of implementation. - Update README. 2.4.2 - Bugfix: Don't ignoring *_encoding options when saving. 2.4.1 - Handling tag values of the form x/0 correct. Thanks to Picturelife for a clever solution to solve this. - Some internal housekeeping. 2.4.0 - New method MiniExiftool#copy_tags_from. Many thanks to cgat for the hint and implementing a first approach. - Saver handling of encoding problems. - Approving documentation. - Using regtest for integration testing. - Some internal refactorings. 2.3.0 - New options :exif_encoding, :iptc_encodings, :xmp_encodings etc. to change specific encodings See -charset option of the exiftool commandline application - Some internal improvements 2.2.1 - Bugfix: Ignore filename specific tags to avoid encoding confusions. 2.2.0 The Encoding Release - New option :replace_invalid_chars to handle "bad data" invalid byte sequences in UTF-8 Thanks to Chris Salzberg (aka shioyama) and Robert May (aka robotmay) for precious hints - Support of different encodings for commandline params and filenames (neccessary to support Windows) to allow filenames with special chars Thanks to uwe58 and others for hints - Doing different commandline escaping for windows and POSIX systems Thanks to Michael Dungan for the hint - Update Tutorial 2.1.0 - insert require 'json' - Drop option :convert_encoding (use Ruby String methods instead) - Make the test_access_coordinate work on different exiftool version. 2.0.0 - Drop Ruby 1.8 compatibility. - Using JSON to parse exiftool output (need exiftool 7.65 or higher). 1.7.0 - Support exiftool -c option for formatting GPS coordinates. Thanks to Lee Horrocks for the patch. - Switching from shellwords to manual escaping. Hopefully it works now on Windows systems. Thanks to uwe58 and jpg0 for the hints. 1.6.0 - Type conversion in MiniExiftool.from_hash. Thanks to Ethan Soutar-Rau for the merge request. - Switching to rim. (No longer troubles with echoe.) - ExifTool version detection delayed. Thanks to Sebastian Skałacki for the merge request. - New method MiniExiftool#save! Cherry-picked commit from Wil Gieseler. 1.5.1 - Make rational values work on Ruby 1.8.7. 1.5.0 - Supporting exiftool command-line option -m. rubyforge request [#29587] Thanks to Michael Grove for reporting. - Supporting rational values. 1.4.4 - Fix escaping of values for older versions of Shellwords. 1.4.3 - Fixing rubyforge bug [#29596] (Quotes in values) Thanks to Michael Grove for reporting 1.4.2 - Add .yardopts file to gem. 1.4.1 - Update documentation for using yard. 1.4.0 - Allow symbols for tag access with []. - Refactoring tests. 1.3.1 - Remove TestEscapeFilename test and releating test photo because the latter produces errors on windows systems. - Version check in prerelease task. 1.3.0 - MiniExiftool is now ready for Ruby 1.9 All tests in the test suite pass. :) 1.2.2 - Fixing ptore directory naming convention for darwin. Thanks to Denis Barushev for the hint. 1.2.1 - Switching to echoe. - Update e-mail address. 1.2.0 - Fixing time zone handling. Thanks to ccoenen for the hint. 1.1.0 - Escaping filenames in shell commands Thanks to Michael Hoy for the hint and implementing a patch which was the base for this fix. 1.0.2 - Fixing warings Thanks to Peter-Hinrich Krogmann for the hint. 1.0.1 - Fixing bug [#22726] Making MiniExiftool::Error public. Thanks to Mathias Stjernstrom for sending a patch. 1.0.0 - Be aware changing in the interface: - List tags (e.g. Keywords, SupplementalCategories) are now handled as arrays. - Tag SubjectLocation is not longer an array value but a string value! 0.7.0 - Changing composite behaviour: Composite tags are now included as standard! - New method MiniExiftool.opts which returns a hash of the standard options used for MiniExiftool.new - New option :convert_encoding for MiniExiftool.new which uses the -L-option of the exiftool command-line application (see online documentation for it) Thanks to Henning Kulander for the causing of this change. 0.6.0 - New methods for serialization: - MiniExiftool.from_hash - MiniExiftool.from_yaml - MiniExiftool#to_hash - MiniExiftool#to_yaml Thanks to Andrew Bennett for the initial idea of YAML-serialization - Refactoring of tests - Small documentation update 0.5.1 - Warning "parenthesize argument(s) for future version" removed Thanks to Greg from knobby.ws 0.5.0 - New option :timestamps to create DateTime objects instead of Time objects for timestamps (Fixing bug #16328) - Invalid values of timestamps (i.e. 0000:00:00 00:00:00) are now mapped to false 0.4.1 - Compatibility for Ruby 1.9 0.4.0 - MiniExiftool::Error inherits now from StandardError - Alternative installation via setup.rb - Bugfix Saving of non-read tags doesn't work with tags with hyphen Thanks to Robin Romahn for reporting the bug - New methods: MiniExiftool.all_tags and MiniExiftool.original_tag - Internal: Original tag names (all and writable) are now saved via pstore in a file for better performance 0.3.1 - Typos fixed 0.3.0 - Documentation completed and a Mini Tutorial added - Interface changes: - Test if a value for a tag can be saved is now done in MiniExiftool#save => There is no check at the moment you set a value: the tag occurs in MiniExiftool#changed_values => While calling MiniExiftool#save errors can occur (see next point) - MiniExiftool#save is a transaction: if one or more error occurs the file is not changed! In such a case the errors can be found in MiniExiftool#errors - Parameter opts of MiniExiftool.initialize is now a Hash with two options: - :numerical => read metadata as numerical values - :composite => read also composite tags - Tests added 0.2.0 - Better error handling (i.e. error messages) - Checking if the exiftool command can be executed at loading the lib - New class method exiftool_version - Added tests - Documentation completed 0.1.2 - Bugfix for Windows (Tempfile) Thanks to Jérome Soika for testing - Regexes optimized (a little bit) - New class-method MiniExiftool.writable_tags 0.1.1 - Fixing bug [#8073] Handling the '-' in tag Self-timer Thanks to Eric Young 0.1.0 - New method "revert" - More tests 0.0.1 - Initial release mini_exiftool-2.9.0/Gemfile000066400000000000000000000000471316025661300156660ustar00rootroot00000000000000source 'https://rubygems.org' gemspec mini_exiftool-2.9.0/README.md000066400000000000000000000130251316025661300156520ustar00rootroot00000000000000# MiniExiftool This library is a wrapper for the ExifTool command-line application (http://www.sno.phy.queensu.ca/~phil/exiftool) written by Phil Harvey. It provides the full power of ExifTool to Ruby: reading and writing of EXIF-data, IPTC-data and XMP-data. ## Requirements Ruby 1.9 or higher and an installation of the ExifTool command-line application at least version 7.65. If you run on Ruby 1.8 or with a prior exiftool version install mini_exiftool version 1.x.x. Instructions for installation you can find under http://www.sno.phy.queensu.ca/~phil/exiftool/install.html . Alternatively Wil Gieseler has bundled a meta-gem that eliminates the need for a separate ExifTool installation. Have a look at http://github.com/wilg/mini_exiftool_vendored or http://rubygems.org/gems/mini_exiftool_vendored . ## Installation First you need ExifTool (see under Requirements above). Then you can simply install the gem with ```sh gem install mini_exiftool ``` or simply add to the following to your Gemfile: ```ruby gem 'multi_exiftool' ``` If you need to support older versions of Ruby or Exiftool (see Requirements above) ```sh gem install --version "< 2.0.0" mini_exiftool ``` or if you use a Gemfile add: ```ruby gem 'multi_exiftool', '<2.0.0' ``` ## Configuration You can manually set the exiftool command that should be used via ```ruby MiniExiftool.command = '/path/to/my/exiftool' ``` In addition, you can also tell MiniExiftool where to store the PStore files with tags which exiftool supports. The PStore files are used for performance issues. Per default the PStore files are stored in a sub directory `.mini_exiftool` or `_mini_exiftool` under your home directory. ```ruby MiniExiftool.pstore_dir = '/path/to/pstore/dir' ``` If you're using Rails, this is easily done with ```ruby MiniExiftool.pstore_dir = Rails.root.join('tmp').to_s ``` Important hint: if you have to change the configuration you have to do this direct after `require 'mini_exiftool'`. ## Usage In general MiniExiftool is very intuitive to use as the following examples show: ```ruby # Reading meta data from a file photo = MiniExiftool.new 'photo.jpg' puts photo.title # Alternative reading meta data from an IO instance photo = MiniExiftool.new io puts photo.title # Writing meta data photo = MiniExiftool.new 'photo.jpg' photo.title = 'This is the new title' photo.save # Copying meta data photo = MiniExiftool.new('photo.jpg') photo.copy_tags_from('another_photo.jpg', :author) ``` For further information about using MiniExiftool read the Tutorial.md. in the project root folder and have a look at the examples in directory examples. ## Caveats The philosophy of MiniExiftool is safety over performance. It can not handle more than one file at once. Writing operations are executed on a copy of the original file to have atomar writing: Either all changed values are written or none. To be able to assign errors to a specific tag writing operations also call the Exiftool command-line application once for each changed tag! In short: MiniExiftool has a very bad performance especially at write operations. **If you work with many files it is strongly recommended not to use MiniExiftool but instead my other gem [MultiExiftool](https://github.com/janfri/multi_exiftool). This is designed to handle many files and do reading and writing fast.** ## Encodings In MiniExiftool all strings are encoded in UTF-8. If you need other encodings in your project use the String#encod* methods. If you have problems with corrupted strings when using MiniExiftool there are two reasons for this: ### Internal character sets You can specify the charset in which the meta data is in the file encoded if you read or write to some sections of meta data (i.e. IPTC, XMP ...). It exists various options of the form *_encoding: exif, iptc, xmp, png, id3, pdf, photoshop, quicktime, aiff, mie and vorbis. For IPTC meta data it is recommended to set also the CodedCharacterSet tag. Please read the section about the character sets of the ExifTool command line application carefully to understand what's going on (http://www.sno.phy.queensu.ca/~phil/exiftool/faq.html#Q10)! ```ruby # Using UTF-8 as internal encoding for IPTC tags and MacRoman # as internal encoding for EXIF tags photo = MiniExiftool.new('photo.jpg', iptc_encoding: 'UTF8', exif_encoding: 'MacRoman' # IPTC CaptionAbstract is already UTF-8 encoded puts photo.caption_abstract # EXIF Comment is converted from MacRoman to UTF-8 puts photo.comment photo = MiniExiftool.new('photo.jpg', iptc_encoding: 'UTF8', exif_encoding: 'MacRoman' # When saving IPTC data setting CodedCharacterSet as recommended photo.coded_character_set = 'UTF8' # IPTC CaptionAbstract will be stored in UTF-8 encoding photo.caption_abstract = 'Some text with Ümläuts' # EXIF Comment will be stored in MacRoman encoding photo.comment = 'Comment with Ümläuts' photo.save ``` ### Corrupt characters You use the correct internal character set but in the string are still corrupt characters. This problem you can solve with the option `replace_invalid_chars`: ```ruby # Replace all invalid characters with a question mark photo = MiniExiftool.new('photo.jpg', replace_invalid_chars: '?') ``` ## Contribution The code is hosted in a git repository on github at https://github.com/janfri/mini_exiftool feel free to contribute! ## Author Jan Friedrich ## Copyright / License Copyright (c) 2007-2016 by Jan Friedrich Licensed under terms of the GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1, February 1999 (see file COPYING for more details) mini_exiftool-2.9.0/Rakefile000066400000000000000000000032011316025661300160330ustar00rootroot00000000000000require 'rim/tire' require 'rim/version' require 'rim/regtest' $:.unshift 'lib' require 'mini_exiftool' Rim.setup do |p| p.name = 'mini_exiftool' p.version = MiniExiftool::VERSION p.authors = 'Jan Friedrich' p.email = 'janfri26@gmail.com' p.summary = 'This library is a wrapper for the ExifTool command-line application (http://www.sno.phy.queensu.ca/~phil/exiftool).' p.description <<-END This library is a wrapper for the ExifTool command-line application (http://www.sno.phy.queensu.ca/~phil/exiftool) written by Phil Harvey. It provides the full power of ExifTool to Ruby: reading and writing of EXIF-data, IPTC-data and XMP-data. END p.homepage = 'https://github.com/janfri/mini_exiftool' p.license = 'LGPL-2.1' p.gem_files << 'Tutorial.md' p.gem_files += FileList.new('examples/**') p.install_message = <<-END +-----------------------------------------------------------------------+ | Please ensure you have installed exiftool at least version 7.65 | | and it's found in your PATH (Try "exiftool -ver" on your commandline).| | For more details see | | http://www.sno.phy.queensu.ca/~phil/exiftool/install.html | | You need also Ruby 1.9 or higher. | | If you need support for Ruby 1.8 or exiftool prior 7.65 install | | mini_exiftool version < 2.0.0. | +-----------------------------------------------------------------------+ END p.test_warning = false p.development_dependencies << 'test-unit' if p.feature_loaded? 'rim/aspell' p.aspell_files << 'Tutorial.md' end end mini_exiftool-2.9.0/TODO000066400000000000000000000001601316025661300150570ustar00rootroot00000000000000* Tests for managing tags with pstore * Looking for a solution to dump and restore DateTime instances with YAML mini_exiftool-2.9.0/Tutorial.md000066400000000000000000000144401316025661300165220ustar00rootroot00000000000000# Mini Tutorial ## Installation * Installing the ExifTool command-line application from Phil Harvey (see http://www.sno.phy.queensu.ca/~phil/exiftool/install.html) * Installing the Ruby library (`gem install mini_exiftool`) ## Lesson 1: Reading Meta Data ### A Simple Example ```ruby require 'mini_exiftool' photo = MiniExiftool.new 'photo.jpg' puts photo['DateTimeOriginal'] ``` ### Smart Tag Names In the example above we use `photo['DateTimeOriginal']` to get the value for the time the photo was taken. But tag names are not case sensitive and additional underlines are also irrelevant. So following expressions are equivalent: ```ruby photo['DateTimeOriginal'] photo['datetimeoriginal'] photo['date_time_original'] ``` It is also possible to use symbols: ```ruby photo[:DateTimeOriginal] photo[:datetimeoriginal] photo[:date_time_original] ``` ### Nicer Access Via Dynamic Methods Using the []-method is the safest way to access to values of tags (e. g. Self-timer you can only access this way) but the smarter way is using dynamic method access. You can write: ```ruby photo.datetimeoriginal ``` or also ```ruby photo.date_time_original ``` ### Value Types Following types of values are at the moment supported: * Array (e. g. Keywords => ['tree', 'gras']) * Integer (e. g. ISO => 400) * Float (e. g. FNumber => 9.5) * String (e. g. Model => DYNAX 7D) * Time (e. g. DateTimeOriginal => 2005:09:13 20:08:50) Be aware, if there is only one value in a tag which can hold multiple values the result isn't an array! But you can get one with the Array method: ```ruby # only _one_ keyword p1 = MiniExiftool.new 'p1.jpg' p1.keywords # => 'red' # _more than one_ keywords p3 = MiniExiftool.new 'p3.jpg' p3.keywords # => ['red', 'yellow', 'green'] # if we want to get an array in both cases and don't know # if there is one ore more values set let's take Array() Array(p1.keywords) # => ['red'] Array(p3.keywords) # => ['red', 'yellow', 'green'] ``` ### Using options The ExifTool command-line application has an option (-n) to get values as numbers if possible, in MiniExiftool you can do this with setting the `:numerical` option to `true` while generating a new instance with new or using the `numerical=`-method combining with calling `reload`. Let's look at an example: ```ruby # standard: numerical is false photo = MiniExiftool.new 'photo.jpg' photo.exposure_time # => '1/60 (Rational) # now with numerical is true photo.numerical = true photo.reload photo.exposure_time # => 0.01666667 (Float) ``` This behaviour can be useful if you want to do calculations on the value, if you only want to show the value the standard behaviour is maybe better. The Time class of Ruby cannot handle timestamps before 1st January 1970 on some platforms. If there are timestamps in files before this date it will result in an error. In this case we can set the option `:timestamps` to `DateTime` to use DateTime objects instead of Time objects. There is another option `:composite`. If this is set to `false` the composite tags are not calculated by the exiftool command-line application (option -e). Further options are * `:ignore_minor_errors` to ignore minor errors (See -m-option of the exiftool command-line application, default is `false`) * `:coord_format` set format for GPS coordinates (See -c-option of the exiftool command-line application, default is `nil` that means exiftool standard) * `:fast` useful when reading JPEGs over a slow network connection (See -fast-option of the exiftool command-line application, default is `false`) * `:fast2` useful when reading JPEGs over a slow network connection (See -fast2-option of the exiftool command-line application, default is `false`) * `:replace_invalid_chars` replace string for invalid UTF-8 characters or `false` if no replacing should be done, default is `false` * `:exif_encoding`, `:iptc_encoding`, `:xmp_encoding`, `:png_encoding`, `:id3_encoding`, `:pdf_encoding`, `:photoshop_encoding`, `:quicktime_encoding`, `:aiff_encoding`, `:mie_encoding`, `:vorbis_encoding` to set this specific encoding (see -charset option of the exiftool command-line application, default is `nil`: no encoding specified) ### Using an IO instance ```ruby require 'mini_exiftool' require 'open3' # Using external curl command input, output = Open3.popen2("curl -s http://www.url.of.a.photo") input.close photo = MiniExiftool.new output puts photo['ISO'] ``` The kind of the parameter `filename_or_io` is determined via duck typing: if the argument responds to `to_str` it is interpreted as filename, if it responds to `read` it is interpreted es IO instance. Attention: If you use an IO instance then writing of values is not supported! Look at the show_speedup_with_fast_option example in the MiniExiftool examples directory for more details about using an IO instance. ## Lesson 2: Writing Meta Data ### Also A Very Simple Example ```ruby require 'mini_exiftool' photo = MiniExiftool.new 'photo.jpg' photo.comment = 'hello world' photo.save ``` ### Save Is Atomar If you have changed several values and call the `save`-method either all changes will be written to the file or nothing. The return value of the `save`-method is `true` if all values are written to the file otherwise save returns `false`. In the last case you can use the `errors`-method which returns a hash of the tags which values couldn't be written with an error message for each of them. ### Interesting Methods Have a look at the `changed?`-method for checking if the value of a specific tag is changed or a changing in general is done. In the same way the `revert`-method reverts the value of a specific tag or in general all changes. You should also look at the rdoc information of MiniExiftool. ## Lesson 3: Copying Meta Data ### Examples ```ruby require 'mini_exiftool' photo = MiniExiftool.new('photo.jpg') # Update the author tag of photo.jpg with the value of the author tag # of another_photo.jpg photo.copy_tags_from('another_photo.jpg', 'Author') # It's also possible to use symbols and case is also not meaningful photo.copy_tags_from('another_photo.jpg', :author) # Further more than one tag can be copied at once photo.copy_tags_from('another_photo', %w[author copyright]) ``` Look at the file copy_icc_profile.rb in the examples folder of MiniExiftool. ## Further Examples Have a look in the examples folder of MiniExiftool. mini_exiftool-2.9.0/examples/000077500000000000000000000000001316025661300162105ustar00rootroot00000000000000mini_exiftool-2.9.0/examples/copy_icc_profile.rb000066400000000000000000000007141316025661300220470ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'mini_exiftool' if ARGV.size < 2 puts "usage: ruby #{__FILE__} SOURCE_FILE TARGET_FILE" exit -1 end source_filename, target_filename = ARGV begin photo = MiniExiftool.new filename # The second parameter of MiniExiftool#copy_tags_from # could be a String, Symbol or an Array of Strings, # Symbols photo.copy_tags_from(target, 'icc_profile') rescue MiniExiftool::Error => e $stderr.puts e.message exit -1 end mini_exiftool-2.9.0/examples/external_photo.rb000066400000000000000000000007421316025661300215730ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'open-uri' require 'rubygems' require 'mini_exiftool' unless ARGV.size == 1 puts "usage: ruby #{__FILE__} URI" puts " i.e.: ruby #{__FILE__} http://www.23hq.com/janfri/photo/1535332/large" exit -1 end # Fetch an external photo filename = open(ARGV.first).path # Read the metadata photo = MiniExiftool.new filename # Print the metadata photo.tags.sort.each do |tag| # puts "#{tag}: #{photo[tag]}" puts tag.ljust(28) + photo[tag].to_s end mini_exiftool-2.9.0/examples/print_portraits.rb000066400000000000000000000012301316025661300217740ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'rubygems' require 'mini_exiftool' unless ARGV.size > 0 puts "usage: ruby #{__FILE__} FILES" puts " i.e.: ruby #{__FILE__} *.jpg" exit -1 end # Loop at all given files ARGV.each do |filename| # If a given file isn't a photo MiniExiftool new method will throw # an exception this we will catch begin photo = MiniExiftool.new filename height = photo.image_height width = photo.image_width # We define portait as a photo wich ratio of height to width is # larger than 0.7 if height / width > 0.7 puts filename end rescue MiniExiftool::Error => e $stderr.puts e.message end end mini_exiftool-2.9.0/examples/shift_time.rb000066400000000000000000000013241316025661300206700ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'rubygems' require 'mini_exiftool' if ARGV.size < 2 puts "usage: ruby #{__FILE__} [+|-]SECONDS FILES" puts " i.e.: ruby #{__FILE__} 3600 *.jpg" exit -1 end delta = ARGV.shift.to_i ARGV.each do |filename| begin photo = MiniExiftool.new filename rescue MiniExiftool::Error => e $stderr.puts e.message exit -1 end time = photo.date_time_original # time is a Time object, so we can use the methods of it :) photo.date_time_original = time + delta save_ok = photo.save if save_ok fmt = '%Y-%m-%d %H:%M:%S' puts "#{filename} changed: #{time.strftime(fmt)} -> #{(time + delta).strftime(fmt)}" else puts "#{filename} could not be changed" end end mini_exiftool-2.9.0/examples/show_speedup_with_fast_option.rb000066400000000000000000000051101316025661300246770ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'mini_exiftool' require 'open3' unless ARGV.size == 1 puts "usage: ruby #{__FILE__} URI" puts " i.e.: ruby #{__FILE__} http://farm6.staticflickr.com/5015/5458914734_8fd3f33278_o.jpg" exit -1 end arg = ARGV.shift #################################### # Helper methods #################################### def time a = Time.now yield b = Time.now b - a end def print_statistics name, without_fast, fast, fast2 puts '-' * 40 puts name, "\n" puts format 'without fast: %0.2fs', without_fast puts format 'fast : %0.2fs', fast puts format 'fast2 : %0.2fs', fast2 puts puts format 'speedup fast : %0.2f', without_fast / fast puts format 'speedup fast2: %0.2f', without_fast / fast2 puts '-' * 40 puts end #################################### # Plain Ruby with standard library #################################### require 'net/http' uri = URI(arg) def read_from_http uri, io Thread.new(uri, io) do |uri, io| Net::HTTP.start(uri.host, uri.port) do |http| request = Net::HTTP::Get.new uri http.request request do |response| response.read_body do |chunk| io.write chunk end end end io.close end end without_fast = time do output, input = IO.pipe read_from_http uri, input MiniExiftool.new output end fast = time do output, input = IO.pipe read_from_http uri, input MiniExiftool.new output, fast: true end fast2 = time do output, input = IO.pipe read_from_http uri, input MiniExiftool.new output, fast2: true end print_statistics 'net/http', without_fast, fast, fast2 #################################### # curl #################################### without_fast = time do input, output = Open3.popen3("curl -s #{arg}") input.close MiniExiftool.new output end fast = time do input, output = Open3.popen3("curl -s #{arg}") input.close MiniExiftool.new output, fast: true end fast2 = time do input, output = Open3.popen3("curl -s #{arg}") input.close MiniExiftool.new output, fast2: true end print_statistics 'curl', without_fast, fast, fast2 #################################### # wget #################################### without_fast = time do input, output = Open3.popen3("wget -q -O - #{arg}") input.close MiniExiftool.new output end fast = time do input, output = Open3.popen3("wget -q -O - #{arg}") input.close MiniExiftool.new output, fast: true end fast2 = time do input, output = Open3.popen3("wget -q -O - #{arg}") input.close MiniExiftool.new output, fast2: true end print_statistics 'wget', without_fast, fast, fast2 mini_exiftool-2.9.0/lib/000077500000000000000000000000001316025661300151405ustar00rootroot00000000000000mini_exiftool-2.9.0/lib/mini_exiftool.rb000066400000000000000000000402001316025661300203260ustar00rootroot00000000000000# -- encoding: utf-8 -- # # MiniExiftool # # This library is wrapper for the ExifTool command-line # application (http://www.sno.phy.queensu.ca/~phil/exiftool/) # written by Phil Harvey. # Read and write access is done in a clean OO manner. # # Author: Jan Friedrich # Copyright (c) 2007-2016 by Jan Friedrich # Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, # Version 2.1, February 1999 # require 'fileutils' require 'json' require 'open3' require 'pstore' require 'rational' require 'rbconfig' require 'set' require 'tempfile' require 'time' # Simple OO access to the ExifTool command-line application. class MiniExiftool VERSION = '2.9.0' # Name of the ExifTool command-line application @@cmd = 'exiftool' # Hash of the standard options used when call MiniExiftool.new @@opts = { :numerical => false, :composite => true, :fast => false, :fast2 => false, :ignore_minor_errors => false, :replace_invalid_chars => false, :timestamps => Time } # Encoding of the filesystem (filenames in command line) @@fs_enc = Encoding.find('filesystem') def self.opts_accessor *attrs attrs.each do |a| define_method a do @opts[a] end define_method "#{a}=" do |val| @opts[a] = val end end end attr_reader :filename, :errors, :io opts_accessor :numerical, :composite, :ignore_minor_errors, :replace_invalid_chars, :timestamps @@encoding_types = %w(exif iptc xmp png id3 pdf photoshop quicktime aiff mie vorbis) def self.encoding_opt enc_type (enc_type.to_s + '_encoding').to_sym end @@encoding_types.each do |enc_type| opts_accessor encoding_opt(enc_type) end # +filename_or_io+ The kind of the parameter is determined via duck typing: # if the argument responds to +to_str+ it is interpreted as filename, if it # responds to +read+ it is interpreted es IO instance. # # ATTENTION: If using an IO instance writing of meta data is not supported! # # +opts+ support at the moment # * :numerical for numerical values, default is +false+ # * :composite for including composite tags while loading, # default is +true+ # * :ignore_minor_errors ignore minor errors (See -m-option # of the exiftool command-line application, default is +false+) # * :coord_format set format for GPS coordinates (See # -c-option of the exiftool command-line application, default is +nil+ # that means exiftool standard) # * :fast useful when reading JPEGs over a slow network connection # (See -fast-option of the exiftool command-line application, default is +false+) # * :fast2 useful when reading JPEGs over a slow network connection # (See -fast2-option of the exiftool command-line application, default is +false+) # * :replace_invalid_chars replace string for invalid # UTF-8 characters or +false+ if no replacing should be done, # default is +false+ # * :timestamps generating DateTime objects instead of # Time objects if set to DateTime, default is +Time+ # # ATTENTION: Time objects are created using Time.local # therefore they use your local timezone, DateTime objects instead # are created without timezone! # * :exif_encoding, :iptc_encoding, # :xmp_encoding, :png_encoding, # :id3_encoding, :pdf_encoding, # :photoshop_encoding, :quicktime_encoding, # :aiff_encoding, :mie_encoding, # :vorbis_encoding to set this specific encoding (see # -charset option of the exiftool command-line application, default is # +nil+: no encoding specified) def initialize filename_or_io=nil, opts={} @opts = @@opts.merge opts if @opts[:convert_encoding] warn 'Option :convert_encoding is not longer supported!' warn 'Please use the String#encod* methods.' end @filename = nil @io = nil @values = TagHash.new @changed_values = TagHash.new @errors = TagHash.new load filename_or_io unless filename_or_io.nil? end def initialize_from_hash hash # :nodoc: set_values hash set_opts_by_heuristic self end def initialize_from_json json # :nodoc: @output = json @errors.clear parse_output self end # Load the tags of filename or io. def load filename_or_io if filename_or_io.respond_to? :to_str # String-like unless filename_or_io && File.exist?(filename_or_io) raise MiniExiftool::Error.new("File '#{filename_or_io}' does not exist.") end if File.directory?(filename_or_io) raise MiniExiftool::Error.new("'#{filename_or_io}' is a directory.") end @filename = filename_or_io.to_str elsif filename_or_io.respond_to? :read # IO-like @io = filename_or_io @filename = '-' else raise MiniExiftool::Error.new("Could not open filename_or_io.") end @values.clear @changed_values.clear params = '-j ' params << (@opts[:numerical] ? '-n ' : '') params << (@opts[:composite] ? '' : '-e ') params << (@opts[:coord_format] ? "-c \"#{@opts[:coord_format]}\"" : '') params << (@opts[:fast] ? '-fast ' : '') params << (@opts[:fast2] ? '-fast2 ' : '') params << generate_encoding_params if run(cmd_gen(params, @filename)) parse_output else raise MiniExiftool::Error.new(@error_text) end self end # Reload the tags of an already read file. def reload load @filename end # Returns the value of a tag. def [] tag @changed_values[tag] || @values[tag] end # Set the value of a tag. def []= tag, val @changed_values[tag] = val end # Returns true if any tag value is changed or if the value of a # given tag is changed. def changed? tag=false if tag @changed_values.include? tag else !@changed_values.empty? end end # Revert all changes or the change of a given tag. def revert tag=nil if tag val = @changed_values.delete(tag) res = val != nil else res = @changed_values.size > 0 @changed_values.clear end res end # Returns an array of the tags (original tag names) of the read file. def tags @values.keys.map { |key| MiniExiftool.original_tag(key) } end # Returns an array of all changed tags. def changed_tags @changed_values.keys.map { |key| MiniExiftool.original_tag(key) } end # Save the changes to the file. def save if @io raise MiniExiftool::Error.new('No writing support when using an IO.') end return false if @changed_values.empty? @errors.clear temp_file = Tempfile.new('mini_exiftool') temp_file.close temp_filename = temp_file.path FileUtils.cp filename.encode(@@fs_enc), temp_filename all_ok = true @changed_values.each do |tag, val| original_tag = MiniExiftool.original_tag(tag) arr_val = val.kind_of?(Array) ? val : [val] arr_val.map! {|e| convert_before_save(e)} params = '-q -P -overwrite_original ' params << (arr_val.detect {|x| x.kind_of?(Numeric)} ? '-n ' : '') params << (@opts[:ignore_minor_errors] ? '-m ' : '') params << generate_encoding_params arr_val.each do |v| params << %Q(-#{original_tag}=#{escape(v)} ) end result = run(cmd_gen(params, temp_filename)) unless result all_ok = false @errors[tag] = @error_text.gsub(/Nothing to do.\n\z/, '').chomp end end if all_ok FileUtils.cp temp_filename, filename.encode(@@fs_enc) reload end temp_file.delete all_ok end def save! unless save err = [] @errors.each do |key, value| err << "(#{key}) #{value}" end raise MiniExiftool::Error.new("MiniExiftool couldn't save. The following errors occurred: #{err.empty? ? "None" : err.join(", ")}") end end def copy_tags_from(source_filename, tags) @errors.clear unless File.exist?(source_filename) raise MiniExiftool::Error.new("Source file #{source_filename} does not exist!") end params = '-q -P -overwrite_original ' tags_params = Array(tags).map {|t| '-' << t.to_s}.join(' ') cmd = [@@cmd, params, '-tagsFromFile', escape(source_filename).encode(@@fs_enc), tags_params.encode('UTF-8'), escape(filename).encode(@@fs_enc)].join(' ') cmd.force_encoding('UTF-8') result = run(cmd) reload result end # Returns a hash of the original loaded values of the MiniExiftool # instance. def to_hash result = {} @values.each do |k,v| result[MiniExiftool.original_tag(k)] = v end result end # Returns a YAML representation of the original loaded values of the # MiniExiftool instance. def to_yaml to_hash.to_yaml end # Create a MiniExiftool instance from a hash. Default value # conversions will be applied if neccesary. def self.from_hash hash, opts={} instance = MiniExiftool.new nil, opts instance.initialize_from_hash hash instance end # Create a MiniExiftool instance from JSON data. Default value # conversions will be applied if neccesary. def self.from_json json, opts={} instance = MiniExiftool.new nil, opts instance.initialize_from_json json instance end # Create a MiniExiftool instance from YAML data created with # MiniExiftool#to_yaml def self.from_yaml yaml, opts={} MiniExiftool.from_hash YAML.load(yaml), opts end # Returns the command name of the called ExifTool application. def self.command @@cmd end # Setting the command name of the called ExifTool application. def self.command= cmd @@cmd = cmd end # Returns the options hash. def self.opts @@opts end # Returns a set of all known tags of ExifTool. def self.all_tags unless defined? @@all_tags @@all_tags = pstore_get :all_tags end @@all_tags end # Returns a set of all possible writable tags of ExifTool. def self.writable_tags unless defined? @@writable_tags @@writable_tags = pstore_get :writable_tags end @@writable_tags end # Returns the original ExifTool name of the given tag def self.original_tag tag unless defined? @@all_tags_map @@all_tags_map = pstore_get :all_tags_map end @@all_tags_map[tag] end # Returns the version of the ExifTool command-line application. def self.exiftool_version Open3.popen3 "#{MiniExiftool.command} -ver" do |_inp, out, _err, _thr| out.read.chomp! end rescue SystemCallError raise MiniExiftool::Error.new("Command '#{MiniExiftool.command}' not found") end def self.unify tag tag.to_s.gsub(/[-_]/,'').downcase end @@running_on_windows = /mswin|mingw|cygwin/ === RbConfig::CONFIG['host_os'] def self.pstore_dir unless defined? @@pstore_dir # This will hopefully work on *NIX and Windows systems home = ENV['HOME'] || ENV['HOMEDRIVE'] + ENV['HOMEPATH'] || ENV['USERPROFILE'] subdir = @@running_on_windows ? '_mini_exiftool' : '.mini_exiftool' @@pstore_dir = File.join(home, subdir) end @@pstore_dir end def self.pstore_dir= dir @@pstore_dir = dir end # Exception class class MiniExiftool::Error < StandardError; end ############################################################################ private ############################################################################ def cmd_gen arg_str='', filename [@@cmd, arg_str.encode('UTF-8'), escape(filename.encode(@@fs_enc))].map {|s| s.force_encoding('UTF-8')}.join(' ') end def run cmd if $DEBUG $stderr.puts cmd end status = Open3.popen3(cmd) do |inp, out, err, thr| if @io begin IO.copy_stream @io, inp rescue Errno::EPIPE # Output closed, no problem rescue ::IOError => e raise MiniExiftool::Error.new("IO is not readable.") end inp.close end @output = out.read @error_text = err.read thr.value.exitstatus end status == 0 end def convert_before_save val case val when Time val = val.strftime('%Y:%m:%d %H:%M:%S') end val end def method_missing symbol, *args tag_name = symbol.id2name if tag_name.sub!(/=$/, '') self[tag_name] = args.first else self[tag_name] end end def parse_output adapt_encoding set_values JSON.parse(@output).first end def adapt_encoding @output.force_encoding('UTF-8') if @opts[:replace_invalid_chars] && !@output.valid_encoding? @output.encode!('UTF-16le', invalid: :replace, replace: @opts[:replace_invalid_chars]).encode!('UTF-8') end end def convert_after_load tag, value return value unless value.kind_of?(String) return value unless value.valid_encoding? case value when /^\d{4}:\d\d:\d\d \d\d:\d\d:\d\d/ s = value.sub(/^(\d+):(\d+):/, '\1-\2-') begin if @opts[:timestamps] == Time value = Time.parse(s) elsif @opts[:timestamps] == DateTime value = DateTime.parse(s) else raise MiniExiftool::Error.new("Value #{@opts[:timestamps]} not allowed for option timestamps.") end rescue ArgumentError value = false end when /^\+\d+\.\d+$/ value = value.to_f when /^0+[1-9]+$/ # nothing => String when /^-?\d+$/ value = value.to_i when %r(^(\d+)/(\d+)$) value = Rational($1.to_i, $2.to_i) rescue value when /^[\d ]+$/ # nothing => String end value end def set_values hash hash.each_pair do |tag,val| @values[tag] = convert_after_load(tag, val) end # Remove filename specific tags use attr_reader # MiniExiftool#filename instead # Cause: value of tag filename and attribute # filename have different content, the latter # holds the filename with full path (like the # sourcefile tag) and the former the basename # of the filename also there is no official # "original tag name" for sourcefile %w(directory filename sourcefile).each do |t| @values.delete(t) end end def set_opts_by_heuristic @opts[:composite] = tags.include?('ImageSize') @opts[:numerical] = self.file_size.kind_of?(Integer) @opts[:timestamps] = self.FileModifyDate.kind_of?(DateTime) ? DateTime : Time end def self.pstore_get attribute load_or_create_pstore unless defined? @@pstore result = nil @@pstore.transaction(true) do |ps| result = ps[attribute] end result end def self.load_or_create_pstore FileUtils.mkdir_p(pstore_dir) pstore_filename = File.join(pstore_dir, 'exiftool_tags_' << exiftool_version.gsub('.', '_') << '.pstore') @@pstore = PStore.new pstore_filename if !File.exist?(pstore_filename) || File.size(pstore_filename) == 0 $stderr.puts 'Generating cache file for ExifTool tag names. This takes a few seconds but is only needed once...' @@pstore.transaction do |ps| ps[:all_tags] = all_tags = determine_tags('list') ps[:writable_tags] = determine_tags('listw') map = {} all_tags.each { |k| map[unify(k)] = k } ps[:all_tags_map] = map end $stderr.puts 'Cache file generated.' end end def self.determine_tags arg output = `#{@@cmd} -#{arg}` lines = output.split(/\n/) tags = Set.new lines.each do |line| next unless line =~ /^\s/ tags |= line.chomp.split end tags end if @@running_on_windows def escape val '"' << val.to_s.gsub(/([\\"])/, "\\\\\\1") << '"' end else def escape val '"' << val.to_s.gsub(/([\\"$])/, "\\\\\\1") << '"' end end def generate_encoding_params params = '' @@encoding_types.each do |enc_type| if enc_val = @opts[MiniExiftool.encoding_opt(enc_type)] params << "-charset #{enc_type}=#{enc_val} " end end params end # Hash with indifferent access: # DateTimeOriginal == datetimeoriginal == date_time_original class TagHash < Hash # :nodoc: def[] k super(unify(k)) end def []= k, v super(unify(k), v) end def delete k super(unify(k)) end def unify tag MiniExiftool.unify tag end end end mini_exiftool-2.9.0/mini_exiftool.gemspec000066400000000000000000000072571316025661300206170ustar00rootroot00000000000000# -*- encoding: utf-8 -*- # stub: mini_exiftool 2.9.0 ruby lib # # This file is automatically generated by rim. # PLEASE DO NOT EDIT IT DIRECTLY! # Change the values in Rim.setup in Rakefile instead. Gem::Specification.new do |s| s.name = "mini_exiftool" s.version = "2.9.0" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.require_paths = ["lib"] s.authors = ["Jan Friedrich"] s.date = "2017-09-19" s.description = "This library is a wrapper for the ExifTool command-line application\n(http://www.sno.phy.queensu.ca/~phil/exiftool) written by Phil Harvey.\nIt provides the full power of ExifTool to Ruby: reading and writing of\nEXIF-data, IPTC-data and XMP-data.\n" s.email = "janfri26@gmail.com" s.files = ["./.aspell.pws", "COPYING", "Changelog", "Gemfile", "README.md", "Rakefile", "Tutorial.md", "examples/copy_icc_profile.rb", "examples/external_photo.rb", "examples/print_portraits.rb", "examples/shift_time.rb", "examples/show_speedup_with_fast_option.rb", "lib/mini_exiftool.rb", "mini_exiftool.gemspec", "regtest/read_all.rb", "regtest/read_all.yml", "test/data", "test/data/Bad_PreviewIFD.jpg", "test/data/Canon.jpg", "test/data/INFORMATION", "test/data/invalid_byte_sequence_in_utf8.json", "test/data/invalid_rational.json", "test/data/test.jpg", "test/data/test.jpg.json", "test/data/test_coordinates.jpg", "test/data/test_encodings.jpg", "test/data/test_special_dates.jpg", "test/helpers_for_test.rb", "test/test_bad_preview_ifd.rb", "test/test_class_methods.rb", "test/test_composite.rb", "test/test_copy_tags_from.rb", "test/test_dumping.rb", "test/test_encodings.rb", "test/test_filename_access.rb", "test/test_from_hash.rb", "test/test_invalid_byte_sequence_in_utf8.rb", "test/test_invalid_rational.rb", "test/test_io.rb", "test/test_pstore.rb", "test/test_read.rb", "test/test_read_coordinates.rb", "test/test_read_numerical.rb", "test/test_save.rb", "test/test_special.rb", "test/test_special_dates.rb", "test/test_write.rb"] s.homepage = "https://github.com/janfri/mini_exiftool" s.licenses = ["LGPL-2.1"] s.post_install_message = "+-----------------------------------------------------------------------+\n| Please ensure you have installed exiftool at least version 7.65 |\n| and it's found in your PATH (Try \"exiftool -ver\" on your commandline).|\n| For more details see |\n| http://www.sno.phy.queensu.ca/~phil/exiftool/install.html |\n| You need also Ruby 1.9 or higher. |\n| If you need support for Ruby 1.8 or exiftool prior 7.65 install |\n| mini_exiftool version < 2.0.0. |\n+-----------------------------------------------------------------------+\n" s.rubygems_version = "2.6.13" s.summary = "This library is a wrapper for the ExifTool command-line application (http://www.sno.phy.queensu.ca/~phil/exiftool)." if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, ["~> 2.15"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, ["~> 1.0"]) else s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 2.15"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 1.0"]) end else s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 2.15"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 1.0"]) end end mini_exiftool-2.9.0/regtest/000077500000000000000000000000001316025661300160475ustar00rootroot00000000000000mini_exiftool-2.9.0/regtest/read_all.rb000066400000000000000000000004561316025661300201440ustar00rootroot00000000000000require 'mini_exiftool' require 'regtest' Dir['test/data/*.jpg'].sort.each do |fn| Regtest.sample 'read ' << File.basename(fn) do h = MiniExiftool.new(fn).to_hash %w(FileModifyDate FileAccessDate FileInodeChangeDate FilePermissions).each do |tag| h.delete(tag) end h end end mini_exiftool-2.9.0/regtest/read_all.yml000066400000000000000000000362561316025661300203510ustar00rootroot00000000000000--- sample: read Bad_PreviewIFD.jpg result: ExifToolVersion: 10.55 FileSize: 8.4 kB FileType: JPEG FileTypeExtension: jpg MIMEType: image/jpeg JFIFVersion: 1.01 ExifByteOrder: Big-endian (Motorola, MM) Make: NIKON CORPORATION Model: NIKON D60 XResolution: 300 YResolution: 300 ResolutionUnit: inches Software: GIMP 2.6.11 ModifyDate: 2012-05-31 21:33:12.000000000 +02:00 YCbCrPositioning: Centered ExposureTime: !ruby/object:Rational denominator: 125 numerator: 1 FNumber: 5.6 ExposureProgram: Not Defined ISO: 110 ExifVersion: '0221' DateTimeOriginal: 2008-12-12 11:12:30.000000000 +01:00 CreateDate: 2008-12-12 11:12:30.000000000 +01:00 ComponentsConfiguration: Y, Cb, Cr, - ExposureCompensation: 0 MaxApertureValue: 4.0 MeteringMode: Multi-segment Flash: No Flash FocalLength: 55.0 mm MakerNoteVersion: 2.1 ColorMode: Color Quality: Fine WhiteBalance: Auto Sharpness: Auto FocusMode: AF-A WhiteBalanceFineTune: 0 0 WB_RBLevels: 56833 21761 1 1 ProgramShift: 0 ExposureDifference: 0 Warning: Bad PreviewIFD directory ISOSetting: 110 ImageBoundary: 0 0 3872 2592 CropHiSpeed: Off (3904x2616 cropped to 3904x2616 at pixel 0,0) SerialNumber: 3365614 VRInfoVersion: 100 VibrationReduction: 'On' VRMode: Normal ActiveD-Lighting: 'Off' TimeZone: "-05:00" DaylightSavings: 'No' DateDisplayFormat: Y/M/D ISOExpansion: 'Off' ISO2: 112 ISOExpansion2: 'Off' ToneComp: Auto LensType: G VR Lens: 55-200mm f/4-5.6 FlashMode: Did Not Fire AFAreaMode: Dynamic Area (closest subject) AFPoint: Center AFPointsInFocus: Center ShootingMode: Single-Frame ColorHue: Mode3a LightSource: Natural ShotInfoVersion: '0211' HueAdjustment: 0 NoiseReduction: 'Off' WB_RGGBLevels: 56833 1 1 7 LensDataVersion: 202 ExitPupilPosition: 70.6 mm AFAperture: 4.1 FocusPosition: '0x22' FocusDistance: 6.31 m LensIDNumber: 144 LensFStops: 4.92 MinFocalLength: 55.0 mm MaxFocalLength: 201.6 mm MaxApertureAtMinFocal: 4.0 MaxApertureAtMaxFocal: 5.7 MCUVersion: 146 EffectiveMaxAperture: 4.0 SensorPixelSize: 6.05 x 6.05 um RetouchHistory: None ImageDataSize: 4725881 ShutterCount: 290 ImageOptimization: '' Saturation: Auto VariProgram: Auto MultiExposureVersion: 100 MultiExposureMode: 'Off' MultiExposureShots: 0 MultiExposureAutoGain: 'Off' HighISONoiseReduction: 'Off' PowerUpTime: 2008-12-12 11:11:24.000000000 +01:00 FileInfoVersion: 100 MemoryCardNumber: 0 DirectoryNumber: 100 FileNumber: '0081' RetouchInfoVersion: 100 NEFBitDepth: Unknown (0 0 16723 17225) UserComment: '' SubSecTime: 0 SubSecTimeOriginal: 0 SubSecTimeDigitized: 0 FlashpixVersion: 100 ColorSpace: sRGB ExifImageWidth: 1 ExifImageHeight: 1 SensingMethod: One-chip color area FileSource: Digital Camera SceneType: Directly photographed CFAPattern: "[Green,Blue][Red,Green]" XPTitle: '' XPKeywords: '' Compression: JPEG (old-style) ThumbnailOffset: 3690 ThumbnailLength: 631 RatingPercent: 0 Rating: 0 Title: '' ProfileCMMType: Lino ProfileVersion: 2.1.0 ProfileClass: Display Device Profile ColorSpaceData: 'RGB ' ProfileConnectionSpace: 'XYZ ' ProfileDateTime: 1998-02-09 06:49:00.000000000 +01:00 ProfileFileSignature: acsp PrimaryPlatform: Microsoft Corporation CMMFlags: Not Embedded, Independent DeviceManufacturer: 'IEC ' DeviceModel: sRGB DeviceAttributes: Reflective, Glossy, Positive, Color RenderingIntent: Perceptual ConnectionSpaceIlluminant: 0.9642 1 0.82491 ProfileCreator: 'HP ' ProfileID: 0 ProfileCopyright: Copyright (c) 1998 Hewlett-Packard Company ProfileDescription: sRGB IEC61966-2.1 MediaWhitePoint: 0.95045 1 1.08905 MediaBlackPoint: 0 0 0 RedMatrixColumn: 0.43607 0.22249 0.01392 GreenMatrixColumn: 0.38515 0.71687 0.09708 BlueMatrixColumn: 0.14307 0.06061 0.7141 DeviceMfgDesc: IEC http://www.iec.ch DeviceModelDesc: IEC 61966-2.1 Default RGB colour space - sRGB ViewingCondDesc: Reference Viewing Condition in IEC61966-2.1 ViewingCondIlluminant: 19.6445 20.3718 16.8089 ViewingCondSurround: 3.92889 4.07439 3.36179 ViewingCondIlluminantType: D50 Luminance: 76.03647 80 87.12462 MeasurementObserver: CIE 1931 MeasurementBacking: 0 0 0 MeasurementGeometry: Unknown MeasurementFlare: 0.999% MeasurementIlluminant: D65 Technology: Cathode Ray Tube Display RedTRC: "(Binary data 2060 bytes, use -b option to extract)" GreenTRC: "(Binary data 2060 bytes, use -b option to extract)" BlueTRC: "(Binary data 2060 bytes, use -b option to extract)" ImageWidth: 1 ImageHeight: 1 EncodingProcess: Baseline DCT, Huffman coding BitsPerSample: 8 ColorComponents: 3 YCbCrSubSampling: YCbCr4:4:0 (1 2) Aperture: 5.6 BlueBalance: 7 ImageSize: 1x1 LensID: AF-S DX VR Zoom-Nikkor 55-200mm f/4-5.6G IF-ED LensSpec: 55-200mm f/4-5.6 G VR Megapixels: 1.0e-06 RedBalance: 56833 ShutterSpeed: !ruby/object:Rational denominator: 125 numerator: 1 SubSecCreateDate: 2008-12-12 11:12:30.000000000 +01:00 SubSecDateTimeOriginal: 2008-12-12 11:12:30.000000000 +01:00 SubSecModifyDate: 2012-05-31 21:33:12.000000000 +02:00 ThumbnailImage: "(Binary data 631 bytes, use -b option to extract)" FocalLength35efl: 55.0 mm LightValue: 11.8 --- sample: read Canon.jpg result: ExifToolVersion: 10.55 FileSize: 2.6 kB FileType: JPEG FileTypeExtension: jpg MIMEType: image/jpeg ExifByteOrder: Little-endian (Intel, II) Make: Canon Model: Canon EOS DIGITAL REBEL Orientation: Horizontal (normal) XResolution: 180 YResolution: 180 ResolutionUnit: inches ModifyDate: 2003-12-04 06:46:52.000000000 +01:00 YCbCrPositioning: Centered ExposureTime: 4 FNumber: 14.0 ISO: 100 ExifVersion: '0221' DateTimeOriginal: 2003-12-04 06:46:52.000000000 +01:00 CreateDate: 2003-12-04 06:46:52.000000000 +01:00 ComponentsConfiguration: Y, Cb, Cr, - CompressedBitsPerPixel: 9 ShutterSpeedValue: 0 ApertureValue: 14.0 MaxApertureValue: 4.5 Flash: No Flash FocalLength: 34.0 mm MacroMode: Unknown (0) SelfTimer: 'Off' Quality: RAW CanonFlashMode: 'Off' ContinuousDrive: Continuous FocusMode: Manual Focus (3) RecordMode: CRW+THM CanonImageSize: Large EasyMode: Manual DigitalZoom: Unknown (-1) Contrast: "+1" Saturation: "+1" Sharpness: "+1" CameraISO: n/a MeteringMode: Center-weighted average FocusRange: Not Known CanonExposureMode: Manual LensType: n/a MaxFocalLength: 55 mm MinFocalLength: 18 mm FocalUnits: 1/mm MaxAperture: 4 MinAperture: 27 FlashActivity: 0 FlashBits: "(none)" ZoomSourceWidth: 3072 ZoomTargetWidth: 3072 ManualFlashOutput: n/a ColorTone: Normal FocalPlaneXSize: 23.22 mm FocalPlaneYSize: 15.49 mm AutoISO: 100 BaseISO: 100 MeasuredEV: -1.25 TargetAperture: 14 ExposureCompensation: 0 WhiteBalance: Auto SlowShutter: None SequenceNumber: 0 OpticalZoomCode: n/a FlashGuideNumber: 0 FlashExposureComp: 0 AutoExposureBracketing: 'Off' AEBBracketValue: 0 ControlMode: Camera Local Control FocusDistanceUpper: inf FocusDistanceLower: 5.46 m MeasuredEV2: -1.25 BulbDuration: 4 CameraType: EOS Mid-range AutoRotate: None NDFilter: n/a SelfTimer2: 0 BracketMode: 'Off' BracketValue: 0 BracketShotNumber: 0 CanonImageType: CRW:EOS DIGITAL REBEL CMOS RAW CanonFirmwareVersion: Firmware Version 1.1.1 SerialNumber: 560018150 SerialNumberFormat: Format 1 FileNumber: 118-1861 OwnerName: Phil Harvey CanonModelID: EOS Digital Rebel / 300D / Kiss Digital CanonFileLength: 4480822 MeasuredRGGB: 998 1022 1026 808 WB_RGGBLevelsAuto: 1719 832 831 990 WB_RGGBLevelsDaylight: 1722 832 831 989 WB_RGGBLevelsShade: 2035 832 831 839 WB_RGGBLevelsCloudy: 1878 832 831 903 WB_RGGBLevelsTungsten: 1228 913 912 1668 WB_RGGBLevelsFluorescent: 1506 842 841 1381 WB_RGGBLevelsFlash: 1933 832 831 895 WB_RGGBLevelsCustom: 1722 832 831 989 WB_RGGBLevelsKelvin: 1722 832 831 988 WB_RGGBBlackLevels: 124 123 124 123 ColorTemperature: 5200 NumAFPoints: 7 ValidAFPoints: 7 CanonImageWidth: 3072 CanonImageHeight: 2048 AFImageWidth: 3072 AFImageHeight: 2048 AFAreaWidth: 151 AFAreaHeight: 151 AFAreaXPositions: 1014 608 0 0 0 -608 -1014 AFAreaYPositions: 0 0 -506 0 506 0 0 AFPointsInFocus: "(none)" ThumbnailImageValidArea: 0 159 7 112 UserComment: '' FlashpixVersion: 100 ColorSpace: sRGB ExifImageWidth: 160 ExifImageHeight: 120 InteropIndex: THM - DCF thumbnail file InteropVersion: 100 RelatedImageWidth: 3072 RelatedImageHeight: 2048 FocalPlaneXResolution: 3443.946188 FocalPlaneYResolution: 3442.016807 FocalPlaneResolutionUnit: inches SensingMethod: One-chip color area FileSource: Digital Camera CustomRendered: Normal ExposureMode: Manual SceneCaptureType: Standard ImageWidth: 8 ImageHeight: 8 EncodingProcess: Baseline DCT, Huffman coding BitsPerSample: 8 ColorComponents: 3 YCbCrSubSampling: YCbCr4:2:0 (2 2) Aperture: 14.0 DriveMode: Continuous Shooting ImageSize: 8x8 Lens: 18.0 - 55.0 mm LensID: Unknown 18-55mm Megapixels: 6.4e-05 ScaleFactor35efl: 1.6 ShootingMode: Bulb ShutterSpeed: 4 WB_RGGBLevels: 1719 832 831 990 BlueBalance: 1.190619 CircleOfConfusion: 0.019 mm DOF: inf (4.31 m - inf) FOV: 36.9 deg FocalLength35efl: '34.0 mm (35 mm equivalent: 54.0 mm)' HyperfocalDistance: 4.37 m Lens35efl: '18.0 - 55.0 mm (35 mm equivalent: 28.6 - 87.4 mm)' LightValue: 5.6 RedBalance: 2.067348 --- sample: read test.jpg result: ExifToolVersion: 10.55 FileSize: 46 kB FileType: JPEG FileTypeExtension: jpg MIMEType: image/jpeg JFIFVersion: 1.01 ExifByteOrder: Big-endian (Motorola, MM) ImageDescription: KONICA MINOLTA DIGITAL CAMERA Make: KONICA MINOLTA Model: DYNAX 7D Orientation: Horizontal (normal) XResolution: 72 YResolution: 72 ResolutionUnit: inches Software: DYNAX 7D v1.10 ModifyDate: 2005-09-13 20:08:50.000000000 +02:00 YCbCrPositioning: Centered ExposureTime: !ruby/object:Rational denominator: 60 numerator: 1 FNumber: 9.5 ExposureProgram: Program AE ISO: 400 ExifVersion: '0221' DateTimeOriginal: 2005-09-13 20:08:50.000000000 +02:00 CreateDate: 2005-09-13 20:08:50.000000000 +02:00 ComponentsConfiguration: Y, Cb, Cr, - BrightnessValue: 4.5 ExposureCompensation: -1 MaxApertureValue: 4.5 MeteringMode: Multi-segment LightSource: Unknown Flash: Off, Did not fire FocalLength: 75.0 mm SubjectArea: 1504 1000 256 304 MakerNoteVersion: MLT0 MinoltaImageSize: Large WhiteBalance: Auto FocusMode: AF-A AFPoints: Center FlashMode: Normal ISOSetting: 400 FreeMemoryCardImages: 202 HueAdjustment: 0 Rotation: Horizontal (normal) ImageNumber: 6 NoiseReduction: Unknown (2) ImageNumber2: 50 ZoneMatchingOn: 'Off' CompressedImageSize: 1598477 PreviewImageStart: 39152 PreviewImageLength: 0 SceneMode: Standard ColorMode: Natural sRGB MinoltaQuality: Fine FlashExposureComp: 0 Teleconverter: None ImageStabilization: 'On' ZoneMatching: ISO Setting Used ColorTemperature: 0 LensType: Minolta AF 28-135mm F4-4.5 or Sigma Lens UserComment: '' FlashpixVersion: 100 ColorSpace: sRGB ExifImageWidth: 3008 ExifImageHeight: 2000 CustomRendered: Normal ExposureMode: Auto DigitalZoomRatio: 0 FocalLengthIn35mmFormat: 112 mm SceneCaptureType: Standard GainControl: Low gain up Contrast: Normal Saturation: Normal Sharpness: Normal PrintIMVersion: 300 Compression: JPEG (old-style) ThumbnailOffset: 39274 ThumbnailLength: 1820 CurrentIPTCDigest: dd8d51d28ddf04f08f870e5ff2f64d01 Keywords: - Orange - Rot ApplicationRecordVersion: 4 SupplementalCategories: Natur XMPToolkit: Image::ExifTool 7.03 Title: Abenddämmerung ImageWidth: 300 ImageHeight: 199 EncodingProcess: Baseline DCT, Huffman coding BitsPerSample: 8 ColorComponents: 3 YCbCrSubSampling: YCbCr4:2:0 (2 2) Aperture: 9.5 ImageSize: 300x199 LensID: Minolta AF 28-135mm F4-4.5 Megapixels: 0.06 ScaleFactor35efl: 1.5 ShutterSpeed: !ruby/object:Rational denominator: 60 numerator: 1 ThumbnailImage: "(Binary data 1820 bytes, use -b option to extract)" CircleOfConfusion: 0.020 mm FOV: 18.3 deg FocalLength35efl: '75.0 mm (35 mm equivalent: 112.0 mm)' HyperfocalDistance: 29.43 m LightValue: 10.4 --- sample: read test_coordinates.jpg result: ExifToolVersion: 10.55 FileSize: 251 kB FileType: JPEG FileTypeExtension: jpg MIMEType: image/jpeg ExifByteOrder: Big-endian (Motorola, MM) ImageDescription: clip Orientation: Horizontal (normal) XResolution: 72 YResolution: 72 ResolutionUnit: inches YCbCrPositioning: Centered ExposureTime: !ruby/object:Rational denominator: 30 numerator: 1 FNumber: 2.4 ExposureProgram: Program AE ISO: 400 ExifVersion: '0221' ComponentsConfiguration: Y, Cb, Cr, - ShutterSpeedValue: !ruby/object:Rational denominator: 30 numerator: 1 ApertureValue: 2.4 BrightnessValue: 1.954376479 MeteringMode: Multi-segment Flash: Off, Did not fire FocalLength: 4.1 mm SubjectArea: 519 403 239 180 UserComment: clip FlashpixVersion: 100 ColorSpace: sRGB ExifImageWidth: 1280 ExifImageHeight: 720 SensingMethod: One-chip color area ExposureMode: Auto WhiteBalance: Auto FocalLengthIn35mmFormat: 35 mm SceneCaptureType: Standard GPSLatitudeRef: North GPSLongitudeRef: West GPSAltitude: 0 m GPSTimeStamp: '00:00:00' GPSDOP: 0 Compression: JPEG (old-style) ThumbnailOffset: 766 ThumbnailLength: 3831 ImageWidth: 1280 ImageHeight: 720 EncodingProcess: Baseline DCT, Huffman coding BitsPerSample: 8 ColorComponents: 3 YCbCrSubSampling: YCbCr4:2:0 (2 2) Aperture: 2.4 GPSLatitude: 43 deg 39' 11.40" N GPSLongitude: 79 deg 22' 23.40" W GPSPosition: 43 deg 39' 11.40" N, 79 deg 22' 23.40" W ImageSize: 1280x720 Megapixels: 0.922 ScaleFactor35efl: 8.5 ShutterSpeed: !ruby/object:Rational denominator: 30 numerator: 1 ThumbnailImage: "(Binary data 3831 bytes, use -b option to extract)" CircleOfConfusion: 0.004 mm FOV: 54.4 deg FocalLength35efl: '4.1 mm (35 mm equivalent: 35.0 mm)' HyperfocalDistance: 2.00 m LightValue: 5.4 --- sample: read test_encodings.jpg result: ExifToolVersion: 10.55 FileSize: 660 bytes FileType: JPEG FileTypeExtension: jpg MIMEType: image/jpeg CurrentIPTCDigest: '09b79ab4703f3570ba3140ffc2dba2f6' ObjectName: Mšhre ApplicationRecordVersion: 4 ImageWidth: 1 ImageHeight: 1 EncodingProcess: Baseline DCT, Huffman coding BitsPerSample: 8 ColorComponents: 3 YCbCrSubSampling: YCbCr4:2:0 (2 2) ImageSize: 1x1 Megapixels: 1.0e-06 --- sample: read test_special_dates.jpg result: ExifToolVersion: 10.55 FileSize: 3.7 kB FileType: JPEG FileTypeExtension: jpg MIMEType: image/jpeg ExifByteOrder: Big-endian (Motorola, MM) XResolution: 72 YResolution: 72 ResolutionUnit: inches ModifyDate: false YCbCrPositioning: Centered ExifVersion: 220 DateTimeOriginal: 1961-08-13 12:08:25.000000000 +01:00 ComponentsConfiguration: Y, Cb, Cr, - FlashpixVersion: 100 ColorSpace: Uncalibrated PreviewDateTime: 1961-08-13 12:08:25.000000000 +01:00 ImageWidth: 300 ImageHeight: 199 EncodingProcess: Baseline DCT, Huffman coding BitsPerSample: 8 ColorComponents: 3 YCbCrSubSampling: YCbCr4:2:0 (2 2) ImageSize: 300x199 Megapixels: 0.06 mini_exiftool-2.9.0/setup.rb000066400000000000000000001065321316025661300160660ustar00rootroot00000000000000# -- encoding: utf-8 -- # # setup.rb # # Copyright (c) 2000-2005 Minero Aoki # # This program is free software. # You can distribute/modify this program under the terms of # the GNU LGPL, Lesser General Public License version 2.1. # unless Enumerable.method_defined?(:map) # Ruby 1.4.6 module Enumerable alias map collect end end unless File.respond_to?(:read) # Ruby 1.6 def File.read(fname) open(fname) {|f| return f.read } end end unless Errno.const_defined?(:ENOTEMPTY) # Windows? module Errno class ENOTEMPTY # We do not raise this exception, implementation is not needed. end end end def File.binread(fname) open(fname, 'rb') {|f| return f.read } end # for corrupted Windows' stat(2) def File.dir?(path) File.directory?((path[-1,1] == '/') ? path : path + '/') end class ConfigTable include Enumerable def initialize(rbconfig) @rbconfig = rbconfig @items = [] @table = {} # options @install_prefix = nil @config_opt = nil @verbose = true @no_harm = false end attr_accessor :install_prefix attr_accessor :config_opt attr_writer :verbose def verbose? @verbose end attr_writer :no_harm def no_harm? @no_harm end def [](key) lookup(key).resolve(self) end def []=(key, val) lookup(key).set val end def names @items.map {|i| i.name } end def each(&block) @items.each(&block) end def key?(name) @table.key?(name) end def lookup(name) @table[name] or setup_rb_error "no such config item: #{name}" end def add(item) @items.push item @table[item.name] = item end def remove(name) item = lookup(name) @items.delete_if {|i| i.name == name } @table.delete_if {|name, i| i.name == name } item end def load_script(path, inst = nil) if File.file?(path) MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path end end def savefile '.config' end def load_savefile begin File.foreach(savefile()) do |line| k, v = *line.split(/=/, 2) self[k] = v.strip end rescue Errno::ENOENT setup_rb_error $!.message + "\n#{File.basename($0)} config first" end end def save @items.each {|i| i.value } File.open(savefile(), 'w') {|f| @items.each do |i| f.printf "%s=%s\n", i.name, i.value if i.value? and i.value end } end def load_standard_entries standard_entries(@rbconfig).each do |ent| add ent end end def standard_entries(rbconfig) c = rbconfig rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) major = c['MAJOR'].to_i minor = c['MINOR'].to_i teeny = c['TEENY'].to_i version = "#{major}.#{minor}" # ruby ver. >= 1.4.4? newpath_p = ((major >= 2) or ((major == 1) and ((minor >= 5) or ((minor == 4) and (teeny >= 4))))) if c['rubylibdir'] # V > 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = c['rubylibdir'] librubyverarch = c['archdir'] siteruby = c['sitedir'] siterubyver = c['sitelibdir'] siterubyverarch = c['sitearchdir'] elsif newpath_p # 1.4.4 <= V <= 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = c['sitedir'] siterubyver = "$siteruby/#{version}" siterubyverarch = "$siterubyver/#{c['arch']}" else # V < 1.4.4 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" siterubyver = siteruby siterubyverarch = "$siterubyver/#{c['arch']}" end parameterize = lambda {|path| path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') } if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } makeprog = arg.sub(/'/, '').split(/=/, 2)[1] else makeprog = 'make' end [ ExecItem.new('installdirs', 'std/site/home', 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ {|val, table| case val when 'std' table['rbdir'] = '$librubyver' table['sodir'] = '$librubyverarch' when 'site' table['rbdir'] = '$siterubyver' table['sodir'] = '$siterubyverarch' when 'home' setup_rb_error '$HOME was not set' unless ENV['HOME'] table['prefix'] = ENV['HOME'] table['rbdir'] = '$libdir/ruby' table['sodir'] = '$libdir/ruby' end }, PathItem.new('prefix', 'path', c['prefix'], 'path prefix of target environment'), PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 'the directory for commands'), PathItem.new('libdir', 'path', parameterize.call(c['libdir']), 'the directory for libraries'), PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 'the directory for shared data'), PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 'the directory for man pages'), PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 'the directory for system configuration files'), PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), 'the directory for local state data'), PathItem.new('libruby', 'path', libruby, 'the directory for ruby libraries'), PathItem.new('librubyver', 'path', librubyver, 'the directory for standard ruby libraries'), PathItem.new('librubyverarch', 'path', librubyverarch, 'the directory for standard ruby extensions'), PathItem.new('siteruby', 'path', siteruby, 'the directory for version-independent aux ruby libraries'), PathItem.new('siterubyver', 'path', siterubyver, 'the directory for aux ruby libraries'), PathItem.new('siterubyverarch', 'path', siterubyverarch, 'the directory for aux ruby binaries'), PathItem.new('rbdir', 'path', '$siterubyver', 'the directory for ruby scripts'), PathItem.new('sodir', 'path', '$siterubyverarch', 'the directory for ruby extentions'), PathItem.new('rubypath', 'path', rubypath, 'the path to set to #! line'), ProgramItem.new('rubyprog', 'name', rubypath, 'the ruby program using for installation'), ProgramItem.new('makeprog', 'name', makeprog, 'the make program to compile ruby extentions'), SelectItem.new('shebang', 'all/ruby/never', 'ruby', 'shebang line (#!) editing mode'), BoolItem.new('without-ext', 'yes/no', 'no', 'does not compile/install ruby extentions') ] end private :standard_entries def load_multipackage_entries multipackage_entries().each do |ent| add ent end end def multipackage_entries [ PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 'package names that you want to install'), PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 'package names that you do not want to install') ] end private :multipackage_entries ALIASES = { 'std-ruby' => 'librubyver', 'stdruby' => 'librubyver', 'rubylibdir' => 'librubyver', 'archdir' => 'librubyverarch', 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', 'bin-dir' => 'bindir', 'rb-dir' => 'rbdir', 'so-dir' => 'sodir', 'data-dir' => 'datadir', 'ruby-path' => 'rubypath', 'ruby-prog' => 'rubyprog', 'ruby' => 'rubyprog', 'make-prog' => 'makeprog', 'make' => 'makeprog' } def fixup ALIASES.each do |ali, name| @table[ali] = @table[name] end @items.freeze @table.freeze @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ end def parse_opt(opt) m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" m.to_a[1,2] end def dllext @rbconfig['DLEXT'] end def value_config?(name) lookup(name).value? end class Item def initialize(name, template, default, desc) @name = name.freeze @template = template @value = default @default = default @description = desc end attr_reader :name attr_reader :description attr_accessor :default alias help_default default def help_opt "--#{@name}=#{@template}" end def value? true end def value @value end def resolve(table) @value.gsub(%r<\$([^/]+)>) { table[$1] } end def set(val) @value = check(val) end private def check(val) setup_rb_error "config: --#{name} requires argument" unless val val end end class BoolItem < Item def config_type 'bool' end def help_opt "--#{@name}" end private def check(val) return 'yes' unless val case val when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' else setup_rb_error "config: --#{@name} accepts only yes/no for argument" end end end class PathItem < Item def config_type 'path' end private def check(path) setup_rb_error "config: --#{@name} requires argument" unless path path[0,1] == '$' ? path : File.expand_path(path) end end class ProgramItem < Item def config_type 'program' end end class SelectItem < Item def initialize(name, selection, default, desc) super @ok = selection.split('/') end def config_type 'select' end private def check(val) unless @ok.include?(val.strip) setup_rb_error "config: use --#{@name}=#{@template} (#{val})" end val.strip end end class ExecItem < Item def initialize(name, selection, desc, &block) super name, selection, nil, desc @ok = selection.split('/') @action = block end def config_type 'exec' end def value? false end def resolve(table) setup_rb_error "$#{name()} wrongly used as option value" end undef set def evaluate(val, table) v = val.strip.downcase unless @ok.include?(v) setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" end @action.call v, table end end class PackageSelectionItem < Item def initialize(name, template, default, help_default, desc) super name, template, default, desc @help_default = help_default end attr_reader :help_default def config_type 'package' end private def check(val) unless File.dir?("packages/#{val}") setup_rb_error "config: no such package: #{val}" end val end end class MetaConfigEnvironment def initialize(config, installer) @config = config @installer = installer end def config_names @config.names end def config?(name) @config.key?(name) end def bool_config?(name) @config.lookup(name).config_type == 'bool' end def path_config?(name) @config.lookup(name).config_type == 'path' end def value_config?(name) @config.lookup(name).config_type != 'exec' end def add_config(item) @config.add item end def add_bool_config(name, default, desc) @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) end def add_path_config(name, default, desc) @config.add PathItem.new(name, 'path', default, desc) end def set_config_default(name, default) @config.lookup(name).default = default end def remove_config(name) @config.remove(name) end # For only multipackage def packages raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer @installer.packages end # For only multipackage def declare_packages(list) raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer @installer.packages = list end end end # class ConfigTable # This module requires: #verbose?, #no_harm? module FileOperations def mkdir_p(dirname, prefix = nil) dirname = prefix + File.expand_path(dirname) if prefix $stderr.puts "mkdir -p #{dirname}" if verbose? return if no_harm? # Does not check '/', it's too abnormal. dirs = File.expand_path(dirname).split(%r<(?=/)>) if /\A[a-z]:\z/i =~ dirs[0] disk = dirs.shift dirs[0] = disk + dirs[0] end dirs.each_index do |idx| path = dirs[0..idx].join('') Dir.mkdir path unless File.dir?(path) end end def rm_f(path) $stderr.puts "rm -f #{path}" if verbose? return if no_harm? force_remove_file path end def rm_rf(path) $stderr.puts "rm -rf #{path}" if verbose? return if no_harm? remove_tree path end def remove_tree(path) if File.symlink?(path) remove_file path elsif File.dir?(path) remove_tree0 path else force_remove_file path end end def remove_tree0(path) Dir.foreach(path) do |ent| next if ent == '.' next if ent == '..' entpath = "#{path}/#{ent}" if File.symlink?(entpath) remove_file entpath elsif File.dir?(entpath) remove_tree0 entpath else force_remove_file entpath end end begin Dir.rmdir path rescue Errno::ENOTEMPTY # directory may not be empty end end def move_file(src, dest) force_remove_file dest begin File.rename src, dest rescue File.open(dest, 'wb') {|f| f.write File.binread(src) } File.chmod File.stat(src).mode, dest File.unlink src end end def force_remove_file(path) begin remove_file path rescue end end def remove_file(path) File.chmod 0777, path File.unlink path end def install(from, dest, mode, prefix = nil) $stderr.puts "install #{from} #{dest}" if verbose? return if no_harm? realdest = prefix ? prefix + File.expand_path(dest) : dest realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) str = File.binread(from) if diff?(str, realdest) verbose_off { rm_f realdest if File.exist?(realdest) } File.open(realdest, 'wb') {|f| f.write str } File.chmod mode, realdest File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| if prefix f.puts realdest.sub(prefix, '') else f.puts realdest end } end end def diff?(new_content, path) return true unless File.exist?(path) new_content != File.binread(path) end def command(*args) $stderr.puts args.join(' ') if verbose? system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed" end def ruby(*args) command config('rubyprog'), *args end def make(task = nil) command(*[config('makeprog'), task].compact) end def extdir?(dir) File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") end def files_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.file?("#{dir}/#{ent}") } } end DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) def directories_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT } end end # This module requires: #srcdir_root, #objdir_root, #relpath module HookScriptAPI def get_config(key) @config[key] end alias config get_config # obsolete: use metaconfig to change configuration def set_config(key, val) @config[key] = val end # # srcdir/objdir (works only in the package directory) # def curr_srcdir "#{srcdir_root()}/#{relpath()}" end def curr_objdir "#{objdir_root()}/#{relpath()}" end def srcfile(path) "#{curr_srcdir()}/#{path}" end def srcexist?(path) File.exist?(srcfile(path)) end def srcdirectory?(path) File.dir?(srcfile(path)) end def srcfile?(path) File.file?(srcfile(path)) end def srcentries(path = '.') Dir.open("#{curr_srcdir()}/#{path}") {|d| return d.to_a - %w(. ..) } end def srcfiles(path = '.') srcentries(path).select {|fname| File.file?(File.join(curr_srcdir(), path, fname)) } end def srcdirectories(path = '.') srcentries(path).select {|fname| File.dir?(File.join(curr_srcdir(), path, fname)) } end end class ToplevelInstaller Version = '3.4.1' Copyright = 'Copyright (c) 2000-2005 Minero Aoki' TASKS = [ [ 'all', 'do config, setup, then install' ], [ 'config', 'saves your configurations' ], [ 'show', 'shows current configuration' ], [ 'setup', 'compiles ruby extentions and others' ], [ 'install', 'installs files' ], [ 'test', 'run all tests in test/' ], [ 'clean', "does `make clean' for each extention" ], [ 'distclean',"does `make distclean' for each extention" ] ] def ToplevelInstaller.invoke config = ConfigTable.new(load_rbconfig()) config.load_standard_entries config.load_multipackage_entries if multipackage? config.fixup klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) klass.new(File.dirname($0), config).invoke end def ToplevelInstaller.multipackage? File.dir?(File.dirname($0) + '/packages') end def ToplevelInstaller.load_rbconfig if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } ARGV.delete(arg) load File.expand_path(arg.split(/=/, 2)[1]) $".push 'rbconfig.rb' else require 'rbconfig' end ::Config::CONFIG end def initialize(ardir_root, config) @ardir = File.expand_path(ardir_root) @config = config # cache @valid_task_re = nil end def config(key) @config[key] end def inspect "#<#{self.class} #{__id__()}>" end def invoke run_metaconfigs case task = parsearg_global() when nil, 'all' parsearg_config init_installers exec_config exec_setup exec_install else case task when 'config', 'test' ; when 'clean', 'distclean' @config.load_savefile if File.exist?(@config.savefile) else @config.load_savefile end __send__ "parsearg_#{task}" init_installers __send__ "exec_#{task}" end end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig" end def init_installers @installer = Installer.new(@config, @ardir, File.expand_path('.')) end # # Hook Script API bases # def srcdir_root @ardir end def objdir_root '.' end def relpath '.' end # # Option Parsing # def parsearg_global while arg = ARGV.shift case arg when /\A\w+\z/ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) return arg when '-q', '--quiet' @config.verbose = false when '--verbose' @config.verbose = true when '--help' print_usage $stdout exit 0 when '--version' puts "#{File.basename($0)} version #{Version}" exit 0 when '--copyright' puts Copyright exit 0 else setup_rb_error "unknown global option '#{arg}'" end end nil end def valid_task?(t) valid_task_re() =~ t end def valid_task_re @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ end def parsearg_no_options unless ARGV.empty? task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" end end alias parsearg_show parsearg_no_options alias parsearg_setup parsearg_no_options alias parsearg_test parsearg_no_options alias parsearg_clean parsearg_no_options alias parsearg_distclean parsearg_no_options def parsearg_config evalopt = [] set = [] @config.config_opt = [] while i = ARGV.shift if /\A--?\z/ =~ i @config.config_opt = ARGV.dup break end name, value = *@config.parse_opt(i) if @config.value_config?(name) @config[name] = value else evalopt.push [name, value] end set.push name end evalopt.each do |name, value| @config.lookup(name).evaluate value, @config end # Check if configuration is valid set.each do |n| @config[n] if @config.value_config?(n) end end def parsearg_install @config.no_harm = false @config.install_prefix = '' while a = ARGV.shift case a when '--no-harm' @config.no_harm = true when /\A--prefix=/ path = a.split(/=/, 2)[1] path = File.expand_path(path) unless path[0,1] == '/' @config.install_prefix = path else setup_rb_error "install: unknown option #{a}" end end end def print_usage(out) out.puts 'Typical Installation Procedure:' out.puts " $ ruby #{File.basename $0} config" out.puts " $ ruby #{File.basename $0} setup" out.puts " # ruby #{File.basename $0} install (may require root privilege)" out.puts out.puts 'Detailed Usage:' out.puts " ruby #{File.basename $0} " out.puts " ruby #{File.basename $0} [] []" fmt = " %-24s %s\n" out.puts out.puts 'Global options:' out.printf fmt, '-q,--quiet', 'suppress message outputs' out.printf fmt, ' --verbose', 'output messages verbosely' out.printf fmt, ' --help', 'print this message' out.printf fmt, ' --version', 'print version and quit' out.printf fmt, ' --copyright', 'print copyright and quit' out.puts out.puts 'Tasks:' TASKS.each do |name, desc| out.printf fmt, name, desc end fmt = " %-24s %s [%s]\n" out.puts out.puts 'Options for CONFIG or ALL:' @config.each do |item| out.printf fmt, item.help_opt, item.description, item.help_default end out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" out.puts out.puts 'Options for INSTALL:' out.printf fmt, '--no-harm', 'only display what to do if given', 'off' out.printf fmt, '--prefix=path', 'install path prefix', '' out.puts end # # Task Handlers # def exec_config @installer.exec_config @config.save # must be final end def exec_setup @installer.exec_setup end def exec_install @installer.exec_install end def exec_test @installer.exec_test end def exec_show @config.each do |i| printf "%-20s %s\n", i.name, i.value if i.value? end end def exec_clean @installer.exec_clean end def exec_distclean @installer.exec_distclean end end # class ToplevelInstaller class ToplevelInstallerMulti < ToplevelInstaller include FileOperations def initialize(ardir_root, config) super @packages = directories_of("#{@ardir}/packages") raise 'no package exists' if @packages.empty? @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig", self @packages.each do |name| @config.load_script "#{@ardir}/packages/#{name}/metaconfig" end end attr_reader :packages def packages=(list) raise 'package list is empty' if list.empty? list.each do |name| raise "directory packages/#{name} does not exist"\ unless File.dir?("#{@ardir}/packages/#{name}") end @packages = list end def init_installers @installers = {} @packages.each do |pack| @installers[pack] = Installer.new(@config, "#{@ardir}/packages/#{pack}", "packages/#{pack}") end with = extract_selection(config('with')) without = extract_selection(config('without')) @selected = @installers.keys.select {|name| (with.empty? or with.include?(name)) \ and not without.include?(name) } end def extract_selection(list) a = list.split(/,/) a.each do |name| setup_rb_error "no such package: #{name}" unless @installers.key?(name) end a end def print_usage(f) super f.puts 'Inluded packages:' f.puts ' ' + @packages.sort.join(' ') f.puts end # # Task Handlers # def exec_config run_hook 'pre-config' each_selected_installers {|inst| inst.exec_config } run_hook 'post-config' @config.save # must be final end def exec_setup run_hook 'pre-setup' each_selected_installers {|inst| inst.exec_setup } run_hook 'post-setup' end def exec_install run_hook 'pre-install' each_selected_installers {|inst| inst.exec_install } run_hook 'post-install' end def exec_test run_hook 'pre-test' each_selected_installers {|inst| inst.exec_test } run_hook 'post-test' end def exec_clean rm_f @config.savefile run_hook 'pre-clean' each_selected_installers {|inst| inst.exec_clean } run_hook 'post-clean' end def exec_distclean rm_f @config.savefile run_hook 'pre-distclean' each_selected_installers {|inst| inst.exec_distclean } run_hook 'post-distclean' end # # lib # def each_selected_installers Dir.mkdir 'packages' unless File.dir?('packages') @selected.each do |pack| $stderr.puts "Processing the package `#{pack}' ..." if verbose? Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") Dir.chdir "packages/#{pack}" yield @installers[pack] Dir.chdir '../..' end end def run_hook(id) @root_installer.run_hook id end # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end end # class ToplevelInstallerMulti class Installer FILETYPES = %w( bin lib ext data conf man ) include FileOperations include HookScriptAPI def initialize(config, srcroot, objroot) @config = config @srcdir = File.expand_path(srcroot) @objdir = File.expand_path(objroot) @currdir = '.' end def inspect "#<#{self.class} #{File.basename(@srcdir)}>" end def noop(rel) end # # Hook Script API base methods # def srcdir_root @srcdir end def objdir_root @objdir end def relpath @currdir end # # Config Access # # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end def verbose_off begin save, @config.verbose = @config.verbose?, false yield ensure @config.verbose = save end end # # TASK config # def exec_config exec_task_traverse 'config' end alias config_dir_bin noop alias config_dir_lib noop def config_dir_ext(rel) extconf if extdir?(curr_srcdir()) end alias config_dir_data noop alias config_dir_conf noop alias config_dir_man noop def extconf ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt end # # TASK setup # def exec_setup exec_task_traverse 'setup' end def setup_dir_bin(rel) files_of(curr_srcdir()).each do |fname| update_shebang_line "#{curr_srcdir()}/#{fname}" end end alias setup_dir_lib noop def setup_dir_ext(rel) make if extdir?(curr_srcdir()) end alias setup_dir_data noop alias setup_dir_conf noop alias setup_dir_man noop def update_shebang_line(path) return if no_harm? return if config('shebang') == 'never' old = Shebang.load(path) if old $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 new = new_shebang(old) return if new.to_s == old.to_s else return unless config('shebang') == 'all' new = Shebang.new(config('rubypath')) end $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? open_atomic_writer(path) {|output| File.open(path, 'rb') {|f| f.gets if old # discard output.puts new.to_s output.print f.read } } end def new_shebang(old) if /\Aruby/ =~ File.basename(old.cmd) Shebang.new(config('rubypath'), old.args) elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' Shebang.new(config('rubypath'), old.args[1..-1]) else return old unless config('shebang') == 'all' Shebang.new(config('rubypath')) end end def open_atomic_writer(path, &block) tmpfile = File.basename(path) + '.tmp' begin File.open(tmpfile, 'wb', &block) File.rename tmpfile, File.basename(path) ensure File.unlink tmpfile if File.exist?(tmpfile) end end class Shebang def Shebang.load(path) line = nil File.open(path) {|f| line = f.gets } return nil unless /\A#!/ =~ line parse(line) end def Shebang.parse(line) cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') new(cmd, args) end def initialize(cmd, args = []) @cmd = cmd @args = args end attr_reader :cmd attr_reader :args def to_s "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") end end # # TASK install # def exec_install rm_f 'InstalledFiles' exec_task_traverse 'install' end def install_dir_bin(rel) install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 end def install_dir_lib(rel) install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 end def install_dir_ext(rel) return unless extdir?(curr_srcdir()) install_files rubyextentions('.'), "#{config('sodir')}/#{File.dirname(rel)}", 0555 end def install_dir_data(rel) install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 end def install_dir_conf(rel) # FIXME: should not remove current config files # (rename previous file to .old/.org) install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 end def install_dir_man(rel) install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 end def install_files(list, dest, mode) mkdir_p dest, @config.install_prefix list.each do |fname| install fname, dest, mode, @config.install_prefix end end def libfiles glob_reject(%w(*.y *.output), targetfiles()) end def rubyextentions(dir) ents = glob_select("*.#{@config.dllext}", targetfiles()) if ents.empty? setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" end ents end def targetfiles mapdir(existfiles() - hookfiles()) end def mapdir(ents) ents.map {|ent| if File.exist?(ent) then ent # objdir else "#{curr_srcdir()}/#{ent}" # srcdir end } end # picked up many entries from cvs-1.11.1/src/ignore.c JUNK_FILES = %w( core RCSLOG tags TAGS .make.state .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$ *.org *.in .* ) def existfiles glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) end def hookfiles %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| %w( config setup install clean ).map {|t| sprintf(fmt, t) } }.flatten end def glob_select(pat, ents) re = globs2re([pat]) ents.select {|ent| re =~ ent } end def glob_reject(pats, ents) re = globs2re(pats) ents.reject {|ent| re =~ ent } end GLOB2REGEX = { '.' => '\.', '$' => '\$', '#' => '\#', '*' => '.*' } def globs2re(pats) /\A(?:#{ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') })\z/ end # # TASK test # TESTDIR = 'test' def exec_test unless File.directory?('test') $stderr.puts 'no test in this package' if verbose? return end $stderr.puts 'Running tests...' if verbose? begin require 'test/unit' rescue LoadError setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' end runner = Test::Unit::AutoRunner.new(true) runner.to_run << TESTDIR runner.run end # # TASK clean # def exec_clean exec_task_traverse 'clean' rm_f @config.savefile rm_f 'InstalledFiles' end alias clean_dir_bin noop alias clean_dir_lib noop alias clean_dir_data noop alias clean_dir_conf noop alias clean_dir_man noop def clean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'clean' if File.file?('Makefile') end # # TASK distclean # def exec_distclean exec_task_traverse 'distclean' rm_f @config.savefile rm_f 'InstalledFiles' end alias distclean_dir_bin noop alias distclean_dir_lib noop def distclean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'distclean' if File.file?('Makefile') end alias distclean_dir_data noop alias distclean_dir_conf noop alias distclean_dir_man noop # # Traversing # def exec_task_traverse(task) run_hook "pre-#{task}" FILETYPES.each do |type| if type == 'ext' and config('without-ext') == 'yes' $stderr.puts 'skipping ext/* by user option' if verbose? next end traverse task, type, "#{task}_dir_#{type}" end run_hook "post-#{task}" end def traverse(task, rel, mid) dive_into(rel) { run_hook "pre-#{task}" __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') directories_of(curr_srcdir()).each do |d| traverse task, "#{rel}/#{d}", mid end run_hook "post-#{task}" } end def dive_into(rel) return unless File.dir?("#{@srcdir}/#{rel}") dir = File.basename(rel) Dir.mkdir dir unless File.dir?(dir) prevdir = Dir.pwd Dir.chdir dir $stderr.puts '---> ' + rel if verbose? @currdir = rel yield Dir.chdir prevdir $stderr.puts '<--- ' + rel if verbose? @currdir = File.dirname(rel) end def run_hook(id) path = [ "#{curr_srcdir()}/#{id}", "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } return unless path begin instance_eval File.read(path), path, 1 rescue raise if $DEBUG setup_rb_error "hook #{path} failed:\n" + $!.message end end end # class Installer class SetupError < StandardError; end def setup_rb_error(msg) raise SetupError, msg end if $0 == __FILE__ begin ToplevelInstaller.invoke rescue SetupError raise if $DEBUG $stderr.puts $!.message $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." exit 1 end end mini_exiftool-2.9.0/test/000077500000000000000000000000001316025661300153515ustar00rootroot00000000000000mini_exiftool-2.9.0/test/data/000077500000000000000000000000001316025661300162625ustar00rootroot00000000000000mini_exiftool-2.9.0/test/data/Bad_PreviewIFD.jpg000066400000000000000000000206001316025661300214740ustar00rootroot00000000000000JFIF,,ExifMM*  (1 2iޜ NIKON CORPORATIONNIKON D60,,GIMP 2.6.112012:05:31 21:33:12(0"'n02218L `h  p| Bx, 0000000100 8 2008:12:12 11:12:302008:12:12 11:12:307NikonMM*40210n~      Bn"$% ;  % .:<  ( 8Hy" L \ a f v ~ * " 0COLOR FINE AUTO AUTO AF-A U @ 8@ 833656140100> > AUTO 78 MODE3a NATURAL 0211), o u)~n /+V(P5 CzbyGF=_ 8Y4G`~s5@5=+E?3TEAeMFtj|ЃҢ[^dz3Ky~9ܥTs)α ^Tn~JΖt߆ỦK[iIDZ9I]K} =߃wLUA\ aj3&ϓoF]>+dvM};u>uM^Os6z-a}c@Fm]Byh#bxjphlJY׼k&)M>_B$Bn ;'D"HNsWJNLꠂD 94 OmD2C:˃n1O M)lG3 |B[9K etajЛ-9eƥ]^ӸdikiܿkIӵ^UK;Lț"͏d?U2ؚvq3,6}J_ʰ.upm(H%.]O fxTcf9R{pն*70202|pߪg$4QuRNxP)]d]d AUTOAUTO0100 0100dQ0100ASCIASCII <D(LwHHJFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?h9http://ns.adobe.com/xap/1.0/ 0 2008-12-12T16:12:30Z 0 XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)KmCC  ?mini_exiftool-2.9.0/test/data/Canon.jpg000066400000000000000000000052111316025661300200210ustar00rootroot00000000000000 ExifII* z(2iCanonCanon EOS DIGITAL REBEL2003:12:04 06:46:526>'d0221FZn v~   |60100xN > F 2003:12:04 06:46:522003:12:04 06:46:52 ^W".8@!H    2a!  p6_D   :T ^)rP \70 "bdB8" &(CRW:EOS DIGITAL REBEL CMOS RAWFirmware Version 1.1.1Phil HarveyKtt+www (FFXc 9;FPR@?@?@?GV@?JIe@?@?@?|{|{  ` p P.|@S.|@STHM0100 ''2&&2.&&&&.>55555>DAAAAAADDDDDDDDDDDDDDDDDDDDDDDDDDDDD  &&6& &6D6++6DDDB5BDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"K ?mini_exiftool-2.9.0/test/data/INFORMATION000066400000000000000000000001201316025661300177630ustar00rootroot00000000000000Following files are borrowed from the original ExifTool perl package. Canon.jpgmini_exiftool-2.9.0/test/data/invalid_byte_sequence_in_utf8.json000066400000000000000000000075211316025661300251570ustar00rootroot00000000000000[{ "SourceFile": "dba713d8-cf64-11e2-9b31-7586d7f9220b.jpg", "ColorBalanceUnknown": "0212\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000X'%Tq_8lw\u001A덈܈D1\u0000Cjf7&n\u000C\tx\u0008{\"6MXP5\u0010K~.dڠ\u0008)\u0017)@+|\u000C3\u0005\u000Fˁ\u0019o<(m\u000B\u0002VX\u000C\u0019>Rǐ1\u00053dP1'v\u001F\u001E8o~[pK\u001CIRwa@5*.5@awI\u001CM+br,\u001Fv0QeҚ3\u0004$]?\u0019X\u0011\u0002l(=n\u0006-\u0001z`k#K -\u0017*\ni-J’W=L5\u0012tqrzۇ\u001Bf6^+\u0000x2#k\u000C\u0008]\t\u000F&ɡ\u000BkCuͷPy܉ꢲ\u001Bl9_޶qT%\u0013ZEQ\u0007\u0016|:Y̘;\u0012B˭|iNFADUcʊ\u0015\u0004W\u000E)狈{}،G\u00114:\u001AS\u0014\u001Db+MȜO.f$\u0003\u0003$f.OɖG+b\u001D퟊\u0014S\u001A:4\u0005Gܾ}{҂#\u0004W\u0004\u0015cUDAFNi|B\u0012/M?j\u0016\u0007QEZ\u0007%T{_9l\u001B덂܃!\u0000uCjd7&n\u000F\t\\\u0008\rk\"3\\x^7g\u0011{spt'`BL\u0019=K,h.2 9J\u000B#`\u0001\u0006o<)m\n\u0002SX\r\u0019~>Wǐ1\u00112e'\u001F\u001Fx,9ZxH\u001DIRaHA5*+4@avH\u001CIͪs[8-y\u001E\u001Ew'0QeҚ3\u00040V?\u0018\u000CYR\u0003\u000Bl(=n\u0007\u0000a\"\nK \u0016/i-J“<\u0018Mپtqrzۓ\u0010f6_߸y]2#k\u000C\u0008]\t\u000En'7dkCt\u0000!܉ꢳ\u001Bm9^޷pT%\u0012ZDQ\u0006\u0016?X̙:\u0012Cˬ}iNG@DUbʋ\u0014\u0005V\u000F(튂{|؍G\u00104:\u001BS\u0015\u001Dc+LȝN.g$\u0002\u0003%f/OȜM*b$\u0012G\u0006aX\u001A\\5LO;H)\u0000sׂt-tW񉨎uŜZ_KAPfRĽ\u001DIJgæ0\u0019^\u000B\u0005U*o[IѠ6\"\u0014M\u0014wv\u0011H.\u001B\u000FL<k^8a\u0006\u0007d=dRFС9+\u001FZ%uʏ{.i@ֳ\u0017´mʹ\"\u0016 1;/*\u0005ܚ'\u0004R[\u00088}?wǑ0\u00056B1&\u001F/x,8[rH\u001CIϮwa@5**5@awI\u001CHͫr[8,y\u001F\u001Ev'1PfҚ\u0004\u0000V?~XO\u001EP\u0002#m+ABEHJOj?=997@- @VV@=NA@@nɯXh(' 4 2`,24p??)% !!%( ,-(0*?,<)+!  -AFA82 }+{ gBr~rbS&xn^Pzj[7V*JmbSEfm9 (09@HPW]djpx|   "&((,,f8jXD4& |xtpljfb^]YUSPNLHGEC@><:95540//-,(('%$$b&PVfgx$`!'R@R@S`0(ۮ5: 1 A2 {I V5  `$### ##p#0" !$t&0+$g~x7V*Jmfm9+{}xfg$`!'R@R@S`0(!g9f}  `$### ##p#0" !$tL-Whsu{|  -&K9{>[[h-WH@@4`;a@ @'(i5CB5J%1NRSVUVXWUTPLGB:3,%joprsstrqokf`ZRLD=4){tmf]RH;sh\Pvi   5 " h H Ap"v'Z ^b[>B<S%  S*7 xx        !#(&%" 048=?BDEB=82-(! JPV\abfomd]TGB92)&w)oe*Zx>7=bn"=itl{yy&V$  P(4 gg RVWZYZ[[YWSOJE>70)!nrtuvvwutrnic]VOHA8-~wpi`UL?vk`Txl, w_}B=hglyk$5 UYR$K"  K&2 oo    FC 6O[g/4 h, ; 4 kmiS @\ 12 ASCII<D(LHH (2< :<6 (8D82VP>.bbbbBbbbb8bbbbb.Bbbbbbb.bbbbBbbbb8bbbbb.Bbbbbbb }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzx! ?~?ʏ<_WO?.bxQ)猟pOʏ<_.cxG/P<_*IT}op$*>7ʀbUbPbP1@ 1@ LPLPbPQC Q1F)bRLPbPbb\T1F(b Q`&(&(&)1@ B("\T1F(.(&(!"&((AA\P1@(&)IB)1@qK)q@)q@b 1@)1Na1HESPHE3MOf?ŏ򭚙"ъo0rDb8`"Oۡ}" y\$)I.4>Q8"CT5;ɢVWIב-÷,NO?[њڂ@ T7@#r~:1!m}j4{5j~PG=_Zqp}sJ=sk-_=[?uqO5+0С+ ڛZMԛ=s=J搱n~sUʧ)#RO[3vK;h1QԑAEnsi֦ǫ̀9wrMw&ԑ'>kVrLI<YEnsG8=A"L>e>DkI Abenddämmerung C    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222,"5!1AQa"q#2BRTb3"!1Q"A2q ?z-G͚n-@f@bEFPFPFQ(nb@bFPQ(n΅PFj%@sF(n@yZ5E* 7BР1BР1BР1BР1BРPt(# 7BJ 7DPtJJ:Q(J7BB( Q(D1DtJJ7D1DmZ5EPt(XСc( 1BСc(Bt(Dc( 9УQ(DcJ7BcJ7Bt( 9УtJ1DtJ1Dc (Z1DtJ1DtJ=PfBhQ,(Q,hQ- )-B֘FP(tPQ҅ tEtPQ҉B(J1q+7Dc|DmP.\tQmtPtP:%% ƋEm4Z-e3EhS4(1ET(YLУTZS( S( 7Be9УtJSJ:Q(ZSJ7BB(YLQ( -(DhZӝJDhBrht4, %{cGX;Ʌ8N.OcGҊR)(QJ:)(Qh (EJjhDB%P/IL( Dr8_ӕ 6J5J7D̰ (ts,іFG2Yzcb2_fOvJY#$K?5G&--Ќ]WH2ߔy'~{vBŹB2qqm[O֙h(YOͺgQ8_14o/4t]CGS 4~rʟ:l,H0:;r+9c %qihnzoK[_i ԯ8 rfnDN)Lc晴q\JϴT^~&MD%&m> x>Ϸsq;®{>Ǹ:i?n~:}878Gz999M(E|_D{־ê͞{B+n5诏Tk-u15_:u,9gO#kk^gsSOCq7ƸZD#2:'7ZHHt K&׵_#5_u\8OrqjmM;iK8=ocԴrË> aʡϔ4sfLJ4~MrN;>}D<u/s̴3ImrvnO4-YerKɝ,,Y'Ywpa,jH񙧴c~Y ,Y A&tQ_,M,'Ϻߑ Ĵzo6s:gl l3Prr[eyo|*Q>̖#ٟsaH Ly^9+79 ]u]}Hju?SÜڍW~u8tGL❧}߱f<ˎ3Ԣ {1NyEG#n95.MŲ&;,Mslமj9 /=VupV[tQl 5-U:31zyNucN٧qk,n鶗|ln{GKQ)dR+#)w߱էaŨZ| Vn'};WϴuIbÓW_W4~ǃmo_=OXrzF2xC8T}.Y[+Kѩu FTͽEm1M8&xVxl]Zgwlazۗ4Y]5(활y'8gt߈ڝ ɚ{3AT]ߪt*W۟3#9A[_B埍ۮ>fUR|tγ7)wi1#OMύԸd~}KOeZ_#SꚎ,8~ޝ1Pw(cM,c`kzM:hXQRpG KeI/x&iϫ|Q8*}ovdPճ<}y+X_z]?ek^2?_x3<rϝ.9k2b mG"}KKWf7y\<=v_,^7ϕ6L+n=MIGmSXrxy#7Ev5so/XnM6,qmģ*\p]n1>#lRV.xYSnR_pJ_x 2mӢGM6)gy3wϥY`*xXp49rx1O!扗z;kՓwLc IZMT̀ A0 h *Gt^/W,W9IG k3E7|MFO3mANןNe#lݞcw>cATi:].-+g|IfRܜkgxO~IJti5} k~*>wG휛UWk0K"Iݞ3gj5bssI6i7^(Y̠ [ T@@QlYPmini_exiftool-2.9.0/test/data/test.jpg.json000066400000000000000000000062061316025661300207170ustar00rootroot00000000000000[{ "SourceFile": "test/data/test.jpg", "ExifToolVersion": 8.77, "FileName": "test.jpg", "Directory": "test/data", "FileSize": "46 kB", "FileModifyDate": "2012:07:05 20:28:24-07:00", "FilePermissions": "rw-r--r--", "FileType": "JPEG", "MIMEType": "image/jpeg", "JFIFVersion": 1.01, "ExifByteOrder": "Big-endian (Motorola, MM)", "ImageDescription": "KONICA MINOLTA DIGITAL CAMERA", "Make": "KONICA MINOLTA", "Model": "DYNAX 7D", "Orientation": "Horizontal (normal)", "XResolution": 72, "YResolution": 72, "ResolutionUnit": "inches", "Software": "DYNAX 7D v1.10", "ModifyDate": "2005:09:13 20:08:50", "YCbCrPositioning": "Centered", "ExposureTime": "1/60", "FNumber": 9.5, "ExposureProgram": "Program AE", "ISO": 400, "ExifVersion": "0221", "DateTimeOriginal": "2005:09:13 20:08:50", "CreateDate": "2005:09:13 20:08:50", "ComponentsConfiguration": "Y, Cb, Cr, -", "BrightnessValue": 4.5, "ExposureCompensation": -1, "MaxApertureValue": 4.5, "MeteringMode": "Multi-segment", "LightSource": "Unknown", "Flash": "Off, Did not fire", "FocalLength": "75.0 mm", "SubjectArea": "1504 1000 256 304", "MakerNoteVersion": "MLT0", "MinoltaImageSize": "Large", "WhiteBalance": "Auto", "FocusMode": "AF-A", "AFPoints": "Center", "FlashMode": "Normal", "ISOSetting": 400, "FreeMemoryCardImages": 202, "HueAdjustment": 0, "Rotation": "Horizontal (normal)", "ImageNumber": 6, "NoiseReduction": "Unknown (2)", "ImageNumber2": 50, "ZoneMatchingOn": "Off", "CompressedImageSize": 1598477, "PreviewImageStart": 39152, "PreviewImageLength": 0, "SceneMode": "Standard", "ColorMode": "Natural sRGB", "MinoltaQuality": "Fine", "FlashExposureComp": 0, "Teleconverter": "None", "ImageStabilization": "On", "ZoneMatching": "ISO Setting Used", "ColorTemperature": 0, "LensType": "Minolta AF 28-135mm F4-4.5 or Sigma Lens", "UserComment": "", "FlashpixVersion": "0100", "ColorSpace": "sRGB", "ExifImageWidth": 3008, "ExifImageHeight": 2000, "CustomRendered": "Normal", "ExposureMode": "Auto", "DigitalZoomRatio": 0, "FocalLengthIn35mmFormat": "112 mm", "SceneCaptureType": "Standard", "GainControl": "Low gain up", "Contrast": "Normal", "Saturation": "Normal", "Sharpness": "Normal", "PrintIMVersion": "0300", "Compression": "JPEG (old-style)", "ThumbnailOffset": 39274, "ThumbnailLength": 1820, "CurrentIPTCDigest": "dd8d51d28ddf04f08f870e5ff2f64d01", "Keywords": ["Orange","Rot"], "ApplicationRecordVersion": 4, "SupplementalCategories": "Natur", "XMPToolkit": "Image::ExifTool 7.03", "Title": "Abenddämmerung", "ImageWidth": 300, "ImageHeight": 199, "EncodingProcess": "Baseline DCT, Huffman coding", "BitsPerSample": 8, "ColorComponents": 3, "YCbCrSubSampling": "YCbCr4:2:0 (2 2)", "Aperture": 9.5, "ImageSize": "300x199", "LensID": "Minolta AF 28-135mm F4-4.5", "ScaleFactor35efl": 1.5, "ShutterSpeed": "1/60", "ThumbnailImage": "(Binary data 1820 bytes)", "CircleOfConfusion": "0.020 mm", "FOV": "18.3 deg", "FocalLength35efl": "75.0 mm (35 mm equivalent: 112.0 mm)", "HyperfocalDistance": "29.43 m", "LightValue": 10.4 }] mini_exiftool-2.9.0/test/data/test_coordinates.jpg000066400000000000000000007665651316025661300223660ustar00rootroot00000000000000ExifMM*nt|(i%clipHH"'0221    ƒΒ ֠0100Т#  ~ dASCIIclipN<WTlt +OdOd(HHC      C  Z" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?9v^^Vǎӊj :<?Q[Zo@ Qʯ[Ga$7j9P)x{Ea,,K JE`}kk Y ГӬY ~r>4WGt֏q ~U|s%hzj.UpGQkZs`㎧eNx^K`=>ϲ@q&NFԅͤK!vh5 F<ݯ3szY["a)Z Ŀwh /Bx|7V0 3nj`t<`e*?)˞GFeVt *VP0~O5׹ܬuDמ<jZd6\Zޝtuao\=X~C\^a;נ);q\m#b7c0481@kȇҺ=B60A5wX^L6im{)#>VrKr;V5e=^Wnb'ʥVkC̳ҋ2oⵂ) p8ZKHZ6IJGQ|Ve^I 8\n/kkbygSxPyMǥs3jܤƠ]Dm#y.NzWI"A- YkחӒ=+gTԷ1>氯3\}~jp ~5xXX`WI},be|zѸWSueLZx4`3EO9|gsOEk'y?*|׎z]#n'M~%9OƸe^N'(-u;IZʺYm k%8߹ǰ|-X `y֯u[$[+#ċVWӵՐ 'gJNgdk}OGc]V\Q\ޝpxcr걞BbּZT:v5Ǩ0XEM#pnjWCRvo`ǧjԵ=NN+W֩Mn-biKR]Es+Yv4o)񿈿ic4d{MQ xjXı 0A_4jZ8ߐ~aO״}u_WkBxՐ?Zߴ0s-UAl8s5vϳPGTיjGWX9<+ XXԼFer8'֢>"]_Z![eE:sګ;y,I#k!i%k!9 y÷K$Ȳ`iMjV Z(tUyRN>W m'?J`+9M\Җ3_"T<mx0mg6 ܄*񯮫˿lg:s^[2|x{~5ŋ%|E: 6ɯ?|^ ~kBSUIcrvǨZӯz$5Ayin{MoBpMu]Z^];Js3M 9 :OClExu?é[R\uh[iG\ύ"&ue|GsFJW8EPKU=$r Ii̚O$ʳi# 5I}zDO˷>!V*E2IF(Օҋi.kuuuj9֨ҩ`Tz] amgdF/aD捻?¸<2V'έsX7ufknL6Cs?ONԥrH6 ӶzrZ]겙s[sdJ=<O5֏Fs~')_mR{T,)W;K&f}{ suBO&$ɯ:3EtNsӲڮK>5?:=[ ,٦I+j= KՒy#f霸ϭl@3ףGc㭓砫rAxeE:5ϥ57&dF$Yِ0W:S~AϭuRzݑJ5,jR\k[p+K'6ϥ|MV ;s_ӾfqcSUM?'Gm,&:{Z/* 1߭P-T'Vu?h^}?~nFh;g+# X^mY7'?*'$Z.lǧ\gjVG      " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?-ԍLMIlZ߰YhdIKrzVR%Ea,HPA+u$0fL toxL˔$n ekAqUi7}bzfH'rǕ˃'HI'q_EmOuEܲ23" H Hdsֻ%cv ;g۽pTSbwi{k]Bsz?Zم,\2Iǭm]P\f,b J޴$(h?էF0kT=fv.qrΓ4(B -܏OOWO%BJAYcb"W6\}ʭ`Z-2\Z\I ~gUpRZ6Bw*TskѼY";c`ז$,*q|;i3Mՙ[鄯dh Gs/44rlu5F;[FBt9ٮT C) ymZ+nY+ <;xGc<&I<*GRU1(90}j ,h_w~bz}yV.knl}>nkxbdrF&b<r\;}qLZ4$v;W-fXq9?^1_WskdžxRedLm,8 ӭ|IJav2BK+ ϡoZբ] 9zO&[uIԒɴoN¾-{ɳ,EVqd^] E*o[gmLX|^ϗHO9鑞3^_| WGtEҿDTR?6G+8t=nCz}5E6#;h=5}FtyߊT_%)xfBwl_=}6]Sx[fx!KTfKx.9F2wnӏI{{3`'ӧz].[-p.5]chC.1^X:bK+Hݎ{=+3 WORin`Aܧ?/zȥ'&Wk99 V=HDr2Xڻ]3M6tW?1s KKlc1Mz%h"lמJ454E2; yO^z7u+o1@z,ly8t6t39IXs3pd [YZ2Ʀe.ܖ'z;PGIcPo^Xp,c=u9댰%C( 7>UxǦ+4_52,F8<`gn-Ht˽E>ڻ1٭#)",W'3\}l] %1ׯtmk(3)`!He#Րm,G㻵*̤2=+9JG$t.zuC @ ϽsK!8֏N) e}(hxwrcRs▀- 9?JE@$Q( rN}(5r|=FCcH @' md靽 W8sO0F u)D`c9FZتyO0pӭA, "G B@UY($ޙpwB`xܰi,V}0W` srzN2 Tq\̎M7N1 Y\}U,۵2J&4gD79̥ XVrW7sČ40x 42#TN:B=frd#i_Cך*C塛ɥ-"wf{w!vU|R?cT^cK3m,G^}=Zd u^Ӯ+;kqh=b#8+i6mDA9jѐ S3H r5a\`d=1ʃz\V!*QTXt831#JK&kXA*lb3R$y C1!ʺ>W5}GU{R%ץRՊCf+FxI89Xy$8 1>^=KF1қ UNOo$ʐdgҪ73e>]Q.TW2FF!ĩ_\gHv Ke*Nq4 y T#0s<\1"G:T 2$9ӷ[#+[^ݰDW;2:3^={U8U|e>=MgHLHYfϥzTs]X`LH$}yTIyUΥHϿ?Zn^fd$GqTYG ;r 0:s^C˜aIInXy:ztTWk2Fmp =PʾL131vg^x#=EF1 "yK?Zi%IܴPDpT\C:\F)LdwS^dİ|8ێ? |[$q6+zt*)O@ʹҽ_hdr*&;_H\&#qu{ںFC;aG8MzW8M[SfFF15}ךtf/`cknvDDrc=|"+> UIcCd JYB#G7]Oƪ9x`g֔HMrJ=;_|@#yeP2VN5NOxݝ1j[˪y9\d―Q,5M"Τ_[yL'ٜ4">Kܮ2AW7$#`(㯵{W/͔Lp[hqϮ:חKKV)r܇NAi,Xvu8íztV஬ϲqimx*I#t+*&9_!v.Jp>qR UW {ڼUDι,IO0s5,eW9c[񻌎?:|]"|W"d̸ּ,vc Zl*Whe=Ӳ̚4vmIo)y>a'ַ,K,#QӿJgiڴ~\l#^&6fHp;[zFӝY{,dHn;_ ci&)c>lE1pN:-[~}=ƯT],0$9Rx?ҽMK䉗^*hFvPg ǯ!Vw*66Hqpϧw 6d0(wm6m,@9OsSCH["R`q@1GzM2Hll6؞TcΡdgBHX)Rp{ڽ K7??Ƴ>%]Q1sk"F֞lL;cfOwRQZn\W%~S]ƛnvb@ʿ_ǵSKc%9$:d> #'C^'ZZ3gI}L:#z~kѭwؤHAXuABW;Ÿ/Aq@܈աv m }6-iVom:ۂakM,3D]ģaO|j@̃=q޻kku S$<־ ˭"h-cäfB!1ZUPN#<HWtmC+ ]&*[92~I\J)#=Db%@^:WKmsXα-cA?zI| $IWrۦ{* ''|OUX ѳ%qҦ@\6 dgsvv.G40E @4zj9$})}y)zw4zd@I~F(灎)})z/gOq@ ֌uEΖO} IE+@ #Ydm^=\̡['}3c,(4F$.=WY2 D,#'#_%P03l*1O?JMc6@`çXFbUL(q[wbK'w^ɬ;YмrA++?'-sڷ;)I <*̤UR!ZnwkV*r!hݖ)EGMŞ6b2i DY0I~V8~d{f"Ia7Yz+u#_Bk6LQp \ #QvSg?Oy 8׽dβ *-OJ׾!d_:U "0&1oAo ݓTos~&w\NU[Gy}z]ƫF-ImW2tg@ryb3yumekYc u^[?^~q#l]Ǎnxw62$<2#``qs/]I~n! ,HpբwЖ9<\`yP{y qȎ.6hAx*,iA0cUb Sc!ϖKw\rw]]N@*taֳ.-ՉRHubh̬Bx#*I ,ݿ &FGp!ֲ#/6EPy`NAaB,S/0\`mzf'YjLa V|SFiH85p{P bs *4 xcx869"ٔAT%}D BqM E ;KyLa٥aʹڶёrK41,\ V,bidnxazc?jά 9XGCrH OrU6I'SEer ;gҠvIO=G'zvF76Kj3>F{uV[4ވOm!A8HHAC3猃Va}@brGe'1ӀN U/nP&YPUƫOM;ЋW*\OҬnFE 9yE?JŎ+A)7*";H{S|ix#zu2BY7ESt#V-^3r0O^iXd2F2&0CߧPn?!؏隍4#c<#$fu. %>@9٨+3ɟ%j ,! 늫ǐdp;2sx#VI *9vbb[v;sӥg!c.NxuтoS)H0\E]EG8b]yn)#z)%I6>ҥsfOM뜆^ GV}47N1 fIt Ne(X8^Ms<[*yw&EHn$ug)l^ ՞V#d?PvgݜrEPeuijdOm85%f5'ҹ+*V^o,; $s׾8{>yy>c̿,C$i•<~̶ H8ӟ0~1Ǯ+x+)Ǥi^_MƎ-_nt&Syn"xyz M\5Gb;͞}^W1'8]fvߧIn~В2N` ֵ @Oךɲ6Is+Ac1->i?hXD8]7#,%+;tmDvʒLRp_ν JFH+vA4Kx8e9VݐszcQƦL\;w{޽#U/rr+BѶ3pm< Ev[v\ulk[WU1ۆZq,y{ 5z[cʳl9s=*U$*ոݘ@ImIlIk֝EI@B6ҤǷzlC֧d A;{Trl$T]tdxy& Z^Ed ⋼cȯ'Ib70g񷎬n|)$(o\^^? ߼ʱ>Iu:iˈmDЀcnh$Ln#ӯ;NԠlê*pN8>p&Exr#&}Sn,)%=j4d)2|-/$M<碱?fNK\F-%'iI?:"<& 0<]HYvsC`u4X.D7|91Vg1%(6򛃿?⧵dx˻LT m&E@Q3~8e)iT׾*eFKwTQcG J$H79`ZQi<c=_,<L/ˁVC @xZA= _|jΖ. +I`X猀:¾ѴVKK@&%Xma3׎?5m`9U!C[ ^Ujz=Ͳd{|S|o>dR>YN֯~(\Xj1#N$xǷ1׭|5~ӕDQnO WLl|=$"-cIA`0N;zW~#iwFf~Tg 9AnV0o28ŏ mEm6ː~WmGZs=мV }{}wy.L ?}+ҥ\m Epr3[v$nbFiR6>α8';}F H{ynkwh<*W⽋C?@}¼MiĞL ,$noϵaS3ihkG+zU$GRJatE=ků{Gq k# \{vFo?fUNRzgRKO6{:dž08d|,&wAtU*=3UK.$IsnO i[#Ƴ[J ]aج?ھ^*l66賝EDm HO]ҥCIN#vY+ıSTLr3 tck7Hұok6Q˹!x+ֺm>[UKCċe0qzdtf6n7$_0FGn+Io%Teݗ 0Lǧb;ԡM\tށ28$4u#֏_Z_ZLcҌP{y烓R4h2?1҆^Fǵ0z@ qPY +<c]rة$g@UpNAQޫmI Pnn9w(ʳ7?*H̓8$~ִE;$e_яV=̲YeODG#܏i$7ݐ΃ fy֎w9|zVGU6cD[:A x\ $li0c|quFHVUI$w1#PҶ=:SZbSrc3n$s0zuY&}QTߧky#*)U㌀G95Ec|Ҩ;A 9Z BMBoSGMX,' ?ί128=$.2Ń9)_Q!I&wP۶1-L*B"Ule=jTdё&ѻ( u $OXʷJY7 {VncRPg>rlR{PU%{G>dN*Ą2Z+%8F "D}ďX5ms0>ȖDqqyzc56:4y8Q8]Z9T`3OaS( D&"XU]sOprFN=EY7}ZZKYHBN={t<"YȌ#<ۓP I$Tm(ẀFn^l=r;y9%T^B!LTgDxD$g9ف+ 1YnvݓcށPh.Tg9j+n|rz* !69!/$g#ޓ wn/ $1h7M!Pv+th#jtnx'pi4k"NrTcbg@6HŸ-7J;>uw$cۡ*ԐA"Fa8~*,;BKyVlXdqE|qu;dXlExXp@nGG($Fς傞*[r3]&]fv>sUgSA)Z!.l$s2[ *{m V9*L<KOY^\Ѵ=یRq9$hXdyRzj%, +s~Uҁ)KMxȁv>*WVy$h:I0L[hx2 _ƪ֍LL  q~קJ Z6^(eL+,VlPqJ銲Jʬ"v,@5^cş2(' 8^6ۏ:#':³ )G*0kF\&3;k*fxeq&rgFǝRe'ht1~wB)yg9On=䐄CCwHpkLD.9a Vb@ GN3ֽ8dpԨiM.֌}1q+{IQ7/-Oߔy<; CU8՛;CNH Gj*G!Xs| 2(Mc]5jå"hJ#b.$!gc=:1̅63YS̷ȧΏfPNka6#m^Mk #o+ 8z`q5Nj2ww}Gj޼|dSy$szrcG*#1qױ58|11mYo-Yz~ @_?RA:b[ Ð~5ܸ=5Bņ_A>gGwtHrYeN7w"K+,?(ONn&3m Y=+Y4+xsҾ8}p8[`~p ٞH$Y#pJ?\Է:٢qх= YK1:SWa0#ⱹZ[/p9 V)pgV\cNOߥ*1 zeQ[1tgݖlد^+l|;͹[r_z=, fRx\f/ @9\ *ö[u#r3׊"j:#4u>;,+YC/>IHTLn`SP}66v6xE4 <{s\Yh|iݖdzy$m ][r;rOjwtkJpG5ՙgX|M{y҂+򁑓M9VSzҹκ+TZENHc#vQ( ;sqg?A^w'忁wđ=k $U˱g*?BT|)_yX]CMeǙ!y^orB<tp1#c5XM:Ǟcj?Skh%Qqwo'Lw&6]Ui)$z<1SC3ʢn*r3⬭G cdi#j 9xkfD =GPF+N#)݌xX_&'3A0nS?^NׯZwApUѰv8fVH&t?Nnq6.4.Rٰr0?VQRL2[ bK#f1y9t`BŞgFc>jJL%V A qRI X̫4#v> \nGƣ>8S~5qo^I+?h4&vI t*•$*· }Ҍ]2Y}Ҽ M{OR60 sH^Oc%ԕ~y-@|砯W]G%Ap@ҫi7I#m^ lNЁGb&+q[hvO6l@*㑑ei VTeO\oEMBlȌ2;+(t8-u$SksK$1@ v9Sx>Abr >sMnXJ|/=+U*ihd[YFQ&Hzq[iDe '9>iqA;ib ̖|ŸwTun+TGN';ac(w9+z]a"®+ۏN?wOӦ,P1ݑcs]c*@=^-z|#`t1FO 6d?z-" &\ zJfd'I#ӎ:}Ppv82RߐH`'B~ozCI$cUM6L^|nqcZ;OA:S1~F÷8jF-KntѪPYOQ\hJ͸s^Y-a8ލ8{wJNj/V0f'#; 6=;W_elQLg?f@1?;zcKuǖӮkԡ lpVeinf)X.220L ITxIXV8G#E%9>C f2sӟڽ:tʂ@N)e~#9`qcҐqޜ9z[phIn_CS4'b?1iyJc >a6p3rHmۊ8Srw1N%gk:v жƔym4B?!p9Qgӭd^ZVdp\*a[ֶyQ7G V]^kBI"F]y$ֹ#SK }qڨ1O$r yެʙyFm8,{)fz;#Dv1ҼvPB2-Vز)_~t"dV3j9Q3 FWNZ,BdqSF $*\,ѹHG*(yǧT4ch]I #sfȬ&'?k#v$GpUN+hܥ:<¦VT ϠҞbY6k9b0s֤DH[TLye]"' r#<k2X S cl]`VܪNAyyQK۾38yl'aԵb0"N]rG8buB-`%r[#9NuQhY3ܜ~,2M ⲕ$4rEGIDP[ P:TtZ£Snq=ɸNΒ H=ۚJѶ(۲Cdm<,2dX-9~rsާDHc|#L& c؞򤹂)  RI7C Uj1cH!}n Fd1J9n98)+y`lvUlPkeDI yQ]'R5n>΍o8Hs~pfw!&H##~XUj#iDPL@^}WI K9vЮBd&}*pʪgh9U2lJ uڻiS8T+$HdS)N:trDfN xOSSc`)B1wJ+ iQۿJR8jֶcB&ٮLH1)%`;c_o˯hm-_*?n{"LT;שF:A%32yi9'#<\p)w!@U.0 3ws$2/ ]$Vq3L9 }xXۨX GlzWC 8fv`1?|.gdg|/UӻmLo_Ê:Q6A'{o<mM)#ʟ:ko%" e;gy=3\]DpEEٌ3TgXDrFтB1}+pxNv6ɢqxɹQUw V)(gޣ*FZ]5®8c⹛kY[춳34FA`|n;0KFQq,$zrño`m}3/]#A"ˉ%K'vq`RWH0m>[sL76?0һc-fCg 6PHl=AL7AoC٧J3_wqMEEv2Bď\Ҥm'pT#Apd1|[UvYȊن<{ QTBgig ʸl=+t r񔐅  g?J|;a*Wtȗ`,=k vX N9ậ#X`dx^S66Nz89XX)8[<ֲG_92:W<Y'5$} wV"7HAr…|^tƶ?4$xۿFSsAO*F*Nz4749KUP. h= EPj!zS|V lu;cJ=k=SR3C_ iu4N*BX*9(o8Q1]*he=:g70)Ùn[#*i~ZU$|͡9o߭w4SlZgFla)o^=qXVKC} z=¬)eh:5-TҸݬ=Wx;ң[-g*;t?ilyر2AƩCc 52_Ocޝ:M F3HtnsaSʖ| dnۨm*@ٷh^l c1*(ܕb6s2JpO $ mјnʣqתҳB(br3z3ڌ夶uG"t41U$"E?I֬۬0wGQZdJ'SWrso,B瞠}kR9a$OᎂzSեLش**O=:`nu`9u6pƞavx~UOAߞ&q]]V񘈍v凮Is L1l@m@:!:{WeB3/8}>i0$'(۟zt,=]z}^{ױFU0ýAc$v)UQsayk'3[9HO^5ڰȑ;Tg<kס˪k m~bQ`x ` qڳپBq G;0xo+;+ۧ#Ȯkmen#{kLx'ұUz5o!&IprOEutǚ8}$+#61R $&mA#EJU'w=4(R | x\PLrfPY9*╲Ö* b`q^I8Q۞.L)A "mOAO_cނG朧<᩸ڤ)}=hK`c'R@K}ݢa@Ւt1ʜ?Bgpzz9A<|Ҕu8'098vI<¿{PntBѹQ0vNh)%pe8Z1,\qQ*.~\t$0Fpit5J[ªA >r 1%UqZXoLbLqC(2OnСhs1)(v{x<1Sxjdʳ*˷c?ÑA .șGܼe]4э3Ue-68/ 韭gF ?Hߍ䡝Q̪ǖ'GF-,C.?A%iƻXHxpOsNr#bx*!189Ke(JxOc&p ]9~sQdǚFz|Nc~B$fYpHePܠ%us$xQ`Ҹ/9 sd2ѲY'{^G)r]dꝱ؞=$ahPrA-?O5fcZ#mǧtÎwg `ivw'e) $;ANz8O2V9>Mzz=O:AM81+vVUHs 'cyn+Vp. (^LQJ眈N76R8jT+?8Wv\tCÏ7wsv^!FzӇ$1$=a\T@ 4 9/߮?YwM*@Y*>dlgӮH'q9 jz}*Ω 0G _Gy˖%4#&o.IbgPUG*дF1<.zdڹw+p Wkaxj.'gyEBrӧoNwp*gmr O4gYpEԫc՛%ڬ +ڥ<\V'=܁C+mlɮYILv8늳%{rwsr9h-†USǾpy^ 1b1;Hi15O+rsܠf;6gk`7YhM]u:gH*v¹m^RKvr}FvM)$|s\}ޢ#fgQkXgc\ϒ%;b$Yr3o{7 *mF+HX3\T0cn_W~{ܺԂ6RgTҰekPN:jh͉U$Lk.IZ\8Uh#iҲ>K]_"&j;{f⸔*O8l®Xe[C0p~4Km5H,z?ƶ7͹X2tJ-i1z(XG)` f xǧV#!URx|mpĀ1CXd&]J}A)d[<`cdzU'J0g6k>֟9m3mۃNx]KA;sX$ '*;g=W:vfI?.}ITFDr9wgrX~uJPubAc۷ZkJ7= B66@?ʫ!C:*gִL׌|jpǽqφ8.̕>;VɚW-cXt$瞽*HKѶF'h{E:2| D!K*ö==s_:x$F3({zWA+- 8fzwWxr*ԫ!G#+Ct.~{>T'\:=cP.#Iz瞵6O[J!u9-Լ$-*I8Q#ub:گRLG= ^wVԡVhm.ywPy5 1%x/ϿsǽyVelvJ(,GWd+1mU+ipWh?L^oxU7r_8'򯵯8>܁?pZτ۳ʍ?wtk ߕyq#$2ޱ&ѢgO!SzSi#;`*&m"u$rN_=u<7;f**G^UtW ^7 'j^yW]hQbtzT!ӥS5bϦSCqUWGbH.NZPiı',0[#qe$p7:׵Ҥyry;vySեJNK \Oj!I3}*5i3Ē8s[ihJV<_oJ+u%Њ ٷGRr;vMRȇn?Ӷvf6nǧZ,jۏUqsiJI̷rvGӱuZ"JX]> sRiV}?Nº+8<,'*w=zQfTVT"  7\4Xf)WWU+jM8ĠXJ̡A"6AW|"e)XԳ Ur:dpmg`Z[\:|ّ==:Ҡ*,-=UB@lg?Juԡ^t~Xbry}ѧ hoM_0"r'E<)#eʀ8x[h6,=X~|̏g t\@{jnmYܵw׶%*Kyyڸ{d]IٵŽֺX̒ǨrŒ;~(;>Yѯ^U¿6N8$p~x;G&@To32o.%P ʕ\ɷ-6GsTCKءa03Y3JY!萉k] XM ` zV,U\d;5ԉmrEn֥Yg*XcZwu _A"XѠ>[&8ϯg^-M W%HQf FO*Eav0py#֤LVۣRv^~X2#FҙGNI - #**x5$(ah)6`9bA; Rsߊ ħvFNgI,DN<8?JȑK`27xVܱڅH^Kd̉z~5tEtDCh$1N#+q:[~ÿOOewqZOen z}8@mq1\]MQ"8W?!J3X%)Ue6 .T3Yb"Tjf9V1dM2z`9ʼnI`3;#/؉F?*D#!J?fK9^(H@NFsJ8dh~$ÿӥVo,o3qǭe "2%Y/yd`J00acJҷÅ4U!togHy+Z [ZH๞iػxY %v9H$ܓ]A81 ow 8STdcWw>JqS\ A?>tֳyNI&?H;|")CwXv(ќ?)XӬ}apv :O$L@>G8tԙvnI(Fyk»,_V#%rlAɬv‚ mI`jmԨEqvn$YD 88 ifa yÿ97F5_ðP]y+Hs׭BZL Id!cud_!0* Cr3zswb x=\ٚX?gҽ= ]\Gb$QGQz kICm3NkNS` LdU0k|7{ O BmZmee 0z3El e9 6~O\Fq):W)wt\N<5l1bq6_ Wǜzfl  Qx˙'Prp;:)Y ˦d1otBO}<=u]ȦL=*}~${@;K8q\V)Ϳ>uxl5GY7_JEvtezV81ZCzwI`IDy0tu*E n]N@Ÿnzt5Zdps]4DBFѼrG^fmqpRi6 ݅\r2zzPH$(0q]''+4^|k#2ali \)cG'zTs#ۅUvcfr’凨ΙDD"r~bsܹ@cqR=q۟s)dWʣ[rbGJ-$v!{[y|syuD2C0OA^ck,$9*c7~}+֢3vPH"0v}J&f8SS=K\,dNFO$#wx]W,F`` ;뚍67)_NAV'~U`0{1+7#ĉT|aw:9pI9nH"e Z }IqI( *OQPz:,.r}jJ+R)H'ڃQE=0q@9 ?A!@VKĖju.\N zzWZqRiqlބ<~ 2JOUdo(yFϽaa='C'̞~_CWѤ(w܀}+, *(GFgv_>~{kotQHA$t>ޕۖҍe4NIlYR])=:?)dXT1ϷJH9@hUi@FPI'BXH-iͱ[9#ýC/F*68銭r[u~jՌ U.YH,J2r˱6=OjeZ0sܧ=}qnIZJMhO*.Ig$c럕r{s[;aDIb2? ~\{PKQdXcArFv#n=T"Q$pM uSr:#rp3y G~j[خʒ&G8\xtFx6tD" 5 ՠv2w+ aeVG=ƺcPt3k-$3Uz_yR@?tu Gmn=ǧz=W@$HY+:SBǡ&h/j(?ּOYRG>q;~4Dpsӭ|i u/rGL uWal ō`AqğOB >v׭ɤίE~6'v2>@ǵ|~"gac< gBX~-1vz~uGө.0 TZ61~[X9>xdzpFE tۖ+uk)-d+/$NsmEe|aԅj!6PkO?;ߡOz-tC)UGE 9'"fe9yI IZ`oiv}p9!4g1T"{ sgY.pՋˊ˳dxHץDTlC3ěA#?JI鶏f ( B_P0yoaۜTvEQHDž;Oں{(iM>9~Ezjwg^] ~G ٥۰ `קӚ#m|XN^++g} F7d9dBHWo˼eTrO668m[}kz$bH$ʍzvQ)Cv|ȡH&=3Ư4r[lvS `6>_õw5 +7!p|g\t+,r;#mpHvXP5 r~z_*CXl`@ uӑQhkƭ ]czݲGUa=;ztelt I!q6ó=AWA?*a"o1qa21ZǨybQ<)4W2\p?:FpSٶ*j\8U)2,(߭3S# 廧=Q𨕏̭"Âs_SRnKmA?JcH\n2y~^*4'(0HO܇$1@(0NbŊߑ@u#ڬmXsʌW}?*e)NDD*L|Y%K+pp R,z)gS !4$=NX,r3+4g1y# pJ$kU8ihr4o$r3ַ ;O畝Oɹ0AV,R8_86z2ü!q}>rJzg,@ç8QwRcxѺy{"Nq;VTw1ȇS)COJ\,&Qyc 3w Λ8#z0r.Ʌ)$(?YpHN )jfdvhSyX|I!XI98x#Erܗ1G%P9rEM1} cHpI,V+&2_==땸US,$30H~_r:w0akW;m}wɬ+Ju=Eo4G/3mcߑҰ X{v\ GM t+uGPH6~hRwE9&UY#gEvQYZ͏{SSO6#'9DV4BʒB:x B+Gv 7);rO;r7:{u&K q2qqW`CM퓎z5LyrFc[?tt] ň|BG6CN;wc46oϥ8W3dx- c2 .>_o=yViV*HCY4jȊ?L]jc)ZyTJZQy#K3ySڢyUgyY`==ԧDlvHlz¼뇚+ccx3ZI!㍛XuYwR 7`r08O`:UP\*3F q0$t+1!)%sO4q琮S~3ɬi㝞)7ʼ}lg~> +tEq C2_H9?_]>fxpݸ?Z79ۤt!B(W-x>Ȇf OZpsɫTkhco0hA$Lz\1ű1?Ur~M7\큞9V޾~P@l7^9#nqLU2 )컁S=<`o5e ?K4$(*F K4***r*C Rʧp'yjP=6yyt:+Á)FeX:ӧuv] [g.!'TzwdAJe[y١Õv3O5{^vkc ۱p חhk˦,.U?Rk:|cHto1p=Fq\D4a?;;p;Wcw#TTg9ں9"ޫq$`[q=>v>t4>SŅdv; }abȑȧ79 р a2pNGATXݷKp cR"eWEv$wG$rOو `d隉.±Xp\Rf?.S,aT鎞%)fReU)@0gҳ+x[G"v (X.wI>L͠䞜gQBX5oW҅,ʢY x*}3rsmnUyl~9?Pm'R3~ #$)d~`wLI3Bygfb=xF`'$s#hU\VY7:>8'>,Vwadi" ~93pyw޳-p۳a_=xĴE儏!Tc=qԚ#DM͜nׇ^kˀbޙ:*MB#VPF3^y5c ==gJ! @©f{:*-Ps8漼dzxecC@J *;\CcF Q=9WQ@7d8>ڱgrFibyɯOCb{s&sqV%gdrnxg?wi#F>֥ңtA]F݆'*p#3KIhw| 1ַlM 6P#[zcBdɘ끓ZV\6  ~R\Ai"" =$O_J4 |,nR=3)[3?!]fno(ʝwW!ϱFs :g׷(C; #9Zi6Ffظ19gt-\29N&rrC(h0h=kӥ|ȇbv&rTzl"(2B}Mv,P"Iq:s22_blC89}wvЈ!WAN;v?ST( Mv6,#ǜ{pAλDRiżxrObH#G2o#E[b!era?y̟08Z+>dOeH[v98@RNJ$ftEǷ rv10N~RPڴXޢy%xk`|z[1YduU2YKdsZ Y9#{U!b2aTo 0N3ӧJYfk9lm{Vd.}j6En 3?J2(ѼN'x,GzWD[̎/y-;DG\zȅVK Q] "dYº$7m< 1LÓ;U_Ep!-uI`y隹n#-;sA׊^Ӷ& ~u{UrI @++*^8$ߧr2(i|*3zLgz3fC!9BVEy+ d!6U3ӨniR$fʒhsFH0h0X?6)8}*t1H_RaaQ "o6iPeb<}j? #+sSxc0i{#ڐٝ'PCG=UwC{mQUuB3c8"{؞]i_ݰI:vw*7Mr*J$<o%vI5@2$lJĐ=OF\0F[D R7d svU)va<ʹc98~9$i:ϧc7Rv;TBđr~wX'^qp& žfKn?׹XUWˑk/@Gp?$RfE!D;2w)rghMʄ~,n!lMy989 dI8V b.`H9VH1_;dOUi|mrª ?Z1g2r#ieBLe>gV@Zrql@0Ġg0#5A0]*{Vۣ.T9b{}i*cvsS5jŞE (H%7$Ҝ QI\y퓸O_2P1ث92Ds4 9Ӥ2<<;AeM!_lȋt6рHǧNUFdtuHbp<ۥj=HݎNI Tr{GL֑ϰgHHd{ycowT_$0~=9<.;pۃڜi=[ˑҰVll:z3 TgȒĮA%}xfJy6a8u*y.gwU̸(n?WO1? =;e*G%J5wGݙneo`c<~UJ,6c|Sqr)[M?ʳ̍v8ݎקX%@qffnC $ƾ]Q)DXc\Ɇq+,c!%*ׯJR$-k˒T{qTHE̅Hd(p`DVUdYgda%6Z3kաBRǙXydeqǷo֨]6,e„8o40WtK.ɐ~?zgsuAa]]B]n)FXv_MٞjՋ'V0b$o)cud'}~#l"] 7tTXH/ˑzT+3 ;lHUvF ßӭglV5-e8Z㋄\Fm$FG[V\N2u;"5&(獭 iIs3&e1@8So>O1ye{~"caсUj e:,9╣HF$Ktn68 `msq_Yf)fV(\r[nV^2$a?jRѦvw Ԋ&23^zoADK 튣M B7ݪ9<זj$;o&f3=F1_Ų]cz m*ߐkHQ$>SyBӼHD  ܞS (az⼌v 飻h\gj܅6nkVHжN6zn2sӊʴ}vֆ>a=L {Y%d -_I`ۡO׹moݔCOlr 6|OZYeC(d#*/9W7߰nX^?T-g! ۞=U1f%'9g?hX$ ]H$8N{f)Ծ̗+NA>j?L`taV%;tD27dz~n$mU0HǡkLFVidEX@j6#nD3HGzSx 2I~m#2zן|[)f.$TD$!S[Vyd΅\fp=ٯ@pB=qj:ܣ Ӟ >|ԴMAeAw\bVӼ/ nH'uKY%d8rzp̎KhW2H6^8ňz҉v#S W6tPȱn@ے;~캎w'5I]̌GPt V-(JE WBUSM۞mv~czƛo/'⩶0HXlǽy)*4l#@G2sZؼ3YH[[ºJS2 cJ,4V(~ldx\ҁ9tƲ>\c種;UdvBF'5kcYD6}>,ca6feQ8Nfu $?uV3¦W!<&፧N*]*o1lJzWa[ c#rtvB`A"&6NnG+-6c5f(c1*?6w u[)*/;6rGPqW B Ԃ9VȐ Ɯ(8h UoE%}N{{m(jyg}KZyn\s Ty;x[+ǧіcoCڵn̍sWJ6ȕRSǗ HALNBC6ҨGnԒdVNJv~]޿vӑ'g&3d1*0ֽ[t>\sUmKpda01דgрfc[Nݠ Orb[,%aǿ_֣,&tHb۸~x'wKU11?6y=A)VYI82nh#7B]am``d{V3FLy aV% hUe:zVձg W$gu9aătHOQ2<(hMN+*a*A~\!CqZmlpJ=*ʱ% (G=޵hK'pa,Wa 9TِLAy842GgT6zgu0|#n0:2<.+͙ P%7?*wJBQcI9ϧ|\Lr~D4E @gm nZD[!!sa~uFY'ܑ8vS*|p~Q߭g]Jy)߂A^+HZܵ=T|0>⠕1uBdl#XSf7qwT <Wr';AP^߭lW*. \€q+,;sRGu3N%-ʣ?f Ca8ȔgZfy+,K)}2M\cqr"ʱBu%uB ~R3{v1xϨXGiB . xB">6*9"{p$L<?jgn@=۫2VA$s޻\i$Z]TQ}g!cwOBO@872mao54$+$HFȮn$l n$0kr=yH #q }7IlOFH8__± Qey ulN{Ӧ+?uM y# N}w("W >rGN;R[\.Qӎf;!ˈ ɍA'+jάУ2F~ڳfG` )9^t:2WN$Zɹ)].8,}Z<3DjO\W/;v ⹧t3~eERg O:~RCE!xS6FxQZUwSʃqP_G.>IqFg#v*dSHKRSw"o%P˔p}p3ٕٙ-+N?qûY%-vHB&v1tǥa-SgYRm( R|Sxhݮgde6q3̕9[ynOz33Ԓjj'ڜeky\ Ҝt5,aofE1("*2@'*{Ăܔ8gTt GH/n} ?3JdޠJŠ `[hXN wh@GPyBR#>>*S&ss٨&tY F$AM(C,6n?n^O spFFyRC$#"8'X*-!cmNR,J;tXG;!1Qcf th"L|,Cs3ګ$+ G^=03OJDVthN8ֳfyPfv9 #*K2C %}1UhpbX3^?VrNFDR,ðeXw`A 'w尀 $38ұ%$9fx#gSqԨI$\}pG\fg% ɏpP;y*WYeSYWR͔_C\vq^ 8*U+Юe#j21C JڹFv1%#qڹy3yr A^ '_؏ϔGF'(? ˖7O$.HbqU;+ݫyX5LHBLsa 5WY ֱnfV|bJX1#4Hq3CdoqXqI`8v Î⾇ < F.91EB"勓:=yYKL[Z1IߝpwznYcP#_YZeCjI2Ajn aEq+g6]F\ʠ=3J7I澻,JgSS#H`_+P2Be!eRwdt59]V2pO]]M%+3{& $3h'#`3A*#`sth20\HJsߏjՖO!>Ts}4#,dx1TA11{YQx@Aq5fRLNI N<(|XFx`R~5Z|dSnb!'tcҲMi+͉^ _w-=2:u.\M iwH@FHܚ)OIR(H~U=2yU(YAU$s=д6S*6UNV\heF_݂y&"-̙}]~p^xluYJ?B;qM-b;{ɥXTG^;~|]qnTԹEn?mԖsrc Oۃ^SA9#u}k~Z. n}']]b{6>`\'r;kBK5i#%־ƻd." r5_u+i-(ʑt>ZrR8iWqw=ټm䝧U4#QcڹT|9UëȯWs_bhkc2gFlG|oVHrNnjV\WQ$9iBQs#rA<בR\м AY\(a"%y;[5(ɲYv!O9Upp03xل~AIV#<}MAjZFqԞM=.m~Ź#!8pAHŊl=N31p_aSQОsF lc$֐SU%r;O8G+cʣ W$y~֨:uszBb$re8=] YDw$ =3V42 vKUH"18Yod(5. HHgN838*SkrpRC q֟4Gh=K$ˎAfAPi ps`Q`3 2ZaY %=zqU' (cc֪BJrO9fgMȅVҷ NK\ާJOompm=3 GxrTc#,b8l}hsgjheyʄ#BkID櫤;wzc>z2>`XCqҹ'Hϗ$*=xJoq2ҜH[.#ۥs7hR\ YzEPlfN=1X38 !n\MDo7VUfIb0B.ߚ#C.|aXylv{asҁK3'[wc3фxH̆fXNJЂ2zvX'>SBQpr25V v?Z3XWmV1$ pzo p~az5mdu5v~qy+ #Š%FxqSB;`,r L[jYܫς1ӊ҂v@%ytAcW"yKNr+2 n4uYJǏ?ZڊZprt0F${f;FiZ _99$LfvJ.-dܼ1xOJtO6#'Uݸ' 1j[=4Qx+:{ֱm$ GPyVDSNۈg2}HO2߷[*+#U'ވz3զiJP圦8=iAo;8Ij9HXyCFO_+4^PJ~_rO ,hxc}gr@p΄ c_MJSpL]9(ܶ\ׂ# {Inu!_ ͆w<vՏ5"t/ wo28NzcҺbdCB(g]l\Ȅ R*YDl6,fdR[rf8g>$*[[]:}kE6cQ5GU*H^0?_֖FAN"!,Ϲb{Wd%я*4DYQwc-ՏZ$S"GLEđp=sYQΠ,os'[?(\C_y%GV?V6nP~W4WKS$",{ۡM$*+@Pb_,6d9Hk:b7=rGn}xkmHLfL 1 FnEvwVy,0xbꈑVDg~j7EIdRgWhw+-HO@pq>)!ibcj̢ #ܸ}q޳% DҬ`mOP{`VƪߖQ"0)Fjwۚiەl(#۾*J,ȡrc|UEadn3;"oA;]Y#B y5pDK%D6C1L[[|;dTf@r @.>EhRFTn>d;g Nz5.qrh]mXq!Q\OOζFэzG,$Pʧi=t눆,,w`we뎽*"H;˾󀣞r*`c |a<~n]#((c1pq*ʜI$qE ԱYv`YOQy pۋdsk ᱏuXc|/cڳd 9tR 6#%r4XXc>:tYWga7:w⹧FU—1!$ ,G=h1v~Vv*. y]0q~UHlD4l dNe@#󥾕FIW |P}FɜԮY қ2R8y#h5 av<؏˞`Fڲ$JJ̎7EonzVI>-ۀ#Tsm!As79X-8Aӱ*Ky@ןR^E3,$p$u+4y[weG|.Vи@'IPtꬋ[iJ8=jD$ znxw2Hiv!A@#8J09"MB]0y*L=*Ḏ_\t!vNFݟz22)TOCұnv r@J~~5\!bCXncC'"98שg#RfO,=*;Y.+U=+bTbE|\gŤK,Di:+@8؊9yՔv ΢RH 636tU˰|uX+1U͍ \}&K2Mʹ$n/=RGdwIiW3&d6>qj&dQ>ؚM͹;W`pI4~pL[G+ aj,ެq%ڦ;%iim7n̓OJ̖%Cq '}M(>#{!D졞G`r6a֨$40}k:fݝ%I8RI@!g3ќQ|n6Njj'2t2 ϧ~3YIl< }GjsLҥ*!9l`G8J)|(*pȌ!rcrqG1ͣbtGNY 6Ň9j*S$+9>si ,GzM>師k*=_McvXv9P&AkRx/r3=׺ ֜ [j9 Ýv_'9Ȭn݆CW=i,1j9X6^OZtH"ٚsDn 8T3xɢyBȠ;<{늊vIo ])\ym${Ky+8HNf 2ӧ]컑݌qA*z^}HC=AщӌvǭwӧͩS<ZvY[ !y湿-$>J8-_k?2/9C)>ùsw |w~f곅 &}fG<+.-<ץ}6e!EVŒ}OMg_ @tx8# 4| j~˥M 4`K$V1'Ϸ.6Oq pAk N"'8\z0٭VRGz#PJ|~{@Ax1JKDDe Ex=ʳUQ-9W߀\T2FfMʹ ۭi)CсGy'<~5{:9egCFy\[ dc=U\Y!Xgރ5vV;v"ȣ??8?InXC NTZ# @v}] K Ju o*V'9D59b̧jwd8sK5`?T n<t $ >6責F^=`YLiust#,x6:?h%[ R۹54KP{TMi76Žf@??Y4ndZX$l7 v?F̒li(s1׎¶>ʥ]7ș6}ִR-yw =?JĶI X3БҶml~pTv^G,ћD<v5rZ?L Rtk s%HaLvy9U߶B"gCAJP"'p s?uEY$BR5b*9ǯ>er&eDC I ?>VhBfEI QHm ҬČ.L F]#r2-`i%Tv@ϗ;~ӔI=sV+#A1b_8{Jm,,O'Rlܙ- k`;ͳ?/^LTѻ(;J *)þz1_ޜc/~9J݈21jylIbY&9ZUw͟NjIHK8{Me(lzVC v[FRp *OrES+cib O)M 0m1Ӌ##'ϖ8uUS#خ݈MܐA[I4!vmŽYd22H'׏v6$a)9㷽o\jKv  UI#jTFpUgyJ8#NʫV RY#p ȭ-A$YqrG ~ZylKiSLgThBZ&MѕbF8#CF3!H;A{z/j.w6$!99nZƆ-+IQx'@#BIoYdHdΏ!v O[ʍ;y p8 6cqޠ̖6}z 7-Qa#-NgIr$c ~}]  A,!^(D"+ɻOk*{ȑĒI&'tMo-B46:g뮚uVbcnxgRŭ# )u^8c[2ЍO:#ėR;sPS;A#?q~uNHwG$E $u$d0sJ]Qf̢-YC1׎ޜT-(]yl̍ TnkB9H#06$2# Ƃ)h!W~Ϊ;F"R#fbp`GYFU #a!zYDS1 z~\~cy<{B Ueۙ m-GingrsDX,h`fISXJPr;֨]MüU9QTGL]ӧ v ӥRmx9.CػЌϹXyzq\.jŶGn,&^9!S1Ӝ1H'HCy&Y䜜{Wu{u)uB?'aX{'v| rwpK$$.0vEGgq VʹN4۾ѶvC[StłhFNA?:eJ)󤙉ylڽ5+#5nneH_eGjcL0+'eUBT?I*ƬDm 1qSױLT el&di^VKrH#R4jU;9ۧcI1 HdԲ\]*$wP M87y+X֊7q -qUN ґFpl9Bd/#\޷01dx@;1{@ypȪDm1T3<d(0I[|[3f:O&DP_,v |P=q}*+#]RDtsc[Fa͈= Lk+TE.ƊX2:=zU% Y] 3>oZb%I }VٖՒ5\ S.?$\[Bq'vCG_qUdo'i@=-32YH|-qN? Ė坡y(xR6λ#0edp2%q rK<#4~qYһ6R8s;zXo6+HN ckxj4T]:8}w.qYz,ыB5Qb7ۜWfڂS+`򧧯j`Ei`t\>q2)u0U=ed?4('8>y+(u-z2"u2o `>^zDLTi(\Nz*Idm|<#ʫDĜg9OZ^2>qluY1<̶6wq *GhnV3q"+$cb1+4\n /%[2Eh Nа=OLOEq3OhB ̕;9C2ÃS 8fX(=W|{6䁆۽TW!ܗa})shŕGJ=y ɿr[ʍ^03S<#nf A=sM1I3;) UfhUY>b3Jm~0s䒾ޢ \;nXOSFcRcX׎h@_Y!f0N4- 0;wN=*8dޯeܫ}To##$9JhI%\>T#Sk `\I$a6߳,1?Zp;93I01o.vg7 yL3>BaG'%r!op,=8ɬ Io,ARTcr2&  OU{ydx㉜)_n=HV&_&&E]zn)g$2$r99I(ؚ̹)b0jEGb[&[1jد?}6`{e1l9N>9ֱ(H%ʮV n }+:Q7хRr;ڝhb3^IE1As`մ)'Ы,jyrsxޤȞfY 1T )+\$` :$txYtmIϷ5RR(O0_8<0YDxgQbSǽlO3TT(6qZϼ41Bˀ3.3kc4#Php"~_^34Lօr>U9v@&)"v(fC:Kd:WvݣALuT ƒN\$Konv}FR,l@$51,̲A(>*4Beh*3x*XcRH }â #ÿr3|)ý|^e b"1Z͔-.C2|]CPcB n88rS3 nG$ҽ|6x-L"Ť#j3ϿSQ3$hA?NWWh̋9%?/s7 D#,A^ cb+Et35DűK׀Ȳ,*K˶$jy Ϭ$mNBk`<V*ś "C#.摲I \co֒I±Gq3 Ic(^+0;)w PrmTcZ5bH K:I?Zau Qn 1\acr7eF } !I~G%PQpbR @P׷F>j[sq2>L:k[G#{zU37A@{;'ʩ2FHݴ;לjv8jّ(2?ɪщ#E|jVʐ4P'|:U4Vn,K>jrT|e5r%O\vRybycp@}x',l:UHbB]t>35ĀG1~5%؂Ap{vo.Xc\w'Ӛ;$em'uJӶ=)yeD/+7Aҭ[6Mlz{k.ӥ0y20HFKwp;Hq/+ߝgʑ@0K)Q$޹+FIGlC<;('UıʳeV''AiآA o󃓷'ӵN=2ly" )<+։|eUK13Nr"diZ+y >Z)#؟~*Yp? fynDxǗ!^%=qUM=p|1HJE%r%S2|ڛp,*SLnf 4gh5]e{{X.ÌrP~3$5Nu,I{y 8{=) #k(d ?:3%o0n1G%1$n6sJj}"C[gt0d0ȓZm٥5qsGҒPғ{\w9! N}*m6KG)׏"5_&b8$ EX] ?Jwۡ*B K9.N1ǯf U{}P k>y}j(&v m׸B!Hϗ%H^+R6xd\[8o\ܞhҩgX*zV|j#3v$t.!ydH*ݞQk 01^\JLfZYx{\C.C~ud| <U,"F6/kXQd`:G_åk[3*rXp=+Y@n`GZֲkmxD|ƤJMm^Ii!^ܒ9&,H*?J[u!3 bE.";8W尔$mSSIJewU>+0܁S~ jH HÙFx<Ѩ+ ,+4sTSa@9uԲр[qcvg%#0>8jab9XfbU֢UFbnA(\I=}?UTLQ ymcqПbZLF񢞙<>4ܡ}jjۆ,B B,s6: =1ҩ%i #=>qjΊp۲Gr9ySb좠@^?dY$.DVM6ӥnKqE$(Uنlmc;HYWpnz X.-Ͻf3*\E&N>>}Ҽ, ;jf=o# Н` 9dTwn=2.cL.NGj_a8_,rIn&~GD0w<'=z~U2+xJT:u.L>Q0?jO(C"N&Vv 8Nmy-q`>CSkR }#Fpy?=ܣdRR nb_QF9M0y~RrzJخ"Mۃ3_Ql`DldI9hL[X2T嚘n$\p@'5\у{v[JUKy`~h5]GrC,*O$cǧqPݷK"4s.TAzUY+̨ z[R])"~ :VїcEMmnL!ʢ|׀qR)̋;g ztke\6&!2JF`up|=9]TU/p̭WU)NAɪ_k0g t{U&*ArÌc"a.2 >ZRy5ϗ..V)H<_񬹧2BcbrL}?/gOx$7݂ G\v^j3|DR'ˀS^YQV9'hrFxXo"bNIoAڻ!.{3]f3#tzK$qyo.sך[LqپG?aq ` x>NJ^̆IwK"1d 5Vvh;yFvӥS}̯R+&͇T9r'T "n RDHyG#Ytb.AJ > 24;~l~TKyr"oϙpI^OΧ2O G }A9bdotFCm$sA|!3C - 1viʳԐ*RHY#$qǵYuc¹,vv=<9Q{hIy$({үh0OGv!IKu!vh-9>Z\r:Kp$1h!:~韭PV12ۜynNq֬O%2#ngX{ެ 99H|'Ţ/d&uh 8ߝ*dXٞ nb27b Pߵ“=@p 8RLsN}z~wg<91$RL<!HRV2Jr+iCqz2l_ ힸF@ܕـ?*9!aάW'=?[ n:d+9B>2*H>_ƳS9X_\:O[cF,dsGBa#\NՄՀ9yUy[a 郚$ lVRew*1"O?S R*ÓJqksXԱ>eܝrÜUxۯ砭:/nFG̜>YF  2ZtaHͲhH>իg0|zch4L/9Ud-%D:r+v)ɘ*P1;QHPouR +p: YDXmqjITNO!I)U}H5X>w#k)o=-O8?_ҭ8+$$eh r?_f[&wnǽvSw9PY`Yڿ7Q~XfM灂po^ح;3"[H=K"QzN8ZF<256R>q*9tF$@[:my/ 'һ9jU&V[;ɹ2w0 V5s.EuP.uESuȞ$I!wDgϧj0-ZFYB#7nr;jn/1PAG92-DxB<34'UCN1ǽz(=|@_J[ȋv'Y;) : N;|Vvɹ0[+{!vT劲{\-[{~dUFWFΨc2 ooni5;DF؞XS\Ѿy Z\9d2f,ڛp+sQP\NB8#w~A~MB;5X R[x+k+]-nIٟ=8kt#hf_y?Ba*GZUyo^ȈӢdqqWX*l^|4rbw vgqS%1*@%`N3*Ă&,ڽ)%,M3#DrKhM:H3^:*+[8-1L:~@eAq{\JVH%ΙpFOJx4M'w ֽTW׏֩˾Kp"tӊtg/YCP8$2"gAW›}nyp|lKg*dg9^zdW-g:IlJݮO!T/@yKpk*5+OoUIXHr|qOU*IW9\`m#T(ꐜnG+뜌YLh;wƬQB8?>˕@H ך=KbA4l[ST%\Ƞ{uϠDm`w1`z6vl\H`2+jQ(᭮&0G-󴎛qӯCSakcJ \*XRy$t8hg$)1!wjԵ3wc^T|S캅 Yq?;gkOՕ~RGRn1ްfhʥdqFrF=kPa˺w6 BHbѝ pr=>.oBѮaQciPyǷ'N |\b!MsMUJ*nz WE$}0>~[H,@Hg_j:D<`?9 mV^<ۈa3Ÿ2(~eSԫ$,= r x֎!t>bZsn8CW''52ɋ) @=}(V19r9־ KB5lG$?$=\c]Κ Korڹ8Q2=+\G\0s};c!Q?yG W3-߷apnR*9#Vl2Ck&*$w)cǗY<UFe6rqZƠr [BFleNVI%cchy=`COm빐w?ʵRFS;D6mۥ> `O6'Z,$aNI]I,@\׭:9en qtc+# g,0|-m#n=9BKxITFY%1+eU'(o.kvG` 8Ȭ. 1UV p>^-H[h)RM#Dn=Oq[BFG34m8#&us0;9֪K,I<s+/*JeT{\&cBqcjr;/9˷fX号94l7?uSB98FHA8ҰURIKۂˀr=t@W1Ig랃zV-C(`Âw]PFjS->4n닅mU?S&0ͱrBgk>i09Rcnz玿Z`su!D?F1'ۊ9wJHG#ֳXXxn՘ rr\+%ŢHq;UB}n%F91=Eof'9cOR*IJ[LNGV-I%?OF ܌SdI gv>:θY'3!g ~SʿpMoΉq."D-iXӓڮ*F䌎S@}֧X3쭗$wBeuആ26qVkN$Z1&Rރ3m忓u#pN:NO-G3NAssL.J]#@8jgf$, 6=NsRbIX}?`i X.=uR{^1#<@:ҮrG a'cIj-c+'\#ҕͬK"pa!zgڳdA#8K6 .=:zFbB(9}YbP G'{9G|X~*(Tuïu8 gy)h*s2L40OҠ:C"Ң$)TXF9|' )*G5A&\m@Oz>LbR,Alu?$ѩ¾w {f n-o^>Ts>rUf_-*;嘠$DO >b0w0FSGYDX 7pt8)r)8(Hyb8:P!I^H)\{J\IeS::#E,NrLI=EU10zV4?(Ýs ՠ,WqP Lq;`]sS&-#)oۀ CEWO9%%uڒ)$D,"6N78S%LPA#o|ry)Yx8lj%)b<+EL2J enqX*َUBc9ZZFh9*Ȭnӆ2JQW3qַV4C,m A>rI ),W2O r+;To/&lG t LL?eڼ`H338py$,&Yll|\xkeiHcUޙf'+YR{X0Gm7-ܑUie9L֘"'n>C{X2q Rgx9P?JDS&ljBn3gɒY$D}"mr>~5v0|_,}z秞2ʳϥz$ꄗHQ)Y؜q\b LscYCrYnƌ#{40痈rsD*UrPr3\\]P̊YK.ךs"FKo#tJ$I$^;G'?{xL%Z,. MnJd)vMU"xcץ7VɞO2%"P7`Xr۶9,Ҫ-JPȭ`gNDbcHl =ҴHJ. G9ޝ!'"lt<֑<,q-[v+#ZY6_* d'ֺ6h͐mr8"7e~+6iNd !EOsd;HOjˑ@NcvqҴ)"yΉ ܔ0i}F?j;%_3B.̌t#u2|1C$&?xǩJܷ(v~RG=ދh~r  ;#8yS3.`?C)U bē㊸2Gɴ(qzmW4rAr9Y7dgv itoOmC^T2_.EY-׀s桎4"]8)y5쑴qYݍ9f;r?.o$ ]]yYn1׭>+Yl텴p.G%>':!EǗsRvd;z*b S*!h0Hگ\\E$d>_Lzn7704cTVR ǯZUkxQX8kͨ&'{kaHb%̧v:sk|-ek9ag FryzbPZI+pn Isq^hek*C7Xb+־F: m%G.! ۀ?khP3o1YG#u"$1%\0=:sSZj i3#Ҽ {j$K!{֠-dm_$0o00n׶?A*# ܃ӿ# L Hj^3<V1#pT};WB:"rqfkÚb!g┌w>:½[Oz%r+U#FڈyRH<JI\Xׁұ FkaK9pXǭ|Ϯtח#jvLg^͕ èoQhܐf^jI夗 ?7>[W?T޿u ,㧭rb$}]v}R<@JJ5VI~L-2:U][W[}%c # s\V!i&]br1k.,?t_7EE-^ɺIYbaI$v^mwI؈|㌹\Qu2rҙPDe?+gVaڰ[ 0+^!ݳjTR` f !dZ[D 2>(Kc=y?5 Q 3[6fe;T&Nzp;}k˩VƮ:6@QI[/)bT GF 6NzqҳleX>eurƐ6UkipB?{:9!1wHJ,OoӥS@|!^X9zcNY.lEUr+<b+u#p瞟LJWu KNO_֒&VThu^f;8Xw3{1ۘ"9e(#xaL9cu.T\~@PJ1 mŀg21 na;$q߅JJ\>lw?J[j'u>2*yehpqG )1/r1RMO' ET'ϐěqӧZ]Gn$m<ϭTd/fkb[˜qI+L#%f2=2=?ϭ1k3ro\*<:TgN#H",@=p;P.#,}* 73(Pd)}EH$IʹGAߏƭ2lk [oW_@~>4E!DBqY^(M!K?Wֺ+w2]MB9utM(Rg?9=qUe{Ռ2*NϕOGN%w!?35N[b](+8ҭݤ!F*K6Vll4.$S<*x.%rN,m#/$QȀnqxJiYP,`0@UҼq}eJtic1Bdڜ_ַS!oV<܂ c#iV(70N^;cҢTiVCIsOVh$L8#cPRBYV(B?/+kc8UWf0ț /^0X)ǾqZG){ 38ݹ=9*`͹FrrƐ" ( Jd݈ n7'pU{O{b$@ӥC;]$gMONAS#@%G@z[U`Cx}cM3;: eOsLEmʶEnrN=va򨤄F9,$^Ih(V5PG) QY M-[I-EIg-}kS@uO c.ު摍mIU 6rWh5HX@;Gg s2@8ё׽:#3;VN= =(e43$hvBVMGLTQM1)ڳ)d?usRNι=R`$BG̒ 9߹Mi@ϖ5ʻs<˱Ubێ0ڪCXI"R1g8d2ɑiRT ev΂N8%[ rst Q-RtWO1?ZLvn6|޹~QRywĊw7W?zUɣy#edD>JϛaB~ث~|{vϭtƙ)+& : KqB:0翭F?bHJqU_a>ӜЁ 3U w+y,~7sj2"Od'wr{XU7'>Ơ SG`p@_2)Fʓfi˜?0\$,_.oQϰ隥 bY'@?/qެKYOOTGKtPt?i5{9n9)3JUV`;8'7rvH,,#SL@y8̪;E,/ );$^+$e6ؙ $psV=VaP"_.8H>JL?G` <ڧ9^sAa^{&2pnj+$s`6}@[ ±/ .yeh`@@QFFTG1:"#P8RF@V FI#>-t-HQ̀y^Úr(21q=*PAu17ЂG?֫Nw"@5QkcTr>V󤔅?JH.VIY2|ntfI2=v  Xgd3Y|qYCA%"]_>$q1e#J3v)fXEb[{,,8^ҵnDbO&XG³--)LqnzaC22U@ѐ(kEf +zUm)+.2cEKjTiw[nUIsR/`?ۉހ2d+͜{Hض6.;z5b]s,p4kۜ/<4[y _3mݣP'Uu,G\W,跡 C[2N$ք=ytcV2|\j*؟ u[#s$pǍێW=IWӮ~̖y* E%Hxt̑~$@|T#Cy#uҒ*}f1^n&. M6 +c1J7H c嫷.rwx=W9O=äDۡJkqnmll@\:)mMiq!$ǒ1dWO%Dv;G\ utob_PגKи(w85ק=sudE'<\]n-p,A!GX'6}Oָs O;2[OjGiɭ#a_h2|zi A/$flv6Á^ 13pk˯R1_^H| yN [N\7u{ Pǎ5. ۭ_dڹ]KPO14*Fv#KY~?'TKRQș¿OCy\eS4gFy\{6FY> x=uWʉLdi{{_y.f~5ʙip۴kqkEnxȩ@:r~c!2nN0}WAb(2Ĝ:W֫gcJ@h7ır{fT\6p7' kY!mۀd+I],?&3}N=+MPO$86n6+$1P3;q F$Mm,j+G^+'>|nұ )|'ҮG;K*R66#=OT[c)}#P܋rn'HtJ}E#6vĨdpݑ=ZM+#lnlڳQΒO-8&B > l6®^T_%rI Q X,yf2>c3FA.sgNTsDp1zqZ ,)O-F|ͷq׭l tNCWn $s>a3bġ9##_˺ 뷯'v-Y"Nn0N匿gyjR'gROG$gE@HV2xRraqI?*΍  Jį|Ժt۟?,cm 7-[CmTm;0<~²ngkUv7nfQ-mj1Iɼ s׎׵ Bvg ϥX4d@G  $K;Ǒj ~[p1?n+hU}@۷Vt2m7N9G_y߹yQ}n2Qw̉D6<~Ho#5"| Ӓ_QY9 sYv圔oʣq[8g\Hc_oS֮,NVB.9׷ΔDv){Ht':BC& 8wd $Fp>!ñdڻ9.E)E|q'4IIGӭWycbd%̘MsZ)2knζ_p -oÊۺ#fٿ=N X߈;vD'T zdթ I%GH@s=xtO"QY{T$ۊt(ė%aaM\^JʁGeA'jڣ2<[BCښư(w:* P##f!س61_iݼI±F6  ,&e®2GrJ4 nVEVS7A(!xp`W}*'W:sL uD~_qΪ4iUl~ֱ#v[vp;H'Y! 0F;`UJ#Fʀ[quwqaXOZ'.HLlW8 $SWns*Ig{q L+zd>TWP($da޶˱|UYE!q;U gS{`TM:FSE][%8zB=ۄ2Aǿ5 T_Rw̲62V\G,LUdeȮ`xр~rO$*yl22*<{~ZhM:hU~#۵?~YQOƳndq%>6"]GIUel;nuF]TCAHefl<չ#iZy#c^x%NIEE)h̠ q?32qkrԒѓ Oe=vq~>"1]8\4(ˋd1PA*?$ *p>A4w Q=KB~5$qu&`N2}?ƪJ&"gT`r8:EAjZV%T݀s}k&yvwW|9ѴJ6[67GCT.$c+OJaS'Y°ҡH/0zbnTV  7I1s:V`uH䶁9ؙcd9q)讬Fbl|,'m4m!UgGF9YIqUcO"na޵F{ x(W?ZW,Q6V&$(H$JCz7XO>c$K8Rr%;y:%^p aF„ɛ #F1q_^{UhtVPr*S%Gp{RD&h"y,ϯS?# Qv24%ca&ђT5_LC,j89+ӣ&a8}j{iyiL|$cZdvց5IYpHan ې{w"HvٷqJ5bCkz?(333{ˆKNUahAg2b!G `> Hǧs=XaO6Dt;x',OѢҜDZ0pÒq? )sYׂ;`CLG,O& VR;~t0ԕt?2y֧a潹fP@#e=k_UeI+`&K" H8s^A"ƲmW%Y OZ+ĹڪGBp} S]XʒZN$c6V9HhNy>˻ݬj*gaߏ]MpvmYX~5 @Jy@Ccn#Y$dfQr}@ъMlMV0^kB/plI~Hb=k%-l3"8~l5gr16{ͯ\P[[]Kop\ֺX@h$Y5[7(!9S9Y yP:qY,#<ϥyU%sa3P`HZ'R~HO?y~VSJo ;ߗlQ$Rė$:׊x_͂'d2C~5RI+u`n*kzŤqܪ{u^Mr-9Dfo0cc޹OSX`_ {rzW=>;.Ҥׅ.]-JǢI H89'ʬ[k_gSm&Wjqڼz}!H#kc(}3Ҷo"fcq7fC@ONzҿ*3՞?S2tU+(d(ĐZ0v}&Tv2$,Ć$s۵sKDE9aHҡd$Q&gJZ`1ՂYy"Gw7')G d- FF3`t`L1#mKO p ioB=+*v7.!)w2=Z$^H~=j_apsPF/ֵ f`-gzڼOP4b$ ?(S(,xrxlsctLDcEy.+'޳l (6s$.dpJq"dgTǁaa+4h"Lt X؂|ܢX9RL*LpJx\5I_gR=?³Dyp<`Z;rWv5 Ycı' )-vaG~8sT1Ow BsҤ[p"@h{l.0$ݣ']!HN7*#KF]+|/)&`ۏ\LU[A(UhLH;qϭUrpo,L8i\PKg9>~Tί%0pF2Xnr֌ʑFƳy`'ҳK$Fy9$JoqanTHڀ#fg0;'FTPGw#!7*'"V?.1ץ[@W>R#1zYHUA'9ѹAЖS֠XB4Pb'~Ǟ2;uܲ&B6Y(#YbC=U] YBv',8 Kq$dj$up9Qcc[)~eR #ȿgː1#h+u(0M{U|Gn}=iH0@LUf!PWv6uV0,2Jens~Ulb | 9'2gёc5(0= Fp@'~uΫ{G3 ӎTu20$|sܑӽ`|"(}+B /˖ƴ'f2}7<= 0pGEl7%vTgԏ~ƭ9|2+p~ȧsjj0yLUɊ8uʓ֢a&&7Leǯҫ`$es܎15rJXN'9lxI:ZҕnKW,RG~kGm$!֊]hg"pV<1<;U s#Uifxf#! b|#DG+Oh'B)$S (=Nr2!AV9U9[pO=)R),7 9 s*#Yx>a+2F;qjÇUxZ&V*9 y}x-̸D)~lcE{)#9PUPDiϵ l`>.Wtp${اy 4l#sT`򣌴1 gbFsMI"H|/v0|vW >Y'bTۙ%$u|r0hNqk ccI#, o_R+ *AϞ& ΄ oH'$?  +g׷_tdtyEg #`G[9 LaE.7GW4ql#S"*<Gjː[fvG+H˱.b3nerz{ԍyX?0gֲE66v*߾?dG@:VA:} {::rox?+:ΑTqRFܚ՚8@|Rˁ8T@׭wQ^͒}aHVEäg#cUnU%͑efYt^\ n:Y4[Rюen$%F0}^m~_Ȕ07't-d pFG|Xˆi"|nsV%VgR?<7?Uksrr0Ĵi^Id/7@5jX,!Pyi8r1^|f_9B@ sҮ2`P9ajh.cDH>`:n0q9=?Ź&(]Jۋ2*qRи-M_0皭4RD  I#$R\J_z$pHLOSäuAy]#9~ÊI & /Rw>ӥX˄G 08|?g*1hw|AY3 )H1쬲̪#a r` eJHnCy6=zjJtm #7.7cD.c򌑃 |ǕpHȽ(H\|ªC{|% ξx9Q4埝{ȲvvFLYGUH(ߏX],FBdI]wbqi K9wFd۸MajW$,;JeE,Фǘ9 FF&I/8\!xe27[d@cszKKJ 2;OkHdyHHgI&DqA}+pXK!㒋ԊSPH*+.7VzOq7ڄ%KG$qJ# «:=H-zx\/' +' $9WavI,'fOwp:GM#ձJÚA'Vxԯ=ҢyDH+0+-"8GL($tn6j־(͔3>\gV3?y[xfSpӸ NG4 6Ylw{t3V2H%9@,у" (#thӂG+늬DTTSC Y}wVs95*ZR}dϽSTq0?Y+yqA=^8mƠ@qYtQwFen .%Cx9SK$0@w?Z$XP:$[#ITk'& ޜd?JYih(3ҪJZHmɶTVeGVV`^HDC^:i$ d,pzrx44200iu9y7 neF{S yZ7ǧQS!+L$+pyyw ssߎ,"Dlr~#2+u]. xp?uf(q+|=,YݙԀMǵG!gh@#$/;ҝMlĶ6gs5\}ޭu^hupwQkrҾLJʳ1 c2=fb]֥Rq9#cҭI,J!uGyԔ\cj  q @ġ~\gZr*' W2du5P79pdӚڜvef옶1 ۏڵݮD`F7/G55g<nEb::mގ>Qںo-vEe_c:ԍlGqROkcɋ^VGYWP3CֵQ$KB\zW:5TReTpqqp[BGfso@cm#p~l. Wx9`e9{blFJk89:O"`T1.}0}aAp=`8*OG;1+n'5Qs:$n3CϽUyџ# R,2Cq$¬< .BIKL VkKp7qzV7J sY`ae@@=),v\h=rz@m3 \n"Ooݍ?jD5eRYbrǷGIhͶg'j8g^{3F&YBHT6vbvѫN;NzfAVtCpǚʉ\UQ9WZ0n|e$c46Vf,Q M 1 Hc\}>_Z~%@>"] we9-Զ=IiF+ q#*v'RFwH Nn'$$-!Rzթ(5ѢY!BEW2 XV&=Z],p<y>O*HbZ/7!AǶ{V[c")(gW\98'VveJѲ*4'#v{R/U8?ϭYXgdxs glW\j&8+9G22ʜ8ϱY< ,y~A9*=>W1{XoKە lH0 MI:R2bn})ic"'AHߓR\C4[ݥpC g5w\rGYvx8=G io(ӭT2 r[G21L+YeSi7n'#2j/fZq8I}.?Hk;HY`s NW dqVCV[b9YjyYeFD :*%VPː܃zVh !$ySWึ|y!TrG#4)0bXM- Ep„0U)Y*ٟo,?!Z].ekp,"5?U9_*WgB嵸pVV$89~|ԍu;$0@P=Ҵw;^7Y Rv@dm8+2iXİʕx[3|¡7"4w`#Zs1F."'n©Izc£#!Hq|gֺ %rW^8ͷWȡx|{3!MssLWh ‚1.R ]-/$ _^մB=$%>zRk}~wez'֩ݡya2d:ey89\ '.=+hn̿-D,ȿtn|ӻA #vY1fsfe\esq+2y e9M93?fi=1F 6c⳼ok6nNIdlH>g5I.g"Tt'# 3q8 M/~)$ Fzd^N\DJۮj8.+;<}2G~ht|;v<|o$KK 0w987 {*!<':(#Y`zJ2 ;}&@a޸'>\8lsC 3ry)GxU~:%F pa>2 h0W&f)n~烏\ј[oZMqc劲nQ,&{`g†={M nDiSnaZYBxȷ$~>qt(bzJӳ*g'9YQ$xgS9°{ҫw0bݱI@^ym#DpCdNzߞjȑgN1ӑ:4U$BoTLUSLKb{w1sH$#? ͒h.FB?vs-2Gsp?0`M+\0sW)hw<%c-׮sUX˴nswZȎ Xe?)@XFG?Ś+ ;+.b`u$MӓsG89${}ʫ x^jۺm;+\NO㨬\hu^@Jhc+ʏ3;M#,HmnqT`Zj+fxbJф~\o+:$T xM]43}éUtnG;tP޾, $hŶ#\v[`FF^΋ya2>K+zyIrA+|1_I>Cjj7#pӦ3U{TJ\nu8R9C$si`7mTVbB|=Oc,p^isZTi$9 8`;j(0ice 1 Ԍ>3ZP?Ef`lxIdHوUULk yxA UW#H+#"Sޙ)iC,8}sJJЊVީp4 d'TǹyUdBzw#D'l:V4$yp`=nMRM3jո2w\Y8zsgYK*׽ &WyUx,ӗ[I1خ6%RDĦ @pqdfue 8 :䁑Ʒ'HUD x=g,K[0zt6fRNU YQ+Yn6֛3lk0E=czq;vI,q5\Fe^te8c' xknuI<>Y;dcڲ%+HW1εy%{x&DxIcй'\H—a]!pf5ZVVGq ?g= v)gfWw$ \w٥YWUۚ- 4[OsӁWWݞ8:E$Tla%z 1L%U{+=he6Ǿ1y54m,QΉ _&v# S3֥2ƀF=jX|>SDc?ayClegPT[IФ\랕,Vt۞pzFM?BJ=S.X/+xN; 1ryU]UO={Ӭt4 T##ڢčlπ`cTIZ;. _2H#wB| mm Hc]Aj\3d u7x 0 N]2ߥHl $Gu9ێNj+BnH"qbFt݀O1;>:4 ȌlfT8 RGuDe4~ /)$nK*:f23S"Vww$KYrN8 L%Ǖ#iۚ|g|$:g(&gd"9R󦼁F̠D# q?J[yV^6ka$n9sD^lA+J.Zdyٕfuht =ްnnaV&ݙR[jxV9˶H#w>\,@FO0BT1i"J}EB֮)% p¹Y1s8*o5ݳ1%G^ W;FhpKsN=)2(d&C.L~\0`yng'ƶNqeM?i}DT.эzJ&YŒc#VWk(I]7=ֵLsV9 #[Nϭh,,#'zKBN38sZ ,!P CJ.N65Cu1:)'J H9g9F" N ."|OU 9'ΧFtKeY #cdp-o&@^PF>Kw?1s= Ф+#^rXbp>`}5#SG_;Ue ,ې\eN}{ԉ%9 5qr*K ,hn d?D,*d/$S Yay_zo2sץgeC?y9uf ȑs= ϼ cNU 85_828qk y {)vT>ٮ˕C6QWm)8 ?.Y_7T do3 kD.V]Esܯ|{i.ehZC*ð7vCҡ_3.|oDVBTGnLn,f/uoYY֑R $Gjd,lqe~nG{)dg$U\2Ͻy¥ܟXI'B:9+3WDʘc?O`9e(vqUc&nbO~RJ)la- "e)sr*7YwK+ބgo/ Un $Q$X`Œuǿ1(LYH ~Ur$!%cc8|#ֹqqp .1 לzWe3h3mR?N+(na"M{Th3h4LIr}Ӧk.p ;TRƑj& =2Kf txw8\|WFUDoݣ͸ʌ {󬻩Rd.©b>l{dVx p>kF<Ԩ?<^)k3O ?p=5QHA;7#x* wV/0fn^ 9qVط-EZwVݵ^&DY@}F~l輾apAD@H_ܨf io݂H܌^ #g_;Pc!pz)sx˹FY@8Ͻ:)C:*3v$u~k/7={Wp>BVuc+Pozu!d8:}*ϘH* I+~'_ `UWi\Ǡ?5L%ϕc빛1||=>^71ǵir«"l\erZFHX0I!av>;0tf}ϖ>9{)"g/nW;r=}+Pk7F9} |>7wEt%yXB@fC^r1'rq?0X}+UBۂ?^{ӊG֫qFnB alqdDH倜6K~2L<3rGsQG3̑IWc}QMgtӹ 1~?VYc,$;UǙèr1''Vmմi#=S¤>駄|Rz`;H'|~4G񼑺"ҫC!N[dOʤ#%VqƧ>f֓SqQ9Z8yyw>8 "L5\zLOP捖E"(GM7E,C-&& ݞ+GQ-׶E!V?ܓv9 nŴta5O9#V\0Pb̬B=?ko4WFýy^,l'$u_ȘK~уݜ;(vxuYQ*uNW d]Đ,֣BG {?rŶ}_jk)Lb0(;.Ik3+pNp֭6cs>##;in.s(x}8s()Ry" ey:@p+GAo*Q$F ܓӻ@ Uk4BC8W1M.igxp)msT2 L{YPޠMHUQ΄Z89 1SМcvOOV*$N8U "aYdג1RfR;ǽ8Sn \d+Go1B$+!*ӣyT#'kX>Kt[x[EOבW<qvn28&‹HHIH@jh:uH>x"EaČqӊ%W<!cu#r;Se01sar XnOu#ަkUC# ߽ӐOO¨d\my J1OMxxc$/9?¬IVW$sozXbFu8'1y2 Z]B$ɭTʥS8d$}x4K-W,Ld*[ng ޟZq1KFz=y49t[KXs݌Ӊ&dpVk"=dhi8c$vj׸-(Th2I ɬF%ӆnTq.A;s҈&e.';0v }/gyf{[&Ƥ%U* 20qǧZv#p3r bWhzԅiRboL4Ik0G'uAF֠m Tqz0/=$`ypyNd'_7DΜPI9ֵcKxb1 P==sXUbEV~>vONVm9R' GNeљ3wtK'^M$ sNLD9WHvp cϥ] 巖8y#j̥īqߜ uK`b ON4fY 8?*d$I #.T{2Ϙ˸h=뎤T{Q4.7)0A$ު-L.=ާFE=.ĸ2LrBUvSq$V5GN{Ueb|Sw׶h>J@ zlO-yH!`/o~B&dIsx-9ؤ_x {qryM>_zVI#,dgOOnTJ900<9L% 9<iq 1i==Nv&$i%C PF8:*<T0|rqKy;0`w~C̀ mCqSsB[KeXx?Ҩ$nIrAʞ:%хgI%.wp[}$)R*+Sۉyl$@9o^OlqUbYX B80 =s/.c?ҳ/`\0"(Lv/v958l9Xk {3du]fd@H jM+Dov}vgfq.H|֌l|Znl>pL2:{ .1$}G>ĈGvl)Sсv߻vG\U+ъ9H\1x%ޜT.Ord9Vnmle!uW3a^Iڝ5jI%!hSlJv,W }:1lK00BF)'%e +X9)elmsި.>)Ęگ 6sK i }jB;>~犅&m7,\c#D6$$ 'z6X5ݳ%bUrz 'y$4CfA8?T`XA%ߺa5dkc420'n)Bt m *R@Um[g2 %l4bi$|)V jI#R`8/Rx!E=}Z xB?.2^k9$); CsH d, oc7<{qQ,V|dJqNR#;~4]i7JFѳ_®,[JM<ӂígX83\;A=*5Ʌ8`_~D;^ðUbPY0@N=A rJ c`"wexR[VX I$=+6'mPŖom=U4i]\Gp:*X0ǷLaPl62Ȧ~/*Ii ǵIBWcc_ a9Ɉ|?y<ʘm?ȚYoZ3DUUOz8#ȂPv8CdJȬ=m/8߹7j:v'C$2 \nܕ 1Zah$,r*{![Bo r2zżKu]/&fn p;뚴15.cdOv@9Wx\x#ݞv?&cesWc׵YʮhZwI%̂{፳̹g2{Ukʒ@fϿyB\'ӟze~X}[#(SrϝsmoWiRtf ' 0jIE7"p&©8ZJ !GJY~K ^XD"!6sԞ;'gS$cx xy,ci#l IRYfh,e|f::J69 K͗& ܮϸ+4kt;IQL&@,~^&(`Q1:^kݴոGՕ篷Nbf6X:*3e\*} N## zW=dfge8}kmc%Lq 2L۶fuHB۹<ց3G *I'˲8Tnp |աfA T*d4^<,İQ󧴷Bˍ凹JL-E#I#<nS ?=9K$ ,#73jPԮs`:O,hbQ̲b'Ҁ2Y[hX`dfg#?iyf󘏔BzN|Lee26r;瞹Hd*}>9F\%3ySI -*ݑa+J-sZ& Z% w;rp1q޶a,k#'@=KEy=v1늬e'v14!4߾$FTAf o^SDQ[EX2Ib21_ZdVэ2zQ[Vff@%p8Qҋ~ h\?"}=?£t{W|6/?J?=$] R7pN{Tt dR ִQr1$^'9+0HPF*X6 ăϭ[2T2b~alǩDNp^+֝/rg9*3b"mєl^6`Hc)FEaZcn#fV"Ѥ4,vrAoϥ+񫀌ē>Zq@po0 h#b1ӗPU,.wz?ncj"YCD9S&ϒ0=f+έq0XcӁn&dvFgֲ- yS G%ȧC%ܷooat0H jNz z \O808U܃,pv3pAy u$}ץh{D!$ W YܒuC+͏RN])\;s6"8d2-8栂PJVk39G&ԍ|0*`>a5$9K@ÆvK-ɑ FExwg?Ob\J`FZ2cn\1ڹw520LgtT\W̘:YQ~ϖHGx+RM!1T1PIά\)`48^W΃و'7JX@H98i,p#$ 4@1u򡙹#N;ԓ\%mpHcIUFRYbE:ڀ&.Hp;Փ+$sBW<J Q'< ʵ/ʲ"b0Hj]2̋hV26Iִ""LB%@7'#k5eE#o1ǯ_ҧ3Yd\=2%),'HY11AA@e7+0@3HDI~?˧iKFS\'*1Gsu= fȲK%J ĆϽO'%eppʤ}P3I8?:?,h ̅I Go]"b99㷭$R$*ZDw)Ϯ2>I56'cG:i] [vZeY1mc=jyRevx=+hȒEKi%w8n=:/@B4jHmӚJnxl79|(eˌyAlZ2Kv bnrH\RKl~ku#nX.BI.C|3[ g,m[]̚^;*7h1ϥ:I$RR:iIjT$r`*gsf {ow=C9xf=ODFuony+>{4\.:OJ4ci'p$z}}ϭ_22ҼrʏsT U%݀p\(kHI̷w{c֣~^vfgc)Ih>ٮuy^TĤqV#; J${{V}Xҫ8|[i!Dhˆ\iU[co?!u*N@'w腸BpvN@_9|߽AqtbCw0A:*30•1R.WY]. :b=} >i?*vu2[fI%CUI? khHؒG0u{,c7{0<֠*6`0@>ȉij*܃zk+"ķ S,4LcG*:U_6'dlg%#PqoPC'y|wQUhE <F1?OZ5<3adS'@,?T8Ʊ~*ctc&ZSg6*:R9sӯeH&Ib/ʣ8Smg3ďdz[s朑$J  |P IDK c= TIx‘=TD|S$%RV.٘s$lpF e}n`AצMB<*4۸6{TD]%?80+=ȓk 95zudnŮ|rHJɚ5q*.S 1?Z#& ':Z-"8\2dr8Q@J.-z|nHVd)GOʡ'gPeccO  Γb?WZKt{fː=֪%cpz\Iު=r';gǭO+:(&jZH/.`k2 :Tw+3ee+ Ud1Čb~h_\kڄ<ϗ_\ұʏ.uD!V9*{&tkYinKey`6o%dH ۓ?cs9 }+ּ,*P+6܌8c_D 8$fS/'ھ-}' wA0"qjf#C1;I b@81#^kV!ުf.,VB"Yc(>2EShi~S?OƦSn ^ap[L{I&NDtT r8ʝ̎$K  f65s1[!<@|o'`\t t%0o\<1*0l([ e6db.ryc2Dpךf̊^IoT)81ƀi71d<)#}9&xP'=yJQ"\0(3tS#7; c2O(-FX1zuAhyc?OCRZ@(]J;VyOSFpWTsI/> >ޟjLX\`Bs@.K6&1#|<1y&^0 oc=/',Gt]j2Ej5c0<J2 irXlU19`y9:T$fh d9tdx"^q֒ [aS>c.4mJNJQ- ́~UKye,KaPegҥKgY#[v1WӦzVR<pNzThC(H)+G7=u?g#h@=XI ʼnf{`F? jѹ8&A P,6%bzA SCʃ˃VGT&u*U#}32F`*H yaH}@Uܲf=tzy*HeÓV%. Hi%Uof<0H tҚŶ`"J1qӜu]e+8[pGark:iHCeIϨe`%w4"Q(*BG?VHڱٸrXYB;o㓜Od$m o8G s4d-4,">qҥK)]OBBj3",qC@ | C"sJ>ǎ+5l0>e2r}9jNWtwϷ<'Ukx_T6^yGaLrkU>bhr܉1I㑋TE5ˠ*ęnT6>ڦLUcOojar[y'YeF03I>zF(dA IUi+˕_Ӷ)'3.~`=jһH^sRn*m<3T`vr^9s |`>`c?S2.FF$wgaI5Osnc9^27EB)c2.1=9CT8r k37"uL UGa!$#wĠ?+2UPΥk<}A<ߍuƷr\D\|A2_SRcVeiey{zIIW!CiR ZMM!T͍1fӿ>e H0+xήDBrNxt\G`U @IWRGǠm,<7p8%ek}kh% cy*g*ɓqcѼHR[H?bG;l) Uvrz׹ eM dI{54l@<} 4 m$@zTWSͼd.P)$ }=xϡEZa]$;vc۞Uxu$7 dNMd|([wRpqxnq?]:I)1 zr 9eHsyPvgcI;l/sϮ*+Q639d) $L: mw* @H/*KBci0\d`9w7 {JReF2";3 cUeh<')-B!e?LYsD+tzW^z@61]Nnqy'ݱhOt )q&7}y=sZM **p3Gr#̫2ydxTýSluvaEGyʺ1`G|wV|W\T KV|{`iHnҽ6ǝRLEV`w~a0agfV̦7 OjhQc6HbL}3ҙy<&Kk Îޢ] =2GoZcbӗeûsAα滖ard8-c5Vϛ Ρ2MGPmcNH׿8|)_D\R+mq +2ĀΗ(QVEޠ%E33,8 t8KWxc. z.1;51 qSu{r,ګ{ R^,r͋}|^j C$C #a3$G}nNc#As\pT ֪jr,r;+~.uy I#>P.ⱍ˛$"bqaMKH$a< qdS+y3Vb\[e$`NƝ[\>Hm O\2h Bts :CqcVwtvc.I$ޤsVs܉K06cE܆{x]ȪrBA,R2lcԪkgx$8bF=38t{ݣw;O?Σ %U1?O_6HRcnq=ª*[S׸=+g*"Y_4qSGy{n qFG?L}* u3\,v?iYu[#Pmx-)Mf#+qQAm7'$?*̭_ΉP28 ۇuېϵSxIuYtD,w\3ڤ]M-gG~BO9Rmq+3$>K!w7A+~^[brH Ī9XjR3 6\o*$ɻ~|s8SE%$mn۬(GOi,JcBBq-TGX3}~42(2n*n"{$ |`cjɲ >SbI׎x.ɍ%Pa{RD@ QYodgpWzQH7Vlᑾ@6=jxGBZi JCw=hNUk<ޭGbV2z|cو&g%~UƾC_`m[(҈6+x+hT!8 O+͡*FVH/XȮKyF w=,N+hjl:#ho}GLypyr$ N:zWm3nToBGLp3ҽ4V1o( n知|Fi.y}.<{t]r#t^dvLR@2޸5!>t$u_mZC;&R->uk2wuh^/ސA!ƂBmk yCcmП|#UT\ {DB&2'cʔy91=M1idq.B6N{g=U&i N0Np`vXqۓ5A.#?2kUsC2d q楒[(;!7/_UbxdGHXrGL|)z~waֶIQY7HgGo5ҒGKpK؜y#j;%e46 }k Y h,G$(ܶƈ91֣cqȬ屍'=+"y$B7ϧJb9cȻ$΋틄WnQa\Fmۏl@\5IFe3>2=%$o%\p<es>VWVʩr $`D]\cYӁ6;6B91 浌+W/#'BF؁bmrr/"1>^⣔J!!n{ƵWH}jX[{)0)?_\Fmc:c)XgBe3}lQj'(9dgV6ܔ68Gnk.aQ"K |ݰ=)n6+Fx'hcM rR0Gמ4sX ;sJ<-IĘiG B7c9J;`rwq>QJD-ynfs$oE}ȡHbGˠ<;{[\Ĩʲ.%Вܷ_4#P|q=NoHzHAϷ^=̪ya_O'N׏+Fc\=? NQ m^A\_oφ";/F'nVbG$Y_21WZo267M3ZÑ|/ #n\%ގtg@s鄎rܬQ4!PU\vinxN1қ$MnIjʗtHб?Z/ -,w6]$ƛ]!mrrc*& 7}pG׭HT/! T.%1qH2:8_n1֫$QLH",=RDfDI4ZŽq"''s؞cos JƜ;Gf<۞}jDΊ Xö)b#".ySYw3me&o615PvpիmypDN͈Yv=ki6 PTnbp?/֬^} w`˟Jn|Eʛ~#nΫTmHh#izs|SpU¨ynQ<3wV4(R98Ϯ>kaej/]nn8SK%,.X(dv իFdrq>¾@m4w,m}8YWs΄Lv'TS{m"\g9_NIpw|;)6}xvqVA4+ q֎Tkbo5(BaWi@QT%ͱXno)8Rc2]y$=z34 jrO:jKo=[<;Ƒ;fr[m,oQ܅=* &=0%M$\<J+$0eӯY3`;+I9yp?042Ji#OJQ2nL{21/uzm9H/$@swGW _d°yiF@VPH*(ZډpTbJ{Igdą>@{x[Qg}[MQr6;}+J luGQ_׋et~*џ QGh;q2{$-h̿tk$852tGvn qɯįFԍZ0gWO^EoSۚ=>ůke>neX=̋K~ʠ$ H 0ޕ[ HR_d;؎³^̯rWos_Y<5 q3&Y<>io1,CnD YU]#n]6-mz|.j ech!GC^ŵH9Byt3"P$&8 ~|[IBƳ"=.8g8\(Sյȏ#vCʮARE7`cx gֲdTd,?AkB"i.>^Oz;ƭܰÜ5j-? |<~MebTG1jaKH88n0_ZBF zC`}3U'8>\# ``~?$SdڱHt{yU Ddo|`).ZHasH͖'O1Ģo?u"dEp=+[%Ecn`Ǯj71nT0JҜnRJϚg 3wIaq[ =gL,_P]eY&WʠBX3SSP)H[ێQEWe-x==Mpy1Ab0Qs#8S@Hd6zV ?ra7ove83ϥJ춾l}qǵk+XvȧE -,pUՑ  =zT[&6Fzn1P,bDvGԞ4X(U@Zg@0;zzKR68:(Dkǟw؞ݘbǻ#,x'=vjRٕܓczFY3y09[6$B!G ӥ1?lK1 5f9XoC219)(Gp5^b @FIEq/*s55F7r1P%ye>CnZV1F38\wƱ-[4B2Wv[c8Һ~9Z80?tѤ 6j6sJx {v&E͈8J٭!l8yCgi'd4K.=OCg標"x+$o8=>X0Ǒc]t&T;N "9#q`ki$B^1?O)G/+FyeV5Iw玹㱟qY$v^8=xݢF$ڈvnW[·.oZk <8nɔЉ%8x6r2f)d0? H?z#^!vn; |c3_/ 烎ZrlKs<׭G3.2u)R:1$zSfwbxi (@#zԷCס=MDHʬ>z2ΓKoO=~'b\5X00_`ƭ#d`| JS@< 'k'gN{UŢ}.3䃌*J?$rl$nRCJ\s1SOJ.==CubvTN L]_w;`V%&˹O# P{zOl{<;Hψj2GkۏOZ \L|أܸAǶNi @*ݓUUFaw =*{G ~T~\OJ2C|H$XʌR6襉YU0}ZI~dQ'<$e'>ҵ3queaʉ3`0ǡ rE~g#NYn 3$oY3cm|&eLL_RKle$}t9fq; v="wA(S#) )c=y_taI=Rfmrj\pJyG?$2K#|I@-rx"2il>d*W,zި9$X2ӵ?lHβ[rH.>R1Y^tR/ r?ڝ%ߴFn#YqRB{UI&F#*/EmpsZ2zViZEVI2c}}Zo!L~n`|ҫ4(iG 3$s"93Ft `#hN9iP<ҦP,)I9YQ;lK!$mt+l$eu*  fՈn^k]$af>Y:<>AVL<6<]]*X8S8$rzp9Mya%oNjRts6edNq׶? 䥸6]v̉<'VxRbi l(z ϖxDP뵈 9U;4[f2OnxV&kG YA @{_G+ЗUH{-37<༖FvNTZ!8rdwL/qNpP<켌``.kn.J@ o,ǘ%WAt~VY ]S<8W-%(2n@9*G>}-ŗ'υ#Wy@pNrycv[vEX=0OQ\޹H54jN@gWavlf>PG_\̡t!ѶQǩ:#dJD:q'T7y; 6[l{{^ >73̭{jW/&s.)#X4KۅK'Sq/"Hv l ϭgJf9y+tSZ.UeǓsҲK1݆ 8:S3$ ,g,HQ I$+'8qi2`4!F`cf@ۢHJOQ5Z܃f\N@?皎W.R /ӿ>Tg:i5p>`cHm5Un N}Eg 1e37?>)33O_3Ej:[LaU(%?A;&܅]QO fF`ဩbxfVߍ\IGTL=)VT2\zcJUB.ѐ؏E'4G>|PDwr!) \m͞xwwXceWۿ53^\6~>dqܽ RJ>FAZ;_ۤe'ן󊦑G6iU$ZB {u?ҭXNeh]Bx`Gt'#o֙Ng(҇$NĂrJJޏd6Y | Hls(:֗t/Gk&v$u IELq-$Z-fɤnJJnݏ\Sd|x٣Nj76!81e}tNѺS+#msͨwS#(nx#?|aio:\ +?*p»QV?iX^iZ?64e"+k,C Hgz*]i"-Fw HWۡFB2#:eۻő@zW׫}ϻLܶ*y1A[ tS^o؇.\s\ʹݻfѓT`R˼'יQk+L0[V1y}sӣl!PݴzqU$͑\1<9;1j"u. fq4o he'$MkOhw9p uw56X6al=r1ELo|+wP=gpQXo(%hph"E.+9$g}+ [yU˘ :w>Uגul^}{Vl-1I[r9#<;q١Ф^Kn];`4QQG0`T9QtO55_=JJs #)")6׏V.R'&GkXqíG>Cm1K$Ю։(W 3\%MDl8$1I|.a; ݈w6/;$GmZMd.vq@1vDbXzO<gYf_OzIe7ch|OZRq*:FU" Vը fV^8_LRCXLS/0P=}*ŚdW9o`t'Pk^+.Ac1֥s.ٔ9#5I8#T;x*w $o9l 3AA.u $a2|y皂8̤,\T呢`&xU`O?_ZKG-̄5QR?XT#NOqɦܩt#*~o4ű2c#=O56 m. u%+Eܧr]dA˸bp$w1BgޮM$JUd;-^qUnè0ەA ^)+5ԍ+RsiEU$ :EDFX!po=V`w}ʯb:oӵ\csD -dfRNA?OOZȹIXOFe=rs*ΒFШs5\\ >j-ݫagȝ!1!~BH© ҃68&Y P7>Ug/=}N}ɑ%mѬp :FSH˻l+3ʮzdc aBqi] lMd+޸>JdkkU.]dFoϭFF D1~VvNzjbBbe'oiȁ:e퀀=6įmwWFy=L1UON:N;srwno:օ̱iTo q\{8@@qZkBҲ)_]B{e 1cf]OyrT9~u^]+T?{<7sW 2 `A=gw1"ǹi$V3'+~+Pp<Qھ >^[v%Jq,%;Fc;c'G9'wDf8#nz&q64dluv%̀ϵr7NER0O6U@9 \plHB y.|35 є Ϧ}PHf)'G%?OCyzܺӹp2ֹ-Nt.ѣې}Ǿ?}. >G0Z,uxmFB m޾_r,Eq+>svuUXڹTy# _G -u.'yr/˻˵I<ҬіufpsY>Z4󓵃}z׳C ˱g6yyѤG *7 6PB:OnT/DA$S1XQnA~7ʈL2 g=0F${cp9sߦ>GI Tk6@;'6%Xs)H<>} ؒ&1Tl?d;I4q.YBcU܅c 0MF:Rqfyٌ)0 0s5zܤy aIF. l暿P Tn?J8s-Ye~^GmxbF_' {h,}<ȹ$x֘Xy1s=d*53\31L<׭D$l bsY\3,I>hokTXai^zzTshbdarlLMˀ2uM;evJRo 0|gO1KO-q_h18ۑt[#yyW2.Y0qLV}ٚa2m {c4.R˲U*Rw(㊤P9%W׶XՒ(f# #C462V/';sU+MGT-m Yl{o8f@Jì0vs~mo+'Ҍ+k2 50};V!hZ2V>X?uƻ>P>^s \ӪEu^ +nQd[ j>UPVl. C0yz+T;2?3!ڪsC};νPcr("N=B $XX%x)"8jޛwVi%<=rjKhdPȄ㝭.+h6_v}˳ZI 䈙1r@'߷n<$k9f|^8OMȿiU"nOpF{`Um*\1.p33]C,FBrҪy3Y~~ݫX`\6B78ma9"0䑐X(s7 f6=5%ᾄI0iY?>8 ],haH'O|hW@\ֹʎ$-8G!v?L)"A$.K0uیc:U\F\rE 9OV..].ϔ _ڸ3H)Uc;48!0'3敔[(YOI>zTdyT@moP{qYwqK3A$u]qnD#ű׷5nXA![(_sۚ y`G",\0/D,2=hHj%Q_N=B/#(1qZtcڷqc(&|AArżXaӧ~8C{xkL81Gr1_W|996@)9wh~Z#,N QZ sc)EN&6[Y;HDIBBI^;W8<dDG "tjbGnMVFAhveDYV)%}Wzixt_ :翭A hq!}"FPFN[  ;ϯlfX\q?9$n$ U"ҐCCGڲ5yD /#ק t]cnIl,7ɚг %᝝NX17~@ThR~XIߕz뚊42ն`ǧjf%e( mL!zR伫ʿ*\ǿZ*4В܉>;8Zpc6~>S #94uE-p$_^f莻Sma 4 1`ҵ HfQyP;Z)e.6Iztj}orp6=@L BR0SG$'(LĴ&y Ueb p '2%@@cʭV~rxP8 $vo1į'8)0DCt)/0`aoNq;!+ $so:yyI04ɊXE9lNTND.9#뚗Gg8\F1w`<138]xӎi,L'͝1^d\ Z<'8cbZ[,0OU}<'#eq+f$S scnZ(sFm/Y9n;aBAba5VIFU$/>ǟs'7HJ AÎ;~@#3*uxO>d2J5n "y,挜~Te4lL'Ɂw4(T܌eIc!AQaϮ:#qדNvD'\VU 3-;y}.(عH # ۙORzuAOfq23;F|ùIޘV偑^48 Iy=k[Xe~vSԀEXG2[@Wwlt'ҳJo.$$0S"ޫdc135$lVHi0])' uǯ[Cc3JQV1a$+ Q_)c>qJv: Fw tJ| U6 #w n#?P%eG)*\^FPsʜ:!vwo$p~4A'y\Tnpb[?i\أddȪr?*HD.m%FF=yTVHH[h@I#L"6vy.  >dMRŰI{1FAVVesSd-n#7ׅϦ:LUr*$L&bQ~j g7`-㿥$0*2H&:ܪ}ߘq_~~IyJ;V"…Ey9ATh@98L 0gdΪqh}$s|Fظk5fU*qJS 9ɘ (# Z$Rɲ&Pnr1xrIv~K+-#;3 ~\ueV.\H,o$r)$c 9j)w\4l|'q=:UV*2c I|7#~cyjſ)WTPl; >eZ $k17iŝď#r@@?ijC {߻|MDe[܎JFysCWUB1n<~_vꌙZ5 "6Tn%GJU_̕(^U K!<8 %!I8;b:Pg$ޅ&-{Gm2ISӷb4XΕ0=.d9f[ϡjD2s}HF#B8"1$ "lfB25}7qn\ 3yQwhL'>~qf4R3žxeK>7۷f\?͚ViK7`fv/I>Sn3޾ ̐\F1 =}FױjYrm}r1.& ə2?δ5(ygץrZep2dm?+p['rǖYo%1T5 AExa8k:y–p#=e^^qH8\5XL)bqV؆+0^$~5r#܃{dt1R[:IC$yݎr{JpsF\㿵4\[ !B˂q׽Go$Xaq?Ǐ:N ܄-g |O$64p~@Y0y2Ż–6hDDT+qZLCcgxػU®zp;Ӎe_5t{QƄ\In FJ7rN8={sLڔ,d%r?1H1EgLiLI"ޫ,RI tQcE2<.XBj܃vKIO|֪4L;A3㜓Sto@J, 7U2FTzWUCxQA9*:`٨WɌF#I;c5#‰q~ԓ=vJ\o)eOo쳙꿁F H't 9fujq] )^6CS'WzH[xƭK%$Dxdz mѲE!(Q_zC\FS;pF 3M {9p8 90$K/X4%1hoCVN;bPzQ0Ihhs7q򤍕-.+=HMόHwSw!kcK袊7(zWeR9>&x"Rcoߕ}/\ƹc5Z9:}+Ovlj^~|SKoķs 3uw'#'Ⱥg[WtyusjRm.qsۥ~Iřgw꿮 NSjҧeƪ$ On:~=LQ$PH^?Ֆ01/ͷkm jvT*=^AABwc,vsC#oɵO 6{ޢ>J_̅Rr{1g+>DQj6M* =GN9݀ }*:ݜyF?5'f*Qԓ=%+;Eܳ8Gp̻6Wf1 ݎ⦶y>q7:6>Dg#'yev Uh+:nZ Yrg :nɉ1<(e)>ƕe鎔*0v3i8㰦,cJqߦۚIH|rp+ VicedǎIcC(X',[<tt54w/̩sj9͢4NlJ{ԂdT1B1+:I1(i'cy+tPĭnXaҡ_2b3ӎYF/gs9Ў|7rvRIHy-^zq4&NEWXێv{VGiII1\%tuC).çlڳU#, $њ cpˌCued6G3y{n xG}~UsS;`FLZ\6õRX#jϱIĒ$w&16N ![嚢GW?9&GH$/"b'8n9\LBxf$/D#B"Hj2I8zVvY63Z@n&ْ0&X'#4Ȥ"m>*vRum9̟i4𕛐8\5`_2]qÁLV|J0G< K9l0xDSC0'bbbo(vss߭cw2un|f68+JIexUaE1cDN&p͂펕b44 pȻ2U:ji?(% ($Hr|zUx++XIz.k>O3jZo1=8Re&T`uXXzs#DpFtr;gi,(b`;ӔIIVC ڶD呦`v¨6=aUxJ`dcOǦj |k$9c2j 3|9T*r&[hH"C^A"HX0r5JwZϱJ9V@>\TֲLK旙a 5FyB0\}8ǥ[iYATaZ茺 v_1犭q4R5  cO5 Jǻ$|OrUK3Dw0 p{VfjVa](Ps+5&GYCB `"$ 899cC6,#4r$OP6<qOjEfqU%?J˵b+$vvH+`'کDcMd>rB7R;b)Cżh]m/Vҫ͉$0PO29^ @J1?#^48GLʂ"c%yQP8=Q%kE $8O)/ϵ2V|#BO>b;p:~si&ٞIg嚥<R9E#8g5P#*NS}& WCu 9an&r q^^,0)'on;~rwy@ʹ㕮lKmoJl&b܋Q+,qoW :EUIO?O_ǭWL-i[FG˫yqۿ"?85|)bV*^K2ND| {7=?;r}L V,0ڔnZD9,ss701.Lwp7C='L)$y# wd=k: dU`LXx:zұxcdMY!{"pMaiú8y@RH]ú|2 72[`$"H/́6IcP*,2Ip)g9)PJ; O52I!vįXnҪr p\?9] +^H :=j8"19# Y$ifh#}F)+Tgz.I=1Uˉ$3}*G񙢅®7r1LhԎX'V me5VeEEb`2x:bA fxEsg%vFF}hY 29E9a=9VlFL$6HW'=S #)W#8f>JK Ros<3ıǒ#d1\֤coTݵ0`6h!Aʩ5̜JObJp4YU@|g$w7n)O.e0S;O򩠞@&V'vXB  "ϘXiQ#:$,=y^BYN$4-Ċ6H ORx;Ɓ2A 67qQ-x3y?1!7&=bs]KyaIAڃ{hfbc%OGe4m +x5dG$O+vwcm#?JȺcF[o|ǯ5MvM>Ga`OΑdzm1IಏkHx5Dm1H gd$^Q/DVD$aw.;jgZb'|sB+3 TRN섷HT'd+$Q|XdsA1  S\tF 2ANALeiɉw*Iln̙GO*ߖQ7X2@VY{Zܽ1ԓg֯ƒEcӜU'&߇h Ni+8+‡wOQT6c<_ hF,H'o<ۊ.1v'0wRIӭs]J2[Ap=jҚ{A bʊ}rrGj$ѣd€{լMc<+ǘ+ ?,VUWA4+Ky6ņ_sm¯-&in8L p~t(>Vh @Ff Gzkw*#H̡#i?ZxaiB(˞+ *q^z\8_5 vS}.Z[L-vim˂tYBHķVA:~Trӥڳ"1ggI1 |.9>D (:enojޙȊKh]v,f~ӷbchҰHwdqPʑ UM+J\Tۜcޝ[$64n8^lw3 rĊ [{ʫ@}05@Fz_@y0İ;(;sWd-VQ/TہWr ` +IҡJ=x"ibH"rኑUDs724ݓqU#HбUl(3#i d898?^}Ȱ5įmqC ()+ ;lbXZIQ)Xq€Q6s q@4)!Q,r7 *#̼ehʟI4M7cP44rHuBv?KlF\+ 9 BZ_RH' ?*Mjoon6I,&vN @C|yWw*eIXETpD%>djßqRF(AܐNqOU..ٍJ$o͐jTذ@zLչ߰2y*wTe`5RF˟εn_Jqa*bg>P}*dIXK䳬'?J3 &IG#24S#Ňf3 z`~5QܼRȤHR0q? ] arb9ΚBh#Dh#~sj q83¸(Ǒsjl^D* ;6Z;t%O0.7>fڅ$.79ZՋ>iI'erHݣVr$RF's!CkNHe_&xyd*}Ar+;EhԘzj+F׿zTp|iN0 D~uD'd[z?QTwoT<0+agEV*A c9 o$q];"S=kv_T2Qc*,pdH'$衆0#j՘@p%Hc4IecYY3D-\;0 aW'"` cGQۧ㊑S˷W,}4UI$jv %_`xz= X l~W*}~خ2FNS='e7*TxƩ$1(x9G8E ȫ?2u}68ts"xqOZG,n,Fg0Ƭ).?i8 ]  {p\7%UKH-ϐ\}91`H̃)&Sq09J#GP;RA4/|9霏޷.dl8H]DTҬ &>?L;)NO>C[1չXQ.4#IlӻyiI!:˟VQDɍqTyRf2`NƶR8弮$b1%r}lzԊ^HiES=C$ E?; q#J*B}[T^g,%\(^F8jbI+4̊[g k)AFXtdRݼ[pNޘKsv+TP|; |j1aK4a /(lՋkra]͂棼qF 9U,aQ#)H0Ʃ\*Yoխe'X9=ygGG vRqxZYvv˹=c!}/oΰSOe;یt=;tϺKW \F9"o; '޾X|J~s"nۭCA?XKy ^u>^, <Շ_Ml$dYdKHǾ:V5"Je$OW6pE,&P\q~c{ F69w׷+@[ ܇fW/}sW%¥-FzɤW;d%+tf[c4ISo'=ǵ}> |/bk(ȱ2&ѻzdXWpGp*a;qH*3:x^_}V|'7PIn2cxpBm1\2tLnmN98(펵UUO=k?#1hWs,*GB88"mZcȩ !9֦[A r1]ۇ~{/0|6'׹̱G4`o8pT+.5LŒrG=j$|%%x'gZ6KK"RJCct!Vn!H840"4IEr.?WKN Lm;}*;NJYDUT=s8E 2:rXeژ#-܏Uoz+9ǩ1г3\qzX [ر Nvr6ϒw>Kqpy6fFeN[l;bI9B䏠 tȉ f~o1pm8RrnڣS+r&ͧYtL$34OLdsoPӯ+K*nzdq Nm_+KcvPNL?/RLY<ƚ ێ+v."0߅Y8?Zd42.o\J"Cxfl6KT jcoXs7%"s0 &Fy[y&iáq·On+:s3'-AjCIF2^>ө7.xh,3?ȂO2}B.7O޶KaFÒq֐2we)cWk*x#&(;|FqW ,A{g~9jq=9e;p>N28Ya(3H>^Sir^T{S|[K(#QQ#JUܜ#v0ܥ6: ss8]wAV5Hfu{K2]b>1pqu&\H6Cux$M![yϠ$4(="(|0w&;ҶjX>$ry>I'5TvjPA*FeF%H;O5\J!E;qoF}'2Ig4>(L ) liy8;z$+o0(p=j+؊<tlUʍb+̰F(O|c=#T°-2xU㶺$c *մ:|XH3:8Zk {%tM#KA$FyYJ[~_ʯ5%Q`"ǖsӯ+.o.9c,XƔc I q'{ qq+yk1F8s%^d|w/GsM7k(I>DbCv zT$tGrvtI21v}[魟Lʱۢ⼁7B30A^99~|#\jh<%?v0j|Wmr rd=iaxk9262=k&IjD$ jHAdw] bMgQb<Ǚ#Ӧ1vs YWSkݼbdAOOAQ-:ʖet26IK\H(+npVbug2-pD1;aqS2UY~c;tY|o.¹426 w$)?N8rVTdHh2>= 9 (V9>֯Cyd I[ #\Z҃[I@p2O֔sK$ XqX%}2n$W"egލsD}잧޸ӱLm W ;Biv˖I=$t<>^65Z9x,PFI,䂫ӞןQQw-̩m9Q7:ɐxkt$2v'u!\+-N=^(ᄈed$+<,,I7#iĄƻ6,nלqY-rѽVFg :QVCriEʩ;?NՔjC/LL/?)mJH$bH1gJ?0*F]g6y<|5ջ+"eu:R$۝,w󼎽?JCn -$թDLuR'/ -E>~LԩEo!1 ''X%]7)90zb9eR`]Ȫw989w%ñ<[^{Vhø. \%Z,Xm|}}(h0,88qҚv3$gvʼnXm0Q.L5<\Tt_%_q۞8{R2i%bA_jw2ËVP1 d߳p"b'{U2#(@KHA"̾jhH,M*>JeR`1D ?v ǧGsA0Kg\Dl(>)nC+NHVdѻ`TN0[|W5%ĸ rn;^35k*'\Uӧ(U0==ֹ9 h rA状&hȒ$385]'81Z)ģ*=1MmQyBa|>b1c? qE"hdG~y<ХN7`Al!I$VBPT*Y#{*۔xSUNxP-Faֳn'*W|@i>Zq&F<FyV7)iS'#9h-ٕY!QOc )F7 䞼;ǜ#{о`EYf{{wG E>rrsH,d&q @y>RVA,a#{N$: (8<ҫycYj;9_z/8Q$L۰\})'RFh &1w ӌQyO#Ѓ@3])$($:`Ud7B ;!v[$4ѶIT7cKo,LLwDH;'Է.'XYxNd; n{ӁZWAqs dHUIGr9=$Z:Mp"դn[ۥ04YQ"d3FWzS 0qgWO@S.$k7"e6!HclB Hݢ[q)sp6 :@"F7 ēRO$8˂jq !.m޵<|T$(JD`%I۴z=Ee $a\t*N z~U㺓Io~Ifn{_~u)ʓC帆QRA-pfdγ %9ÞNǯaꪓ\ClWf#>繬iUCFP.ȇvGok?:KդIK'Vך}GIrU0z=Es36!^l@+Hy-zOG!Yy ޸s|cy˜0}ip j҅1m$Visdgֻha-qIڵ1d8ngkdG8QfѺɞI.08?sڌpMY{5'S8׷[I~,DGj) <[n9^kXR N?:&cqv2Iul2_cpn%Ti@rxWdW>~:9Ȯ#ZaXdJ>2䜞a-r+p;ePG\⹋\5bmczzoye]=C,1!A`z<ͅe @9\Mhg9I "DGɌp1X5,:ͺDǡCL\I7%3H˞֬@OYzASh`O\si')n7?ǵc&L9B>qϧqZR"L'z V ڼD|TES*Fc\nVJu#psO9(MlJ ITdVkgU$ HI-gI6 6O8cyNgaA;`u=Zb  {V78Lnp$I{⤻YT,8#GOsڝmEQi">bP62@RRk2zj͹"2+g8Sb_2B6[yabw`֣|ϲC(HVb7|~K:i#WۚHI"Kn J'=ٛsṗ͢yKOQMn.W{68vF<a5=;By`[czL '>zI"7c9>z~ZWbWWhE D0Z)-ɭXwXrwxaڦ~Ӿ1 qHpyV 3#=$В91:ڧ M\I$C=2=\ުI-!(DQˁ]YEk>Tݎ=x$Ie1 O9YY.=:Z5KR'ی)bat+'Bb9\OӬѡovrsw̳0)̩> dZIX@*8Lܐ$`t{S.c40?& "4qzOt,l taFv-Cn2<OQJ67DO !"\#<NmG< FjFܾW UA@sY11e  : gYRhd/ng ,n 2\wҳй&XZHVo2>In9c{*¯r%>tBV#X!8óayRiRЧSmG<\c'#O9O,(( qC6F=j#giZA8*K2|az4]PH'm"*@dfOnB=ؼu ˺` d(?hIjw'EÞn#5q.]Kx} ~\!~q?Zg%Ė}<ۂpsqX7wOm1;vOlĦ0UQO]$][SE;̀ hRWcV>\],~pש⢼ h Mg$tӺgIcq{Qv_^}i+K,2࣮1WfׅRte4=lU_ O,,`q\GRgL-**zٝ7YK { r~A a89OIY#R`iGQ,,eP\~jh1 $M'hK]i0!*oY$2 zg[+dD|̓qPOy{ tEM)r.o?ϵFCcs)zn޵|5fy$nJdrBDJ}+5<ۀ@>}V$,!lrZȔU8O\̙Bb)L $k*cZF/8nfy_<3DxN{?QD nI'hR5  ʀP[<BFJ9Q銸6>N+yM1'X3o. $_Z[%8dG>PLAi7z(.{rȜ7-8TvW`]~?Ă4b@gSрctKwG,I8 ~z͒y:+I=x$l^~;۾+{̑F vZn%MF{{VS0VA;ݟΚo$.mʹr=)"H2+u(7ΩS=?yf@i,6$3]4 u!v}TO%qXgpϯ[C`'Yh»EqږF߲As r.A8ٷSr?8(q:~^@L),H #pi $E4p>I]K2r #OE O8dvOZc2yJi2dcw$D!L$l qV[L++c8>k)Qdx?Zx3 ech`B}ava$fmGFMe}իmm!sǷ5qDmJ&2*~]C41XE, rPzTLi9`|ߕ2Yg1 0qMwE@hzP&Yu#8F皜@`ep:->s)6:HS(FF VczzD(TYcs}ץZIfkiQ9!F8:P&ggBͻi?L暋FCcZ"gE#7| >a`Z_@T,I˨l6:ҽ~S Lq;^w(Vu8lAl kRvߡO21[uy1ԊθH"WsHUxM4,Qy r;~eyi +Sv?8Wf:{mZy.8 pwV|Nh{ A$z+{}v ®q>yelwb)MG "䒽kRYW2`L,+6rXCް'z$DtM<&x،Yҁ0bO|zrqYzH8zߡ 'Arkq9z=f3VkQ?0Wg5޷:RRw@GJkٝ%h$1>b[LGqj+ @`+Ԯ s4Ӌ p@ ͞  ˈM#Sܑ־湛6{;1& *qg]\ֳ<1#VawSܯ;ԄN̍*vV-*I^Zc9\,W7ݫDr:GgrPt͒q}=+'P}fD7韯Oem۱CG99nd+FgQ#DR cf+.6Fc~N9 QdRiA¤dfhÚXhU Y1 `LQ$nM,9w{隻w勼<^gjyd<,G<*{V4VHT9gjAlfHv2p}ks ߨ^i#J`vZ4=$uuS<$mVKS4,UB{`R*L5 #wV7Ai*C4eP7BKsqu6Gp7zpҳx;Kn ՘nP. :cOzMD 2y&{c$=HI3nIX@^@R1#q]8<ӡ^;^ nPgFFsq\թsEWEPEPEPrTEn?xD\rgivBZ7xw4BNOZyg- $4t[1ڹi$,2$!O'ھ<*~uC|.|kU_n~)ेNV뵟c?bTK;2$|ÀT0<֤H ĞawSṊ)8ZXnniwdZQ4Ghu"+~֥6uV;VOcq߭iӛsT0z+[b89}ŏJd[u+;? z H^关 OU{~!d8&Eqp24l= ҹk+`̷g8oK+ߝdI@ϱ(A}}Ngܰ$FxY@1ۊ@dSZ\ b޹_S\5֬ ŤQ\HVr,o8K+5՜n+|n:C,IA˵yu"to hCD**#7]YXOEe"a E`*nM \mLsc8u"NG-W}r9}kRc$(;L98\N0y=8'w gZl$^l ǽ 4|6@zG ;Xcy䱟ѳ) ߔcDp[Bl<J:J3#MUO?PffN$ۜ/qL,FRGJAJUO.9><Fs5r+*Io:uWgf%߼Qr/DJk7Tr~}D%bbu-Hg" B\P-Yg x=,pR8n`01劂~| ?y8> pI}GXLxtxeLHO8~U[,$\py?ְ~XP+1˂;CˮqB6]Z({ggY {֤KvTSw0pO|ڳ٦Bd 6m}ZڄQB%`?NСb\ go=9˂@#9+GJv/ϻSV31cTndEDod: x@\[36A\0'k1n}k6M@KqՙbIyGp%2HD[z}+>Ygx+#{0VhAuU,ңnhZiD;-9e 8{ԓ6d DP(Q`}*IwCV\?&`0!M{w»]{` q8OaVyt U $~T3~U0).Ֆ&y=$hTvG5 Ȫs?K+4in4qoɌ3qYzZdhܘ@P(yU^SAXWahi#wܯF9p}K S)m̛r9Rf21$m,@wzTn2U' ]^^1ͻbc֪ f̍#88棙"5ϛG'.W+68OlOzj##Uӟe [UUҜ.kЦb OʍYU[ a܏ʭK"$UA|Uc1O-gu;OcBQeP&P*lw`x=UʪfEl8ֳ`,Ҽ |~S~ڴl[(gf[ح*Fex9zqמiXcp/99X̦5e'֨1uYR)ߌWm(葑jz{ 3=1W.VI eݑ\'M*.ne&2K@0_'=J}` 9#V}&en#9%/4ţ?9\*@dlLN_B85L 96N:eY %F}*k '&ps_̆2ݸ+чFl2e`b݀=}1ڠll̦}>0ú t,+WVToۿ}sJG.Tv3z"3;ya g\}kHK$˹t˄38OWBX.2;c*7!=hO2Qs.3:VrbV/)Oj P:~}*^\G42(P8?֏'{dᎽz X l)lF?JVXG+Sæ:A #`vA?N(XgΤz.`)e#3 @6ִ#őJzaPFno?j2["TSU:+r]3ĆMq_REZcJ*x>qڢ@rp oBn;檩3*d(<2qlǂ x\δ֬᷆Ә̋ĈN*lYb8O5w E9m4ώ4X09+k *%]vڽ=Aq Űcxafnf ;Ht_ÌTqmqW{kh^\̰bS;*\<|yϷs9Nx>AXu!2L9?_ƹ6`6}*q*,"FұP̷| ;Yeb1C{G1;܇f>W.1E9BeBN}*?b@q9xr NW^։3"˷ڈ$gh7n '+'13HH|g*E,x=+/nt0ABm?>V/]]BN".H)XR\e2ZH2FX*eHO5R[f8u5R|>/UyOv%vl/+AYebd؈ve|Jr%#k)ukUK5;B']\jԻ5cXC<RzV~vym ;}(GIÒwc{- (zdY8>PeAm^X ǩR>z2Nc89<V-YI98ZҘDcИT$ewibU{DS<^&~gnrx&(؆}Nav9}N;] ߊ4B M̀?N샭ErH#ǞP#lyS$9ۼGj{xfQ["HvKcANQ:ʲkCD`HfjLGZfW'HƻUΨ3Cb0=yr[cr㎸cn, DRzsU&bF2?γ-Ew @'9zⴤY`]cL`tw=ėAN#2[OubkoXg #c@#?gKs@~!>٭7W%;W;W&(©3D'pλ9'lys(͙aVL;]#T=\( ҵ'CtKw>㚪XlV3dc"Lǎ٣9Bf$ ysvw  яL'L df倿/ԓ Z1pƀ6\M lqZ1͹`KgYӭHTAj&33BL1Ҧtb q޳!|ǚyU;’?v@=kQQew  3Myxc w;cHLswj`enn~H&1 GaI†da(@ʼnsڳC'@\VB [y!lp:s\ӗYfX]yʜ4vF FS][oqڡY@z[֍1+#ҳ`Mh C8fg1Z҅{bQ[YrL`}j Dax8d,\ykkĀDtޭMş:{Vt5Z 9U.I#%V_Wz D gIlK{cqmng}8=-1'i}LUCsu֐@<$;j̪mHrh\C}#|ϕ3=Z^Cs(%~uߏ>UYlq 0Tֺ.rͻIn#!ryIIhS2KƫƗe|Af bYnQV2}b25!f`Xot*R};5VC\9jCHGngv?:kY-V?"HҚfȨ$M+'ß12N=3S0[{TJ^=*-񥺲I7| @Is{y"O֪/I/e q,+崝v sXrlvT8ts[sI5_ c VI]4+wq@6ddT[Ay-9-6)0$oLTZ>[iw 30'{sVv6Z\f`=>vm>`Mܓ0E#0n W.KdL?]DW$,e#\Fѝtl#8;vHJN: `{s.'I^ym$r:rgCӚ-"u$УO@A`⺸aor#I>vshılu:Zy;{=3Ҽ: иM'=EHEwv% ϟLuA&x`|n߯5R ?s;~Tj2"mYp8>*hiDi789V_fwD yh1˟ltp|؏sO|c v9Avˆ#?/xE"-#ddɵCe+ۏ?k ddʌ9zMs'EsWTva"yUv#jf`i}[?QR<۱GBxOqR@U])e9A? OK;%xArA#Tn."&fs(`60ON)&Kul`s4]Qr?OLVg8##zʒ4Kh`J珯ԟ*Fhde]1QVY c!:qҚ!o0pڡv9M4c 'T,JUcމkb̫jZ\2Z,]$Iy z{pjmJzwU%ݤer@yӊi 4qHQ6w]~?ZK@G dv!kx^F#°; $(ASc=kH=@5GظF$/@@ZH4jp䚂ݺ,Np8lǠߊm#3y '\# X*s@}0FHnyU"<,J 3XVk7AR ^}sX> y T HpdIHF%cpNz~*ӢnV`ARŶFRXm0yA#SÛFxj&M[_p3վ槻l:+lO\C7qC7Ӟ޵bU x(+P}:~;~<[Hs[6d^I J<$兀;zH7O60yw-9b]^C\D7zs⼿PDF,r_18'Ԏ{dVvY؈;[OOҨ )T<?׭JGnOR KuEsU4FAnt{\[G7A*\X] }1AXԾWKDQ㍸?eЪ4L.s8.#\L ߞxPJ]u%%`Te51ffQ*3DUh0q׫1~G $VsK|.9qڳh0Y]Ā-ǔrs؟1)$ U˕ie|FK2A?^ߗz\لE'@S9( }#:1 cӥMslM \Ӟ={Sж~r{~Ui];ܡHzc2ZT _wM v!hwA9Q>H k Sp& HAg':Y &G̘ufoh[ -cNtȇ0Q$V2VeFkp[F3Ωʳ<¸zَxIHBc_JώYIF݋ݐ3+7D7p"$3@N1_*IUv@HɱTi6<Qk6if=99xV C ,]T)2^Y#3&TS$x=g~niK*&HPJEc($ԚJAjm܍'p*:㎽;̪ţًrv` UMS,(\7{ Z"X27|8Vrب:h9l9,_g)6)ZV,sGnXaR@\:"C8f3 i+czy{CV,gX0`GNi5$t@D?y$L|NXJ*f'i~?ucP1qÂExlmE'|c;w( HcV?4~@J}ȫ;DHڠH1K;k-䥴rx?^)Ԗ]$A,1#0X2sȫ"h&HJ .Kmfp$30VB0ǭjaRU|Kq>G k-Aqlן pgW3 c\y9;5{{{~q?j$s[c$Q4TY 6pO?/q%M3%I3_Y^x乇e~9݌z`N?z8H2Fr {lgi-fRB;ګ7I,%C [N =:id0DZcMV}! JLsM"B#TcK-0AcW3$'s#:a|8' D7mlX:l{cyGG,Ғ)jRl`ך̶ꈳU8뚉K2@%6Uݫ~xV+GsorH xQ֯O$n8)lx1nۊPBR$\pUex=\)dR&l`޹'#&g,CӦG9v*:v>NfҴH]іKņzr+ebct<^~RLwn#\*YqƸS}Ceh'y8+偎9D>`OrdwV˰d௯Oj wF&FI3"ʧdY b˙B|XL۾@t>TR]g<}WP(H2=YQH.K8I0};cڀ/$khZ2@nCY1zq5hF$btaqM #%V*9ߟ6تfA+4s?1J:R˹n*yLgd#۷@.yu'_ϞNʺ#. p^2/ܝfÎ=j彴!FAcyt/"*1BPTY886CX{Fh܅H!x\}d6@{rp Tan TJ>Ith%:)Ul,> Oj  eA$ rsަO;qMH{>`ϯךDVbn=xb_+|GF 1I#g=YeGdqSK}zI4Irg ~?z0DP.&E03*BZR$[vU۔2B289Yr#G1ajC .1;sIޱ`{Vls9p?(`՘dRHQJ0x'=&J%BU)*py|T;pX9ϭ?d :y%I[i%[v|{so8 "o)q_2 ^KsfIqVIwlG zZ$ro\ɷ ' /֫HDʮ}BD"JH;4GHG,ဒ[tdQqIH "y4N"x 4Kv@_:ti'e9N[,Qm:['Բ'ɞ.M qzѷ.RB2H ïUh"HA<y \yrHx<1JyZYrmmsut-y$KyXu<3Tqq $}.G;Rۙbx'pG0xɖslB6a=~,1ufw>a{vL,QB~sϦjHo34(WlEIbft BX;ɍqnFze% cJXT0xJV6[x_2Fw%̘9QI<9x|˷!ǡ4̬'<.Ӝ0艞ypsڅ#)+ 7f,b>fUCF&BHl:dn$SD%,z_1/>=(IhԶS"Lt>i%o8q*gxZ//olJ󲞏S2ֵmIp3'] 1 A?7ʽV >gD)X(\4(7G6#c5JFU(fF#t;JcIn. ?.?SSFb.Jc _?%+͞.\[|e`ac$L"!'ME "dU_+uǘ{3ֹ9zu+v+]unɌdS=%,\}y7U̬0##9M(a= z]]ԣoC8<ji#A PCM,g6v>qVG|_Na35n$+,F9H7'Us,$wpx*,fY$6@ҚјdN[n<{VUHlr,m #ӯj^ =:uMKsy=/,QDKU۝eI;B ?:`&k@?OZNXh%tT1[w,M *=ɬ%"' e'[Y%*dXB\[1X&}VIDJiD+Ac9\/+]n c"hXa㊺]Վ~ʙxLHl$+,˷`.@R:j8|Ȉ*bcXCKrnPg=*' <(?b-XK}Bv̳OPqwhHϼ*pvt]$[`P;sU8ܶ IjWPqjmg3rU(Ip:IjYαq+-COz(ܞѢhw$fS(Ҩ\@^6Oˎ=3W< B\?"MUHcۢ20}jg:$r2 5m•6w7D݂_β˯ ad:e-]Īy^zfK8̬\*G- d |Un7BI+cgJZX#*jL[~X^1U$pB&9UkEuRz`x#B=oHMv?JqTxF`rF[;۰ZQĈ M)B7ceAxci v=x8Zе0ŐI'=;9wBWWD>0g K0`8P\G#As4,*=*IJtUAzdbTU N8))t9i~,,sa$0޲̷ 3tp;W1$p@j,FFXL+QXM[l=:T+vao9F"2}QwYGtV˰Hd2PLe>QǑHۙN }x2-C zg,͂zrOb^Z8;iIUHx=B*;[(%FN^O.ec>$:X-露"m11gҖ-ͽǟ"mďNzb24 "ʱd\0Þ^ŋRJP}랓x`om*w7anZ@g`ʰfV}*ݬaRZ$if'l^su`;V?''Qr+'dm%y0y29ա[AHO9>.H4yyqP !aoJor#@"7bEAcfYwKt*(͎A<{WX2GQ9,8`1ڄ^Rp\NBl=H bcnGqpbf$.zv:|%nn 'G)v^b1s'==jO,,R%99$si/0AeZ)J`+EKӥOb~|yc?C:"Syg{Nfa+2@ړ4+6 8S&SE[V"Kෑ3ieO*,IUP_4畢I20(۞.lsW={v!ܚHU^TPlb|(l8Wc$==Jb)`arv=jlc KYsXV4fw,E߮p?׵Ay=0C{:ڥFBb14HrneG&zw3*K"CȁOEW!EPEPEP&QE3!5t;;k/Ñ6ou hf@c# QM:Mt:YP2I>WY>>v2B1}XeEٝ}F#Fu w&-4 8@ 7w-p#g#Uni?LY^yux 6c5ocZc#v?U{dgb8翵Er,TtqUeXn;`yhyVF#V7$xp1 q(Owr2F~_;bfk\ )=>yElc-nY䌁zwי#r-9d$mvϯn1O4YVvFHH5-s$f0#8Oҳ>$]Ϙ=vWo-'Ȓf(hcץkR5b4[ܯ^GI yeS7`߅QlYbZ T~BHWN@b7w_1|#ΐLp}}M*4 2K8Zib"4dRv@Ķ҄mq2{qW#aFw;c#'֭L 4ٛ f?.}Iu%mYP>^IcH5ġ O)Ls@Ix؉^3PF$(Qy#FEmA-d?#{}bGQN.s"M+,H ?CTBG}Sj(#~sӔo|ˈղF/gZ۬ԩy>'ژFɓ4@ϙc;0z~h@emprxOIɰ<}xV g*Q"UT ;|A+!rv|mlǧ˩Ce$cV&H؈#%C~|9QQmyhB\s簧O++0߁)9+fk㚂%K<~fav1G$\"prF7_ZRv Lu'ieNfp>M$(2p3?kHP.!iNթgrBd:5WZ@0B6B8cnKj#ߜJW'.f1iT,s֪HHXps*C +rN שӚHbtW{A tő~C7im 4coZF*aSnofHZh 8go$`f|sZb4 yw8T/ |߻STT<2E>UsцxI O2Y,WJR<)gU9e?9 ϯ^ i׍bx+=ZR|6 ف#NsR-eHsYP%;R3EO7 mqGL˱r'";%"@O {:M43 Ӷ:#Hve 2=: ,E(S(f$A,>jլ%"a1p>8?C׿Z4qwc1>OӚ˼*$fH\ٌs޴UB b2_ǵEhF³.Nxqm̅ı jb#iqX H"K4i28W8aՐ o#Bppj/q*PV. 㚲%)rn`=AqqB*tB?đtG*4y=R˪hs:viC.-~j?-#ț6Ldgebf2Iӥ:6w7 %S-?TIV?xzxJpkh~w#&l)} ÞGёVt! x8K)`Y'g\spb\yRG'=G)F],y5C1@QE$pI!ǽ5!Q*}(*en]qLh?dl㊔ӳp{~5ʹI"rǑێViܱNJ;[ϵDn!Y#+ o'=}ƅqDfH*򤷅Hܽ.'W$nd'+ p#,|o?Jm)-HctYdp;\Ȏd eI#sBIIS 8Psߟj̗ *FŽ:UCa6nTW!س,|wg\Ѣ*,y;e@p;,HXa ׃~EGՔ#9^'^(`2AYMqŀ'+ Hn(<,]I R]Pd;OlRȱ; UxU~@ba#I$i#VN+"hi#c;Fֺw*)H߹CׯUn k6  Ӟ4(XK$@xfz3$Q_YGlz<ܫ5%rZ6嶈Ny2}jl+K,+>ONkh0o{wg?&r~|uY.p |V1I<+G!sqaBiʃϷa!bo֕ͬo$2d+vI-˞{^J;IG^:WI]J^y@㯭aO$<`KIcnN  ݙG"ͺ1m\=cC2<$\ܡgV\2y`\ӎJȺkO=`0O'HnWN=3X(׷ҷ~[F)@!skHafTB=G\v`H-(Q@z4  n` w=k V%CG|Ӧ+- au cLfz7oIn"L-;gXT&UB3瞽k+UFUY#9qivʅPYiNqzu^:T݌c@99fj4N2]HF̳9=KEاU;=\6qNy'=0{6|pEB2To,_azm kjHu"l溵eDoʡEt@qp@!wz{c)v5$&+l͸L u<1"hnӧI'Ye|2ۅgNڙUCmlq6h[ej#۞Uì]\USy`VthJAPp3\cP}.m21PO=0 }yWK kQGY8%OcX eyeXы #q̷mǸ<恩w$ B^MjS|,}*ܭ ŔQ(DtY>\Xj,.4v10o݅\_jlr$}w;''w`i7cS2q*C2ҩ^`eeNG?ϥAo+>["vqjGx'Uv1eS<:KP-Ev hA33cϭ`Mm/ܸ8 p1+%,/V,U,ЖT71.S$5$tBJQElQEQEQE QAV.iޝ<2Dikj08l`g]=ZYSx2ԭ:Wa_Wq\D[iF3_WXv1lsNq#aO] 6XH ?\wQIxbu=N α;,(޹s3ϔ ׯ) l&#m6FO6{9T$K]Ho }jV H7"qJ.bA'֤Qw5ͶB1ji-2Enw)v|tutSٌȬ+F"6pץk3<>HmOֱ%y$yr@O>VQ#^3JC@bx}8ʼ# qD)܃P NJnV 8`] ͓)P ps}qZ0X]g w#ULɚ6P7`te$8Ovǰnfm;4>Z4iLP2z !;N~d׭RdVܱ>>Sӌf4F>dj3A2_6muoz vD fve$=d{~NӀ?*H2އ)oFE&D!W`AYJ-MsHв9 aH@ r{fQ 8.IEG]d#^dQ`d0 ʏ#rzVdK{o#ks~oNqHv6"W!Y.p?Jć،Y|#]%݆1iC*8Uw?2A=?* nIey8Є}WHH`\ӚVhqM9xޤBE"<I#nw G;TU2(O'jo|LWIf0cc`jqm&A&$q6UXCyKRO)H̞cyr> H&H 9A2H1 gl6czԓB|C!;~nC/eyHJeC-z(1ڗbgfT`҈9OSU#4LTp1:)B@O=I-#ilU* IGqF֓ޤd rp5,:.}9WTbc=|s@WTj/C`vb v꿎t>?+{~/$0HfE=c;ߥl}88vetOž|1,ENc[i[ΐ/\W?$aGnycr{,ס[Ev ӝҸrʈMr܀;+Cb#Zjf_3Ά"tgf|J˒xyY˘GL?Z۔y=*HFr3ě3btF6aCkӱ)$)9BO?nsW$ 0@CS#Bb+#ač#M|*;b,q;>T³n|#fU`aVr@Ī<ֳomYXTC'\.= 1(].䐕YG5n;{d&̶HM=Ty*>>o\.#-1*3$ScB'`c?hvኰ㌣z~=j-80y_>89Xtؑ6CcׁgT'  GWb U؍Jǽg,I= \Q-oyW2]!n_`8ivgtD/˹I#4EsyF%#*O\$t$WE+2clN%̊G#6rcSu v)1qPCr#߆V;;@yK;xc,lwGlHoSP"Ih!{ *Ʈgt8\) T, DwzRj3!q& zu?CgۧT8 n1 V Tvno+Nрzd%bS^K UhTvKd֬O2)VG~V}X0A0Ut7eSYJ=9Z;K3ި~T N?W-'iRh>O2?yeuRgubN過'yn=sSqhmڒ!s|]ךגSnh2\`$skj'%Aw ~2mx h\7Ҵ,gd##j&t9Y'"?HVN5k+,6€WZI{X11$,kTDJ%ʠ+՟ʺۦxLf1s` $Y7~jK3vϱjDpf]@ _fC.⾼w.e01" d+pZִ>W+H%W܈f8ryƳn^6f Qǯ_;72w;Pc:UbjH!ۂP23 DksHI>X$~8V)# O|wִn&Iy9VY"ic& tmS+ ܊p~HmVy"\qϽE>$^(ԻmPFs':[WE.YՑzOljY\,`ʈ^{o*v?~%xE;ieS.Bjns16r 24cb@q@r6mrx~J[v N)USihH=܆{hF4`˽SmxyFsFkO=`/ 8nǭjFg,^ib9 EbLwIUϾ3֭+Z[]>Hakxxab?nlM2w@TҥKe dw6+}1o,+W'cVE4fIc8'D6qE+yGU엌J0ĭV_rDMj>WƊ I'6) (l}}S( ( ( ( (QE%_>Mo·2!_W'˧R]E¸%oF*?1_]SLIxʐ?k KXeaBNw|zL304ZW=}_0Gd4'ρ6I?JXYxg&[.^9f68vf"P[ 8Z># h.%++̌on~߹q ut&[Ww8]w&OB*$kf@+9-Nʹ(fJ?<֭Cts*#c=jn O5HpzzO$&I. VpVV.A+%ʡݏZEiK0 䟗>>˵,呛!Ӓ3>յ%ٌikA'id>y368+qD#v3&YqJ>ui'USr?^kϞV77RMm/kkKvC &NQz`*!x.1܎6O=3Z6ۧxݗ,@J6떗AnPʸL?J݂IO:5V~L'5iW;#nnEW\{m'YxP;WH(-+;HqHSBySsv+oZŖyt^Vc;dT 6Q7iϊLM?}R@W#g5-󻴪I6e>as0HIݑg'L%Nf`o˯I#2e[UV[>[ppf4 0zO-2Ju4wd q~TŹsehr3fY݋,|3='ڨ#.y6'ɳi,@XqP9°gg[Do$I&4rqִUZpa&5y_cfMݣc 0#I.2;AQpddy xgAq3;; ̘϶JTw2$AA;5dXa.@y~p>ǭM n*98=Tڡc2SHn֏ıĎ_Z3)lhBi`PJHa+JHvöD0c(9?5T߼mCǩ>kI.㺺UG V`hZܲaATazf]Ȯ*pܨIVTd#c}ETJciZ"Fzf+ ;GpKv vkmGey]FzQX]eJHOҬ8Q  n~}*CzO,=ҬJ "L$o$Lm]Ӳ[,?1t9qh .dQaڨn*+0 wnG=OGYefoo9d3'$x2g[`4lI[h]: 84IaIprJ(v;8"exc02K0ҘO'<ӟUV-*\]I8_\ @0a~e*,`dUY4 ɀ$nfːB ~'W\Bk7ÅL|?ZnV\2{LuZY5v$xg!KϨH\,l_R$4SDb'9r8eEU09Ҙc`j[:)#= wMĝ>g o] ^x KoD?(*- 7\uKLq:O6Ҫۺ3?x=%K] Jz|гDrWġՐ'+x* aGVI$}v1y6@B3EVr& 9e3_mqIOt  bIP]}^.#܅}js8F$<7D$I9  zTt82YjK{UhcOo2q wGnrVۣ̓3ƃj`z!(|Ʉ2' 7>*K(x.0m_Ϝ ve v $Y qgzΜGLg֦ Hb3.xJgY MTRg"a}F=IhFhx?6YVk, 8g2@ 0>#Jb6chrpTK\ʻ@T8Gҥ9&eq9_Ƴs<yv-=VsF%h/!ʟQ{֓Dc7ܮ6 )V'qM[0h`d}RW,̗>r;ϙ{#wPM?wYX.%50U^גA#J|>ɘ[FZb>ѐUvjEYeck$'k94I`m&sqSG3ry>5eAD[.pG?gH mr3W^Yek5}=!NN~nֱF4(2@cOoZHmeVqׯfK&4|7cۉ,KqG\#NR̍ ]ߙ9 1[KHbock*4,@<֭B_9#+x4lo60 &y;Zxc.aU3Zg0boFa5dN][?!}Jݫ$.cIw2>I11TQ~?JhH>WJ.f{V%,I8yF^h6h~uv>{VHeY&m_562:BHCT7= Y~ǔJH9q+SfDqT?ˎIUfUk{\qs] gUs Frsڹdf0 pGZ]qs>j,~ѷ,Ö?_z;9i< OK8 @Bۈ۷sZDww='+~3OyR.T1hd^M@<*Ԍx Ue+-"(+l26,2NY(GN#2oĩ1\N⸒Tyܿpg{՛ە$R-˫ee=j3y=ۗRs{Mً'N$\ T7P$@^Y8ݚe,[AcQϮsT޶ý<3V_i.Ȼ<>09qj8T"Hy䁖eEF^Cq܊rguVwj1G_βR33p-1R4h91*B?$#lop==TΉ7톉,~9d[k7I(‚$Ll{&HRI~x-qwSҬ3%g aMQ\;m#yNGCAHחIG_4{V"^츙E-w#{cE\{I.0N$ci#ڠQH#`r<{g(> >D)\)̢"0:}zqV๕eU0Oa}0}..3wqnsAZ+o;tEGm flћ<\k XeNnPM"}b`c/<`dѧAk5@O033:㏭e=bND൙av>n ’Khظq֦)w[_ҫM3 70\}I%FT( ( ( ( ( ()-J*|x>+3!x#M{v@W |RV-s__;Oc zֵQN6g:,CYx%v+O f$G;ʩXax9_o51Gg$kɞVm6}Wם+l.1bpЯuv߯͛+XS'rjT$(W9-#9<5[q Fyf6ʇ6)xڼGS٦ Bnu\*AgP=1w /uSW1+\hE< hgQ/<z: \Z!rDF:t]5F rm1FFХ5iq& ,J6>LՀl3D.3Ry=k^2bUm"Fǘ^ IY8 A?Zk?1)•gL{}+;n&uܤ!¨w+ۿzѳg Py19Z>aGn 90x&/wX^Luw8z59R?ڻW # FxH s^[i<gh啎JJܺ@dwiT\ǥqT]ޠFf ,I I9pNާ׷Sq+P=*ym`^s\6/\Hcв( qDj8;8\4P31N-'cdUr@1**jG,SңuBN*đ#֙l291CU.`ܪHKۆӽ  +pᘂIydf f;7'2EWWvf"`bA i%͒PNZN]ѵ U?yYXqIҩGw訬QzԒ (TڟqqNKZD\ř3Hor1#*8oJ4lmH|¥0`}1p1P UvCH>OW`bP) c<{δYd7$H_TYJ'*CD%NwdJE-EJ LeG埔^C2 ϔ.G5U9Z0hت.̝1g7)f1g%H}K,Uvgb33׵B6$2nvlgCNŢfgĄNG :Q[h9x=sG~0gc$#Y]#.:g HGDpCoʖ(Om)fcp*F) cPE2;2 ?*``$E耖 <,I[#a4 A9RaHO==eybMo8[:S%v*K~cҐDT*xC+*`Ys4-,*A#"+xI;+?q\ 2`ىH~ hr$JLvc] 𯏼kG!C)9ϯ/,b]ow| R[.KYC$* b; ƖXUݰk_źiR!`61s1 `3@m_zUIu(tJw.\Bk:[Wjt"UpB˺eI1M*{teagDcS }zըGK.?Te”'}ā܈4+ L^&");eȒW-pFza:#9J9ei  ̀u8npbXOc\Er(deghx ܝ~`=})[`Y{Z|lX@>5M&g(؄Z ˜LJ&GC Fi2&d|U?,I.a { }=q֩ ervV,RMx?wȚ%+;4;g$s4R$bnJ4 \+zdTȯn&xُ%5`J3גA$՘G%w-ڰj}7)_)gi1" )ˮ|LZ1\H` g=N:B_NSL搖$`Dil7$z"lFAEMq ɶWUiVlN{ViψĨ9Ta '^( ?k>IR' XS0; 6$Pr#9eQ/܇s{*Cncm 7uc`DbH̒N2 8ոJ cl\#=JK0 dt>( 7 1|6O kMd&+xRTXvz΍R(l$!/mKa`/lZs\ĖD!+0zIɗ#i~ƒ&%&vojH6[Bk29|@!R~Qzbi1BHo9OӜ2;XŘ8]]=sXEbrKUq54oM1(Cry|1xO0@$s{b:c3(bC$U$۬M&Eư!'KyB@8ǽsQf嶬G db]ICSmA#K\H?jdeJyP`N{<,`|0݃c>55[7X.FOwg8=ɞwB 1!śp=з041chU 5Eq^:vuYR[PL7' -bxBpc]nwmssrg6Kx~w׃Zw8U[Wo?^/.Gomenv;gzuebn- 2]6N1y9n,_55aycֲoM-ğg1: qlÂNV 2y{fH;6`ӎۑ-ܼ2x,|pHdY# R" u<ӞԹ5\]Ē4P21 qީ\Q/lnU'&2m!qۏUFsN'G^"l\{7L^)\$b#??_cSi;,CJ{})M/:IDV##=jsHN$DÍ.+BMF(ͨBgnjsQ\Mg,rB6JtڥB 5F4Gi_q< e=pZg;1>[9ޣ<I(뜞+nXea$i%: Cse<7 W8p1u@L\[s6\1לqy~CKhݹ@~by>#oWy!?.{z,819T{56}2^[h>Ra#}=jX/GkZ eVܚhbρ# Cҏe<2?*baf퀩$GjH# e3 KgsEWEPEPEPEPEPDW} 5U^(`jz y`WOVsp\ےUx+.44# A_P?5-]پ| g$>aXp$G}3F8ukm>[&fؤn`^#U<dC1^yqqr/C&p?Ho)] Q~YD 8A {Vsy~JGFœ*ؒeEL&qW4ccu;[@Wzw[ iYc7H#+EbmZHҺ<9 EbN- 1 Oε#$F9 f=s\twjmw}+N*P ?GZRz1XX 략 =0x hN咨˒}x6ٙrN@+w+,v]}t7 ̪:U[Cpcfe!Za<7Πe ݞ(cO}qԉ7&#A8Ӛђ͵}DBE u*giH=U$̙lϿNOj:it碟cR:U_& DұֲIQi&0œ{t%"f;FGj,nHyP,('l: X@Ws/1U51;`IpP6\{dL Q|7~`>Wom$}NI) $/یF1uxCmScB\'˻;v0S9Y%\8D82H X?)J&61;I8yʒՋ*Y!pz'0#GsҺ n]S ȏjIhI'Y8;ʎ'|VX{$X>AR0Gҭs**VyP28j刓,(~U$~G4R0"6OnF@+sԊXF@[ye=NG9WDߴ`9*mif ?,JCW(ȁBIL1k(i0vJ4|\qT#:霟j2Cނu,/bI>SľaiOv ,/oƬ$!Hݓ_;9OG&,A#V$eOTmFY23}=ݫ .OZd3'v},, u֜PyQl)ti6G@e<$jj,2BʲsUلm [!L" (T?Ks$IW(PӠJjș$FC22q, nlgޭ$hx6'>4n V2-\A܀px`_)0g`?ǾjH,G'3.@zScx<geBLʹ"}*I{Yv@~߀-ڎFݱC)8Nq0hdc\lтYdpSBUk2dq  1\G87 qzes.d^ n<aZB]r}1Hl@[8msҟjH"\RN?S#;ҹ7UlqCdUgNGS}QH|23}j8ĒG #"o!eW$ʨt=GqV6ic%_Od(C,;6Ue)#i^w^OjWTD v7Oa^H2(:㿠V.+H5b*#.?ΐuf!RK{/\A,6'?9Vd_I+=a;Pvu5ݲCrc&r;sӭ}}X&~c0AǦ| 1gt1L$~^^aU y~??W6t+sN2/wqJe;張*eK|6.swKHKԆn)d` u瞼Tp.vUq]v0r.J#M:9{?BfGɂI^;~VI!;=H5^ ([z>쬀%F"! ZG皝7LyWc 5OpfT;?rsǧz koM°-{u qE$YlbySeq.xnOU 2)lU,$Įp7 1'id9/0UȰdH ,!B%NQP`dqCq0?{XH;ݠ1[y cuJ6YngqVegI3yҳTv|cy9fzR8VRKAʐ6m N@Qs[.%Fb}ha(6igdVtnƓ̷Pҳ㑢(c2w3$7y( }zPF6YY#B=9ooaĞxu;d87<t'ӭe3Zg9jm2Tːc] YF6p̻}k%$YF¦ fcs޹hVgTmp@篵Ti!8(\zgֺ#$ha =tvʳJW`*u V0yp=j"y fXLw>դ r8edL3/ʪ}1y[&S8x@XsHȚYV`[9TRY.U,Zo\ ̇=Oqp ϰ~Lz{Bb[fU6 HpBdz$xvBFO9XC^g_G؞Z⒬.$ČQzDN{<Wx.t6x〾~5tʼ# %AWS?pl#+\\AjZKoo-R6~L~sE\[W!ҹŗʕ3q<2,[ȏ*0l|׎r8c17`Kp>?nG8\}b'y. xGpr;Za>\dۜsS%pW)XcS!I?^ɍ{v`1S<NG @^UBc\m "]%fWwg[j; Z#-VB.yxZ WYda-82)nqM #V3ȶC/pN9#Eu TL ?+}9a`HxvD7X?XU_=uP9YI-^f s XR$;gB-qC?f/FFF/C·7&Hkꤨ,I\۫?>BztM fN㧥RuQq$sG.3s98ͼ^ky'd`|xTEUe1Ucն $g#3yp;Nx$?/544"u\'΢gKWgRp;ΒՄ yOGp Ȥ#iMZ*!R48G >SՓ+:pF7zg֪k$,G[Aȵb)fvE sHco29eLA˚en󅅕i㚖YPtU3p= d$#)_֬+ȂX*HNzsT3,bX$=ĩ㊵YEN˒:u)J">YFGj[kEoV?#7994ƢB8F1۟__𩄱M4lfpF=^[ƶXNWvwؓj)P9àQ8N,{ q m7F7}&XQ9 }GҬJ?ȥev$!V]Cb'F0;csҶlHvblRq瓟ǚYdq.c'' ʁAD9rK0j yH$f=G_I&Tfx`[A;P!}$#L<[|Ӛ+}>ЎyG;l Ǹ.pMQ 6.y3D3JX' UbD7g>M>]J($\@FF8ךxOLRohH;P'}kѼָ3LrUMIhIpM{N.t*ƭ7f81hUWL3ƺ"}@y5ߍdDlO; n来G=aĽDƊ#f-8_T[I-O3IScGRV S6 [u?WAc(LƌS$)㎕˝'1g}ZQtLvT+JϨ%WDﱸ,!?E3przA丝vog9 g*M&0waJBo Mn #]v0q-s+2mf) jh`QE\`֘؇x20#{brLgrc>+9%krd6w#E'+os. $L yĐ1ڠړ$Ad ۋzj,ӷIfxQ bGOOQ϶?:͆HhT1ڹ\FfT)O8qc'Ί;,^4m׵6YR4\nB>Z\y@~r\B#ėvӶFWp\sU-$mĤ>g+Y vA\Is%ĮQzI$ oTՄW,A,QG$3|sזӫ bl=+^8x1'֩ݫK$ 6OR9& pVypx'X@[;yeF?E?xq])Ņh/=?fK+:!'+ BE:cP3kp,tB̧;ƹ0#HɆ{m:0U#;PpB ?(C)Nb#Ę=@֤;&9sNޙ{U;i-b%JȌ1cqӊC.ȣ80r@{\ۊ) !\T|3j!VkVyP0!IG|V]$l1?gڪ1#U"B3sǩS$5#6u %[I-7$=,,$yex̠yk{{yLр oW9(&uf}8ſ#ۣya}{?ZF2T{h(T_^{[4 kgoީLd LlM8AHU"B@|,)OoaYaEoL,(;*u_5TؐO?qm.`pOڰ%/ F&h;K5=Q 0 p}V#7rtҩ]M ;}eL9_6'XIn]1ң&k䑕&|md@SOdgEk mBlUgdxw@@H̸֨*y,a ̳xD yO5;< 1b\y'~#Jmߞ3J_Tx"_> ?+kXgʍg3߭XWlMYX?\uV*0GAKv5l,68=g$uӊt?9PCyz}j80!yYNx cӾF9H$stzu K-si0pMRJeR]H zOjj6"#þssLN7ݤp=ǐL4 E/*haV(ll TnԮY~6=8)a;x T뙡W,1=A4FHql.6e_,rmdT;)wP"EbAsT䑤IanLyJr<ێƐ:;"JwHPh幸FBߊ`uYk [nw`yFwz+ztVTlcΟLIfb J撗cnU/\yQ9 zcTd[\AbY$>Yw|0?{<[i.mAI!t<~&m4ɼ9>{/''/[_-WxSд12Q9o:W"XP:ڥdӮ`-w"4:#t9 rI/q}[ky捊P:w5^ 'I&8Wӌu[B-F+62GQ^E;3F~E0=3NQW?j(t@B((((((媵25[DݎTzֿ(DŽ|sz K_pJ2_ԍ). :J|?#S[`;APA#<ήtһkv8':eBv(:<楎B9.dp8<}01QK 8?m7prϭ@n#$]_<ƀQ?1Oi* 6g7.;V\DG @9qہ*Y.O.D >j7kr,`bɅjhv)JxLѴΌI ҆FH ZKh},O͟sOjIq;L|!x=kö!$g吒0U8k n3y$SɌm'ǥkN|mdm\O\S˖&#Ҳ0URmj@gyŃ( \)??s+ $π*fVmOiJJ{KےsOz-cYe_כX»&gwKyKDCy2O>WLLk䰄%iGuqlT#;pz?\6IR J(ucV{F[ HmAҮD@7p׀`Ac w +]H: ܗwD2e'%XO@+l2N۟i`9.Lq2OoP9haIT;$@=I Pew,u9L[TAs1K3eR&wǽ*M8y#z4 1&88/BL!?x52bExa\lw_$=Ip:gn˂\{j;m˽(2\n0}ODUkN$(>'ď9J9ib)Kw!?hFMt, $VX%|3b>ҖSr4 #U@fʌTx;DS#/ܜUQwF2"H NW'%WqҴm \0޲KNG&+`4dq[fǙ;H幊Y,~O=)wfn1ڕGtWg]A},SD1|x 늡H :wMM:SnRycYrf9`'n=sԉANUp}*NmUNNyv*.&P''I" sNg#"_n[皨ʘ&kXrq)Ь(e g6G֪wGl0F󞿭KDĪ` v2Bv߷ m,*|m' ~Te(vJ&x>&dh_,6qg]t$2#NKm9ϽHSp6K}mKvgU30:1Kp2%(r ~8+R[AY g9c }1T㻞IGh!ڠ?[ĉ)^jxJѱ܊߀ E=4dۦW%p:vRHiؓv99Rʸs swNٞ$a!۴7)?R?*THU(;C{}>)&Rce;Srp`A*ߨҐpZ2&r@z 4wPۜ]60Jlg*)0w:~2B\*(g pmo3Fmsʌ^=Q@짂=Lf\J~]ׯZ!ds39"/de`qӡQH$Q9vwcI)_k E\~2ϖB Vb{cvgn?Ƶ%|(ymܫc=k&)"L> ?O_.fL!fW+o}j Id|$JqlKie%(Idߜr? ӥƣow TG첅nU뎵! ci։13 ,BpOjŊHi#ee.ߏkKK~5UigR:M8eǔv9]#cM(F 9i|Vĸl9FWV.یC P21b$x8XsS [̧vpA9r)E& y] ogEgW1G_L\5M}0OALaXHHǙ!.c/«ؼUa!HǧjޝWQfއi ̧gR+,$bJǷZ΅GYyiu r)^~ \Bй;VNu^3=y'yd;> |XO6ʗ,/ӌ}=:;A fO/2nsJ-Cs+n6c>N!<2 jۂg뚯%"c<0 r95rM2* k.G(mMJ,$ j!g{|fJIakӜUh77dT!9z[hs$1a{Ǝ3?=$vY4[qPqzcq|_A>6 RbHռA9?t{TyrM-!`[ڝ,P.m&g5U^"02,7 ^}=H偕丒Vm%NW+"Bc%ӄ9>sQGk t{U;y^(-n]vNy˃2' 1- 1ܴ["%USϘ8miL) 0:ޯ'R&k(8#`c;s[]=1-1x(۸sZeihפOK ~@ʦkuy)#Bǃ#U:HQb*8;GGsWo.8{9 ;sXvlHr.d?8]ҽżp#9`;xQdr> ҮF;kc 2p~G;y ^{gyOֳn?usML,!$lO&YUdX:5I]w886YǾ]&GO< lcbM.tOkK;Aek*\tۂ>Zӽ$Hs 1\\n^k1i`ѡGIl!%n>KCV%=v;v&Ls+U$xI m9SB1n-,3Gl|$;C N{$gt>{}Bz{s+>#̐q߽b̓7 Ѯv3ֈ2mncYO5ҟ:/hh] sӥA4i6׉Z<6l.-kʬ|I O%*ôB@Ws޴9z WYkܛ╳L-'%=J`xTz`8LO;3#X)\[}I4,j ׵La/2vѴ6v%˄bWs5 "G#|@jM=( ?Jd\\w1 8ǹRRE%G0>͜s?J[+!p X0LIK -2y5NKin$(۰<YM-ޜ:U`>q.Hp3*:Z]x@ېPOQM Z:nV$g=N=jM\ ,Ӡ8pFqOҰNdRIŻ10{󎵯u91I6eiT'!͐8 < n.Ч}qj4K;IKۚ.Ķ5o^sV]ʼHZռ7m^~*7+#ArC _F1?J;],jXmPzw4Dh洷[53=Z:yZ|ͥ wϯZۨ;ܐa26Ec4I21yQY$DW-,xjkdIw$㓸dU(Qi]#Ycʺ5,rkk,+pcqY5Hdy*.q{jmD64m)/=%b. u'5[2D5XMN wo ͯgܙی[4S5ɟjNDazTI#V6O((((((0HЂ5GJqNmX n8^:GX.Auf_♆ם%gGK]$~un)vFTÏ2eyi! HML],q39`|=p&H$E+usW(3LmVsa~=G5j[ƀ},ilIg`$ u5KEmYA8iC"QN_Ztv7lJ?$,=+n<d|xؘ5L I F&CHҴbdDL0'=}0Oֹ]:t#J&GV7` aNW$=y3ZTΚ5YDJK mGcuWjleWZ,:I#In󐡇?CޠB5Cn6Xѷ+w,wJ@'HT\|'F+60z5Eʯœ2?V8r)`.}^C`kXIhYK$hrxTs֯RIr;+>F!B -jJŕ9Qt?^(J$lfr܌`~Z%}rc-vVţ΋#IfGUxx4:YFœ20CAUșc }ZR"Hβ;mL>Ҷdwڗr62{FZu+1 OZ·hv$չgugbyY hP}x#7V$¥GS#2p?I' 0P8+o-пC [\6mϟgt­YTJ@m F7/:ҭ&Yh$qߕ*7׹T)%lF=ޣ'1N]I bUyz+2" P1i P\3ex%_8m9Us慷p#y`IEJCB"P<, ӭ6(W [>rWԱw63H Rgc)PA=3ԧn|[T>}8PG1~bˉ `:G^: ,ܲ84r#zPn Sz{w(J8}sϵ=΃/1?(#V6#;|g#nu$86ՋxDKo?O:ȭo1$ Wf=#ked25WnP6qzF>l$% m|=jz@шmĖINn)9,C( r>֋RҤk4 +*[F\mr{.in;4b6@zTWHuhv5| çȞI,) #ϥMIX`^rBGaϰP{22.?:F2;fYRsdA,ʨ;ڱ pve)+t&c4K˜gtVHdmS4XȨi0pϥUOt]Ql1K-!:@`34 qNېNrT{tp3dV,ͅ`*Y2*+k>cG'yG9.e1Eq2ʠ.z~I|(b_;OS DtgdԌ~DE~J'5{)q,+A`qӵA"fx^#Ңʤ p&YncG/,]<) HZF60`[9XNzLY̑L|?֮1mk0kHyx{if`RTlFbDOҳ!WYD p8}VVsxXxKY#6`#qU7Jw$?/xI]R n99WAƍ \ 2,ɍ^UBkRv!VEtφg7,TCpF럮z+4;LMl}6'Wo &fcíE1R0)w85 Dv8JꍺKkxas,mi@0Ax׎qd&)>\cՁ+Pd[(c!=S2%{fx$A튫ib۔Np=GXY'Y ǵRӞ=85%dKql۬KڅִCHLiռxHH#q`zz.-bh.#u2OYҙ=y.67JYק=j{{<#ď2j_hzg'WBozMƠ;}q\7y0#&T=2HZh=,U][F; sz wgP7)U%,sos&:u8gʠ෱=oZ\K)eXRT# zs\5NNSV!:ɀTz~Qf5r#C$m'ްa+6bm@R>NW@%6؊d +HєHohls-HnESԎU O%F'?3mPWy;Y ߂OjZxC"a%}AKE Ve s)ucr%e!W`Ԫ0rOf4[3Gp:c᎕!+xHEmXcJHlfV& CU`]_0iC +%T!!T,3r:RnK\F7n>cjqy]H7E;@#: [itx# 2?f8˶L58&l+?(1ᑒ3Ȣ6*O(4QEEPEPgQhY%ռоƍGkz^ x)sϬ&PkfYo^5[qϡͽYk$_j!v濠*cmc$agʕ=:u_ϯ^\WtB}'Ӛ84_?C Φ5^~_1_$pDw0OM\Ž NŔfpr,&}n9ɢ'! m#zU"~ t6ڲ2,17v`Xʝkv{yYv]gi<"H$eT\7P8 ndk$s^xY7+n2g``1TBvϽ }W4j:Ȍ0}SX7K0c X\1ɲ}AsR[ʠB #5D蠖c d+8>c#I 8=qҹ.N;Df J[nNyId y# O=u3dKycU<O#ںKDÒ3ۭyإNGZJ92+|s]jˉ-IsDŽ\Fz)Wh:보Fi" hT$3WV{ "L6>fd2H+Ój nM >ǚݴY<ۋFB΂I[q ӎ÷5`fT2GܯP38R sǧ 9 s֤lvQ0p4»eKs#c0qjifv}탷LbgF&a2jԐHm<z;iޘN2)@f&-NsӯzbO a*OjX#( .3$q[=m+WF\=+13E$ZiGޡD)Bqקz|Τw*fGLL6?/?ϚQѥ؇ XQqG4~K;*o.%vxaOnR)6ʲ̜u_ζ6ɓw;b/r<ǥSIcs> Ir3Q'] 7mb ` t-䈄n=rxu2i>^V*@Gj]Kp.FsN^2cc$?1{71ĮZQJp:=G^K=O䠐AVhX L1NXS#r3nii *|3|1q(\d H /OZT;s:.ySLjl~voP,W1gbcV[RiX0i2I]' c^(_ 1̱4_CMWbIe̍cON/ ,%r8=40af\n-9>t?q+b8G&8#x\x9>7 {TeN26qVhdC<3g9cvU>\IݐK'\ NF76Ӄg)\ ~BHs<rɧDM$grFq1۽HI :f2HqM%tNVIޠ.cEm;>_\;~uӥD̊Y|4݌=+pHwGVٺyLς221#>l62T$.?)LZg2huhlUW^GUvU#B-d")ˇ9#=z-,n:u=64lt;T3AZd]3 #b폐 G}b&/=#'2PP^4囩R99^\l0D<)$ *`r .i1*M(fGp3T$y m0W~5CVZXOKx9o4260n*R3ȱH.G`}F۪JwZy!ɴl=xc(u F`y>UdS%:0ʫ=N$ ޷Cc뚸-NY= 3XxI8~_)v'{z|6 'qYwREt]NY $%5>nNK,gpyY|qd&$-W 3?)+3*hD\ zWȞ*@yr6OWWqG))*d_|>spj!%69uQѽ|eU?Q[]HB =u+xZ8Y e9Oh[ʊ7DVݝ1}*5fP !Ʒ/u-W%p%NϢ1,cXMC'$e.P9{ס]nR&Xǖ$ӑP?5CDW9o~_cCإ%+uoIJIgyR \neqM2 #I&h5if%a#kɚN o,%X v`sqj%h\=ujI&qӥS&FaIןODs;kGF"\G1f>J!E,douK+De8 d3|>p:g<{+al +'K,q.^e.bt0LHzG885"I _"o}]JIAj݁O8JlB fo?pI#9ڧ8 yq1bj$ސ&)&WaS4R=td ,X{LԏkNsdT\3sc5uw ])n w?ǽt2H>qq5H;='rl/۳>ϱdieːGˑzRt(pŎfO~n2N)gGy'wKu BmWKL "@Ɓrf xSo J#dIC_ךY_9$\ VDe ygkY+՚UvB#9b>INݬpA9&4[o3I1tI/b *̎F1GtZ}U1E` dfC|lKد$c]#5+H&xn'ð{Vi8k=ԹM..th2}˻wY}A%@*d(yj5K?}ZWQ5rnmC=q".tF`ɹ9ӭRUOIIvOu%H#᧸ýЌ~5qe!Xl6rJDI)dH)3, mۜS3ZiY[oI vH<r,#(Oi)^O. Aϭ@VZ(s ()H&QAR*r^*2`Aj(QEEPEP)-R8 qSS]w#8e{S2jK̚ phO=ӽ*6:Nxg~`3ޝ5Ӟ|ifaDpUsڿŖrk[~2SObA/ϞDٗ"F[7|g>!Kne sө繘FĀ Sv:Z xYg=Ir[Eđ_- Jp8?jmq٥ͪ>O~#w};V(wd{㰮ISؾkSP,1ύ;m߯ӖIX @;I5,>btjj`8:՘g s zt:E-3v98kv/&ݏW +Asg]yn̹tgG?c8tXBj`&EamWJ)!-"o^]TwS:}*2Z4y>_ǸoYJY[V `࢈I'wۥmYEϵ G+<6HXmYK~XpYzʸ BIknC8gf$+HIA3t?+f@O+B>6"vߴ#ï3(U ' It#F"SY6"vi7;w?_I3c ͂Y9SɘWf Ҡ<OzR-- QFI@<ϧJj\UF#*"vg|aˌӧM̎Q=sAaH7|FX8OҖQ̋5桖&xVc"21on߆),m~I轱jfۉD-$K\d=Hf!duyr1Q3o2 {Tec^EV ^Y%,2l+N܃|ߜ9UiaE93zU]dyo%;x1=-E{h*v|gTbv׭@8Pa‚]l-0"/mņzSFWȪή!Tr;ZHȬgp0K3zCn_XS NXc?>bFl uA_Oz"_|a!zx*e^ES8<%/vFC71~S csH"UkG=eU 򝄖heUG2+?QOJ Q>SB%qSAq+MOB>Cc9#ndq2 \2pTgTF4+H;A9=B %rO- 1F*( pve!x< OץCp m#ZPqu䞸#Laٷ8gV.bo;j}G@<0BnC1sNnX1dG5qdF=V6I%P#9,h G9r['Λ5<&fHS`h9 S\WIFs2U^~i)+IXAOOOs=x(2}pY6E|I`'U!FBLI.{LcR)"#3)'ʴ wǿVk &81 _sWp#`?Y~#w `v_b}VEv,F۶fHvA_q>L{ ,^T{d(TRHv=9-_z ǵHmte(ٌd\w9UfrH*q)X{6*Fqg[YBdyLHϓW\ G yKHܧ0=W]302WkFdz*H$Sa{sBsHާJ|THԜ#=+rr Q|JJ9ڸOsF2aJmqknݣr* `VLsE#1d̿7_\^*u#8;5X`#>xV6c[WBRd~Nigi>|ϔoL`Z?$x:prw5)q|s+/"HST dM4?G42C(ߌdGق V;vl4ZCc UYW${]UH~ҳ.֐ eIa) w!3ִ-^0.\zt12q&ŶCnqOLY}RC9( w r3l"H|i,I@T9g(L9x'fʔPXsw9'8'#}udd7 \CsM 8U3++ Ie 8xgh# ^qךb pے,w)#sK{;]m*Dq(1륎v}4e`ІG9Rɶybgw%~@;dW"Y}eҡhU[6rU]\+\He9ZЗ͍ϱcf;k4Ak ̭)I ?=)r)`ݕ'm{*C4jo#[E*!0s 9qdV}r֋=s+  T;`uP\Φ)Z܏:8: CO:TרTYeD&n^SCR[/ qHzzu_"SJο(V`*dl#ĘmӓeI'Ha!w-`k65!l֢D^RVr>`G~l\DDIZͲ4R\pJgsӜTFBSy/t=*.Ij_"H/e?A C+4CUByoP[S\42yB?n6*p 3a'5pHPBީLo-rHBo0Y[e0! >:aTFP'W=s(yؒRwd?ҳ4Юj_c(PdgQF[ntc;`sk ;{{Ro".1LIf\yrĦb2@\ ctQ &K_%L1-΅s8895-9. /][7ڲR t<{VfnYai7B$pqлv!/(2~ږvY"Q:Pn*݈U,fZ- #yķ=s7*byH9_ql{U#R$!fv3YW[HKw rrjцOUf8 79QM[ 0s Q0؝^9!6CyfC砪V,qCS$%?=͛Gk= F5.,2(?ΕmJ)F9sЂ9"E7P:teAT8C( dTg>}867;Fn+hx-/f^ˉ^y\Üh:ki: =OZӸhBchӁJǎYUDo* Č=qVR:("e8nnՌԒB%-@RxRHO4_~nϽUAl=V IYDn3T1(w,y䉮>΁-'OOj܉(Yۦ95`9y^0 ήù$q/\qJиJH u>=H&ۓ`B۶ ǯHѢE$L xԎO4;4130e8X~IneʣF]# r[<:*ʐ #H@!vg?OCLh=oUd=:ՔU `#LpzVemmQm \`By_jK.zR`lO!CG'*\YA墴.1z⠞͑3nM䡍՝XQ0ϯ)>`æ2sT3ǹC:r߯Z#29 @MREHWI02z R  *8+FG]#pp{< 傜mϹiAT,$no5R^ yMy ɟޣ扚'wvs)1]Əp![! cW`H3/'?=h'0%pS[R@$J^î={x,P+>`)yg{ .H c5Fr^"V*O%po$.sqIvc[F,W?wv!9JpO{zU=&C%מx9?lݔF K8"E1L(sfw!3F$\]J,sb>y#1rvԻ䎭PK~KG4mp}GJh/C\Į:NT[zI ۹z^*D F ed©"q,>{l* KR31riVuqxiۣ`,0y ?ҪH&V2ۊm2{ r01I"tڮRB*F,Ab?2_p8fX#y0%#ՑkB$1m1ڸw⠒%x/ap@*gUA{  =[PbΒ;.FøJ{+\#[nS1UUxe eA8$p}[yUpNnS`T?9OcLT,QfaAR@jGFCa0h2J곉7:*XzgB'ڋG[v0#Jk|toRYmPy;V;fpyGWwٔB8rbs6<9cG8fG[p1AӨH[,qV rD]Y_,W<{f?|_m7;vVFXcHs9NH7K 8Wd6=}ycm?s2cw,䓎!c Y>lYV 'YFOqNx"AHp5"4`:~ wqx!QjJ_ ɬk >mx< R?Od،[Ɇ9qҫ겠vloP%].+V>_{eH~{cXshfs$`@V+1d}*XhD63OrҲ4xH.,}+m f>\r::kC˺7LU@v)Vǽ#`VkoA{lc㞼{c#k> ;'&dR~kC 9-&P I=;~uc夸09)ߞz%$Q RB]>dR$1>BeX,>f{Gry 1\#ØRp :=QX+yU-b7F׿5^X.0%L7 ~VQwfq'$9!h67ͳkQ勖s+8GyN9Ud{vvmʨ=?Ʀ6dh;$\bUZA "P c5}+2F7 e= ۟ 78 xZ< "4J,OBZXLՉq 1ɺ8)=}µo›%K2{dC43M4qpvvƪ;PhCݍX}sZ4w^||_jG" }+D*#~Y uyPsa3n t"QGC 53[9%O^Ed0"ل]̈oe/qGqUr#d$F&FmLTBA 褖rx5mв_1mHY֑Hʲ,@ ݈>j{x%]1c#63*:es<:[pw!XqC-밝ʼgc`٭儹3Hnj *-LOt7QuIhU\Wn%YlѲztLHۈM͜ZZ, gLU[_÷L0>H2,,#8 `mD'I'/%~ij% i%q)I..eR]]6ǥ66$< ~&&`.c/H3dסO/1(m+;zS"X- h8`}=M9MM>BƐl53r?z#9#-X1Fzқ$"":6\3׌^jkPK/d,c$2(F^pTyFGL@[+`Q xҳ-I.@=x{;nu.IRi69O!yu lʬ89Aj# /Ts  b- 3wf k%瀖2TgZ&y20(M4ҹUG=PLQJ i!?mB!l=HJd%NZ\`psqVnDI _s -$l3@uI4r+?А((fsEQAAEPEPEPsVE[ܫB<Ej 1S?%w_P]u_h[L pgvB7LϖZ?oca+l 02zS;(G,u*Q W+yolɍf|&5>sEI5nb\$&ݕzؔa,oé#~}F,$`Sc?oZj>Ē# *guh =6$o0F&'`;0j,y0k;?Q, ޠS(1\VxTỈ]/tҳd'ӟSX‘|!qOZYل01f'Y!df<6N=0;4g@Dxʃ-{b#O#j<[9h\UE) 99nJRQDہ"#ws>Iָ{QJd`H+z\-mGc;_?!p`=_]4fm-g^im3WWڠ4t"Ri2qZY cw(5iH$Pdl8uIrӬ5t$R^n"6;#YҗhL(FNU* f#sצ=IC%#| w"&D:Hd2dW=9<65CGx4k)]HOVmc$^lX˟POoe۳o0s@-89FJp m98>YgRRn~bCs];ءI](\2yB`RryuV"Ȕ 28e4)= S3:Kch,ݻu/2l%r 8sQBdWA;?=sS;<~U`PУ,9wgצ?´aBD.ŏp+ t1#+fo5PIq@dT:a33+!$I8>{TnD ϖIJM-vf $€:0֐$O>i]Y$6*?㈩^SODhldb3׽S1FTgw~&*$Ц~QǷҭ\ҧT@#96a^|o U5iщB(?qőai2HoPuF۷[hd/q;z_ʵ-%Ñ f GnzD24o ;BX`0jH3\I:*WR3h҂ M >I0AqIZEU`gF,6aOCƎnTL)R>R;皮m$,xE"cP#f7mR*<#, 8y50I&fd'03*HV'e/=|S t`Ow:tYaaxPoD_7~SfURӁ;֣X HrJ̓c#4o 0Caڗ̊Wmt8y89VCpXI69qU亀C| zg6F}wV.=V1HYWCi;I:#暬'x~dW)&7:=ThJf?3|+'qKnL.$0r9)IWU>]};7H]r C6ͷ$$2 ԑ5? [h}q$ LNd @ǯ>72Ď2-O G(i8 m'O""BpY;qVc{}<6ퟘ}J2$As;ȫ 'vA̙zjn6l+!${nNe48ϸYwEI-Ur\*:s^RjG-Zq\$g~#Җ)IVaO׎J.O3s_\xH7̸X#-v>#%\ƲY!dmCd{}rgMsm'qL.Lۿk3{cU9ª͝q9cymXAп pp0iV!7^wǎatiAN=CbAwdw52WwJҝ+LϲQߘ#i7Uk[YbDa#$#:_r zU[$gy" ^h4 UQlK{whC|h?6s}=iMh6P=35RIŕ%:9W]FnLv]{o7$THNJg^eum @URIjA4+2vUR2%>W\ur B 0:{Ui$"6F'RI`:s1kJxNݏLhi Pb7(Y; ٮm]ybespmAqpGGN+&tREǓv$l4-<?h3xfueIUB P29SP]@O7 *s'XIf$]q rXO׽KOp$%HxoC[pUxXXaIUC+JӀp푞LCɖ;#PSӭTXLX!YcBD,@֌ϲm+m2qRX,$0HxA#瞵ft(N).(Eeܾ]mini_exiftool-2.9.0/test/data/test_encodings.jpg000066400000000000000000000012241316025661300217730ustar00rootroot00000000000000.Photoshop 3.08BIMMhreCC" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?(mini_exiftool-2.9.0/test/data/test_special_dates.jpg000066400000000000000000000073121316025661300226260ustar00rootroot00000000000000ExifMM*bj(2riHH0000:00:00 00:00:001961-08-13T12:08:25+01:00022001001961:08:13 12:08:25C    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222,"5!1AQa"q#2BRTb3"!1Q"A2q ?z-G͚n-@f@bEFPFPFQ(nb@bFPQ(n΅PFj%@sF(n@yZ5E* 7BР1BР1BР1BР1BРPt(# 7BJ 7DPtJJ:Q(J7BB( Q(D1DtJJ7D1DmZ5EPt(XСc( 1BСc(Bt(Dc( 9УQ(DcJ7BcJ7Bt( 9УtJ1DtJ1Dc (Z1DtJ1DtJ=PfBhQ,(Q,hQ- )-B֘FP(tPQ҅ tEtPQ҉B(J1q+7Dc|DmP.\tQmtPtP:%% ƋEm4Z-e3EhS4(1ET(YLУTZS( S( 7Be9УtJSJ:Q(ZSJ7BB(YLQ( -(DhZӝJDhBrht4, %{cGX;Ʌ8N.OcGҊR)(QJ:)(Qh (EJjhDB%P/IL( Dr8_ӕ 6J5J7D̰ (ts,іFG2Yzcb2_fOvJY#$K?5G&--Ќ]WH2ߔy'~{vBŹB2qqm[O֙h(YOͺgQ8_14o/4t]CGS 4~rʟ:l,H0:;r+9c %qihnzoK[_i ԯ8 rfnDN)Lc晴q\JϴT^~&MD%&m> x>Ϸsq;®{>Ǹ:i?n~:}878Gz999M(E|_D{־ê͞{B+n5诏Tk-u15_:u,9gO#kk^gsSOCq7ƸZD#2:'7ZHHt K&׵_#5_u\8OrqjmM;iK8=ocԴrË> aʡϔ4sfLJ4~MrN;>}D<u/s̴3ImrvnO4-YerKɝ,,Y'Ywpa,jH񙧴c~Y ,Y A&tQ_,M,'Ϻߑ Ĵzo6s:gl l3Prr[eyo|*Q>̖#ٟsaH Ly^9+79 ]u]}Hju?SÜڍW~u8tGL❧}߱f<ˎ3Ԣ {1NyEG#n95.MŲ&;,Mslமj9 /=VupV[tQl 5-U:31zyNucN٧qk,n鶗|ln{GKQ)dR+#)w߱էaŨZ| Vn'};WϴuIbÓW_W4~ǃmo_=OXrzF2xC8T}.Y[+Kѩu FTͽEm1M8&xVxl]Zgwlazۗ4Y]5(활y'8gt߈ڝ ɚ{3AT]ߪt*W۟3#9A[_B埍ۮ>fUR|tγ7)wi1#OMύԸd~}KOeZ_#SꚎ,8~ޝ1Pw(cM,c`kzM:hXQRpG KeI/x&iϫ|Q8*}ovdPճ<}y+X_z]?ek^2?_x3<rϝ.9k2b mG"}KKWf7y\<=v_,^7ϕ6L+n=MIGmSXrxy#7Ev5so/XnM6,qmģ*\p]n1>#lRV.xYSnR_pJ_x 2mӢGM6)gy3wϥY`*xXp49rx1O!扗z;kՓwLc IZMT̀ A0 h *Gt^/W,W9IG k3E7|MFO3mANןNe#lݞcw>cATi:].-+g|IfRܜkgxO~IJti5} k~*>wG휛UWk0K"Iݞ3gj5bssI6i7^(Y̠ [ T@@QlYPmini_exiftool-2.9.0/test/helpers_for_test.rb000066400000000000000000000010611316025661300212430ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'mini_exiftool' require 'test/unit' require 'fileutils' require 'tempfile' begin require 'turn' rescue LoadError begin require 'rubygems' require 'turn' rescue LoadError end end include Test::Unit module TempfileTest def setup @temp_file = Tempfile.new('test') @temp_filename = @temp_file.path @data_dir = File.dirname(__FILE__) + '/data' end def teardown @temp_file.close end def assert_md5 md5, filename assert_equal md5, Digest::MD5.hexdigest(File.read(filename)) end end mini_exiftool-2.9.0/test/test_bad_preview_ifd.rb000066400000000000000000000014461316025661300220530ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' class TestBadPreviewIFD < TestCase include TempfileTest def setup super @org_filename = @data_dir + '/Bad_PreviewIFD.jpg' FileUtils.cp @org_filename, @temp_filename @bad_preview_ifd = MiniExiftool.new @temp_filename end # Feature request rubyforge [#29587] # Thanks to Michael Grove for reporting def test_m_option title = 'anything' @bad_preview_ifd.title = title assert_equal false, @bad_preview_ifd.save, '-m option seems to be not neccessary' @bad_preview_ifd.reload @bad_preview_ifd.title = title @bad_preview_ifd.ignore_minor_errors = true assert_equal true, @bad_preview_ifd.save, 'Error while saving' @bad_preview_ifd.reload assert_equal title, @bad_preview_ifd.title end end mini_exiftool-2.9.0/test/test_class_methods.rb000066400000000000000000000040501316025661300215640ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' class TestClassMethods < TestCase def test_new assert_nothing_raised do @mini_exiftool = MiniExiftool.new end assert_equal nil, @mini_exiftool.filename assert_nothing_raised do MiniExiftool.new nil end assert_raises MiniExiftool::Error do MiniExiftool.new false end assert_raises MiniExiftool::Error do MiniExiftool.new '' end assert_raises MiniExiftool::Error do MiniExiftool.new 'not_existing_file' end assert_raises MiniExiftool::Error do MiniExiftool.new '.' # directory end begin MiniExiftool.new 'not_existing_file' rescue MiniExiftool::Error => e assert_match /File 'not_existing_file' does not exist/, e.message end end def test_command cmd = MiniExiftool.command assert_equal 'exiftool', cmd MiniExiftool.command = 'non_existend' assert_equal 'non_existend', MiniExiftool.command assert_raises MiniExiftool::Error do met = MiniExiftool.new(File.join(File.dirname(__FILE__), 'data/test.jpg')) end MiniExiftool.command = cmd end def test_opts opts = MiniExiftool.opts assert_kind_of Hash, opts begin org = MiniExiftool.opts[:composite] met1 = MiniExiftool.new MiniExiftool.opts[:composite] = !org met2 = MiniExiftool.new MiniExiftool.opts[:composite] = org met3 = MiniExiftool.new assert_equal org, met1.composite assert_equal !org, met2.composite assert_equal org, met1.composite ensure MiniExiftool.opts[:composite] = org end end def test_all_tags all_tags = MiniExiftool.all_tags assert all_tags.include?('ISO') assert all_tags.include?('ExifToolVersion') end def test_writable_tags w_tags = MiniExiftool.writable_tags assert w_tags.include?('ISO') assert ! w_tags.include?('ExifToolVersion') end def test_exiftool_version v = MiniExiftool.exiftool_version assert_match /\A\d+\.\d+\z/, v end end mini_exiftool-2.9.0/test/test_composite.rb000066400000000000000000000010411316025661300207330ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' class TestComposite < TestCase def setup @data_dir = File.dirname(__FILE__) + '/data' @filename_test = @data_dir + '/test.jpg' @mini_exiftool = MiniExiftool.new @filename_test, :composite => false @mini_exiftool_c = MiniExiftool.new @filename_test end def test_composite_tags assert_equal false, @mini_exiftool.tags.include?('Aperture') assert_equal true, @mini_exiftool_c.tags.include?('Aperture') assert_equal 9.5, @mini_exiftool_c['Aperture'] end end mini_exiftool-2.9.0/test/test_copy_tags_from.rb000066400000000000000000000032031316025661300217460ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' class TestCopyTagsFrom < TestCase include TempfileTest def setup super @canon_filename = @data_dir + '/Canon.jpg' FileUtils.cp(@canon_filename, @temp_filename) @mini_exiftool = MiniExiftool.new(@temp_filename) @source_filename = @data_dir + '/test.jpg' @canon_md5 = Digest::MD5.hexdigest(File.read(@canon_filename)) @source_md5 = Digest::MD5.hexdigest(File.read(@source_filename)) end def test_single_tag assert_nil @mini_exiftool.title res = @mini_exiftool.copy_tags_from(@source_filename, :title) assert res assert_equal 'Abenddämmerung', @mini_exiftool.title assert_md5 @source_md5, @source_filename end def test_more_than_one_tag assert_nil @mini_exiftool.title assert_nil @mini_exiftool.keywords res = @mini_exiftool.copy_tags_from(@source_filename, %w[title keywords]) assert res assert_equal 'Abenddämmerung', @mini_exiftool.title assert_equal %w[Orange Rot], @mini_exiftool.keywords assert_md5 @source_md5, @source_filename end def test_non_existing_sourcefile assert_raises MiniExiftool::Error do @mini_exiftool.copy_tags_from('non_existend_file', :title) end assert_md5 @source_md5, @source_filename end def test_non_existend_tag @mini_exiftool.copy_tags_from(@source_filename, :non_existend_tag) assert_md5 @canon_md5, @canon_filename assert_md5 @source_md5, @source_filename end def test_non_writable_tag @mini_exiftool.copy_tags_from(@source_filename, 'JFIF') assert_md5 @canon_md5, @canon_filename assert_md5 @source_md5, @source_filename end end mini_exiftool-2.9.0/test/test_dumping.rb000066400000000000000000000053131316025661300204020ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' require 'yaml' class TestDumping < TestCase def setup @data_dir = File.dirname(__FILE__) + '/data' @filename_test = @data_dir + '/test.jpg' @mini_exiftool = MiniExiftool.new @filename_test end def test_to_hash hash = @mini_exiftool.to_hash assert_equal Hash, hash.class assert_equal @mini_exiftool.tags.size, hash.size, 'Size of Hash is not correct.' assert_not_nil hash['ExifToolVersion'], 'Original name of exiftool tag is not preserved.' all_ok = true different_tag = '' v = '' hash.each do |k,v| unless @mini_exiftool[k] == v all_ok = false different_tag = k break end end assert all_ok, "Tag #{different_tag.inspect}: expected: #{@mini_exiftool[different_tag].inspect}, actual: #{v.inspect}" end def test_from_hash hash = @mini_exiftool.to_hash mini_exiftool_new = MiniExiftool.from_hash hash assert_equal MiniExiftool, mini_exiftool_new.class assert_equal @mini_exiftool.tags.size, mini_exiftool_new.tags.size all_ok = true different_tag = '' @mini_exiftool.tags.each do |tag| unless @mini_exiftool[tag] == mini_exiftool_new[tag] all_ok = false different_tag = tag break end end assert all_ok, "Tag #{different_tag.inspect}: expected: #{@mini_exiftool[different_tag].inspect}, actual: #{mini_exiftool_new[different_tag].inspect}" end def test_to_yaml hash = @mini_exiftool.to_hash yaml = @mini_exiftool.to_yaml assert_equal hash, YAML.load(yaml) end def test_from_yaml hash = @mini_exiftool.to_hash yaml = hash.to_yaml mini_exiftool_new = MiniExiftool.from_yaml(yaml) assert_equal MiniExiftool, mini_exiftool_new.class assert_equal hash, mini_exiftool_new.to_hash end def test_heuristics_for_restoring_composite standard = @mini_exiftool.to_hash no_composite = MiniExiftool.new(@filename_test, :composite => false).to_hash assert_equal true, MiniExiftool.from_hash(standard).composite assert_equal false, MiniExiftool.from_hash(no_composite).composite assert_equal true, MiniExiftool.from_yaml(standard.to_yaml).composite assert_equal false, MiniExiftool.from_yaml(no_composite.to_yaml).composite end def test_heuristics_for_restoring_numerical standard = @mini_exiftool.to_hash numerical = MiniExiftool.new(@filename_test, :numerical => true).to_hash assert_equal false, MiniExiftool.from_hash(standard).numerical assert_equal true, MiniExiftool.from_hash(numerical).numerical assert_equal false, MiniExiftool.from_yaml(standard.to_yaml).numerical assert_equal true, MiniExiftool.from_yaml(numerical.to_yaml).numerical end end mini_exiftool-2.9.0/test/test_encodings.rb000066400000000000000000000016721316025661300207140ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' class TestEncodings < TestCase include TempfileTest def setup super @data_dir = File.dirname(__FILE__) + '/data' @filename_test = @data_dir + '/test_encodings.jpg' @mini_exiftool = MiniExiftool.new @filename_test end def test_iptc_encoding object_name = "Möhre" assert_not_equal object_name, @mini_exiftool.object_name correct_iptc = MiniExiftool.new(@filename_test, iptc_encoding: 'MacRoman') assert_equal object_name, correct_iptc.object_name FileUtils.cp(@filename_test, @temp_filename) correct_iptc_write = MiniExiftool.new(@temp_filename, iptc_encoding: 'MacRoman') caption = 'Das ist eine Möhre' correct_iptc_write.caption_abstract = caption correct_iptc_write.save! correct_iptc_write.reload assert_equal object_name, correct_iptc_write.object_name assert_equal caption, correct_iptc_write.caption_abstract end end mini_exiftool-2.9.0/test/test_filename_access.rb000066400000000000000000000030621316025661300220370ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' require 'rbconfig' require 'tmpdir' # Thanks to uwe58 and others for hints class TestFilenameAccess < TestCase @@running_on_windows = /mswin|mingw|cygwin/ === RbConfig::CONFIG['host_os'] @@fs_enc = Encoding.find('filesystem') def create_testfile(basename_new) tmpdir = Dir.tmpdir filename_org = File.join(File.dirname(__FILE__), 'data/test.jpg') filename_new = File.join(tmpdir, basename_new) FileUtils.cp filename_org, filename_new.encode(@@fs_enc) filename_new end def do_testing_with(basename) filename_test = create_testfile(basename) # read m = MiniExiftool.new filename_test assert_equal 400, m.iso # save m.iso = 200 m.save assert_equal 200, m.iso # Check original filename maybe with other encoding than filesystem assert_equal basename, File.basename(m.filename) rescue Exception => e assert false, "File #{filename_test.inspect} not found!" end def test_access_filename_with_spaces do_testing_with 'filename with spaces.jpg' end def test_access_filename_with_special_chars do_testing_with 'filename_with_Ümläüts.jpg' end unless @@running_on_windows def test_access_filename_with_doublequotes do_testing_with 'filename_with_"doublequotes"_inside.jpg' end end def test_access_filename_with_dollar_sign # Thanks to Michael Dungan for the hint do_testing_with 'filename_with_$_sign.jpg' end def test_access_filename_with_ampersand do_testing_with 'filename_with_&_sign.jpg' end end mini_exiftool-2.9.0/test/test_from_hash.rb000066400000000000000000000015341316025661300207060ustar00rootroot00000000000000require 'helpers_for_test' require 'json' class TestFromHash < TestCase def setup @data_dir = File.dirname(__FILE__) + '/data' hash_data = JSON.parse(File.read( @data_dir + '/test.jpg.json')).first @mini_exiftool = MiniExiftool.from_hash hash_data end def test_conversion assert_kind_of String, @mini_exiftool.model assert_kind_of Time, @mini_exiftool['DateTimeOriginal'] assert_kind_of Float, @mini_exiftool['MaxApertureValue'] assert_kind_of String, @mini_exiftool.flash assert_kind_of Integer, @mini_exiftool['ExposureCompensation'] assert_kind_of String, (@mini_exiftool['SubjectLocation'] || @mini_exiftool['SubjectArea']) assert_kind_of Array, @mini_exiftool['Keywords'] assert_kind_of String, @mini_exiftool['SupplementalCategories'] assert_kind_of Rational, @mini_exiftool.shutterspeed end end mini_exiftool-2.9.0/test/test_invalid_byte_sequence_in_utf8.rb000066400000000000000000000014241316025661300247330ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' require 'json' # Thanks to Chris Salzberg (aka shioyama) and # Robert May (aka robotmay) for precious hints class TestInvalidByteSequenceInUtf8 < TestCase def setup @json = File.read(File.dirname(__FILE__) + '/data/invalid_byte_sequence_in_utf8.json') end def test_invalid_byte_sequence_gets_unconverted_value_with_invalid_encoding assert_nothing_raised do mini_exiftool = MiniExiftool.from_json(@json) assert_equal 1561, mini_exiftool.color_balance_unknown.size end end def test_replace_invalid_chars assert_nothing_raised do mini_exiftool = MiniExiftool.from_json(@json, :replace_invalid_chars => '') assert_equal 1036, mini_exiftool.color_balance_unknown.size end end end mini_exiftool-2.9.0/test/test_invalid_rational.rb000066400000000000000000000005721316025661300222600ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' class TestInvalidRational < TestCase def test_rescue_from_invalid_rational mini_exiftool = MiniExiftool.from_json(File.read('test/data/invalid_rational.json')) assert_equal '1/0', mini_exiftool.user_comment rescue Exception assert false, 'Tag values of the form x/0 should not raise an Exception.' end end mini_exiftool-2.9.0/test/test_io.rb000066400000000000000000000027451316025661300173540ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' require 'stringio' class TestIo < TestCase def test_simple_case io = open_real_io mini_exiftool = MiniExiftool.new(io) assert_equal false, io.closed?, 'IO should not be closed.' assert_equal 400, mini_exiftool.iso end def test_non_readable_io assert_raises MiniExiftool::Error do begin MiniExiftool.new($stdout) rescue MiniExiftool::Error => e assert_equal 'IO is not readable.', e.message raise e end end end def test_no_writing_when_using_io io = open_real_io m = MiniExiftool.new(io) m.iso = 100 assert_raises MiniExiftool::Error do begin m.save rescue MiniExiftool::Error => e assert_equal 'No writing support when using an IO.', e.message raise e end end end def test_fast_options $DEBUG = true s = StringIO.new $stderr = s MiniExiftool.new open_real_io s.rewind assert_match /^exiftool -j "-"$/, s.read s = StringIO.new $stderr = s MiniExiftool.new open_real_io, :fast => true s.rewind assert_match /^exiftool -j -fast "-"$/, s.read s = StringIO.new $stderr = s MiniExiftool.new open_real_io, :fast2 => true s.rewind assert_match /^exiftool -j -fast2 "-"$/, s.read ensure $DEBUG = false $stderr = STDERR end protected def open_real_io File.open(File.join(File.dirname(__FILE__), 'data', 'test.jpg'), 'r') end end mini_exiftool-2.9.0/test/test_pstore.rb000066400000000000000000000011751316025661300202550ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' class TestPstore < TestCase def test_pstore pstore_dir = Dir.mktmpdir s = MiniExiftool.writable_tags.size.to_s cmd = %Q(#{RUBY_ENGINE} -EUTF-8 -I lib -r mini_exiftool -e "MiniExiftool.pstore_dir = '#{pstore_dir}'; p MiniExiftool.writable_tags.size") a = Time.now result = `#{cmd}` b = Time.now assert_equal s, result.chomp assert_equal 1, Dir[File.join(pstore_dir, '*')].size c = Time.now result = `#{cmd}` d = Time.now assert_equal s, result.chomp assert 10 * (d - c) < (b - a) ensure FileUtils.rm_rf pstore_dir end end mini_exiftool-2.9.0/test/test_read.rb000066400000000000000000000032141316025661300176500ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' class TestRead < TestCase def setup @data_dir = File.dirname(__FILE__) + '/data' @filename_test = @data_dir + '/test.jpg' @mini_exiftool = MiniExiftool.new @filename_test end def test_access assert_equal 'DYNAX 7D', @mini_exiftool['Model'] assert_equal 'MLT0', @mini_exiftool['maker_note_version'] assert_equal 'MLT0', @mini_exiftool[:MakerNoteVersion] assert_equal 'MLT0', @mini_exiftool[:maker_note_version] assert_equal 'MLT0', @mini_exiftool.maker_note_version assert_equal 400, @mini_exiftool.iso end def test_tags assert @mini_exiftool.tags.include?('FileSize') end def test_conversion assert_kind_of String, @mini_exiftool.model assert_kind_of Time, @mini_exiftool['DateTimeOriginal'] assert_kind_of Float, @mini_exiftool['MaxApertureValue'] assert_kind_of String, @mini_exiftool.flash assert_kind_of Integer, @mini_exiftool['ExposureCompensation'] assert_kind_of String, (@mini_exiftool['SubjectLocation'] || @mini_exiftool['SubjectArea']) assert_kind_of Array, @mini_exiftool['Keywords'] assert_kind_of String, @mini_exiftool['SupplementalCategories'] assert_kind_of Rational, @mini_exiftool.shutterspeed end def test_list_tags assert_equal ['Orange', 'Rot'], @mini_exiftool['Keywords'] assert_equal 'Natur', @mini_exiftool['SupplementalCategories'] assert_equal ['Natur'], Array(@mini_exiftool['SupplementalCategories']) end def test_value_encoding title= 'Abenddämmerung' assert_equal Encoding::UTF_8, @mini_exiftool.title.encoding assert_equal title, @mini_exiftool.title end end mini_exiftool-2.9.0/test/test_read_coordinates.rb000066400000000000000000000011171316025661300222420ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' class TestReadCoordinates < TestCase def setup @data_dir = File.dirname(__FILE__) + '/data' @filename_test = @data_dir + '/test_coordinates.jpg' end def test_access_coordinates mini_exiftool_coord = MiniExiftool.new @filename_test, :coord_format => "%.6f degrees" assert_match /^43.653167 degrees/, mini_exiftool_coord['GPSLatitude'] assert_match /^79.373167 degrees/, mini_exiftool_coord['GPSLongitude'] assert_match /^43.653167 degrees.*, 79.373167 degrees/, mini_exiftool_coord['GPSPosition'] end end mini_exiftool-2.9.0/test/test_read_numerical.rb000066400000000000000000000024401316025661300217070ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' class TestReadNumerical < TestCase def setup @data_dir = File.dirname(__FILE__) + '/data' @filename_test = @data_dir + '/test.jpg' @mini_exiftool_num = MiniExiftool.new @filename_test, :numerical => true end def test_access_numerical assert_equal 'DYNAX 7D', @mini_exiftool_num['Model'] assert_equal 'MLT0', @mini_exiftool_num['maker_note_version'] assert_equal 'MLT0', @mini_exiftool_num[:MakerNoteVersion] assert_equal 'MLT0', @mini_exiftool_num[:maker_note_version] assert_equal 'MLT0', @mini_exiftool_num.maker_note_version assert_equal 400, @mini_exiftool_num.iso end def test_conversion_numerical assert_kind_of String, @mini_exiftool_num.model assert_kind_of Time, @mini_exiftool_num['DateTimeOriginal'] assert_kind_of Float, @mini_exiftool_num['MaxApertureValue'] assert_kind_of Integer, @mini_exiftool_num.flash assert_kind_of String, @mini_exiftool_num.exif_version assert_kind_of Integer, @mini_exiftool_num['ExposureCompensation'] assert_kind_of String, (@mini_exiftool_num['SubjectLocation'] || @mini_exiftool_num['SubjectArea']) assert_kind_of Array, @mini_exiftool_num['Keywords'] assert_kind_of String, @mini_exiftool_num['SupplementalCategories'] end end mini_exiftool-2.9.0/test/test_save.rb000066400000000000000000000043641316025661300177020ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'digest/md5' require 'fileutils' require 'tempfile' require 'helpers_for_test' class TestSave < TestCase include TempfileTest def setup super @org_filename = @data_dir + '/test.jpg' FileUtils.cp(@org_filename, @temp_filename) @mini_exiftool = MiniExiftool.new @temp_filename @mini_exiftool_num = MiniExiftool.new @temp_filename, :numerical => true @org_md5 = Digest::MD5.hexdigest(File.read(@org_filename)) end def test_allowed_value @mini_exiftool_num['Orientation'] = 2 result = @mini_exiftool_num.save assert_equal true, result assert_equal @org_md5, Digest::MD5.hexdigest(File.read(@org_filename)) assert_not_equal @org_md5, Digest::MD5.hexdigest(File.read(@temp_filename)) assert_equal false, @mini_exiftool_num.changed? result = @mini_exiftool_num.save assert_equal false, result end def test_non_allowed_value @mini_exiftool['Orientation'] = 'some string' result = @mini_exiftool.save assert_equal false, result assert_equal 1, @mini_exiftool.errors.size assert_match(/Can't convert IFD0:Orientation \(not in PrintConv\)/, @mini_exiftool.errors['Orientation']) assert @mini_exiftool.changed? assert @mini_exiftool.changed_tags.include?('Orientation') end def test_no_changing_of_file_when_error @mini_exiftool['ISO'] = 800 @mini_exiftool['Orientation'] = 'some value' @mini_exiftool['ExposureTime'] = '1/30' result = @mini_exiftool.save assert_equal false, result assert_equal @org_md5, Digest::MD5.hexdigest(File.read(@org_filename)) assert_equal @org_md5, Digest::MD5.hexdigest(File.read(@temp_filename)) end def test_value_encoding special_string = 'äöü' @mini_exiftool.title = special_string assert @mini_exiftool.save @mini_exiftool.reload assert_equal Encoding::UTF_8, @mini_exiftool.title.encoding assert_equal special_string, @mini_exiftool.title end def test_save_bang @mini_exiftool.orientation = 'some value' exception = false begin @mini_exiftool.save! rescue MiniExiftool::Error => e assert_match /Orientation/, e.message exception = true end assert exception, "No exception when save! with error." end end mini_exiftool-2.9.0/test/test_special.rb000066400000000000000000000022141316025661300203540ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'helpers_for_test' class TestSpecial < TestCase include TempfileTest CAPTION_ABSTRACT = 'Some text for caption abstract' def setup super @org_filename = @data_dir + '/Canon.jpg' FileUtils.cp @org_filename, @temp_filename @canon = MiniExiftool.new @temp_filename end # Catching bug [#8073] # Thanks to Eric Young def test_special_chars assert_not_nil @canon['Self-timer'] assert_not_nil @canon.self_timer # preserving the original tag name assert @canon.tags.include?('Self-timer') || @canon.tags.include?('SelfTimer') assert !@canon.tags.include?('self_timer') end # Catching bug with writing caption-abstract # Thanks to Robin Romahn def test_caption_abstract_sensitive @canon['caption-abstract'] = CAPTION_ABSTRACT assert @canon.changed_tags.include?('Caption-Abstract') assert @canon.save assert_equal CAPTION_ABSTRACT, @canon.caption_abstract end def test_caption_abstract_non_sesitive @canon.caption_abstract = CAPTION_ABSTRACT.reverse assert @canon.save assert_equal CAPTION_ABSTRACT.reverse, @canon.caption_abstract end end mini_exiftool-2.9.0/test/test_special_dates.rb000066400000000000000000000025651316025661300215450ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'date' require 'helpers_for_test' class TestSpecialDates < TestCase include TempfileTest def setup super @org_filename = @data_dir + '/test_special_dates.jpg' FileUtils.cp @org_filename, @temp_filename @mini_exiftool = MiniExiftool.new @temp_filename @mini_exiftool_datetime = MiniExiftool.new @temp_filename, :timestamps => DateTime end # Catching bug [#16328] (1st part) # Thanks to unknown def test_datetime datetime_original = @mini_exiftool.datetime_original if datetime_original assert_kind_of Time, datetime_original else assert_equal false, datetime_original end assert_kind_of DateTime, @mini_exiftool_datetime.datetime_original assert_raise MiniExiftool::Error do @mini_exiftool.timestamps = String @mini_exiftool.reload end @mini_exiftool.timestamps = DateTime @mini_exiftool.reload assert_equal @mini_exiftool_datetime.datetime_original, @mini_exiftool.datetime_original end # Catching bug [#16328] (2nd part) # Thanks to Cecil Coupe def test_invalid_date assert_equal false, @mini_exiftool.modify_date end def test_time_zone s = '1961-08-13 12:08:25+01:00' assert_equal Time.parse(s), @mini_exiftool.preview_date_time assert_equal DateTime.parse(s), @mini_exiftool_datetime.preview_date_time end end mini_exiftool-2.9.0/test/test_write.rb000066400000000000000000000130061316025661300200670ustar00rootroot00000000000000# -- encoding: utf-8 -- require 'digest/md5' require 'fileutils' require 'tempfile' require 'helpers_for_test' class TestWrite < TestCase def setup @temp_file = Tempfile.new('test') @temp_file.close @temp_filename = @temp_file.path @org_filename = File.dirname(__FILE__) + '/data/test.jpg' FileUtils.cp(@org_filename, @temp_filename) @mini_exiftool = MiniExiftool.new @temp_filename @mini_exiftool_num = MiniExiftool.new @temp_filename, :numerical => true end def teardown @temp_file.delete end def test_access_existing_tags assert_equal 'Horizontal (normal)', @mini_exiftool['Orientation'] @mini_exiftool['Orientation'] = 'some string' assert_equal 'some string', @mini_exiftool['Orientation'] assert_equal false, @mini_exiftool.changed?('Orientation') @mini_exiftool['Orientation'] = 2 assert_equal 2, @mini_exiftool['Orientation'] assert @mini_exiftool.changed_tags.include?('Orientation') @mini_exiftool.save assert_equal 'Mirror horizontal', @mini_exiftool['Orientation'] @mini_exiftool_num.reload assert_equal 2, @mini_exiftool_num['Orientation'] end def test_access_existing_tags_numerical assert_equal 1, @mini_exiftool_num['Orientation'] @mini_exiftool_num['Orientation'] = 2 assert_equal 2, @mini_exiftool_num['Orientation'] assert_equal 2, @mini_exiftool_num.orientation @mini_exiftool_num.orientation = 3 assert_equal 3, @mini_exiftool_num.orientation assert @mini_exiftool_num.changed_tags.include?('Orientation') @mini_exiftool_num.save assert_equal 3, @mini_exiftool_num['Orientation'] @mini_exiftool.reload assert_equal 'Rotate 180', @mini_exiftool['Orientation'] end def test_access_non_writable_tags @mini_exiftool_num['FileSize'] = 1 assert_equal true, @mini_exiftool_num.changed? @mini_exiftool_num['SomeNonWritableName'] = 'test' assert_equal true, @mini_exiftool_num.changed? end # Catching rubyforge bug [#29596] # Thanks to Michael Grove for reporting # Part 1 def test_quotes_in_values caption = "\"String in quotes\"" @mini_exiftool.caption = caption assert_equal true, @mini_exiftool.save, 'Saving error' @mini_exiftool.reload assert_equal caption, @mini_exiftool.caption end # Catching rubyforge bug [#29596] # Thanks to Michael Grove for reporting # Part 2 def test_quotes_and_apostrophe_in_values caption = caption = "\"Watch your step, it's slippery.\"" @mini_exiftool.caption = caption assert_equal true, @mini_exiftool.save, 'Saving error' @mini_exiftool.reload assert_equal caption, @mini_exiftool.caption end def test_time_conversion t = Time.now @mini_exiftool_num['DateTimeOriginal'] = t assert_kind_of Time, @mini_exiftool_num['DateTimeOriginal'] assert_equal true, @mini_exiftool_num.changed_tags.include?('DateTimeOriginal') @mini_exiftool_num.save assert_equal false, @mini_exiftool_num.changed? assert_kind_of Time, @mini_exiftool_num['DateTimeOriginal'] assert_equal t.to_s, @mini_exiftool_num['DateTimeOriginal'].to_s end def test_float_conversion assert_kind_of Float, @mini_exiftool_num['BrightnessValue'] new_time = @mini_exiftool_num['BrightnessValue'] + 1 @mini_exiftool_num['BrightnessValue'] = new_time assert_equal new_time, @mini_exiftool_num['BrightnessValue'] assert_equal true, @mini_exiftool_num.changed_tags.include?('BrightnessValue') @mini_exiftool_num.save assert_kind_of Float, @mini_exiftool_num['BrightnessValue'] assert_equal new_time, @mini_exiftool_num['BrightnessValue'] end def test_integer_conversion assert_kind_of Integer, @mini_exiftool_num['MeteringMode'] new_mode = @mini_exiftool_num['MeteringMode'] - 1 @mini_exiftool_num['MeteringMode'] = new_mode assert_equal new_mode, @mini_exiftool_num['MeteringMode'] assert @mini_exiftool_num.changed_tags.include?('MeteringMode') @mini_exiftool_num.save assert_equal new_mode, @mini_exiftool_num['MeteringMode'] end def test_rational_conversion new_exposure_time = Rational(1, 125) @mini_exiftool.exposure_time = new_exposure_time assert @mini_exiftool.changed?, 'No changing of value.' ok = @mini_exiftool.save assert ok, 'Saving failed.' @mini_exiftool.reload assert_equal new_exposure_time, @mini_exiftool.exposure_time end def test_list_conversion arr = ['a', 'b', 'c'] @mini_exiftool['Keywords'] = arr ok = @mini_exiftool.save assert ok assert_equal arr, @mini_exiftool['Keywords'] arr = ['text, with', 'commas, let us look'] @mini_exiftool['Keywords'] = arr ok = @mini_exiftool.save assert ok if MiniExiftool.exiftool_version.to_f < 7.41 assert_equal ['text', 'with', 'commas', 'let us look'], @mini_exiftool['Keywords'] else assert_equal arr, @mini_exiftool['Keywords'] end end def test_revert_one @mini_exiftool_num['Orientation'] = 2 @mini_exiftool_num['ISO'] = 200 res = @mini_exiftool_num.revert 'Orientation' assert_equal 1, @mini_exiftool_num['Orientation'] assert_equal 200, @mini_exiftool_num['ISO'] assert_equal true, res res = @mini_exiftool_num.revert 'Orientation' assert_equal false, res end def test_revert_all @mini_exiftool_num['Orientation'] = 2 @mini_exiftool_num['ISO'] = 200 res = @mini_exiftool_num.revert assert_equal 1, @mini_exiftool_num['Orientation'] assert_equal 400, @mini_exiftool_num['ISO'] assert_equal true, res res = @mini_exiftool_num.revert assert_equal false, res end end