pax_global_header00006660000000000000000000000064131041047210014503gustar00rootroot0000000000000052 comment=15443df2737907c26fbb5e8606ff844952fe2c48 picard-release-1.4.2/000077500000000000000000000000001310410472100143675ustar00rootroot00000000000000picard-release-1.4.2/.gitignore000066400000000000000000000002551310410472100163610ustar00rootroot00000000000000build build.cfg .pydevproject .project .idea *.pyc *.pyd *.dll *.so locale *.exe .DS_Store po/countries/countries.pot po/attributes/attributes.pot scripts/picard .*.sw[a-z] picard-release-1.4.2/.gitlab-ci.yml000066400000000000000000000010131310410472100170160ustar00rootroot00000000000000stages: - package variables: CHROMAPRINT_FPCALC_VERSION: "1.4.2" DISCID_VERSION: "0.6.1" PYTHON_DISCID_VERSION: "1.1.1" MUTAGEN_VERSION: "1.36" PY2APP_VERSION: "0.11" package win: stage: package script: - call "scripts\package-win.bat" artifacts: paths: - picard-setup-*.exe expire_in: 90d tags: - winxp package osx: stage: package script: - bash -xe ./scripts/package-osx.sh artifacts: paths: - MusicBrainz-Picard-*.dmg expire_in: 90d tags: - osx picard-release-1.4.2/.mailmap000066400000000000000000000003421310410472100160070ustar00rootroot00000000000000Frederik “Freso” S. Olesen Lukáš Lalinský Sambhav Kothari Sophist-UK Suhas picard-release-1.4.2/.travis.yml000066400000000000000000000016461310410472100165070ustar00rootroot00000000000000os: linux dist: trusty sudo: required language: python python: "2.7" virtualenv: system_site_packages: true cache: - apt - pip env: global: - PIP_INSTALL="pip install" matrix: - DISCID="" MUTAGEN="$PIP_INSTALL mutagen>=1.23" - DISCID="$PIP_INSTALL discid" MUTAGEN="$PIP_INSTALL mutagen>=1.23" - MUTAGEN="$PIP_INSTALL mutagen==1.34" matrix: include: - os: osx osx_image: xcode8.1 language: generic before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then bash scripts/setup-osx.sh; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq && sudo apt-get install -qq python-qt4 gettext libdiscid0 libdiscid0-dev; $MUTAGEN; $DISCID; fi install: # Set up Picard - python setup.py build_ext -i - python setup.py build_locales -i # Run the tests! script: "python setup.py test" # Tell people that tests were run notifications: irc: "chat.freenode.net#metabrainz" picard-release-1.4.2/.tx/000077500000000000000000000000001310410472100151005ustar00rootroot00000000000000picard-release-1.4.2/.tx/config000066400000000000000000000006301310410472100162670ustar00rootroot00000000000000[main] host = https://www.transifex.com [musicbrainz.picard] file_filter = po/.po source_file = po/picard.pot source_lang = en type = PO [musicbrainz.countries] file_filter = po/countries/.po source_file = po/countries/countries.pot source_lang = en type = PO [musicbrainz.attributes] file_filter = po/attributes/.po source_file = po/attributes/attributes.pot source_lang = en type = PO picard-release-1.4.2/AUTHORS.txt000066400000000000000000000004621310410472100162570ustar00rootroot00000000000000Lukáš Lalinský Robert Kaye Philipp Wolfer Picard logo by , © 2015 MetaBrainz Foundation Interface icons by Carlin Mangar and from the Tango Desktop Project . picard-release-1.4.2/CONTRIBUTING.md000066400000000000000000000071051310410472100166230ustar00rootroot00000000000000# Contributing to Picard ## Coding Style As most of the other projects written in Python, we use the [PEP 8](https://www.python.org/dev/peps/pep-0008/). Though, we ignore some of the recommendations: * E501 - Maximum line length (79 characters). The general limit we have is somewhere around 120-130. *Recommended video: "[Beyond PEP 8 -- Best practices for beautiful intelligible code](https://www.youtube.com/watch?v=wf-BqAjZb8M)" by Raymond Hettinger at PyCon 2015, which talks about the famous P versus NP problem.* The general idea is to make the code within a project consistent and easy to interpret (for humans). ### Docstrings Unless the function is easy to understand quickly, it should probably have a docstring describing what it does, how it does it, what the arguments are, and what the expected output is. We recommend using ["Google-style" docstrings](https://google.github.io/styleguide/pyguide.html?showone=Comments#Comments) for writing docstrings. ### Picard specific code Picard has some auto-generated `picard/ui/ui_*.py` PyQt UI related files. Please do not change them directly. To modify them, use Qt-Designer to edit the `ui/*.ui` and use the command `python setup.py build_ui` to generate the corresponding `ui_*.py` files. We use snake-case to name all functions and variables except for the pre-generated PyQt functions/variables. `gettext` and `gettext-noop` have been built-in the Picard code as `_` and `N_` respectively to provide support for internationalization/localization. You can use them without imports across all of Picard code. Make sure to mark all displayable strings for translation using `_` or `N_` as applicable. You can read more about python-gettext [here](https://docs.python.org/2/library/gettext.html). ## Git Work-flow We follow the "typical" GitHub workflow when contributing changes: 1. [Fork](https://help.github.com/articles/fork-a-repo/) a repository into your account. 2. Create a new branch and give it a meaningful name. For example, if you are going to fix issue PICARD-257, branch can be called `picard-257` or `preserve-artwork`. 3. Make your changes and commit them with a [good description](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). Your commit subject should be written in **imperative voice** and **sentence case**. With regards to the contents of the message itself, you don't need to provide a lot of details, but make sure that people who look at the commit history afterwards can understand what you were changing and why. 4. [Create](https://help.github.com/articles/creating-a-pull-request/) a new pull request on GitHub. Make sure that the title of your pull request is descriptive and consistent with the rest. If you are fixing issue that exists in our bug tracker reference it like this: `PICARD-257: Allow preserving existing cover-art tags`. **Not** `[PICARD-257] - Allow preserving existing cover-art tags` or `Allow preserving existing cover-art tags (PICARD-257)` or simply `PICARD-257`. 5. Make sure to provide a bug tracker link to the issue that your pull request solves in the description. 6. Do not make one big pull request with a lot of unrelated changes. If you are solving more than one issue, unless they are closely related, split them into multiple pull requests. It makes it easier to review and merge the patches this way. 7. Try to avoid un-necessary commits after code reviews by making use of [git rebase](https://help.github.com/articles/about-git-rebase/) to fix merge conflicts, remove unwanted commits, rewording and editing previous commits or squashing multiple small related changes into one commit. picard-release-1.4.2/COPYING.txt000066400000000000000000000431051310410472100162430ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, 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 or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's 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 give any other recipients of the Program a copy of this License along with the Program. 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 Program or any portion of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, 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 Program, 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 Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) 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; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, 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 executable. However, as a special exception, the source code 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. If distribution of executable or 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 counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program 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. 5. 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 Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program 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 to this License. 7. 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 Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program 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 Program. 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. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program 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. 9. The Free Software Foundation may publish revised and/or new versions of the 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 Program 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 Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, 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 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 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 Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. 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) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. picard-release-1.4.2/Dockerfile000066400000000000000000000007101310410472100163570ustar00rootroot00000000000000FROM python:2.7.12 RUN apt-get update && apt-get install -y build-essential \ python-dev \ gettext \ qt4-default \ python-qt4 \ libdiscid0 \ libdiscid-dev \ python-mutagen RUN mkdir -p /build/ ADD . /build/ WORKDIR /build/ ENV PYTHONPATH /usr/lib/python2.7/dist-packages RUN python setup.py build_ext -i RUN python setup.py build_locales -i CMD python setup.py test picard-release-1.4.2/HACKING.txt000066400000000000000000000001201310410472100161650ustar00rootroot00000000000000Translations ============ See po/README.md for information about translations. picard-release-1.4.2/INSTALL.txt000066400000000000000000000041721310410472100162420ustar00rootroot00000000000000MusicBrainz Picard Installation =============================== Dependencies ------------ Before installing Picard, you need to have these: * Python 2.7 or newer (Picard will not work with Python 3) http://python.org/download * PyQt 4.5 or newer http://www.riverbankcomputing.co.uk/software/pyqt/download * Mutagen 1.22 or newer (1.23 or newer for AIFF support) https://bitbucket.org/lazka/mutagen/downloads * gettext Windows: http://gnuwin32.sourceforge.net/packages/gettext.htm * a compiler Windows should work with Visual C++ 2008 Express: http://go.microsoft.com/?linkid=7729279 * chromaprint (optional) For fingerprinting (scanning) files http://acoustid.org/chromaprint * python-discid or python-libdiscid (optional) Required for CD lookups. https://python-discid.readthedocs.org/ Depends on libdiscid: http://musicbrainz.org/doc/libdiscid Due to slowdowns in reading the CD TOC, using libdiscid versions 0.3.0 - 0.4.1 is not recommended. On Windows it is recommended to use Pip for Windows: https://sites.google.com/site/pydatalog/python/pip-for-windows and then just: pip install mutagen pip install discid there for Mutagen and python-discid. The binaries for Python, GetText (msgfmt), fpcalc and discid.dll have to be in the %PATH% on Windows. Installation ------------ After installing the dependencies, you can install Picard by running: sudo python setup.py install This will automatically build and install all required Python modules. On Windows you need to have Administrator rights, but don't put "sudo" in front of the command. To start Picard now you can use: picard Running From the Source Tree ---------------------------- If you want to run Picard from the source directory without installing, you need to build the C extensions and locales manually: python setup.py build_ext -i python setup.py build_locales -i And to start Picard use: python tagger.py Running the Test Suite ---------------------- To run the included tests, follow the instructions for "Running From the Source Tree". Afterward you can run the tests using setup.py: python setup.py test picard-release-1.4.2/NEWS.txt000066400000000000000000001356721310410472100157220ustar00rootroot00000000000000Version 1.4.3 - xxxx-xx-xx Version 1.4.2 - 2017-05-08 ** Bug * [PICARD-1053] - Picard does not stop analyzer while moving * [PICARD-1055] - picard hangs with: RuntimeError: maximum recursion depth exceeded in cmp * [PICARD-1070] - The "Convert Unicode punctuation characters to ASCII" function only works in certain tags * [PICARD-1077] - ID3v2.4 text encoding settings are not saved correctly ** Improvement * [PICARD-969] - Search dialog webservices get queued behind matched album requests * [PICARD-1034] - Picard not seeing TOPE and TOAL Version 1.4.1 - 2017-04-01 ** Bug * [PICARD-953] - Album shown matched even if extra unmatched files * [PICARD-972] - Removing album with saves pending does not remove pending saves * [PICARD-973] - Pending log messages not flushed to stderr on quit * [PICARD-988] - Drag & Drop not working * [PICARD-990] - Picard violating ID3 standard for TXXX frames * [PICARD-996] - Disabling the cover art box and enabling it again doesn't bring it back * [PICARD-998] - Disabling the action toolbar sometimes doesn't work * [PICARD-1005] - If a cluster is moved to the album side of the main window it gets moved to unmatched files * [PICARD-1006] - Drag and drop for cover arts doesnt work on OSX * [PICARD-1010] - Unsetting View/Cover Art doesn't work permanently * [PICARD-1011] - Toolbar tab order incorrect after PICARD-908 * [PICARD-1014] - Number of images in release info is calculated incorrectly * [PICARD-1015] - Artwork tab of the Track Info DIalog doesn't show changes anymore * [PICARD-1018] - CoverArtBox doesn't show new/removed images after unmatched files are added/removed to the album * [PICARD-1023] - Directory persistence for Add Directory needs tweaking * [PICARD-1029] - Fix ~artists_sort metadata variable * [PICARD-1042] - Missing import for PICARD_APP_NAME ** New Feature * [PICARD-258] - Visual feedback for changes to artwork in before-after pane. * [PICARD-1000] - Implement artwork diff for albums ** Task * [PICARD-943] - Remove monkey patching of file write methods in picard formats * [PICARD-1041] - Replace Ok button text by Make It So! in Options dialog ** Improvement * [PICARD-223] - Remove should work when Unmatched Files is selected * [PICARD-951] - Always use HTTPS for musicbrainz.org * [PICARD-952] - Use Cover Art Archive over HTTPS * [PICARD-961] - Mention AcoustID on Scan button too * [PICARD-980] - Drag&drop cover art doesn't work for images from amazon/google images/https links * [PICARD-1012] - Buttons on the "User Interface" and "Scripting" pages are smaller than buttons in other places * [PICARD-1016] - Multiple images in related tracks confusing * [PICARD-1021] - Picard loads all pending files before quitting * [PICARD-1024] - Allow specifying a configuration file path * [PICARD-1030] - Allow to add/replace cover art images and keep existing cover art Version 1.4 - 2017-02-14 * Bugfix: AcoustID submission fails with code 299 (PICARD-82) * Bugfix: Ignoring "hip hop rap" folksonomy tags also ignores "rap", "hip hop", etc. (PICARD-335) * Bugfix: Picard downloads multiple 'front' images instead of just first one. (PICARD-350) * Bugfix: Saving hidden file with only an extension drops the extension (PICARD-357) * Bugfix: Add directory opens in "wrong" dir (PICARD-366) * Bugfix: Picard should de-duplicate work lists (PICARD-375) * Bugfix: Tree selector in Options window is partially obscured, pane too narrow (PICARD-408) * Bugfix: tag acoustid_id can not be removed or deleted in script, renaming or plugin (PICARD-419) * Bugfix: Can't remove value from field (PICARD-546) * Bugfix: Can't open Options (PICARD-592) * Bugfix: "Tags from filenames" action stays enabled even if it is unavailable. (PICARD-688) * Bugfix: Using the first image type as filename changes the name of front images (PICARD-701) * Bugfix: Fingerprint Submission Failes if AcoustID tags are present and/or invalid (PICARD-706) * Bugfix: Picard moves into the selected folder (PICARD-726) * Bugfix: Picard does not support (recording) relationship credits (PICARD-730) * Bugfix: Picard repeats/duplicates field data (PICARD-748) * Bugfix: Number of pending web requests is not decremented on exceptions in the handler (PICARD-751) * Bugfix: Divide by zero error in _convert_folksonomy_tags_to_genre when no tag at the release/release group level ( PICARD-753) * Bugfix: Directory tree (file browser) not sorted for non-system drives under Windows (PICARD-754) * Bugfix: Crash when loading release with only zero count tags (PICARD-759) * Bugfix: No name and no window grouping in gnome-shell Alt-Tab app switcher (PICARD-761) * Bugfix: Lookup in Browser does not and can not load HTTPS version of musicbrainz.org (PICARD-764) * Bugfix: Unable to login using oauth via Picard options with Server Port set to 443 (PICARD-766) * Bugfix: "AttributeError: 'MetadataBox' object has no attribute 'resize_columns'" when enabling the cover art box ( PICARD-775) * Bugfix: Pre-gap tracks are not counted in absolutetracknumber (PICARD-778) * Bugfix: CAA cover art provider runs even if cover art has already been loaded (PICARD-780) * Bugfix: Toggling Embed Cover Art in Tags and restarting doesn't have the expected behavior (PICARD-782) * Bugfix: XMLWS redirects incorrectly (PICARD-788) * Bugfix: Handle empty collection-list in web server response (PICARD-798) * Bugfix: Amazon Cover Art provider does not work (and does not have a lot of debug logging enabled) (PICARD-799) * Bugfix: Cover Art from CAA release group is skipped even though it exists (PICARD-801) * Bugfix: Multiple instances of history and log dialogs (PICARD-804) * Bugfix: Empty string lookup (PICARD-805) * Bugfix: Will not load album information on any albums (PICARD-811) * Bugfix: Redirect URL is not encoded which leads to http 400 error. (PICARD-814) * Bugfix: Not compatible with latest Mutagen (PICARD-833) * Bugfix: Can't save any files. Get: "error: invalid literal for int() with base 10" (PICARD-834) * Bugfix: Picard 1.3.2 shows cleartext username & password on status line when errors occur (PICARD-839) * Bugfix: Cannot fetch cover art from amazon link contains https scheme. (PICARD-848) * Bugfix: media-optical-modified.png icon still displayed after release save when two files match one track (PICARD-851) * Bugfix: Release that Picard will not load (due to disc with just data track?) (PICARD-853) * Bugfix: ValueError in metadata.py (PICARD-855) * Bugfix: Improper detection of Gnome as a desktop environment and no support for gnome 3 (PICARD-857) * Bugfix: Apparent non-functional tagger button (PICARD-858) * Bugfix: Picard does not read Ogg/Opus files with an ".ogg" file exension (PICARD-859) * Bugfix: Setting a large value in in $num function as length causes picard to become unresponsive (PICARD-865) * Bugfix: id3 deletion needs to be improved (PICARD-867) * Bugfix: id3v2.3 does not properly handle TMOO ( mood tag) (PICARD-868) * Bugfix: Coverart providers duplicates on reset (PICARD-870) * Bugfix: Restore defaults broken for plugins page and tagger scripts page (PICARD-873) * Bugfix: Coverart providers erroneous save (PICARD-874) * Bugfix: The metadatabox doesn't correctly show the tag selected (PICARD-876) * Bugfix: Length tag for ID3 is no longer displayed in the metadata box (PICARD-881) * Bugfix: Removed tags are not removed from the metadatabox after saving the file (PICARD-882) * Bugfix: File Browser pane doesn't check for path type( file or folder) when setting home path/move files here ( PICARD-884) * Bugfix: mov files return a +ve score for mp4 container leading to errors (PICARD-885) * Bugfix: "Restore defaults" doesn't log out the user (PICARD-888) * Bugfix: Broken 'Restore Defaults' (PICARD-907) * Bugfix: Messagebox wraps and displays title inappropriately (PICARD-911) * Bugfix: An “empty” track shouldn’t get an “excellent match” tooltip. (PICARD-914) * Bugfix: In plugins list, some plugins don't show description (PICARD-915) * Bugfix: Plugin restore defaults broken (PICARD-916) * Bugfix: Does not use UI language but locale on Windows (PICARD-917) * Bugfix: Preserve scripting splitter position (PICARD-925) * Bugfix: Having trouble submitting AcoustIDs (PICARD-926) * Bugfix: Cluster double‐click opens the Info… panel (PICARD-931) * Bugfix: Status bar not cleared when selection changed (PICARD-937) * Bugfix: Open containing folder not working for shared files over network (PICARD-942) * Bugfix: Warning: Plugin directory '…/python2.7/site-packages/contrib/plugins' doesn't exist (PICARD-945) * Bugfix: Additionnal files aren't moved anymore (PICARD-946) * Bugfix: Search window error message does not appear translated (PICARD-947) * Bugfix: Open Containing Folder duplicates (PICARD-950) * Bugfix: Errors when directory / file names contain unicode characters (PICARD-958) * New Feature: AIF support (ID3) (PICARD-42) * New Feature: Test and integrate support for "local" cover art into Picard (PICARD-137) * New Feature: Display infos (album, artist, tracklist) for clusters without release match (PICARD-680) * New Feature: Add download plugin functionality to existing UI (PICARD-691) * New Feature: Fallback on album artist's tags if no tags are found for album (PICARD-738) * New Feature: Add m2a as a supported extension (PICARD-743) * New Feature: MusicBrainz/AcoustID entities should be hyperlinked in Picard (PICARD-756) * New Feature: Support key tag (PICARD-769) * New Feature: Export / import settings (PICARD-901) * New Feature: Search releases from within a Picard dialog (PICARD-927) * New Feature: Searching tracks and displaying similar tracks in a dialog box (PICARD-928) * New Feature: Search for artists from dialog (PICARD-929) * Task: Picard default name files script refinement (PICARD-717) * Task: Update Picard logo/icons (PICARD-760) * Task: Link to the Scripting documentation on the Scripting options page (PICARD-779) * Task: Remove contrib/plugins from the repository (PICARD-835) * Task: Raise the required mutagen version to 1.22 (PICARD-841) * Task: Renaming save_only_front_images_to_tags option to something more appropriate (PICARD-861) * Task: Allow translators to finalize translations before releasing Picard 1.4 (PICARD-895) * Task: Raise the required Python version to 2.7. (PICARD-904) * Task: Bump Picard’s copyright date (PICARD-912) * Task: Add Norwegian to UI languages (PICARD-982) * Task: Provide ~video variable for video tracks (PICARD-652) * Task: Improve error logging on AcoustId submission (PICARD-708) * Improvement: Link to Picard Scripting page under 'File Naming' (PICARD-22) * Improvement: Restore default settings button/s (PICARD-116) * Improvement: Speed of Ogg tag writing/updating (PICARD-133) * Improvement: Allow adding/removing tags to be preserved from context menu in the tag diff pane (PICARD-207) * Improvement: Make it easier to remove everything currently loaded in Picard (PICARD-210) * Improvement: Bring back keyboard shortcuts for editing tags (PICARD-222) * Improvement: Case sensitivity for "Move additional files" option (PICARD-229) * Improvement: Metadata comparison box shows that it intends to write (and has written) tags unsupported by underlyingfile format (PICARD-253) * Improvement: Add more descriptive tooltips to buttons (PICARD-267) * Improvement: Allow musicip_puid and acoustid_id to be cleared from tags (PICARD-268) * Improvement: Make it possible to remove existing tags without clearing all tags (PICARD-287) * Improvement: Disable recurse subdirectories should be added (PICARD-291) * Improvement: display how many "pending files" left on lookup (PICARD-305) * Improvement: Handle MP3 TSST/TIT3 (subtitle) tags better with ID3v2.3 (PICARD-307) * Improvement: Customisable toolbars (PICARD-353) * Improvement: Ignore file extension and try to read anyway (PICARD-359) * Improvement: Make it possible to unset all performer (etc) tags (PICARD-384) * Improvement: Progress tracking (PICARD-388) * Improvement: Add ability to handle multiple tagger scripts (PICARD-404) * Improvement: the option "select all" to save (PICARD-476) * Improvement: Option to load only audio tracks, i.e. not DVD-Video, CD-ROM tracks (PICARD-514) * Improvement: Picard should use OAuth for authentication (PICARD-615) * Improvement: Improvements to WMA tags (PICARD-648) * Improvement: Only ask to "log in now" once per session (PICARD-678) * Improvement: Show codec info for MP4 files (PICARD-683) * Improvement: "Play File" button should be renamed to "Open in Player" (PICARD-692) * Improvement: ID3 padding not reduced can result in large files (PICARD-695) * Improvement: Set option 'caa_approved_only' disabled by default (PICARD-705) * Improvement: Validate fpcalc executable in options (PICARD-707) * Improvement: Improve File Naming options (PICARD-733) * Improvement: Add --long-version/-V option, outputting third parties libs versions as well as Picard version PICARD-734) * Improvement: missing info in the help file (PICARD-740) * Improvement: Pass command-line arguments to QtApplication (PICARD-773) * Improvement: Use the more detailed icons in more places on windows (PICARD-777) * Improvement: Use .ini configuration file on all platforms (PICARD-794) * Improvement: Use python2 shebang as of PEP 0394 (PICARD-806) * Improvement: Display existing covers in File Info dialog (PICARD-808) * Improvement: Use HTTPS for external links (PICARD-818) * Improvement: Install a scalable icon (PICARD-838) * Improvement: Use HTTPS for requests to the plugins API on picard.musicbrainz.org (PICARD-852) * Improvement: Use magic numbers to determine the audio file types instead of relying on extensions (PICARD-864) * Improvement: Multi-scripting UI is very basic (PICARD-883) * Improvement: Allow scripting functions to have arbitrary number of arguments (PICARD-887) * Improvement: The "Restore defaults" confirmation buttons should follow the quit confirmation dialog in style PICARD-890) * Improvement: Replace submit icon with AcoustID logo (PICARD-896) * Improvement: Rename "Submit" button to "Submit AcoustIDs" (PICARD-897) * Improvement: Use UTF-8 for ID3v2.4 by default instead of UTF-16 (PICARD-898) * Improvement: Restore defaults is slightly broken for tags option page (PICARD-902) * Improvement: Rearrange the action toolbar icons from left to right according to the expected user-flow (PICARD-908) * Improvement: Add tooltips to “Restore all Defaults” and “Restore Defaults” (PICARD-913) * Improvement: Make PICARD-883 UI have adjustable widths for list of scripts and script content (PICARD-918) * Improvement: Move Options/Advanced/Scripting to Options/Scripting (PICARD-919) * Improvement: Move UI options page up the options tree (PICARD-921) * Improvement: Add $startswith and $endswith string functions (PICARD-923) * Improvement: Make list of scripts smaller than script text by default (PICARD-924) * Improvement: Wait for save thread pool to be finished before exit (PICARD-944) * Improvement: New guess format functionality should use explicit buffer size (PICARD-970) Version 1.3.2 - 2015-01-07 * Bugfix: Fixed tags from filename dialog not opening on new installations Version 1.3.1 - 2014-12-20 * Bugfix: Picard should use the correct Accept header when talking to web services. (PICARD-273) * Bugfix: Picard refuses to load files if any path component happens to be hidden (PICARD-589) * Bugfix: ConfigUpgradeError: Error during config upgrade from version 0.0.0dev0 to 1.0.0final0 (PICARD-642) * Bugfix: Windows installer sets working directory to %PROGRAMFILES%\MusicBrainz Picard\locale (PICARD-649) * Bugfix: Last.fm plus tooltip help elements are all messed up (PICARD-655) * Bugfix: Regression: Tagger script for cover art filename does not work anymore (PICARD-661) * Bugfix: Retrieving collections causes AttributeError: release_list (PICARD-662) * Bugfix: Artist name makes it impossible to save (PICARD-663) * Improvement: Support the new pregap and data tracks (PICARD-658) * Improvement: Set the originalyear tag when loading a release (PICARD-659) * Improvement: Web service calls to ports 80 and 443 do not need explicit port specification. 443 should be automatically made https. (PICARD-665) Version 1.3 - 2014-10-20 * The "About" window now displays the versions of libraries used by Picard * Picard now correctly handles matching of MP3 files saved in ID3v2.3 tags (which is the version that Microsoft Windows and iTunes both use). Note: You may need to re-save your tags once to get them to match in future. * A sort tags plugin is now provided as tag data is no longer displayed sorted by default. * A new tag, musicbrainz_releasetrackid, containing the MusicBrainz Track MBID introduced in the May 2013 schema change release, is now written to files. * Add %_recordingtitle% (PICARD-515) * Fix plugin install bugs (PICARD-444) * Fix Options / File naming examples to handle primary/secondary release types (PICARD-516) * A new advanced option is available to permanently set the starting directory for the file browser and "Add files/folder" buttons. * Requests to Musicbrainz against your own account e.g. for collections are now handled through SSL (PICARD-337) * Refresh of Albums using Ctrl-R and selection of Other Releases are now more responsive during batch lookups. * Main window is now emitting a "selection_updated" signal, plugin api version bumps to 1.3.0 * Append system information to user-agent string * Compilation tag/variable functionality (for tagging & file naming) has been split into two: - %compilation% is now aligned with iTunes, and set only for Various Artists type compilations - %_multiartist% variable now indicates whether this release has tracks by multiple artists (in order to prepend the artist name to the filename as shown in the default file naming script) * autodetect the CD drive on Mac OS X (PICARD-123) * Ignore directories and files while indexing when show_hidden_files option is set to False (PICARD-528) * Add ignore_regex option which allows one to ignore matching paths, can be set in Options > Advanced (PICARD-528) * Added an "artists" multi-value tag to track metadata, based on the one in Jaikoz, which contains the individual artist names from the artist credit. Also useful in scripts (joining phrases like 'feat:' are omitted) and plugins. * Added "_artists_sort", "_albumartists", "_albumartists_sort" variables for scripts and plugins. * Made Picard use the country names also used on the MusicBrainz website (PICARD-205) * New setup.py command `get_po_files` (Retrieve po files from transifex) * New setup.py command `regen_pot_file` (Regenerate po/picard.pot) * New Work tag (which for Classical music is often different from the track title) saved as ID3 TOAL tag. * New Composer Sort Order tag (variable %composersort%). * Improve the Other Releases list to prioritise and separate releases which match the correct number of tracks and your Options / Metadata / Preferred Releases settings for Country and Format. * New %_absolutetracknumber% variable numbering tracks sequentially regardless of disc structure (so you can numbers tracks on multi-disc releases without a disc number) * Support dropping image directly from Google image results to cover art box * Add %_musicbrainz_tracknumber% to hold track # as shown on MusicBrainz release web-page e.g. vinyl/cassette style A1, A2, B1, B2 * Show the ID3 version of the file in the Info... dialog (Ctrl-I) (PICARD-218) * Fixed a bug where Picard crashed if a MP3 file had malformed TRCK or TPOS tags (PICARD-112) * Add --files option to setup.py build_ui, used to force .ui to .py regeneration (PICARD-566) * New setup.py command `update_constants` (Regenerate countries.py and attributes.py) * Made Picard use release groups, medium formats and cover art types also used on the MusicBrainz website * Use MusicBrainz Server translations for release groups, medium formats and cover art types * Add checkbox to toggle debug at runtime in log/debug view dialog * Add a plugin to add Artist Official Homepage relationships to the website tag (ID3 WOAR tag) * Add integrated functions $eq_any, $ne_all, $eq_all, $ne_any, $swapprefix and $delprefix. * Add %_performance_attributes%, containing performance attributes for the work e.g. live, cover, medley etc. Use $inmulti in file naming scripts i.e. ...$if($inmulti(%_performance_attributes%,medley), (Medley),) * Add optional `priority` parameter to `register_album_metadata_processor()` and `register_track_metadata_processor()` Default priority is `PluginPriority.NORMAL`, plugins registered with `PluginPriority.HIGH` will be run first, plugins registered with `PluginPriority.LOW` will run last * Add Standardise Performers plugin to convert e.g. Performer [piano and guitar] into Performer [piano] and Performer [guitar]. * Add support for release group cover art fallback (PICARD-418, PICARD-53) * Add a clear button to search box * Honour preferred release formats when matching AcoustIds (PICARD-631) * Prevent ZeroDivisionError in some rare cases (PICARD-630) Version 1.2 - 2013-03-30 * Picard now requires at least Python 2.6 * Removed support for AmpliFIND/PUIDs * Add support for the Ogg Opus file format * It's now possible to download cover images without any plugin. Cover Art Archive images can be downloaded by image type * Improved directory scanning performance * Prefer already-loaded releases of the same RG when matching files * Allow dropping new files onto specific targets * Add basic collections management support (PICARD-84) * Allow adding custom tags in the tag editing dialog (PICARD-349) * Fix replacing of Windows-incompatible characters (PICARD-393) * Save both primary and secondary release types (PICARD-240) * Handle errors from the AcoustID service better (PICARD-391) * Accept HTTPS URLs on drag-and-drop (PICARD-378) Version 1.1 - 2012-09-03 * Always show basic tags in metadata comparison box, even if empty (title, artist, album, tracknumber, ~length, date) (PICARD-201) * Fixed AcoustID submission failure after removing files from Picard (PICARD-82) * Allow multi-select in new MetaDataBox for delete/remove tags (PICARD-194) * File browser remembers last directory/no longer crashes on OS X (PICARD-104) * Removed the "Run Picard" option from the Windows installer (PICARD-11) * Refreshing a non-album track correctly clears previous track metadata (PICARD-220) * Fixed the preserved tags setting for tags with uppercase characters (PICARD-217) * Added a completion box to the preserved tags setting, and clarified how it works * Store lyrics language in tags instead of text representation language (PICARD-242) * Fix various oddities in the metadata comparison box (PICARD-255, PICARD-256) Version 1.0 - 2012-06-02 * New UI: Extended comparison of existing vs. MB metadata & tags (PICARD-43) * Merged the renaming and moving options pages * Removed the VA file naming format option (there is now a single format option) (PICARD-159) * Add %license% tag * Made %writer% available to tagger scripts and plugins with contents of songwriter (PICARD-21) * Allow two multi-valued variables to be merged in tagger scripting (PICARD-139) * Allow multi-valued variables to be transformed in tagger script and then set back in tags as multi-valued (PICARD-147) * Fix $copy not preserving multi-value variables as documented (PICARD-138) * Load/save free-text tags for ID3 as TXXX frames (PICARD-148) * Fix writing of MusicBrainz Work Id / musicbrainz_workid to tags (PICARD-88) * Handle mimetype for embedding cover art from EXIF jpegs (PICARD-27) * Change cover art box to open MusicBrainz release rather than Amazon * Support manual drag-and-drop of cover art onto a release via cover art box * Only open browser on left-click of cover art box (PICARD-190) * Fix Lookup in Browser (previously 'tag lookup') for clusters (PICARD-186) * Lookup in Browser will now not use MBIDs to lookup unmatched files/clusters * Add Date/Country to CD Lookup results dialog (PICARD-198) * Fix/reset album folksonomy tag counts while refreshing releases (PICARD-4) * Plugins actions can now create sub-menus using the MENU class attribute * New plugin hook register_clusterlist_action * Display the port Picard is listening on at bottom right status bar (PICARD-191) * Make album drops from right hand pane to left default to "unmatched files" again (PICARD-33) * Remove .DS_Store, desktop.ini, and Thumbs.db from otherwise empty directories (PICARD-75) * Update artist translation to use new alias features (primary flag, sort names) (PICARD-200) * Deleted tags aren't indicated as changes (PICARD-165) * Picard log entries have inaccurate timestamp (PICARD-45) * Interface doesn't allow keyboard only management (PICARD-103) * Added option to preserve timestamps of tagged files (PICARD-31) * Added keyboard shortcut to reload release (PICARD-99) * Medium formats weren't listed in order in the "Other versions" menu (PICARD-91) * Couldn't select multiple directories in "Add Folder" window on OS X (PICARD-74) Version 0.16 - 2011-10-23 * Added AcoustID support. * Fixed track metadata plugins. * Added new internal %_totalalbumtracks% tag field. (PICARD-16) * Track metadata plugins now run also on non-album tracks. (PICARD-7) * Fixed custom Various Artists name on the %albumartist% field. (PICARD-5) * Album artist is now correctly "translated". (PICARD-1) * Unicode punctuation is now converted to ASCII by default. * WavPack correction files are moved together with the main files. (PICARD-15) * Unicode filename normalization on OS X. * Original release date is now saved into %originaldate%. * Allow tagging with localized artist aliases (PICARD-17) * Added a quit confirmation dialog. (PICARD-46) * Standalone recordings can be tagged with relationships now. (PICARD-10) * Refreshing an album will refresh its "other versions" listing. (PICARD-8) * "Unicode punctuation to ASCII" now works on album-level metadata. (PICARD-50) * DJ-mix tags should only be written to the medium where they apply. (PICARD-20) * Support URL redirects in web service/network request module (PICARD-54) * Jamendo and Archive.org cover art is displayed on web page, but not loaded by Picard plugin (PICARD-52) * Edits to metadata in "Details..." menu not reflected in UI (PICARD-13) * The status bar/new metadata box is updated when a selected file/track is changed. (PICARD-14) Version 0.15.1 - 2011-07-31 * "Other versions" menu now displays release labels and catalog numbers. * Added CD-R, 8cm CD to the format mapping. * Picard no longer fails to load releases with new or unknown media formats. * Threading issues that could occasionally cause Picard to stop loading files have been fixed. * Fixed album metadata processor plugins not working (#5960) * Fixed loading of standalone recordings (#5961) * Fixed requests stopping at midnight (#5963) * Stopped using QDateTime for timing requests (for Qt 4.6 compatibility) (#5967) * Fixed display of ampersands in the "other versions" menu. (#5969) * Fixed use of numerical functions in advanced scripting. Version 0.15 - 2011-07-17 * Added options for using standardized track, release, and artist metadata. * Added preferred release format support. * Expanded preferred release country support to allow multiple countries. * Added support for tagging non-album tracks (standalone recordings). * Plugins can now be installed via drag and drop, or a file browser. * Added several new tags: %_originaldate%, %_recordingcomment%, and %_releasecomment% * Changes to request queuing: added separate high and low priority queues for each host. * Tagger scripts now run after metadata plugins finish (#5850) * The "compilation" tag can now be $unset or modified via tagger script. * Added a shortcut (Ctrl+I) for Edit->Details. * Miscellaneous bug fixes. Version 0.15beta1 - 2011-05-29 * Support for the NGS web service Version 0.14 - 2011-05-15 * Fixed a problem with network operations hanging after a network error (#5794, #5884) * ID3v2.3 with UTF-16 is now the default ID3 version * Option to set preferred release types for improved album matching * Added support for sorting the album/file lists (#75) * Fixed OptimFROG tag reading (#5859) * Fixed colors for a white-on-black color scheme (#5846) * Added an option to replace non-ASCII punctuation (#5834) * Support for writing release group and work IDs, currently unused (#5805) * Fixed saving of the release event format tag (#5250) * Added support for the language and script tags (#943) * Plugins can now use track-track relationships (#5849) * Allowed external drives to be visible in the file browser panel on OS X (#5308) Version 0.13 - 2011-03-06 * Changed Picard icon license to CC by-sa * Small UI consistency changes * Albums with tracks linked to more than one file are never marked as "completed". * Fixed matching of scanned files to tracks while the album is still loading. * Support for properly embedded FLAC pictures * Existing embedded images in APE and ASF files are removed only if there are new images to replace them. * More strict tagger script validation. * Fixed the $truncate tagger script function. * Proper rounding of track durations. * Fixed a bug with saving images larger than 64k to WMA files. * Added a $swapprefix tagger script function. * Release events with a date are preferred over the ones without a date. * Files that are being saved as marked as pending. * Updated .desktop file to allow opening Picard with multiple files. * Handle the "open file" event on Mac OS X. * Added timeouts to the HTTP web service client. * Fixed a bug with albums missing the expand/collapse icons Version 0.12.1 - 2009-11-01 * Fixed deletion of all COMM frames in ID3, which was introduced with the iTunNORM fix in Picard 0.12.0. * Restored native add folder dialog. Version 0.12 - 2009-10-25 * Live syntax checking for tagger script and naming strings. (Nikolai Prokoschenko) * Support ratings. (Philipp Wolfer) * Support for user folskonomy tags. (Lukáš Lalinský) * Embed cover art into APEv2 tags. (Lukáš Lalinský) * Embed cover art into WMA tags. (Philipp Wolfer) * New high quality application icon (Carlin Mangar) * Support for originaldate tag. (Philipp Wolfer) * Restructured file naming options. (Nikolai Prokoschenko) * Added option to select the user interface language. (Philipp Wolfer) * Highlight fully matched albums. (Nikolai Prokoschenko) * New script functions $matchedtracks(), $initials(), $firstalphachar(), $truncate() and $firstwords() * CD drive dropdown selection on Linux. (Philipp Wolfer) * Add disc ID to album metadata if loaded via disc lookup. (Philipp Wolfer) * Add expand/collapse all actions to tree views. (Philipp Wolfer) * Added DCC media format. * Removed unncecessary and confusing PUID lookup threshold. (Philipp Wolfer) * Fixed saving of copyright in ASF metadata. (#5419, Philipp Wolfer) * Write TRACKTOTAL and DISCTOTAL to vorbis files. (#4088, Philipp Wolfer) * Added keyboard shortcut to toggle file browser (#3954, Philipp Wolfer) * Write ISRCs from MusicBrainz into tags (Philipp Wolfer) * UI improvements on cover art box and icons (Carlin Mangar) * New Windows installer (Carlin Mangar) * New plugin extension point ui_init (Gary van der Merwe) * Updated plugin options page (Carlin Mangar) * Python 2.6 fixes. (Gary van der Merwe) * Fix PUID generation on big endian machines. (Jon Hermansen) * Fix lookup encoding for non latin characters. (#5233, Philip Jägenstedt) * Fix infinite loop when using Qt 4.5. (Lukáš Lalinský) * Ensure 16-byte memory alignment for avcodec, fixes issues with enabled SSE2 instructions. (#5263, Philipp Wolfer) * Use default CD device for disc ID lookups if no device was specified. (Philipp Wolfer) * Preserve file information (bitrate, extension etc.) on saving. (#3236, Philipp Wolfer) * Allow empty release events (#4818, Philipp Wolfer) * Respect the option "clear existing tags" when saving WMA files. (Philipp Wolfer) * Detect image format of cover images. (#4863, Philipp Wolfer) * Don't load CD stubs. (#4251, Philipp Wolfer) * Set match background color relative to the base color. (#4740, Philipp Wolfer) * Fix infinite loop when using Qt 4.5. (Lukáš Lalinský) * Fixed various issues with the PUID submission button. (Philipp Wolfer) * Fixed copy and paste (#5428, Philipp Wolfer) * Fixed loading of files with corrupted PUIDs (#5331, Carlin Mangar) * Fixed redirection handling (Lukáš Lalinský) * Fixed writng of iTunNORM tags in ID3 (Carlin Mangar) * Always restore window position so that the window is visible (Carlin Mangar) * Updated translations. Version 0.11 - 2008-12-02 * Support for new FFmpeg install locations * Automatically remove whitespaces from MB hostname in options * Release date from MB is now optional * Fixed per-track folksonomy tag support * Evaluate tagger script for album metadata * Show donation info in the about dialog * Support for .oga files (Ogg FLAC, Ogg Speex or Ogg Vorbis) * Fixed loading of performer tags from Vorbis Comments * Load embedded cover art from COVERART/COVERARTMIME Vorbis Comments * Allow setting the "Move Files To" location from the internal file browser * Copy&paste support in the file details dialog * Correct handling of "; " as a separator for sort names * Minimal support for TAK files * Fixed parsing of the "Pseudo-Release" release status * Fixed reading performers with empty role from ID3 tags * Don't allow empty file naming formats * Interactive password dialog * Fixed checking for non-Latin characters when using sort name as the main artist name Version 0.10 - 2008-07-27 * Fixed crash when reading CD TOC on 64-bit systems * Fixed handling of MP4 files with no metadata * Change the hotkey for help to the right key for OS X * Replace special characters after tagger script evalutaion to allow special characters being replaced by tagger script * Actually ignore 'ignored (folksonomy) tags' * Remove dependency on Mutagen 1.13, version 1.11 is enough now * Escape ampersand in release selection (#3748) Version 0.10.0rc1 - 2008-05-25 * Stop analyzing files that have been removed. (#3352, Gary van der Merwe) * Automatically disable CD lookup if no CD device is specified. (Will Holcomb) * Don't abort directory reading on invalid filename. (#2829, amckinle) * Add an option to select multiple directories from the 'Add Directory' window. (#3541, Will Holcomb) * Avoid scanning files that had been removed from the tagger. (#3352, Gary van der Merwe) * Folksonomy tags/genre support. (Lukáš Lalinský) * Added menu items (with keyboard shortcuts) for CD lookup / Scan / Lookup / Cluster. (Lukáš Lalinský) * Add taggerscript function $performer(). (Lukáš Lalinský) * Lower the default PUID lookup threshold to 10%. (Lukáš Lalinský) * Compare tracknumber and totaltracks as numbers, not strings. (Lukáš Lalinský) * Correctly escape special Lucene characters for searches/lookups. (#3448, Lukáš Lalinský) * Use MusicIP Mixer "archived analysis" to speed up PUID lookups. (Lukáš Lalinský) * Add language and script to variables. (#3635, Nikki) * Option to initiate searches with advanced query syntax on by default. (#3526, Lukáš Lalinský) * "Save Tags" item in options menu. (#3519, Lukáš Lalinský) * Create empty "plugins" directory by default on installation. (#3529, Lukáš Lalinský) * Added default release country option. (#3452, Philipp Wolfer) * Added release format type to release selection. (#3074, Philipp Wolfer) * Convert Vorbis tag "tracktotal" to "totaltracks" on load. (Philipp Wolfer) * Save 'arranger' to ID3 tags. (Lukáš Lalinský) * Store cover art in Ogg and FLAC files. (#3647, Hendrik van Antwerpen) * Album title not updated when related 'Unkown files' are modified. (#3682, Hendrik van Antwerpen) * Match selected release event to existing files. (#3687, Hendrik van Antwerpen) * Allow multiple files to be linked to a single track. (#3729, Gary van der Merwe) * Don't use mmap to resize files on Windows. (Lukáš Lalinský) Version 0.9.0 - 2007-12-16 * More custom tags in MP4 files (compatible with MediaMonkey and Jaikoz) (#3379) * Fixed MP4 fingerprinting on Windows. (#3454, #3374) * Fixed CD lookups on Windows. (#3462, #3362, #3386) * Set the %compilation% tag correctly. (#3263) * Fixed location of saved cover art files. (#3345) * The Picard window now won't start as hidden. (#2104, #2429) * Fixed reading of length of MP3 files with VBRI headers. (#3409) * Fixed WMA saving. (#3417) * Fixed saving of comment to ID3 tags. (#3418) * New mapping of "chorus master" AR to "conductor" tag. * Fixed system-wide plugin path on Linux. (#3430) * Use the earliest release date by default. (#3456) Version 0.9.0beta1 - 2007-10-28 * Save ASIN to MP4 files. * Add a --disable-locales option to setup.py build. (Santiago M. Mola) * New threading code, should make Picard crash less and be faster. * Replace initial dot in file and directory names. (#3204, Philipp Wolfer) * Fixed caps in the default cover art image. (#3242, Bogdan Butnaru) * Fixed broken naming preview. (#3214, Daniel Bumke) * Re-enable the drag/drop target indicators. (#3106) * Fix adding files and directories from the command line. (#3075) * Don't show the cover art box by default. * Lookup files individually for "Unmatched Files", not as a cluster. Version 0.9.0alpha14 - 2007-08-05 * Fixed PUID submissions. * Fixed drag&drop from Finder to Picard on Mac OS X. * Don't save files from "Unmatched Files" when saving an album. * Renamed "Analyze" to "Scan", to avoid confusion with MusicIP Mixer analysis. * Added plugin API versioning. Plugins now need to define constant PLUGIN_API_VERSIONS, otherwise they won't be loaded. * Added option to overwrite cover art by default. * Never wait more than second for the next HTTP request. * Fixed setting of the "Move Tagged Files To" folder, if the name contains non-ASCII characters. Version 0.9.0alpha13 - 2007-07-29 * Bug Fixes: * Fixed drag&drop issue on Windows Vista. http://forums.musicbrainz.org/viewtopic.php?id=693 Version 0.9.0alpha12 - 2007-07-29 * Changes: * "User directory" location changed: - On Windows from "%HOMEPATH%\Local Settings\Application Data\MusicBrainz Picard" to "%HOMEPATH%\Application Data\MusicBrainz\Picard" - On UNIX from "~/.picard" to "$XDG_CONFIG_HOME/MusicBrainz/Picard" (usually "~/.config/MusicBrainz/Picard") * Picard no longer logs every action and doesn't saves the logs. To enable more debug logging, use command line argument "-d" or "--debug" or environment variable "PICARD_DEBUG". * For plugins: - metadata["~#length"] is now metadata.length - metadata["~#artwork"] is now metadata.images * New Features: * Save embedded images to MP4 files. * Added option to select release events for albums. * Added internal log viewer. * Track and file context menu hooks for plugins. * Bug Fixes: * Deleting files from clusters increments total time rather than decrementing it. (#2965) * Update metadata boxes and cover art for selected items. (#2498) * Display error message for tracks. * Fixed drag-and-drop bugs on Mac OS X. * Added %releasecountry% to the file renaming preview. * Cluster multi-disc albums identified by tags, not (disc x). (#2555) Version 0.9.0alpha11 - 2007-05-27 * New Features: * Added "Edit" button to the tag editor. * Bug Fixes: * Fixed initialization of gettext translations. Version 0.9.0alpha10 - 2007-05-27 * New Features: * New TaggerScript function $len(text). (#2843) * Don't compress huge ID3 frames. (#2850) * Move "Add Cluster As Release" to a plugin. * Allow horizontal scrollbar in the file browser panel. (#2856) * Removed "Basic" tab from the "Details" window, "Advanced" tab renamed to "Metadata". * The tag editor can be used to edit multiple files. (#2893) * Bug Fixes: * F1 for Help instead of CTRL+H on Windows and Linux. (#2485, Nikolai Prokoschenko) * Tabbing focus transition from search isn't as expected. (#2546, Nikolai Prokoschenko) * Display an error message if launching a web browser failed. * Fixed web-service error caused PUID submissions. * Change function $gt(), $gte(), $lt(), $lte() to compare numbers, not strings. (#2848) * Fixed kfmclient launching under KDE/Python 2.5. * Fixed similarity calculation of non-latin texts. (#2891) * Don't try to auto-analyze files with "loading" errors. (#2862) Version 0.9.0alpha9 - 2007-05-12 * New Features: * The tag editor now accepts free-text tag names. * Load 'DJ-mixed by' AR data to %djmixer% tag. * Load 'Mixed by' AR data to %mixer% tag. * Delay the webservice client to do max. 1 request per second. * Sort files in clusters by disc number, track number and file name. (#2547) * Support for any text frame in special variable %_id3:%. * Ignore empty ID3 text values. * Windows installer: * Removed DirectX-based decoder. * FFmpeg compiled with AAC (faad2) support. * Bug Fixes: * Save XSOP frame to ID3v2.3 tags. (#2484) * Use attributes like 'guest' or 'additional' also from generic performer ARs. * Fixed capitalization of %releasetype% in file naming preview. (#2762) * Fixed 'python setup.py build_ext' if py2app and setuptools are loaded. * ID3v2.3 frame TDAT should be written in format DDMM, not MMDD. (#2770) * Don't display an error on Ogg and FLAC files with no tags. * Remove video files from the list of supported formats. * Always use musicbrainz.org for PUID submissions. (#2764) * Files/Pending Files count not reset/recalculated after removing files. (#2541) * Removed files still get processed during fingerprinting. (#2738) * Read only text values from APEv2 tags. (#2828) Version 0.9.0alpha8 - 2007-04-15 * New Features: * Notification of changed files in releases. (#2632, #2702) * Bux Fixes: * Don't open the file for analyzing twice. (#2733, #2734) * Save ASIN and release country to ID3 tags. (#2484, #2456) * Variable %country% renamed to %releasecountry%. * Save release country to MP4 and WMA tags. * Don't take unsupported tags into account when checking if the tags are 'complete' and the file should have 100% match. This fixes problems with showing the green check-marks for file with limited tag formats, like MP4 or WMA. * Ignore missing tag in $unset(). Version 0.9.0alpha7 - 2007-04-14 * New Features * Remember location in the file browser. (#2618) * Added FFmpeg support on Windows (MP3, Vorbis, FLAC, WavPack and many other audio formats). * Lowercase the extension on file renaming/moving. (#2701) * TaggerScript function `$copy(new,old)` to copy metadata from variable `old` to `new`. The difference between `$set(new,%old%)` is that `$copy(new,old)` copies multi-value variables without flattening them. * Added special purpose TaggerScript variable `%_id3:%` for direct setting of ID3 frames. Currently it supports only TXXX frames in format `%_id3:TXXX:%`, for example: `$copy(_id3:TXXX:PERFORMERSORTORDER,artistsort)`. * Support for WAV files. (#2537) * Removed GStreamer-based decoder. * Implemented `python setup.py install_locales`. * Bug Fixes: * Failed PUID submission deactivates the submit button. (#2673) * Unable to specify album art file name mask. (#2655) * Fixed incorrect copying of album metadata to tracks. (#2698) * Added options to un-hide toolbars. (#2631) * Fixed problem with saving extra performer FLAC tags containing non-ASCII characters. (#2719) * Read only the first date from ID3v2.3 tags. (#2460) * If the remembered directory for add dialogs and file browser was removed, try to find an existing directory in the same path. Version 0.9.0alpha6 - 2007-04-04 * New Features: * Added option --disable-autoupdate for 'build' and 'install' commands of the setup script. (#2551) * Automatically parse track numbers from file names like 01.flac for better cluster->album matching with untagged files. * Support for the new sorting tags in MP4 tags from iTunes 7.1. * Strip white-space from directory names. (#2558) * When replacing characters with their ascii equivalent, 'ß' should be replaced by 'ss'. (#2610) * Track level performer ARs. (#2561) * Remove leading and trailing whitespace from tags on file saving. (#892, #2665) * Support for labels, catalog numbers and barcodes. * Bug Fixes: * Artist names from ARs should be translated, too. * Freeze after answering no to "download the new version" prompt. (#2542) * %musicbrainz_albumid% not working in file renaming. (#2543) * Track time appears to display incorrectly if it's unknown on MusicBrainz. (#2548) * Fixed problem with removing albums/files after submitting PUIDs (#2556) * The user's script should be applied also to album metadata. * Fixed moving of additional files from paths with "special" characters. * Internals: * The browser integration HTTP server rewritten using QTcpServer. Version 0.9.0alpha5 - 2007-03-18 * New Features: * Replace Æ with AE in file/directory names. (#2512) * "Add cluster as release" (#1049) * Text labels under icon buttons. (#2476) * Bug Fixes: * Fixed fileId generator (caused problems with drag&drop if files with multiple formats are used). * Original Metadata not greyed out when no tracks are attached. (#2461) * Better detecting of the default Windows browser, with fallback to Internet Explorer. (#2502) * Better album/track lookup. (#2521) * File browser stays 'hidden' after first time use. (#2480) * Track length changed in Original Metadata after save. (#2510) * "Send PUIDs" button not disabled after albums are removed. (#2506) * The Windows package now includes JPEG loader to show cover art images correctly. (#2478) Version 0.9.0alpha4 - 2007-03-09 * Bug Fixes: * Fixed case-insentive file renaming. (#2457, #2513) Version 0.9.0alpha3 - 2007-03-08 * New Features: * Using of 'performed by' AR types (without instrument or vocal). * The "Replace non-ASCII characters" option will try to remove accents first. (#2466) * Added option to auto-analyze all files. (#2465) * Bug Fixes: * Fixed file clustering. * Added %albumartistsort%, %releasetype% and %releasestatus% to the file naming example (#2458) * Sanitize dates from ID3 tags. (#2460) * Fixed page switching in the options window on error. (#2455) * Correct case-insensitive file renaming on Windows (#1003, #2457) * Relative paths in the 'Move files to' option are relative to the current path of the file. (#2454) * Added a .desktop file. (#2470) * Release type and status should be in lower case. (#2489) Version 0.9.0alpha2 - 2007-03-04 * New Features: * New variable %_extension% (#2447) * File naming format tester. (#2448) * Added automatic checking for new versions. * Bug Fixes: * Fixed window position saving/restoring. (#2449) * Fixed iTunes compilation flag saving. (#2450) Version 0.9.0alpha1 - 2007-03-03 * First release. picard-release-1.4.2/README.md000066400000000000000000000027631310410472100156560ustar00rootroot00000000000000MusicBrainz Picard ================== ![Build Status](https://travis-ci.org/metabrainz/picard.svg?branch=master) [MusicBrainz Picard](http://picard.musicbrainz.org) is a cross-platform (Linux/Mac OS X/Windows) application written in Python and is the official [MusicBrainz](http://musicbrainz.org) tagger. Picard supports the majority of audio file formats, is capable of using audio fingerprints ([AcoustIDs](http://musicbrainz.org/doc/AcoustID)), performing CD lookups and [disc ID](http://musicbrainz.org/doc/Disc_ID) submissions, and it has excellent Unicode support. Additionally, there are several plugins available that extend Picard's features. When tagging files, Picard uses an album-oriented approach. This approach allows it to utilize the MusicBrainz data as effectively as possible and correctly tag your music. For more information, [see the illustrated quick start guide to tagging](https://picard.musicbrainz.org/quick-start/). Picard is named after Captain Jean-Luc Picard from the TV series Star Trek: The Next Generation. Binary downloads are available [here](http://picard.musicbrainz.org/downloads/). Support and issue reporting --------------------------- Please report all bugs and feature requests in the [MusicBrainz issue tracker](https://tickets.metabrainz.org/browse/PICARD). If you need support in using Picard please read the [documentation](https://picard.musicbrainz.org/docs/) first and have a look at the [MusicBrainz community forums](https://community.metabrainz.org/c/picard). picard-release-1.4.2/installer/000077500000000000000000000000001310410472100163645ustar00rootroot00000000000000picard-release-1.4.2/installer/images/000077500000000000000000000000001310410472100176315ustar00rootroot00000000000000picard-release-1.4.2/installer/images/hx.bmp000066400000000000000000000623321310410472100207560ustar00rootroot00000000000000BMd6(9d ϞآNw;tafyww;t;t=vy‡'~wwww;t;t;t;tEz뙮Рƒ4wwwwww;t;t;t;t;t;tS١Lwwwwwwww;t;t;t;t;t;t;t;teiywwwwwwwww;t;t;t;t;t;t;t;t;t>v냡'}wwwwwwwwwww;t;t;t;t;t;t;t;t;t;t;tG{뜰Οē2wwwwwwwwwwwww;t;t;t;t;t;t;t;t;t;t;t;t;tUݠLwwwwwwwwwwwwwww;t;t;t;t;t:r8m9r;t:s7n9q;t;t;tldywwwwwwwwwwwwwwww1_5h8n9o3e.X=f,V/\.Zv뇢}%}wwwwwwwwwwwwwwwwwwZ7a2[pY;t;t;t;t;tH{족Хȓ5wwwwwwwwwwwwwwwwwwww;t;t;t;t;t;t;t]Jwwwwwwwwwwwwww|owwwwwwwAy넲s;r;t;t뎹Js۩;t;t;t;t;t;t;t;tw1]6j;t;t;t;t;t;o:p;t;tK}%}wwwakkkkhgkkkkk<wwwwww;t;t;t;t;stS|8n;t;t;t;t`rq`;t;tK}%}wwwQkkkkkkkkkkkKwwwwww;t;t;t;t;rL};t;t;t;tPQ;t;tK}%}wwwBkkkkkkkkkkk[vwwwww;t;t;t;tO{Jtw;t;t;t:qEp;t;t;tK}%}www4kkkkkkkkkkkhswwwww;t;t;t;t=vZ;t;t;r5bٷR?w;t;t;t;tK}%}www%kkkkkkkkkkkk(zwwwww;t;t;t;t;t\t;t;t:q3aڨk;t;t;t;t;t;tK}%}wwwxfkkkkkkkkkkk6wwwww;t;t;t;t;t;t;t;t;t;t9p4`أ|;t;t;t;t;t;t;tK}%}wwwwVkkkkkkkkkkkFwwwww;t;t;t:s7m6j6j6k5h2c:dֲ};t;t;t;t;t;t;t;tK}%}wwwwHkkkkkkkkkkkTwwwww;t;t8o.ZCmOxOxOxYzn;t;t8n1a1a8o;t;t;tK}%}wwww9kkkkkkkkkkkdtwwww;t8nhؾpppitdv뇢ݠLwwwwwwwwwwwwwww;t;t;t;t;t;t;t;t;tv냡١Lwwwwwwww;t;t;t;t;t;t;t;teРƒ4wwwwww;t;t;t;t;t;tQ‡'~wwww;t;t;t;tEz뙮fyww;t;t=uyآNw;taϞpicard-release-1.4.2/installer/images/installer-header.svg000066400000000000000000000630501310410472100236010ustar00rootroot00000000000000 image/svg+xml picard-release-1.4.2/installer/images/installer-wizard.svg000066400000000000000000001034001310410472100236430ustar00rootroot00000000000000 image/svg+xml picard-release-1.4.2/installer/images/tango-install.ico000066400000000000000000001301761310410472100231110ustar00rootroot0000000000000000v h X.00 %u  n h(0`eefijimnnmmqpppwwwssxvvzzzz{~}}}})')(##$$637796 LHZZ\\AANQ`[lcaaefjlssttvvyy{sww{{||{~{ŶøŻŽȻʾͿ¿ÿĿľ¹ſſ¹Ľ300.0172 9|ǯ}I0 2 Kÿƽm2  1: ))))('?3úuqK.㦦@ %{n`vJ6宦!ؼkgov6䮦A9ߧ~tjiljtw6宮"ԕ_fHFGhzy9寮B/ĿeES~|T岮#IʛQ-zO`3岯Dpٻ< QW0岲#̬b+[aV3彲Dɧb\* VX3彽$pƧ^[5\b1ŽRIDz]P>4&3Ž$/ǽxNLP=MTŽůcѽ]NPYZq;Ů$9䲦||}sos3Ųdϣ||:Ů$ %Ų|||Iǽp3}6ǯ33Ǧm7}6I6 Dz33:pǮU6:ǀp;769Kϲ6ǍϽ6ǣѽ7ϣ7 U13TK,1J; 0;9::::::::::::::::::::::7tYtYtYtYtYtYtYtY?tYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtY?tYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtY( @```fffhhhilkmonorqpppvvv}~~ NNaa~~~~wwxx~~ddº¾ǿûޏƽȽȾp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq"#gxe9#xth`\(UUTthZW?@9]]]] e_XV6D)3NFv|yyyif fyfJ'Drruv~~]:yyyy i[B://4o"z{yyf ]{E:B[]i{&{{{{ iiB[]fi{{{iEyii{{{{"![y##y{**{y{%%"#!!!!!!!!!!!!!!! ??( SWUKM{X[sehgfiljmmlonnqpprrqtsruuuuu~>?egQq/Pp",6@J[1qQq/Pp  =1[Qyq/"P0p=LYgx1Qq&/@PZpt1Qq/&PAp[tϩ1Qq/P"p0>M[iy1Qqұ/Pp  >1\Qzq/Pp!+6@IZ1pQq/ P6pLbx1Qq,/KPip1Qq/-P?pRcv1Qqϑܱ/Pp!&,>X1qQq673,0'%3//0'?41&3  3***910$/  4++* 50.$* )4+,+#1+'%'+.04,-, 9+%)4---"4$%(*.3..- 9-**+/3...'!29943./. 6666, 3///)6666666637/0/ 6666666636433077777777330-+++++++++PNG  IHDR\rfX_IDATx}ř73v3 @ dAr 8w}g}}l}pgLpl09#B@V0[]]]ݓ'+P[_#FE#F!&11ĈQň F*FL1bT1b@UbĨb#F#&11ĈQň F*FL1bT1b@UbĨb#F#&11ĈQň F*FL1bT1b@UbĨb#F#&1UAЇgr5[K}=1lOR_L9ayl[ƗZbj`?`R_KF#r%&I((;-$0J 42R_K=lYZ$0 $z-h&US,S`Mh]~- K}¨zLDzգlYScc}/3{$F) à7^v%J1oer,OMM̊F6E;w;glH5@SCbIZZp2Ǩx{Źuߠ;v|Q{+&* 1NG|@WSSC -1cz*t}F:MVDwj"~c޼r/J츱dԷ)*r$M6ɔg_`$jھ}dMBF-W,\&֭[iu=V\Dɖ[* }xpMJOa@׿HsBQ=iڴitQGi~O>$S{J} 1*'N;NK怯&CF AQ'O_.spp ޽{K} 1* N8^x MO/=䋲"&_e+Zt'j_ݻwbT(&MH?| |#RG(`"u<͒%K^C=Ĉ@{|I瞣͛7}FrEYUUC. {/gy&hTW9OD[ҦM~ #K}\d y\{4ejnnvi`nWPI-kEgьb>oDz,`$ F~x'>A'W(^ZQGR&t~_ѫɅEr CMy[SSp tsO5j?9Hm 6ˑ|(6=\:Ӹ*X9An@Qꮻ\PiáKM$0;QP$ [oqM@ =WTX[l`quG9,2zQYRǼ0=}BO~h#luod@_ 0:ޭHѧTP]K}٢, ছnTb1Nrh#~Ptp7 ?2&|G~۵N$1FTPP?ALTsիW}tgFb:@ A o:,] "DE!#,&@L!,(E8&AL(uݪU<3̝;ϟcQdr!$E0aH3F@DȖV A$Ŝ@LX@ &?y:OctAG; Ev 8nܸVR!&/ `t"& 4xHb ",aF %ƏQ 3(i.0:@D%=[εmѢEt9Db1D0TowqtWFb1D|\"1Tb޽{ˮ =Xꪫ"|b1J"B L!&hwwb 6y5bQ@㏏@wY0><gڏHF|0I@ڹ׊.AFL! L(@GGy$]wuQ?rJzs ɢgohԩFW3,Lɭ[rB8\x 'D@(JŋiҥQ?Od-0a!6/Ub˖-"[%43<@ȅQ߲ADR ogٳg=4Wϱ"B}``6l@V^a̟?y晘 A0`XQ4qlٲXq;~1 >mr6|F);qpbg,BH@6AĹ۵koY&c[~f@.M7ݤ(0Q􂕌w}~> x+.Dz,:蝱Bf"]LH\B+~@ v1䂨TeV@g_ <^>{IA=X*L)@[AQ/_ , B+ lΝ=?яZJ.Ũ!R;*_ #20%x)mqLgr>&!B\۶m8̖җTiK"**b(v\z1a s>Mx3d@}'m-jF[KVCJ( ^iG?@]FrL=C FB @vO+r}PSUF/f#0O<1s@x^=I'DMMM6`@͗X|5DBp_lMGC;޼y]@8K9~NK?@<4J$̙SvQ>|Bhr|l &ٞlgmڀ| << ]l @7P(!>}zDO+kCTS0 +0 a 3ɠۯ~;imm9^?hW$S yϗ2xM}q jr&`(PѦ\O{.@?7ЈI97Tv5~$"Мfw񩘄̫3 Ƶp]z< V~p>ap AyAeRH:DvPx)(LLQ<Q!VaEw74N(~V$Zwᑀ)I^<B(~ 22=UcQ[@ $ ҺGvi*;^~g;*!?x$5 g{R{lvs~VMzmZ{?DY+C& 4 Ze$K=@` R5wo5So}q4[)g ?2+ # U} U$H SP$0h F (CPuQ<1XڟN}ހ@_Mu.}2K A$ Gd _J*0vEц*Aq=ji$]7]pAԏ Jn!e!~*W{sQɧp5`y:p*91pOݕ5Ǣ@~[>y j 0 j99vT1QLZ`<` :(р`E;ƲᓟT={6/!IFo[ψr~$``6t 0r%yI@'0"ljJ8+H*e?v?;VȳH"sВ~k&UCsڲ:H~U~\wg^HÏЩFF8k ?ǝOJpTgs{ I70Iy&y^3~?񏼂daQM0;:h&;/ lbڷpkzE(KW5~uI{ٻ` %^0U|oƳ`*I|n"85'dױ' !}LbfMGri!h3rŨ#bE0y3=Я! _@L-X?!C rwɁKB)2b{U4װ۝q~)7쥛yvm*(D8AT3|G?&sف '"?e0U@)Lj/}3:ڳi(&yeDNI5Z3y"A0T[ +H'ylr%\e\ 絍i㪼.'O$^G0ah}dg}Q"Nb$Pp8(9=ai蠯~5yQ=?K}vaC@A bU$lGrni9mKN>6Cb\%gÚ}G/5SILm Z*bPGjRvadlEf!Izm $҃1#kBUhaG ̚:Z|Z?bՓm=#d ئ]D!bKRSSFvi  "0a'@V:=b ~,lAA:owT`{/Cuook 'ـ <l'=p '[0mkO[8qcwi/! \2^|Ez͘Sp<#B֨"bG0Æ \ۦӔwsΡcqmxwd@sEF# i{{ L.AW`۶wkf8;NC%У)SVԋQrO}=س{HDb 6]ohD!~;A|fcq fR!m ޾Pg{L%KW%d7:ب ,V֜5#Աc/ňڔ"+ aZ5ipebg}b[X[hv.P(>=7L*n};a(& x)T( ڀۉhX\4.&(mUhH0d;ak>9OGm wVOppNёGr$"|fƍ|@]}' g 3FijbfV3j%b&3fPsi`DFuV23<A`x0L!t=LtY;BbMM.3?s&zim7fLi+A+Cr;ʢ3kۑfL|Lx5 &.aȝ(O<g Ps%x㍞NMdA]{qsa@/Ф?׬1z-W]Mcg0]"Ņ  54sL3:vtvnaZ@v&5!G#nJ;[>$ @'@Ǡ FA #jF ;`J'uTfkN1 iϱǺ͚5OF`Cg@Ӗ{WBDP95ڵos{9$>gh>-g?ƛi_T (fHلvjznoS{?~ݓ^!w S r@z5Ji+>%r #db*]nگV 25clWCiM9Urj0::pUcP+6MkhOJB. LN~zM`L?;fΜa[ta@Pbի0I%`@QHڅ<:Z;u]&TQE")`/̟?ud78ގl!%W/5J#uk0 4G?:e0iΠ!ì2Yӆuws '2р&s,2iX0}aG{0gwY@4f#?)ʸ袋x G|A6V5hoogDA?CSU㟤}ߓ'M=9"S D ԓV* !BhL 9uBM 9>5)_g|Ѩ'(Mk}.8W&a*>ێ^|][_% w?d+TK}tߦZDai֓Τ~9]A>m:,;w7IYBP*_W[g&3@w.yTܖuPL.c հ^;tWWu ۲%?w ;y_>&9=>pݺDamo(%o :?p~]YŸ-V5ߤvvxf:[w>{Uo "4@j.?Loz?g,&a@)"~gAS#@E|uLU=8A۶oI'{3 xz`C\8 `wiE@9D&eb YPgK1~bN99>1jگ*~]-WDŽi惿W_]Xvp8ﳩ@" YC&s N@owxn!wO[?{ 8HD?? ZV0{zQC4wRӺu.v-<%L74Rmça i 23|( |$ od-AEC8-_^z3yFlʖtc %bhoIjJwɓi-xH#p5y҆[#RebprJ/ }#jt?͛#y'2vw@ M Ҭ6m*sޫϘ9 hllvn$ vz0hÇ[iĢwBm!= zzx)45z}( +'UPl_oP~g<ۯz>BM\NWo呃;ā"l؆Z4#47ǭI ̇矏@=8?;hPcƴvmVH9}9ӈӓ$Bw69۲p WVoό򞈢  w<1v-;߅# I[npڵ(q4Mzyr。?iF@>YɡsrBؿ֠$dA^_mBH ?@. 70*SqPN x _ C)<߾n^ɏJ_U> ̪ 3V]ܳg/ lٲ7t7^Lط`u^z1:Ռew;h`D8$D Ĥ'w;M̀,H@4yMv'#}>C(f "~H?YiltmH@00mi u/w7OT;v=Kd`i cX;=rwN0PʂzfB ތ.%h"`B %R!І5tYvrmHp!rL@¾Z tIB YE&`ggzf>!(4-F6~c'>Am(>oTCC4O?{bffgL#4F:bb~Pky$ꌨf!vX!0H +C5ʩx/=Q'[Gi@WHh$ر2(;gDQ%̀mh|)$L7!Z;\c6~>PRblgZޣ2f>w\ƃ6Cdj?ozI[)y@s0#NXDANKaD@|?OWn] Q׿"Q L"[6]}h.}3I)2 97' mvl;/=cd&LG--]מݴ{/m߶{Gyv<0-*= HL#&{xߓշiBS0$ 9@P ln)wa((j2KpaE4^}6ߘwT gݤno7YG ~.y\@ =|Oo/?R5|1V[{0NF;i##t@R0xoTΞM;8Nr\pY fAS`1'OTx[pH`,TE #_`LsK~uV0?U_'hd>w;&ҥKy[ WHGW `X]C 0-L0g!^F􆘫{mB}ݴq| b&5@C>cdC2L3b; tINZ֭zz9Ьg @7$k{ j 7o [@G7#+ i_DbZa?[C $ zc=֥gO xm|8(E_Wf o9Ֆm4Ą>}Z0;ރCT̆Z[hzRA.i8qCv`&sma Қrb|K$f*Uk Ii.Sq*cN91lMRS{シaPP QA:zi}( A~ÁnߛO^)ɧԆ=/FFBlŋsb M԰S2RFҶ*!%ciwu9xy普mp~ӟ*;g7g0b #9hXsî dO0Daꯚ/y%z!3r2ػ@7tC :³i`d9ג!R~^T4{{wG9YfgHf h]5L[j=Àlz+[&2d傊"(I`.CJAFxxmM90XFd ^v :CC۰60sipD< >{ VƜ5756&@ ^tDCu uWc 4651mYd 3# ]X$ v_ϟ 2_Th*[at3MarFpF#V]zO?(?t1ǡ'2凊NoOkSEA,[R  S G]F;So_-xS\6,?94 פs(LY-АL{2q49z@PZ'uwd2|^\M+ܖ;&b^~:BLz=wk۬Y:sEJ߰rR5ܙ&zw'Af_ZD‘+mC ^O> HZv?!ff^̙sc1(-BL{0'jgܿw~XnNe$F {zՄ%hmtJ v5)}˗SǭMٮE1ЎsqmC'>[B#4S1|pa{{P Hi m K HG7>/!g1x/mk'ϚhسE?[!֖1T[WMIwK/'SrL uv޿PQDS>@*q@^EK/Qmy {.-^s~~LlCgȀ f mx!(X# ٱDN9n@z.=4.7k:Pp7 ܐu0zM a$r!*Pj`T׶cup "p >9&g~߰z|YDDom['tw-#UK#p b1`oXfa5H5g&~d'EC!'I6'kh~Nc0T lٲuU@Q(iGw<,[nP;$#i;Rxx.2擕[S(Joklfoܸ e-B]!!Ʉӫ{GeiDvz3x++H@J4F̦Z*O*t&I虨 l=sMbB4=Ĺe u=& j*N/'0y**;g#s# r{9!kp S@8 @ BUZΓ ldc;IJn!H\BO I#B3 MxgHxĄT @xHd>k|a@Նmmtr5| ࠤB GLxU}lT$)"|B Uq+'֭Lc?jHBÛǤcH1m& (BAr-9*-تB 1/E4aL'˖-~V{*&$uu]wG*|`Pl`)`l+^J 1:F%dpahdð7m|ץƓ=eZHaZlj0HcN$SԬ!cYc57'IZ>Ra'3=<\{߭l RFԧġ2P&#>Taa_ ۞ ,ztb1AaCRf  VWV jB[prC}_OiU|!mvy|&$0$X_ L5 x&(zO$E>p5|v (@)yٴc=ۑ$ʅv=&'O!`۩Jd@ g'5(Fs0F<T uڂ!`` {&2́lv%SP7Rۛ0/Qa;e.q># JUW_usPa UZMe]߻kc&TDhR[=jӚ nKQ*~Adp(XBȻL{~H 4c Wv?l}% PM2ӄNϐT}DA?H֫i[p^M X >A~}&z^!Og0$h$׹~`OMF/ݎ ڟ"ꪫ5vP*@nv` @ oYIRiCPLnb $=]{xR4AD@!X;LY,qZC&0í Z: $B @s=V,akL?E;Ta +zCC !R;E/} z_~|8N whB!hB#iå^=x0of8 ^IUVy*KYE~"]D2\> 7j *s2M 8C0aD<#Ч4 A&^vrbP]woohz_s8$O*ܾ ىgB7BPn.ĶmT[hdhӏ?B=)"^>p)T(@9~gzOw\) ݟL ,kY!6q>zV:@ mw+_sNlo~f[V?ƄG7$$K\s_Jrj " NfVG -[ 9@-ИLr<> _|o;Z`%ɽ^HÙM@38.AZ}};::ts9qt& ,53#KfC\NYg*0Zϔ=g_S@&b C%R:#U*)|_x_SM/s$/0xF 8k_ 伖dR.bsssVvPꦎYzP3ᦀZ5@^{־(|:vTĀ gʂ/{}ϓq{O-fz)S&axxs_WEv\4ZMA¯IQkOMfͿ!kh/55թJ/*z}v| \S>ckjhД)c1m O>=# jrI$!Y QOO9hZ|D: epz|wBs"(Hy! 13\kۏ믿)|;CM:K0[bۅ {Їu/)--->77S&Ã'N̎d@~mjqSHs r5a7P~U]Y&0/ڛ~٢>~{7YrL:K8S̙c9?|>hNg^1` ~:l;{zC%ihhxSA$ m9H|ORT(MC|~cn\w=ܸa8z~GBQefC38iӦ}M>)&QbDl֔RX&c۶HlYu+;5> 7]ڰoznАt3 p%1 IH5#"81zg;? wZ/,+WX o}nYz5c-~do3!?sb&PO'O=W4mC8# C0 t$ $ 2dlޟ iZMZig& Y_ #oPZ|0{`kSYf zY_A(h@5k%K]{H4*܈ OϤ[]vl/b0aI i8m(j 1q {%`_h :N _u'}H z׮]KVbgg'~!~ئM(D 8YsܹsRSS3'UUЃkll@uvL=A, K0A#tڂI@١@8QmaU{4|ZM>Gom 09w}A5\&z~y[o#C/]Xì;묳njkk=B{~8 DUf܀GAv' |DvBّ ZDvT6A𛙝G7(߃gJa;8P܂>BzĐIC1® $A{sf̘qkx:7JM Z+a1@tgp ݜb "|Ql7 k#7a#i'}XZD4K5P-[ofIzݺu/~;r\z)0H ~(RT"%1vئ:tY M\|lQCޒXLuB;.Ǐe} /< 8cvƣ2L* aيd^M@t(&g A@|3ٿR|ܾιG6h|MO1X'ޓO>B.}Pٗ_v@);[O(Ewl9m <O{)||{b, 2  ]C v2!=K3 fZPg6Zᇺ;̂oe8Tv(+@rXt=ZPzԩSۯ⊏ G l'I;1F@A6!h^P';}&Ё/;o{=}B|YU-@ˆ w6 ' HO"1ZJOi\9肠@Xx1'?Za#p 0Ma3~sHZ&'_/fIۘ <wֽ|,`¾}>#wXb9?ujh@QF = T;.yŤIZ/˦Mv"eh+p&"#m˴]hiX:;;=ԟvիW|}_xu[d߀s(Q' * }6z{|r{O>y)gfsq`:Cxt!W"m$>/tcMx2 (cm۶񈴨/ЇH= שD 4qADoc@ N3gιLE&~3gΤ6= m TH]]]}>so[!:[MUGC XȧE ?L|6BsYz'r A ̬)aA!&r\Xu?<[/ ܋|??^luNlHz24SR{Ν{#\x{{;o0n8 `2gƋuCQmG{/eګ03ٳgD Dz"> ŒHqlcF:f F${ooʕHdhAC& %h$OT:t TTJˤg"izƌK,9Oe~ ^Xv#?ˌXw~ª)X!2(adߏQ!pgeSvm3rbT>m~f2xoSRy.kL #ydeR(& hLŸI$41G}iӦlEL*6 }+֯_k ^':}Zlɠ(GP/&‘9&7gΜqƎ{r !Lg f7u̎588oSOO[OBmyCZY@ "U}?;]uT= DaLNlsΝpALjaJmmmKMMMXRD"Q}dd t0m6Z 7XT]%f(\2hh12#_=R97)۲ uc TsAg>ˈ% z SeZ|64H!x AL1tE1 Y ΖVِAZ9_#&@Lv6NЃz\5:I+4q|"@BK x?_E>@PbDl O!#~@;1(%T||FbPL1bT5b@UbĨb#F#&11ĈQň F*FL1bT1b@UbĨb#F#&11ĈQNGmvVIENDB`(0`    !)1;###iFFF\\\kkkmmmhhhUUU999S3) !!!!!!!!!!")3=GU;;;rrr藗aaa"""wC1  !1 ? B D D D D D D D D D D IR[dooo槧SSS S5!$$$qgggzzzzzzzzzzzzzzzzzzzzzzzzvvzvvzvvzvvzssxmmqhhkeefſÿkkkQ$ (YYY||||||||{{wwĿooo .'ddd¿eee dddss``Ż_eee$$ȻſeeettZZľý˽¹Sfff Ǿžfffvv##ĽefNQ{ž̿úfff77øŶ96jlʿ fffvvAA~)(οƾ!fffAA{)'Ľ!fffxx7763lcƷfff$$`[LH{sfffyy ļfff\\Vfffzz&& fffccefff{{ffffff!fffvvvfffdddfffdddfffdddfffdddfffdddfffdddfffdddfffdddfffdddcccaaa___Xllliiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhkkk___X___]kkkhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjhkjiii___W___gmmm{~}lonlonlonlonlonlonlonlonlonlonlonlonlonlonlonlonlonlonlonlonlonlonhhh___Y___qmmmggg___Z___w___`````````````````````````````````_______________________________________[tYtY?tYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtY?tYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtYtY( @   )KKKirrr삂oooDDDu;%!(,,,,,,.=+++e{{{ݚxxx***m5\\\cccggggggggggggggggggggggggggg???maaaLLL/```gggdd```¾ƽ6```gggOOȾ```xxɿûǿ¸```hhh»~}~~```~}~}ȼ```hhhww~~~~޹```NN```hhh6```aa```hhh ~~```6```hhheee`````````hhh````````````iii`````````bbbiiibbbfffрfff`````````````````````````````````````````````iiieeeUUU```Ȃmonilkilkilkilkilkilkilkilkilkilkilkilkilkorq~~bbbUUUfff```偂z{{```fff ccc$````````````````````````````````````````````````___ddd??(  3SWUSWUSWUSWUSWUehgnqpknmSWUjmmruuorqSWUfilprrqtsSWUuuuX[spsrpsrSWU>?SWUuuuegSWU>?SWUuuuKM{SWU>?egSWUuuu~lonlonehgSWUSWUSWUuuuSWUSWUSWUSWU3SWUSWUSWU3SWUSWUSWU3SWUSWUSWUSWUSWUSWUSWUSWUSWUSWUAAAAAAAAAAAAAAAApicard-release-1.4.2/installer/images/wiz.bmp000066400000000000000000004556561310410472100211670ustar00rootroot00000000000000BM[6(:x[ϳϳγγγɳſĿĿĿþ¿¿¿¿¿¿¿¿¿¿Яг״ӳڷӳۺӳݽӳįƳԼ³ԼԼԼԼ³Խ³ԽԽ³ԽԽԽԽ³ԾԾԾÿԾ¾Ծ¾ԾԾüԾƽԾ¾ԾĺԾƽԾԾĸԿżԿÿdzԿdzԿԿԿǷտ¹տ¾տľ¼¼տ½ƿտþ½ƾĿ¾տ½¼Žľ¾տº½տ½ƾ¼ĽտûȿǿĽտ¿ŽǾǿտþƾƾļտºƾƾտĽƾŽžžļžž¿ľĽĽĽ¾ýýýý¾¼¼ÿ¼¼ýĽĿý½¾±±±±±´½¬ó¼ij¯ijij¶ij±©ij±¼ų±¯ų±ųò½ųò¶ſƳ­ľƳƽƳ½ǽƳ¸ȽƳǬҳĭӳijӳijټӳijٺԳijپԳij­ԳijƭԳ½ʭճͰճԷճֳֳ̱շֳŷǰֳųֽೳųϵᳳųůᳳųðᳳųŲᳳȳᳳͼɸĵʿź¼ŴճŴڳŴڳŴڳŴڳſڳڳڳڳڳڳƽڳƵڳǵڳǵڳǵڳǵڳǾڳڳڳڳ۳۳ǿ۳ǵ½ɽ۳ǵĨ۳ȵ¿۳ȵ}}}|||κ۳ȵ¼xxxaaa````````````````````````````````````````````````````````````````````````___\\\ZZZXXXUUUSSSQQQOOOkkkĽ»Ǯ۳ȼž```eeeĶ۳¼vvvddd栠ݠݠݠݠݠݠݠݠݠݠ۠ڠءÿ۳```۾ddddddddddddddddddddddddddÖŘ۳```Ĕ<<Ľ¼ٻÿƏ۳¼```Ź%%ǿżĺøڼć۳ſ```Ũ```mmwppǻŸĵô۽ùξ۳ø«```Ƶ~~99ǼƷŵűï۽ĺȼХ۳ȶȰ```ƘwwĸƴƱŭíܾ»Ƽʼü͆۳ȶζ```ǼDDêüǼ˼ο԰۳ȶԻ```Ǫ```nnw{{нƽɽ̽ԋ۳ȶ```ȵ}}''žýǾʾϿ¹Žذ۳ȶ```ȜOOȸǵƿɿ̾Ļ̀۳Ȼ```ɿuuÿըǾ˿¹ƾ܌۳```ɫ```ooxжԺğɿĻݫ۳```ʴ}}}||<<ǻ¹ǿddGG@AZ]|Ѹúƾǀ۳```ʟIIĹö³ȿƾzv%%egЮľùƽԀ۳```ïVVŻ·ĵƷ{st¬Ļ݀سȵ```˭```ppxdd¹ƸQN)(ɰȾĻㅅdz˿```˳www{{qqƹ{--|ɷǹƾ吐{{{uuuuuu}}}```̢zzĸx~z¾Ƹ晙uuuuuuuuuuuu~~~Ӿ```Ǹooü}43 yûʾ珏|||uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuqqq```ͮ```qqyaaĻa\75zŻĽ郃uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuqqq```ʹpppzzTTǺ{10zzʻ̾ uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuwwwuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuqqq```ͦFFȺ|B@**w~Ȼ܀uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuyyyuuuuuuuuuuuuuuuuuuvwwvwwvwwvwwvwwvwwvwwvwwvwwvwwvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwrrr```88¾|~fa]Y}v{ĹŽ̀}}}wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwuuuuuuuuuuuuuuuuuuwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwsts```ϯ```rry÷뭭{|{wxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxw~~~zzzuuuuuuuuuwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwsts```ϲjjjxxooꉉyzywxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxwwxw```ϪHHր```ɨ 쵵```а```rrytt䆆```Ѳcccuu|66쳳```Ѯnn¿```ү++򩩩```Ҳ```sszff̀```Ӳ```ssz{{üރ```ϰ))荍```⮮¸Ꚛ```Եfff𧧧˼␐```Գ```ﱱՆ```Ăހ```ՉÀ```ֹnnn簰̐fff}}}```ֵ```ᦦƢ```}~~```٩```}}}```ճ```xxx```ؽvvvɾ```ppp```ض``````ppp```к```ppp``````ppp``````ppp```ڷ``````yyy```ϴ````````````Ŋ``````ܹ`````````ϭ````````````ɒ``````޺`````````Ϧ`````````tttbbb{{{``````dddwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvvvvvvvvvwwwttt```eeeyyy```eeeggg````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````aaasss```eee```ffflnngiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiigiihjjstt```lll```gggmpoilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkjmlsss```lll```hhhlonilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkjmlrrr```kkk|||```ijijmlilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkilkjmlqqq```lllzzz```kkkppp```kkkxxx```kkkooo```kkkttt```dddffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffddd```kkkuuu````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````nnnpicard-release-1.4.2/installer/picard-setup.nsi.in000066400000000000000000000176021310410472100221120ustar00rootroot00000000000000; Modified to conform to Modern UI 2.0 !define PROJECT_PATH ".." !define PRODUCT_NAME "%(name)s" !define PRODUCT_VERSION "%(version)s" !define PRODUCT_PUBLISHER "MusicBrainz" !define PRODUCT_DESCRIPTION "%(description)s" !define PRODUCT_URL "%(url)s" !define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" !define PRODUCT_UNINST_ROOT_KEY "HKLM" SetCompressor /FINAL /SOLID LZMA Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" OutFile "picard-setup-${PRODUCT_VERSION}.exe" ShowInstDetails show ShowUnInstDetails show BrandingText " " ; We need this so Windows 7/Vista lets us install what we need to RequestExecutionLevel admin ; The default installation directory InstallDir "$PROGRAMFILES\${PRODUCT_NAME}" ; The default installation directory InstallDirRegKey HKLM "Software\MusicBrainz\${PRODUCT_NAME}" "InstallDir" !include "MUI2.nsh" !include "InstallOptions.nsh" ; MUI Settings ; Make installer pretty !define MUI_HEADERIMAGE !define MUI_HEADERIMAGE_RIGHT !define MUI_HEADERIMAGE_BITMAP "${PROJECT_PATH}\installer\images\hx.bmp" ; !define MUI_WELCOMEFINISHPAGE_BITMAP "${PROJECT_PATH}\installer\images\wiz.bmp" !define MUI_ICON "${PROJECT_PATH}\installer\images\tango-install.ico" !define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\classic-uninstall.ico" ; Abort Warning !define MUI_ABORTWARNING ; Welcome page !define MUI_WELCOMEPAGE_TITLE_3LINES !insertmacro MUI_PAGE_WELCOME ; License page !insertmacro MUI_PAGE_LICENSE "${PROJECT_PATH}\COPYING.txt" ; Directory page !insertmacro MUI_PAGE_DIRECTORY ; Components page !insertmacro MUI_PAGE_COMPONENTS ; Instfiles page !insertmacro MUI_PAGE_INSTFILES ; Finish page !insertmacro MUI_PAGE_FINISH ; Uninstaller pages !insertmacro MUI_UNPAGE_CONFIRM UninstPage custom un.RemoveSettingsPage !insertmacro MUI_UNPAGE_INSTFILES !define MUI_UNPAGE_FINISH_TITLE_3LINES !insertmacro MUI_UNPAGE_FINISH ; Reserve files ReserveFile "removeSettings.ini" ReserveFile "${NSISDIR}\Plugins\InstallOptions.dll" ; Language files !insertmacro MUI_LANGUAGE "English" ; Adds info to installer VIProductVersion "0.12.0.0" VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "${PRODUCT_NAME}" VIAddVersionKey /LANG=${LANG_ENGLISH} "Comments" "${PRODUCT_DESCRIPTION}" VIAddVersionKey /LANG=${LANG_ENGLISH} "CompanyName" "${PRODUCT_PUBLISHER}" VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "© ${PRODUCT_PUBLISHER} under the GNU GPLv2." VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "Installation for ${PRODUCT_NAME}" VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "%(version)s" ; Install Section !Required req SectionIn RO SetOutPath "$INSTDIR" SetOverwrite on ; Files File "${PROJECT_PATH}\dist\" RMDir "$INSTDIR\imageformats" CreateDirectory "$INSTDIR\imageformats" SetOutPath "$INSTDIR\imageformats" File /r "${PROJECT_PATH}\dist\imageformats\" ; Write the installation path into the registry WriteRegStr HKLM "Software\MusicBrainz\${PRODUCT_NAME}" "InstallDir" "$INSTDIR" ; Create uninstaller WriteUninstaller "$INSTDIR\uninst.exe" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "${PRODUCT_NAME}" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\picard.exe" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "InstallSource" "$INSTDIR\" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Comments" "${PRODUCT_DESCRIPTION}" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_URL}" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe" SectionEnd Section "" imgs SectionIn RO RMDir "$INSTDIR\imageformats" CreateDirectory "$INSTDIR\imageformats" SetOutPath "$INSTDIR\imageformats" File /r "${PROJECT_PATH}\dist\imageformats\" SectionEnd ;Section "Phonon" phonon ; RMDir "$INSTDIR\phonon_backend" ; CreateDirectory "$INSTDIR\phonon_backend" ; SetOutPath "$INSTDIR\phonon_backend" ; File /r "${PROJECT_PATH}\dist\phonon_backend\" ;SectionEnd Section "Languages" lang RMDir "$INSTDIR\locale" CreateDirectory "$INSTDIR\locale" SetOutPath "$INSTDIR\locale" File /r "${PROJECT_PATH}\dist\locale\" SectionEnd SubSection "Shortcuts" shortcuts Section "Startmenu" startmenu SetShellVarContext all SetOutPath "$INSTDIR" CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$INSTDIR\picard.exe" \ "" "" "" SW_SHOWNORMAL "" "${PRODUCT_DESCRIPTION}" SectionEnd Section "Desktop" desktop SetShellVarContext all SetOutPath "$INSTDIR" CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\picard.exe" \ "" "" "" SW_SHOWNORMAL "" "${PRODUCT_DESCRIPTION}" SectionEnd Section "Quick Launch" quicklaunch SetShellVarContext all SetOutPath "$INSTDIR" CreateShortCut "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" "$INSTDIR\picard.exe" \ "" "" "" SW_SHOWNORMAL "" "${PRODUCT_DESCRIPTION}" SectionEnd SubSectionEnd Section "Plugins" plugins CreateDirectory "$INSTDIR\plugins" SetOutPath "$INSTDIR\plugins" File /r "${PROJECT_PATH}\dist\plugins\" SectionEnd ; Uninstall Function un.RemoveSettingsPage !insertmacro MUI_DEFAULT MUI_UNCONFIRMPAGE_TEXT_TOP "" !insertmacro MUI_DEFAULT MUI_UNCONFIRMPAGE_TEXT_LOCATION "" !insertmacro INSTALLOPTIONS_DISPLAY "removeSettings.ini" FunctionEnd Section Uninstall ;!insertmacro MUI_STARTMENU_GETFOLDER "Application" $ICONS_GROUP RMDir /r "$INSTDIR" SetShellVarContext all Delete "$DESKTOP\${PRODUCT_NAME}.lnk" SetShellVarContext all Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk" SetShellVarContext all Delete "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" DeleteRegKey HKLM "Software\MusicBrainz\${PRODUCT_NAME}" !insertmacro INSTALLOPTIONS_READ $R0 "removeSettings.ini" "Field 1" "State" StrCmp $R0 "1" 0 +2 ; Even more attempts DeleteRegKey HKCU "Software\MusicBrainz\Picard\persist" DeleteRegKey HKCU "Software\MusicBrainz\Picard\setting" DeleteRegKey HKCU "Software\MusicBrainz\Picard" ;RMDir "$APPDATA\MusicBrainz\Picard\plugins" SectionEnd ; Checks whether program is running. !define WNDCLASS "QWidget" !define WNDTITLE "${PRODUCT_NAME}" Function un.onInit !insertmacro INSTALLOPTIONS_EXTRACT "removeSettings.ini" FindWindow $0 "${WNDCLASS}" "${WNDTITLE}" StrCmp $0 0 continueInstall MessageBox MB_ICONSTOP|MB_OK "The application you are trying to remove is running. Close it and try again." Abort continueInstall: FunctionEnd Function .onInit FindWindow $0 "${WNDCLASS}" "${WNDTITLE}" StrCmp $0 0 continueInstall MessageBox MB_ICONSTOP|MB_OK "The application you are trying to install is running. Close it and try again." Abort continueInstall: !insertmacro UnselectSection ${plugins} !insertmacro UnselectSection ${desktop} !insertmacro UnselectSection ${quicklaunch} FunctionEnd !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${req} "Installs ${PRODUCT_NAME} along with the necessary files for it run." ; !insertmacro MUI_DESCRIPTION_TEXT ${phonon} "Installs support for limited music playback in ${PRODUCT_NAME}." !insertmacro MUI_DESCRIPTION_TEXT ${lang} "Installs translations of ${PRODUCT_NAME} in different languages." !insertmacro MUI_DESCRIPTION_TEXT ${plugins} "Installs all of the plugins that are bundled with this release." !insertmacro MUI_DESCRIPTION_TEXT ${desktop} "Installs a shortcut on the desktop." !insertmacro MUI_DESCRIPTION_TEXT ${startmenu} "Installs a shortcut in the Start Menu." !insertmacro MUI_DESCRIPTION_TEXT ${quicklaunch} "Installs a shortcut in the quicklaunch bar." !insertmacro MUI_FUNCTION_DESCRIPTION_END picard-release-1.4.2/installer/removeSettings.ini000066400000000000000000000002121310410472100220760ustar00rootroot00000000000000[Settings] NumFields=1 [Field 1] Type=checkbox Text=Remove settings and personal data Left=0 Right=-1 Top=0 Bottom=40 State=0 picard-release-1.4.2/picard.desktop000066400000000000000000000007241310410472100172270ustar00rootroot00000000000000[Desktop Entry] Name=MusicBrainz Picard Comment=Tag your music with the next generation MusicBrainz tagger Exec=picard %F Terminal=false Type=Application StartupNotify=true StartupWMClass=MusicBrainz-Picard Icon=picard Categories=AudioVideo;Audio;AudioVideoEditing; MimeType=audio/x-mp3;audio/ogg;audio/mpeg;application/ogg;audio/x-flac;audio/x-flac+ogg;audio/x-vorbis+ogg;audio/x-speex+ogg;audio/x-oggflac;audio/x-musepack;audio/x-tta;audio/x-ms-wma;audio/x-wavpack; picard-release-1.4.2/picard.icns000066400000000000000000002355541310410472100165250ustar00rootroot00000000000000icns;lis32wywwxxwwvwwwwwwwvwwxxwyvwwrttytstutttttttutstytr<;;=;:=;<@;;;;;;;;;<@;:=;;=<s8mk//(.^o^o(.//il32{uxxqxwwxwxuxwx{xwxqxwxwxw}wvw ѳvw ywxvw 洄wxvw zwxvwwyصywxvwwͶwwxvww wwxvwwwwxvww wwxvwx}wwxvwwx vwwx vw~wzwxvwwxwxwxqxwx{xwxuxwxwxxxwwxuwx{qtstttststsxtjtu#4QШtu%Ihu鱀tvutu 9itɀtu Oj,tttu ] k,t{yttu ijt鞀tu,kkk^ttu:kOtttuIk@׋tuXk1 ⸁~vttufk"t~ttu&kdttu 4kk`bkkUt|xtu 08$ NkFtttu73tutututjtsxtststtsvq9<<;;::<;<@<;9<;:8<;:@<;<<ʻ<;: Ӿ<;=f`=;: ڳ<;۞;: <;MosG;;: ؇’<;CԠB;;: ˆ<;Qk;:¸<;UK;:<;JR;:ڮV;: JFu}i=;;:<;G٤;;:<;RipH;: º²<;Dզ@;: ¦<;ed;;:<;:<;:<;<<<;:@<;:8<;9<;<@<;;::<<99l8mk0„7^o?G w M^M^ w  ?G^o06ih32 xwxwxvxwxwxߘwxyvwxxwxwxvvwxzvwxvww߻xwxw܀xw ~؎wxxw۴wxxw߶ywwxxw wwxxw ܘ㶢wxxw|wxxw¶wxxwwxxwxw xxwwxxwywxxwwxxwwxxwywxxwwxxwwxwx xwwwxxww~xwzwxxw|wxxw}wxvwxzvwxvvwxwxxwxyvwxwxwxߛvxwxvwxwxxutyuttsUutuutss+ututuututwutsUutuutr&FȎutv.Sk>WЁuts.VkΕpYutts #[ktButts6kti/utzwtsDknkfutttts TkhVk`utts bkRkQut܁Ȃts!kkzkCutvts1k}ȃkk4ututs@k%utw{tsNkf utt}yts]kWݔtsjkHŀts+k:͢wtƯ{ts:k+ut|tsIkiuttsXk]uttπtsfkNutΏts %khUA-.ak?utts#/ Mkk1utΉts5e!uts"utsutvutrutuutsUutwututu+ututssutsuttsUuty::=<;;Ʋ<; ʁ<;<πɴ<;US;< Ĕ<;rp;<ׁ<;Aڞ>;<Τ¾<;\۹;;V;< ۢº<;O]`J;< ڝ<;;<ـ¤<;a=;<˜<;b۪=;<<;>۸C;< <;;F|۷B;<yׂ`;<˓و;<p>;TۣB;<<;D;<<;ۻ~ۄ;<<;e;pۤ;<<;ۣZے;<¿<;VԀN;<–<;ZS;<<;<<;<<;<;><;:=<;HHFHHoHHMHH^&HH, HH^HHo4HH;HHAqHAf|HffYHffHHffHHffHHffHHffHHffHHffHHffHHffHHffHcffH|ffHtffHHffHHffHHffHHffHHffHHffHHffHHfAHHAHH4HH;^HHo HH&HH,MHH^oHHHH6HH>^HHoDFit320hwvxwxwxxwvvwvvwqxwwuxwzvwwwxxwyxwfwwywqxwfxw۶xwvwxvwxwvwuwpvww xxwxw txwwܖuw ƭyuwuwϋׁuw|ww ŵցuwyZYmuwzYY[begfd`ZYY[uw`Ybpvwvqleւuw]]pw׀uw߀yuwuwŶwuwx˶wuwwօuwрiYc~w݄مuwy `Y[ZY|w uw `swte{w߆uwuwyw܀uww}wuwɀ柳لwuwnw߅uw~„qwuw佅vw ڇuwȹwփuwxwځuwnwuwzowuwuwuwwuw}wuwrwuwxnwuwswրuwwօuww߀څuwvwuwnw׃߆uw}qwՉuwuwۋuwwuwzwuwpwuwyowuwtwuwwuw~wuwtwuwxmwuwrwuwvw uwwuww݈uwnw߈uw{pw uwpuw߉uwr\Y{w݅uwr\Y_gnaYe{wօuwr]Y^emuwk[[rw ߅uweY]emuwr^YmnwۏuwdfmuwvfY]twԁuwo]Yww܄uwvcYdwuwl[Yuwuwt`Ylwۃwgrw txwxw vxww یpvwڄߋuwց ՀvwԅՄxwځրxvwvwxwfxw۹qxwywxwfwyxwxxwwwzvwuxwwqxwvvwvvwxxwxw{xwwwustutvutsutsutsutjutuututw+ututuutrutu3ututuutsruto3utmutuutuqutuyutwřutut߂|tuutv߄ԍtw uttusut gVntsutȀVʑtuut qe{tsuttwutmbtw 8žvDut\gtuSǰwtxIJ}VWqtu+ҟxt}dVWktu#D 謕aV\mtuĉtu&KiAIA1_VVepmqtxԂtu)NjkkCZVV\ܔ[VVfttu+PkqZVZsVZlt΀υtu.Skpmhc[VXrtVVtu.Vk p.utpeZVViՀt^VVutu3[kۃkh.uthYVYtlytu8]kkke)utsbVWົztltuLknzkkd&utiWYԃti_tu\k+"ka$utlW~toVׂe_tuikk^"utlˀ߇t `ҞvVVmtu)k xnk[ut{[YtVftu8kkXutVVwtmW^ktuFk9&k7ut`btktuUkk9Z΀k(ut potntuek҆skhutomtrtu$kt߄kZutsWVqtxtu3kpxkJut bVqܼyV_ttuAkmrk;utqZVYpttuPk+utq_V]otvtu_kutpiiotk‡tujkautqtu.kP࿒ttu=k@܋tҀͅtuKk1tVVtuZk"tф]VVutuhkfZVhtyjvtu(kVZVtȝtu7kFqlaVVہļkVV`tuEk7uttnYV䂀VWVg^tuTk'utpYVߔZVVams`V\zVVltuckiutoXVk̈́iV[mtq\Vetu"k\utnYVatrdYVV\ktu1kLutp`Vvtstu@k<utrljifa[VV]̀tuOk-uto_VXtu]kutgVVtuikbutlXVtu+kRuttnXVtu:kAutwutp[VtuIk3utDŽtqZVytuXk%uttpZW€Ōtufkhut WtoY\܀tu&kXut̀VɆtnXm~tu5kHut _`ftlVytuDk9utthtuSk*utfct_utuakjutavket_VVutu k^ut l\ؤ^Voty_\tu/khT>*OkOutVet{ʀʅtu=kiZB,4gk?utyXVWgtgztuMkkiZB,Wk/ut]jrtl_Ղ[tuJA,@jk!utYot sYZxVetu'_kdutu؀`tjV[rtuHkTutVotjXV^qtu0dkkDutgatuĚtsjegntuUk5utXqt}ڂ{tu=%utVhtuڄutuutW_ttuuttЁXZrt iVntwuҞutπVΒtwۋttyrettsts ٰtv~ats pVeƒԂ_etusZVY^^\Vt\VVW˺WWqtwZW^jqtqcVXVitpstn^V[dmtkZV[ktutqmortolotuyutuqutu3utmututsrutu3ututuututu+ututwutstutuutjutsutuutsutvustw;;:>;:;;<;<;:;:@;<;:;<;<@;<<;;:@;<@;<;<;:7;qg<;<+;KE;<;=;;pͭӀc;:@; =,Eے;<@;ۣ,ۡ;; D3Oۏ;=;gԹ׀W;<3;AՄ1;< ķ;P25;:ĉЁ`?;?ӀU,-:;:Ӂǁԩn?;Eeʁ:,7;:Ђ ϲЁƄЄ8,/7;o;Bׅ7E…;:.,}o;Rˁףvƀـ;:972,,΃B,,bك6;:;8.,Z,-,d́=0;:;9.,~֎l0,,18;1,2fzQ,,7;:y;9-,Bڂ?,/7;:/,4;:~;8-,7Z׀Y;4.,,/6;:;92,Mq;:;:;:7641.,,3w;:;91,.;:{;4,,};:|;7-,sp;:;MyvG;8-,we;:;?ـ֊=;9/,U;:;;:.,B;:;eՀS;9.-;:{; Z.fۊ;9-2uR;:;ۨ,۟;8,DԂ٥F;:; 31:ە;7,A;:;{Ài;7ŀځ;:;9Ȅ4;c5L;:z;2N͂A4;{6,,~<;:}; 72Դ|4,9;sQ1/х;:{;{i,4;Jˌ;:}fc;A.,-4;5R;:}gcdjszlco;n/6:;75p/;:|gcipzxdd~;O/9; :.0rɞP,3;:pchpzicx{;=_1;6,/:;:oqzrdh;,9;6.,09;:|gc™;i>1;=lh;6447;:ocnŒ;[\-:;EC;:xdd;`р,5;<<;:kcx;vր-0;lѭҀf;:t;O..:; @,Eۖ;<;NԂ٭n<;۪,ۦ;<3هV;;AiE3G۔;=ljٹكѱԀ`; ۵jLMVώ1;<@zF,;قԡi64;:@.,-00/,K]2,,-a^--:;.-069;:2,-,5;:8;80,.28;6.,.7;<+;:78:;979;:=;:9;:<;;t8mk@++ݳ! w Uf4< w M^29oM^18oM^(/fwDU'-fwCL%+^oBJ$* 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 $*AI^o%+CKfw'-DUfw(/DUo17M^o29M^ w 4;Uf w ݳ!++ic08 jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2d#Creator: JasPer Version 1.900.1R \@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP ߂8&d G63'H*+npy~NY|`d*1 =oA{MH,DB7߂(|W$# Mu?U.a,Nj1Yv=FQQjy)C)J߂0&wg{N1KΉN!vPGZ8)kB(2j2L*#6 ?߂Pp3A6BAH :"VB>kse3qcoͦeM#?%xw }b;(jvXͺC:3Ϣ;T4[jkE?eH;%6d8y~PI&d!O\Ɠ&;+ݟ]`KrRyOj z!EHN8a$_X-dG *E! N ԝdzC+.^zzS`lg&}n28@k!rvs~]su>(Y2iVm<ĬP-)9܊v ~A3s Z^Ջ6ncbQ9^ 2yQ.˭|Gi-䠘MjBWZbSOM+5{"ހ 6b--I`_Q^2{SIF' Av>4 B!lI0V85xl/ߚGb9g&W .*$Գڭ3-)%3aK#aV}qʟ[}iuMJВYNGYr|:?# r6EWc)LjWeO"7O$74R %zrND0 9S%pNrm}cȭ ?ƿ:? :jUw3Ed"0A|m\}Ca ^$pڻC#2t 1 w5@g0"[᫷= Q^܎lo?؎=~z43Ӡsͮp4Q uzL?q:A>ň >*wyX` Bs,=0v+£nRQ¾,.o3*Z1D}5;]ԡ5+UݏD5t<9a3A8-G(;YNR)2)̬(4jH3aNEsDzF;5R,DjR\fJ*9_RY3vP-o֞"&{W y3̎͞5ӅtL=_>;+i2"$ *ہ166p0bx^.5hm?:z.PS_P3BL$iv @,lŌ(dNT4ypRAPh|1.?<)Z/+B A@t㚇(&DQ4&F> 伌5a? !'ȑZ),/d3ՙ..Y`z] eZqO ?J)N`Bж2&!]*1 u4}z`Y]Ue92a(c\L43pg b˜ϾU[v5oҗ=ʯ$;b fp FMkl 9j (%:V#NP:ɣl'Z'B(/@B;V#HvaqA׻G]:+%Ĭ kkC(\Lo|ӑo!.Lm̝1JɊ"-[l!,; qw(w¡7@5AϸrÜ5jHBj9tvlM 7S#&w ԘH[h"ԤI1'5\2F:wœ=t}5zg)AIlES Rf '2yܥhxהNyI9~^o>~ z0ߥՐ5IQ (|/^Ҋ&c]oeH [czxڻ{&slUnx߾rCԞW}7]j pFXĖ34&6H5?&{KhCt$\>18=³nB a Ͳ7aBT`*v6 ++y!!s@ YuVZU;?c,`<4X2 kHi0Î8Bƹ9xelDb#2ﮌZ#Mk$Q$7)*x]]ɼW~!t^ }wB+WdoҖUlVhq+ŝoqJ=E0ɔ- _#i\DED4hC0 .(;)ZyLdH`' VAQPUYcL_f:Wk0OJ́تW B@(\cd` |C(IfT\()dxd ]czӨ^ߕPn֎*o Zfk6I'gPC~?\^5.G6QQ 8ISU.@$d4IS*J.ew5u_T}6nޖ+Q?eJ| mjoI,yfRP;znЭR^]\" >ɞ@8+/0d ֤!i׾\r0\,Qd#~C+_XGYO$4oaw7" tZrqb3o[K X1Az, b`ZqR*<ʩc6b-oU[ݡ[:XֵSe^ 0s!Y|eȔQ''°zhOf9@- Y/Jv[҇-r]t5ٴcG[ :=**l83PǍS/ )1BM/g$t,0U 7tt28,Sτ7O5Ct>+'8bxQ+1w7D" +I/".bW<2 $iU"/ٟ֘t xoF?tEkޘE WzM]op+B.án~P_o;zI܋qF.9Ez\2؟C.sC/}Wk䣞Y ޷G yH\J nifqg R< JWa?;NKvQ,X1.y5j\VSۍB479\^$/AGHƯ)DJ'PUXsL{^]G"ZwD;_'thI~A*x8@r}hgCYSCboSj^͟Й@*k9.7S($8#B(h"x{.I*%4\/HVMkR01^\rbq>>83Y]ej*(JHd`TF1 =̳<(JcN H4e -&`N3"z+i nwLuҋqs"T6$ NM +ս2ƧN*tMqC~b!ԥbQ^S|kK4K~ <7qy?t`ݶSNJ$ڲ toQ%}.SeaB=YS檇pW`ܸH⍧g_K|G3kR@i? " H}i^J䅥@j^swQ3H<8aeOEODeE ڕ9t4`'ة;LHߪ=gZIl<1)>7o|iB Id|ghL4Oࢼ6Ffޘ*#ܻB[Y< YO#DH%`6X+򸽧# >BdS2Sxؾ9灠DmvF̽p5DY$'vi ޅj$sʧUT{6 )ƻA8tT*Ł0O){Տb{ʹe!.v #χVfXvj#-dG 2a+mz6xnJMDMM=Bh,a+h"z%[2 }a ~+ãioL~%,=jEMR4]H*|0|ALz ۼBZ GKNFl# +fYjUfX͇Hhƛyxc%ҼlZ~s2t]դ 2Sd~qE㧆n7!{eƒikFr8R ^qD(NgwJ@~nqwo'm2@儆o9"54tCq(їZ/=Q+ޝT^@@6y&{*%I#G@)}p=F:'׿x.0F^Vuv"àRh@*ZB>mj^+yQ4-rsT7"˹ƎrlQyYnr$̽U!;*{߬wdF7 B#T[Oc T/[`WR~ Ys%;5B-X'>ZMʻEPa)1j,`a> 0| ?iL]"_O)7b4Ma}W͏b+i2SJh4j܇C<2 Z;5dm1Vq0!@RK Qh?z>Ƕlo\TxYӎڞ|[ ]n+L<<@ЋpY~a^%fO{?T'om@ݰKw4b~w샛Y:ڷ:e7v:=nY,~\hƺ"H":k~{0e SjnQ }) Ӽ43E6'o꾁pWrA|FίULy #\SD&ȜC _1E+5튗_CG˫4hŁKPquZ&2M潲) l"/.[:g߽/U|uk:(ԐZ#޻Vӈ:N3Zkp({K8rM'n~w伓Ku*p4_ u9WH!ÆHٰy'8](t ߁l&M WMyc:z*ñuS&o_EA%*2TɌ׾>D@wS"-ilX%WLXU 4,e3Kqiɴ\6ѯ^,T^Jfgc؏ՉP&߁{_24!G,rRo(#]%-r3贎;0 8Ϝ*@bȉAvyK蓞b3X_ eIYEǭ +4ݓEDddm/Eug'?@\ӛEHUq5.&l {NXN15EMqdXg ^8uK5t+Zc= .TXxUŀ~Sz*I,y7LLg\Bh\E#^h* O t7˲К}:{^c{Tyl]d5S996` {NdLA> fVn`%jM{pŅsU化g+|?4KB Xz7>".  pZ1l|ӏ* vE,sz%Z*b_) Z.7;n-*U&=7x3Ӑ%fˇ9"7GF`."4zح1olju0?Aޭ[jis´A2l%OD/n6y^QTC GPlR|/yf,*n)I[)3Pl{|Ta՛gk Cut{F&ss'՝e[LWcr;gƣ2Q#AR`dw|cԾa=_;tIj ފl愑t2?R&lmtɹPbftj+H`?HHCg8'"\x“̘drgG7h>W7zNszDOgyxjMfJG._?>RTAG.5e[(@G%@^s*&^7bIN^enTSDⰒ7aV!Ջ:YS(g՘N" F߷O+'}+mLrVDqɻgǰF!օ3nќީ5~'g44-3_Y~ tڇ圃}TSr#ה$ A:d&%봑E`6p6H+&q_DY~T7b@Toa~k *QnpKS~Rg{LSz@tgKʱ nq5Mok[$DfK02.:;TouKK·'AO$KO DC6鏪4ҚEI,us 4' 7ӱB\y>a9dz !8՛g0ST.PÚX?ͱY6 b TXޭ3@=BfAdFBIS`k-q0I-|jP!n)GgGۚi/3YQorLH+x qS+&B4/YuGD0&jv.b`8,cjH룈/I( $sD1/qhe`:`۝> @'`jҥwSƹA-"z);+͡Z߅sn[)Ew$MUYv\ݖdɏ  '#K( A"h #'ac=Bt9Έ0ԋ3D@=~#%|b\هJb17>ƠܤOZR1Sdq 'lw68 tIWFf7OC,Oz2l`˔ּ5orZ?.:}ꍾ,Z0NhjoZ~8vժ؅u֚" qXt10TJ~>Bļ:b%q*lidʥ |7d~+q1]n&lz*!EZf8㉆ db`!t~BiLPV#ɷ d8hV?  {0I-fu&[ڤJ3s 4uHaiYwFSCm‘՘cu~T{c)jtVMP,:̝4I(&[9MRe\vC~{E|pg gD@K.[p[;K>XgJܝK|4oPW3%׈WƔS^5tR"| +/̔Ba, vRН>T+\*a\#19N"@g153U6B@0T.D.+TN_Yi 8gX9u<ΝZ:tKO9`W' Ic)IuQʚPTNxuq䓤+υ#r!IXF!0m;?6^2H\F0|[y$6er:JDYY[jL<)n챼qm}0 pEUӛہ:fbfШW}m_tϞ*I wYyax ifJ{~z+嗎AB .3ÿEQvxCvKAu5v;>r8id<ƞ!|v/h c5y:P'brZ}U,H!|5! RtL!A+ Kf$%T1yI=ZH9,,S0 m5[è[zd9׻VIŪF,BvщA{ d'3kKfP>;x\!.![7\V?A񔛌d=5(wMf8P*jJX@sGsS+ӱpyΛk2\,ff 3))~.=(y @O[n6e47AKW9#w/<ķh$rZr!x[zfRzxOV%%#ݘиs,*(M}3qbͭͱjT] $wAc¯Sr oZ7;/X=xq oky с|+xC{pȾA]&_<}% 4; Iߛ>%DbW17c7pNr>nٝA)J{u;#ʥY->Y}kPА1b2$z%Lq]jf9oK8 "aىq@Syi D۔ iL~3É3X:9AZ=!#`8ujZE5E7ߎNȩ") C.Ih.8*z!`?w ɤe \"kxwor=!qQU߰Z`FK3 #6_8>qT jQJ];v~[QУ cE}?Sί/ EMtpr/B<ݪo }9#\.R` 蛑Ϡh\oSJTmmJ矷&<94OmG?5:4KOAocXY8p+ Pu{5q.3TEs<}4>tg : SD&:ѸRX@J`'oi5g +7ô?[;{ktuIB62Sb*`%I*{r;Nb^/:h@%*Hzj8?rDnnF%0Q rˋZ 09HR%>/{|H>*`2k0=²VӳE!<\P~# @{D򡜩ÉGmܑT s{1;7ˬ*']C\^Y\b 0wndoQK,!(QdӞʥDFLw 3ШhlԙXZ֗71M^U`\p$ z#5ltӱ@~xT^mpn dϸ‹{Xխp`Jx־(aLVkqKE&&t Lw&\#[qзÐU(v/ Ũޑ*r"S~Qzұ`~ GGOkJzF^?&\4 n#냳UYgߋr3bKA{eyzL?mCU|jg8g܍Pڑ aD'_Nv]j^.?GM%d8+vP:d9S6G'Xv *||y*p?DY3ӏ` i)uNl]8y#[\H*("w'lwq-?+@١*H$aȣ4JD晴[C$ND@EM^.J pE6z~'k sa#YnK}Dzl[[L>jx, ߬ICȲs'BD[={qTX?1 (r: pV@=gqΛ#}DVI:B(P0j#y"ykߤXDK&2(*M3Rk U㘳<^鵒HHyB3}x۱]%kO,Ѧ`%~o`\f'Ͷ`v2x .E%sosԉ|TK$1CQH|$78oTv}@{yeq$%;ǃ(YFk^bHήtL:L,&yE^. =L f ˷]ƋB/ؗ#NV B\%n6~ύ m~r\! #\<2U'wg?|6R?'ؚm5<=t`0 IU~&l

'R-wzcͷyRkKb2G"[Q)D.o2AE'1nZs+(Q%3`> _ N;|ghV޼YXC#L408eײ%2Q~Wmi3L #Qp[# ;&f?~Qz.Ȍ@TD-l߷8URsΈ:O%2S:k' +rs=AaPBp8*ʐl÷ Qn+|g<+ ̭)Fi5Sœ;JN:ޜNQaܘ6!]@Z[ߔ C ˽>/2UY8oظTkn-dh+lΓg> pfʠQ7^>Bkj.n"WZ.4J,x87g h /=(SVG\|eژc+cef((I0[<|%/H?ۛT!vL.W/ƣ᜙ J謍Iu)S18îR d]#ǐD`ŝ.h/L1NiIA7+<(rb!};%kF-IzHhka?@ sJIF-J TlF3Գb56ť`hBl04odVUujSyONŒ}zs/)!לNn0F5ߜ4NXU!Xl#_y淋-*% ĤF(k~֗9[3OPˬP { 8Sc>>$} d~X3 "\SH;?P)d.^aX>ӈ`%7"kVzo4;7yv?Z'@5 ٹTKI9^&K=O"2JEHHRF QX  67Myfc[T }=hVOv;nTDiyyKeJx.8T 1E<ɸ4(ԜճC8u|v|n3sebm'nml ºN]ùڙ'%L+0bh3Tn&5жaCĎinHeNƀJ+cF4̧e^<D<*0I#+ˋmuZЮv}vSsG"jX鑪AF=-*vD| oqwD5 \Ae'@U!haq-Y[ JhwMZŨ(c],}NiZu* ݛy>J(S}Ɵ)ai/ 9t8W擾ňf^itz.,|Sllh֯d6uF\ (>aU~&W+'ܦLyc7#\G*K)* \_MtFMVЎ~xmw!64xeZ~c|L*[S^ ㊯*8$u}Rp8WXN:4#ZWJ 4Px\7Qx L~&W rke`0픤\9WPlI-@#"E"GZBzqz#ůWRBq~YẌw3Uo ;kؙem׀32HLQ5n" g 7i@܁n~_$2WYMW)nQgˠgxz}w:`d(<]usfPw]nZ$FA淄G3Ta`QLD]@2f6xZ)ҊHJW& 4<ۄ(fTt[]ܓ BP8Q4 EQ d̈bZ" qﺔWR,C2 }uq Wd=K݊+!7g-q]b'NKN[I$D6:ҁ)L9.UJg U y0Pok[ڬƉ֨O-b&-VT+sb.j`없+.(2I8 a 99 >{$|tWcE}I 2k4^&%gO#@6:=x| Cd!ūm Z5zs+yMF` -Y#e2L:,L  4rU T1O=\<"$d82Y]qI !TrMr&[d1)cCD!mGIyÏ ;ױ .5^һyyt6eB4#фD,[v:ulc|):n55j,=uyD{2UG')H~0"38gj\H}U8Pi}09yDw"\%Ȃf~ef}{u!ӗƤz *wtjƲQn;˟۞`]XFʈQin?\kobn^I)]S;cCnQ癱_9 )sie!Ș-\F!!h]O$B:4ү Y;Ӣu68 pvFAh>"d,5dڙ.VGki~!v'@TZv4z X c_oh鬮DqxS2~.мήܗpsŝ$%nj]oBl5րveԶ]æIl~ GMP)+Oz?l>`BJbW'|0sŵ#U&OЭ YYP jca$& a%+Evl:Q]x|`tϰ'*ܸ(&xnZW+HƉ]"G<%kʖ1 GzwW'ol/C#zT2Us a'u}`y+hcEsÓsǮ_kiVE϶U95֙^Ƈ(]u[ʞڭ}RV$~YgӲipYHxu.z9jןyS$ϩ&EJhn;ZϹɨ5YiT_&a:it 0*`Gݲe0H?7mV/KQL_.PǵY|"s<grXɤ/'S"@d߱6U-NcٱF*% frOe 4)fs +]T|9aؘ*wUOE-L.+~"GUBVZ :\P? ci?TLZ,΅8=xdh GBsK F2T*-nW9jg^{0pfg5t`-l>XjY64[#ьfr/;rx~i+~Æ8])ObWkݸMsK?H R|/S1'QGx:#S hZ\#륑0YG0~&=st&,\<" C9l //viEýRwm)~}GsL1By>?j*+ToħYheλYZ״c@' LG/XWx?TK7esz fZb~I6J)w:]( \fbitn-*W0~Q P;GK2p4sDnŗ+ˍ6{yu[4u6NJ_8ciؽj߶A dP?0F9*n/)If,Hc8j@='p,"A]p;s B6gNrI<-ԗanKBXgi0^N:;$A{B<ma#KA;֗=cO@N(99})wYSwPoT696nY~€p I gRLyQ_nW RR3@s?Cy<@#ԯ-_g1 %P9)"_H'ٷ(yYIk| VF/g8t 9EG9PoiABL" &i.XE3Vs푢y,DS\ЊxQYE~ɭuũqtE*UwִK;Natv)K26ey]TuoB`om 唛٦k-s62S}]f"\T:zs3%95k[0wb$6yN@DM嘊qm#,B7K%vZXmtww@VIT8UB..ZN>_vb1Ԅë<6{ jdhh҅96J'SZ?n] hWh̲S6v+j4ƕL)t(Y3 1q*p u*wJԹnq}S3CczVWRX}jihgR#Y5HQ MCI%qk{\6S'!5wC{* <@U9rR뱗:DeE;qw(sN FYs,i6Rl[2؀ &-Py YCa&xz+%_l` =))\|75q r! &4 "SDx/9vZ uʦ; ~p8OE*qóM#XQGIW]x個,7Qo=t_O LaMDZ^(DMԋ},R^`Hz.)in .A6ܗ^`WE 0!KD@T)(7hBCS~`B N$vegF67/,{3A=_a@'FFzr5<6&2йq-W3V_ItN<'zwalyk.S+QTJ:1玂"b#آx37muq XH7֯c"^'B7ss Hi{w hb5SVx %%*!\C7*q9ebZi]Q7/wF6+P5V~XjhۯW@ƛg,Ge*z# d@#gsN/Spu՝PV6 Y䂰z(}?0CxGD%\##u-h rn+~=Z?ŒR-Hn$^y6h~*z@~+M"`NIy41YB,?tr0)bYm[j0vh dON:@RX" 1Dёt n`؛>(V^;SEDrC 3jg!a].;hm@ɵ!:Ӕ28L%M uy&at u .;_IUގqFQ>!\'< u qD5Ƽ)#EЂְv10NHFFv4pi:^8UV ZQ#*\EIԫ1=] P:#n13Uu)VZq< 4ɂnN/+:glοd?BDȀP,%ySZr1 k"6ײk3fF6#ܱAY̡2$lr,T5O="9)0+IʟF7Ǖ/d1J3Ib$wPhNW޾_)Cdٔ rʇق@B+⍌ذ>2\*Q=l]GsYW(sڔ<_k?h٘ډN5}g9g u}DFWYުyhoÖMDm?gzw)w~457;t[ոL8UL2\v߯T H0װM)*5#w,v+0^z"CgnP  anÏL rìFᗥzxnMk!T m! }Rmoa@(/1EH@))F㒫7dS$rR&|_a 6|/6[ 0,LЅq5"s9D/B#P?Q v+ʂ$V1_H.\+q 9ov@EW'3 io4PRIwY@nae"yWLG%T>;q:BCۆm&Jt#'lEFdUn`B9-As$if_4[U6(^|uPr!DGBAk%nw^Boq "RX%1E SKd~O``Ul$`|}uO>ϻWDra?s)F7a$zsA%`[}pT2 ]e;A5fڂ k6u ,f110FE6n a[-`~%:0]q}81ZDF.DF4u C#2 'l ApO =-83뗖_p&4W{-;t_ȉ_EGx'a;%kx1 E+yQI/}s: ;5 9AkK#qO]&K33#->b K,[JB] C~mg3WFt YP\n w5ʧ. SūhR9,-,nb*cwk$.l]ub\:D󮼗ߓ` i]_`(!Xr;IѶOsm]Q|#dRK q211 6$J$ ؔHnL?EfA\տCIr?VR g#$B|ZMgJ:h8X`d&ǤpEk%R" Z_iƻKJX(~{Y&OtG螞DD%-zv.giʆ]k7c]Vw\K0eDw)Qⲳ_0 ns?~`S{fI ahY$y Ղ2_&)cnI |[?_۷?oj~ݒ\_U{ J_%'iJ<cƽf(iiŞ] j9tR۱ ='D :=;/ d9Kvۍ[ᛦD`,o w^?MNt]yN0Ƙ[xuj ftP!ɬmiawWqš1 *C,-@8 <@]}* &Gp&eМ},SG9c# ˥P2W-.$q+ĝ(Y m Xűn;^!ga`<Yʻ17!mg}ҡMy 3h "?\}D|йA޾;;tJ5;!{m(wnC/Wd]gH}scKNHŢ uZ!I+3Ò?ZԩsԔ+o:\߯' 3E:#F)GR .>uA jRPw]M)@c}iw@={ Aswu;o/7\Xt8_Ty@xKe ~+O00NI44*I^% ] 1R/ ?.B.b?[ |(k\#6ʌymo u9u!llR_uwƌ0e r0e RJ[WκiqdX᭷$ӷ-kX9\{mz99?(R/nqW ~rj}o(~,8x/OPQ[NPDVj 98crc|ݐACm!1Y\?ݯTU(Jg~ 371Z&f`CHb}]Q_2hZqI|ZyuF´r3.O?5'EXS?As+氖K'z.!J /%?T[Qz:L1=!vF65={Ԡ a҃dդb\{&\N#wyH3Z?  &0Fht OD@`}+s< 娛([iB"fQdP!$Åd؄ $aG=_# &?&*R>: R]T@zٙHRLaWQ@P̓cPś#6EH# Q}V2,>[OQ$`+n{jUc⑙ L w L;y/Thz-Q2S)X'fް#'*Jk,\Ɗzr|S!<@D/!?'6JV &7Qw|j`hۀ08S`,wkv_tQk|imaZҾ2.yy2.Oc<^dLiN©Y_g_UCϰFaoo8"{S'owzxEW ]%@ne9ÑW slZkLwA9c}SU7@ ,F"z+zNoEoM 8zY: 2Xe@IJ!F|gd*8KAW}mH/($jB#$<L ]Y(.1mhI;*Q ZZ]a_?guN7Hi/ %H6 sJyrQ|ʕqG_ZW5Ş>k^0^=M-sw:#<fz+ɫ1?/Yjω)Z'^YAc1 .9vwrY؜69ԜXƵ8 [O0ɶM\Mo ׀X3>$ Qr<3Ϩb6ч0F-j6Oc,- [m%C2|@*$hf:l7rqOl7Ғ Hsd[Y6ȍbCDcZk]z(^@pm$Gj"bw8y4mo|;2ug3Z0kǮҼv{S.ft*ɷ?tpΫzI{ nIJAHý|!ļrU#ʞt(sy[SWLMQk+S |qRRlI8]K+"hHi sEjLOCtr0)u f >2MK#.s#'+@g4.ZCdiz]5/B`DmNE'sUf0ʈ$e5j mАnAC!~Z{Դ<^f{BLa>N7d-G2臱{Pg><&I$# { m=;~!vz3lq{c> = Z,a̓Pw;ƃ7u=Ll]l͒({T"N#W'Ńy1g؆D+\>iS2"ttujb㵗t7ʫ-gDc|?V(\J)=o8/~*|m~(ϴ'Ɋ9ӟ@ySE[`dM)}a퍖wnĽ N-cNoY5q4 iIި;cGl+ewbzPȯQnXD۬YAgNg#~>Ȼ:^f4L O&9Y[ vﴬ2uj7 a < MvN3U HLl!b.RtM5bmĜ}p'$]Pԝ^$Kd+sbqC3-K R05>[Z7iiO  ÚP.tt{Hr@, Fa{? w2\~`+ dQ˚!S|gB{ZIBYŲUiKiLw+bNA/eaz?I>'\a=ap6Ƶ}er]9GCb&u]Tb:C"-yMئo6tOyusA9j@Ww(0FhLOpaBj|7XѢ*Gvz[g_66;Ou?{ 4JB9L\;AC 7[чDY .^Obn`ߐ fdV ^YS:f\wv8S X}^l.'߀) UlpQ1}%5Hس,(4X$}ygXv*^_.1Pޙ멏/FUwGߒpZJ`))j'i]8䎗1W7'7)[Kb78[˫ ԎPWw2@5FrbWǚ0hOeӹ}3<` }Ue.ѐ <H~a m黭 $,43O-1vt(9wv wI7/o7 MZU\p7ecCp|%WDlDokX$s %-xM̈aB<:"8^vImS:nx/QjvȦ>9(k H3rO= e#tbat aӌ-avN{6J;sY>OFefG"Wyp75r@IReC%V#WII=n!pu;\T|u;`!b8vy/f)lF_Q_\dPERn( L+..V=1U4"0ˠ1yo2 o+#8D_2Co+JR@$QK˻W*ۉ3V \:UqbSitj]Ve\ oM"X`s-YbGJb[ŒdYvl^l>ErƕUӞm=~">r,+wTi{*9f'GHߛl#Y= )C\+f+q}GǬyAwH/T?VAacP "DΏ>@;CvA~3. ؚc͐EATl2 ?:f/HuY !s-n\ ^+_DO?"RNܷ3 vf L}v 1a [Ӯ O] h8ѝ$0̶B|Aڤd`dR>''7DXy+U3tf=n6:Uf3TW47hO-UͿI創 P-A)Fp=n1/~Xc8Dz;B]G=πKjۆm%sJ# P9/QrT#_8gnN(rW9+r <4g;hu%j?ujg\)T-YѺ4H 4?o2 ׹sy.^[B\̀iZUw'(COh7W&TPH5Rq 0tc͉DԻؘ1 dD%(͢ 2׽NכȹMr+D޷ON !3&"Kkd.c]|F1-I v(͡Y> 2D˜rnr)c]9cK QZ.h$L@jDLw5p Eё1i6ǵ!#d""(T;p),bL^#ץ\uyhOdÓop_9ןcͷ :*x, ϟj;פ#a !&?l+b'J4]ܕB9mߠU9С24|ͮgCʱfpD( "%^Iyi}=t=D RJ$ pmcS~9iaXMr;N']iJXy}Q >wMxM.pdxenVﺻgVmrWZm2fRa%sFnLV)S#? Ji^pZJ4QK 2 iN0P%n~Eo,5፹ι|)Q=Le)$/8 kJvQߟG@wVi:G$__KfvʩƝĢϭIGCuÔ =ܯ^(azdwjs7 '6':2jFOePEV_}JDt|x qegpCd9jmYKE] @\Z8exJ+/D=^mOlEZ%hݻ>ۡT?N0![eKۂXS0xvnpP;$;w Rt1am!+ep[0݉h梕jps#r )U4T#/}"$gXɺEEdJ$" PPD7D *b1 cG˯tK!\?|ψ31kwM^!ղZiЪJ 3}& Ly"!!1̧kATzD|~$a/ } 3m]4{<ػhgG|rb` z^VmپhY )_9&RT$pxf6.7)P;/c随cmjKO>\a.4&pUgp9vZ|I?o,*K3D |>Gb"^r? KzICb iI@;6_4p|( }߼/5-<ymȖĐ<̀@S-qokCW}Lǩ.!}kټZރbx8AŻB崌 Mzl]YY4Xr'~p5Ű( $w%( Tr|؊fMڜM5āK` )GCg Ll~3F`Fq֣Rml);.F<"[E] Ⴘw͑h$DfqSwX`]|@]=(Ȃѵv; GmbXN0Ϩ:?׀ڲ S緝]*$%(أR d+@ݮNkq`$:LzB$IDPr5L j|>T#n,%! 'QW|ƼÇE ~39fٓ׋弯\֮Z8EɈ v=d: {ґ$AR:G4CzIߠϜ}r=^jdֈbki%xZO[?{~+h֖%`*U*]Tg0}? C %d|1"g7+p|dOsdt??T#D"Z?n=6?xK`6"Z)oy}kYoS@[m;l}&Gdtu-IF#i85_lɢ{^2 _,>$}߳Wab!WZ.; whS&9'̩ ,y|.6S{0e~yI=scm<{R3FXtF<^adVej**j ^DAO -eGnW[13&kOT^`o/껄\hԔ %w[82E!%)}t;B; DlK# 3%ۂƎtW+'h2)FihF1<P9XI *V'o(zU$@>H@Xjlr2ӠjvS4&(" Vg/QY<敺;{W6ەpRg# ǻ _ .\f^~2AUD6m3n b0T:0/a;eBOSE}[|3bkд? ~&B{) ]hW=9Aک[Picq`{յJyOˍ#bE 4[^&QK4I4`-@X3~$ŠudD%B;n1_1IƛG#$S_;!@w[)4 L"8݁V ؾ]}Ҷ"hOxD;p!Ա꒐\N@I˿,lPT+n3=)_6ZڑIXR~K^ۥ:FNW zE cZ\FF>AL㑘!|#- 44zvFt VTTnwX Ѿ$765 j ,n,%zoF|l#u|Uoli֞7pm?Ki3I!q(_ڷጯU6 b+:yCvͦkf}d(ZWwQ|0PZ>84{û421M b5Mt"PHْMn{s,$}O4>꿏ˋQ=q${][RQ`_\Pyd K,R 5ZZw&BrJi*x` @l{Sc<|0"a<3Cِ=3, [ V+Lĝ|@̠6kEznjq+aV\: j hrOMP|Bal%Q_6όc_.]<Ӣ 6|a\j`ö49Gh*k };'Gx"5{%.xrYmU07'D ʉ^qE+\gDEt"3ax 4Hݞ|f7&&7&W|s,YJb>cS y"+**BCIE"O. eNq̤V=+_X|7E`LrO' y1c#gg"4MFs˝ %}T =Ys W o_"\#DO4%22U1ĕU$hJZ֐e2H^ n=R21m? מ+M8J=gskvr\:45jH +ҙwts7jɥ^g'GMfX^-኿2Қ^āMIH8g:A e@'gt _pzYb5kS2(?1BqazZZN9ƆctŽwR-r9 _`{ ^uv4E&:782_dmL])@ 7d>^:m@i;#nN%怊b{-SAd͐A,MY>w8i[d`&\(ugs\nK({u/ yqY? 's`Q0²lqM]u_:P ߱;"aޫZ>wn]`l0w9"fs WpǑ2 wMԢo-}49|V5; 14٫# MUH F9`;߁ B8EYYt8+P5^;Y7*~+ƒj?jyz;ܲkUr8|s%tH]n$wd/O2g\ )nTi VdY~ 4 ,-R_' OĐ1VEq˟9&w|_`Hn+%ݏIՂyz}7_(­0->W,pP0c:-j%!59kJ4PObb玛NF7'U=0(Y^4*5OQI↛x)Dq!9{~>c0i]~K):7JLlRCX??hrγq`ѓ#6ZA1ꡛ%A {OLS޽C@SZJibeZgHtoB՜b6 n2¹)FY3q1>R k|o S/ZD T8Nbn~ymg @@"@R% ?!DMS sE\Msݒ$6, wELm{_G6<T6kBY e"Jؚ/QI?8iĻu?/o/^: lZQ0ǀK@r3%l wˋ0ڛ"0ЁW$t~'0FS2gcV] OBpo)}ݛfK c0yh˷Pۯ,i)@wWkWxM홴SUH0?QJRWfs1p8&u ; o4 6G}Ûj?6b5Xr(^*as W ]j?]֜*Tus'CosL/:V:Ho8OH[Ec(!-b鬠l|2km~8)5OD,4.woKj1&|ڹ9'W#2V_hӗ?MCH"|fA[ƍ>>;JQ/u/xe;= 9pD[vnvmᾔY 7K،"# `Uʱmh_ %*w"=@Q.uZMTmˆzՒ@ɼd" @%J2W6,;q ?!NN omje':lS]8r$Ј?e+6zfdDLS;DHiV}`e%yYQ4\6!aW߳/- (J)B1`&%*DxkdT0xӀ["iD!}xYmUEQY{ȿ=oSXqtD*Su,I9s(^}CXHrh'Un&kEG ˹p0?TS3;4~aF$Л&Qk##,cbOgGM"j؟p:M?ae0>+5vQЁ6/p|UoBiyK-:Ζ5J A9uvބN^9)R5(R տ+D[;C @G]ŔĶ%ѪJvMyG;dK%\Q*pzTwt&]HNF~)L|nd`۔MiV@S= ߒg8XWyJ +ʤkK)`h|zC]xU2ba #ҧC˺M2MR{$v5nM7%F`z@3'?*w]ʹqѡ<洁L`$@RMi=x%cI=.zr/ֹ –g$"k;O/ϐU)Za "N>W}~*ri=Q;Ri+7Ġ/-CdR#Rqn+.%yG:6 I-圦fj["[mb\ճ\Ӈފh .GQ iY$Ksr@ zѝ6q%'"Pu2H!Nm:#L`yĂ:v"D G8 4*sȣ| g, 5KID%U﹘xEel0~=g:7e^gjԨMۙQ_Jmt?m[mQhNS/Pս+m2/ m:Ȩǿ$@rm'91Gji V90f2Few]@Z 맢?lMpL۸kDC6LcCnp9Gso|OLoU)9xvu9[MM{#˭eTNP2Z|7n:<(ѥ|?ޤӖS-Hˮq#I4̉f|l|cy0⺯a\zȥ\9!^v";RF\=t#=p,VFKW_fBdX=_opKRՋO>aA==Sc/J[a\GOv~h$aáf6=-1peڇ>+OMX_~^R ?`x/jxEq@72=L>n\_/ uîR򗔼*Hr,JDg?%G473ulBbn)lw)57LS9QC%Fܭ7̈A3)|F`c$LU=P'!pZ8Iu# }抓A- r%D=K_ܨEYxfԣ!%ꓪ͖Udf(sFK 97'<3" h+Wo1 l\GIq#zAg 3}bzmu^Ft6ZyB!ϴ!7xahX* OP1]_Oh#߄nߝfwy-HiֵXx[pHJyE隸F1$:M3FAKF W}a N*~|m6Zir<ݕCijtJY[tu h:44~}wzo$56+UcV8hlkt>j(X~}l- a&X3f`a)V>4dE@lmw3~ĤI;']E FJe` &0? Vl"ҬbRTw4/\OlYZWche%;ղnF#F mx'*]}%zd^B &73ҞHGxⳋu,1@ lj\E߫cTlz7 39l< dEC3H`F3鱙uӱA\F0CO!V/a1"{K^q 4!=VRpo6ԁ*Ь6YH8hkpR?sᗍ4NG?&縟e~*1lTZhkȈ'|rUtdp[PBK[x\&124O <"R)dDelxkDç2i;DukBsjvv!kB #A[2HR^g@u4e~<~ oji0C0NF 2Opicard-release-1.4.2/picard.ico000066400000000000000000003177521310410472100163440ustar00rootroot00000000000000 (.  ,IV  (S00 %[  R  h( @wxvwwxxuwxyyy z z"}#}$~%&,,0134789:@FIIQNOUX]^`bdfhijjk9q:s777777777777<9777777777777#1777777Xx{W77;11)*11%977777EtvA7;111111+77777R[aJ7;,111111977777Hs77&111111zjLGdg[@77;!111111mywT77777;111111$7MlR777777;11OiY1(9777SN77777 .K}h0 7777Q^777;'e _|197777DqsB77;$Ukp/7777P`cI77;.\uF 977777to777 -:n~fC77777@ZV=777;"]rb9777777777777777777777789777777777577777779777777777977642?( vvxww複&<.8jnC#)@kA!L:E_ KLwm: R׵s^*t!.Bg 烥t\T@g-dsJGfW8%Ev37=B=(0x4K@S >P.\am)@ fO]g [C=to zj-ٷE_d-ʠ@.$&C8kδ@:GWYJDiy0= 5g"J9K5dZބ1Wj ~>&fΚz+R.tVhҼEk< B3-V!@^>Peɝ^M-@["HaJ '|5(o)^jwUt*J+*e,22-OBȅg͞ {Lk[{.aۡ}>nQ$~kX̦L,>a^٢ %}3-OR Mvԩ&Wз{ʢ a!׬oF%K>ۓi]$ɡ}o# w0gZ^!ٓ",,wMSEF-@9@@@AnHKOWhա}~LˣsCj~wL M[N0R(+M-stu)pqܷvo:H@qA JdKF5{@4ϊOJo3sAP{-}gF-s܎9Sf&. .O Z:3fMu=Qf-_i%8ĬҊ|]_гr NT@ָfO~qn᲏fZ$-6cLˣV@",#}f~ɀ*j{.rOރ `w}us^ia2I\-Y0Bt>]\YlL *p[٢<{}s FֹfO~^ G=yM)zh+LˣI(@kA!̴P&mKLwm<'NGv]j7=mGr9_$CmW*39S3+"ޭv3ɹAlp͞|]|mmJ%93An଩1,dgZ*n%\ `~uɯ. l]jܜ+^Vh(rPP R\IPLQ4zTshdC8kɛT.v(e6 bI/d8\' ?13|-kGOh53od'q*P >P.\Ǣl!(̴<:;n<\ Δ8#[(.?G3#`=ÃriK̞bkn]0lᙁ2a]l & տpX[7$]'_7O+9 +[>`Lˣ3pMWj0[A Y țSޯ7L4* *VPF,',:hkp&3L\Bt% u>q=$Bs t^«F[ʴL:=4pW[ w܄H4|Xm5-<_Ư4t2 8)Agpz* }p8SR,`R8cmϫΙtMx.KH\nоTLQob4gヘ+A?{キ|9rh״a]ϙqvgJ1@TΨ}m&˄5!=}p W~Ez,2 /fvƏc6eK)‹6:(M^c'ч*R` OP9+O5kBo-ZzWO4 PE2C陸vT-d???yg݈ r@$M\H9vv쩏`?oLҨ_?r0M}tr]x텗^ēߵTaw8zo&Da<!2[_5"__/e_e~'+[Pm}t%w#Ǚ6zшJ{x]kLIK@CJ-_(t€7@XMBm`q@>v͙9pR~\u2,No{Ɖf7R`l xPU\Ds}}ͨ5vGZYӔ z{zB &Qc;!5bؿэ}zFYpP zEI~6oBcBםr]d>;q1bz(@ƈJ1Ra~m襧%NU(j57BC%Mƴ"X.R߁ Ocwe nF;oŢّF&L]dpμ4g)O`/3v!o j9 B߆ eC:O> DT4mCmv0!⩇(E G<":'_3D!:Xm ND@4~ 'WxWmI!6wAO-OKA>bI9p9T'm h9@ yhηn`'G+ NЍ+`Mqz+Qu_6Kh3Ci1ca3Foex<~} 5$m2 "W yخ+u) F ePڿ0![0hZe9/> J[)PX Ya%B)!0>h'T]wu q0 g2.Zc}6[2 ̵ [cp&3 ]MTCW`0gHt"dInMEa"#3 Y4 /Pa=,+à?܃'/*|߯I::kyybgAXh5X&p:pwʘNO~ǁ ,^^ 3{ƒNdb)Z@yX2K(," ^)<0:%r1}ƮB=\l_j*Q{ލƯH8qDd9l^sG (b\}5_wk(OQүC!ɝĔ}Ɖ"3ӊN-mUR(%0e& -|cٰ0Iu ³W,l4~^Z1BO=Q_8â& v,1rRfBјŝ2K1z(R !&^hjS E"wRSVL~ Vw|Y=0^v#`opK5|VժYX$ js l7']ZrÎ Mn?6٩]s1g?}0ht:|$+2 򉶴`E6\0?VP#pEþwT1T=w}aI&ОDlK~t8A]GhĊ^ůF>%?}]/?oycI= 8Ӕ0JlP) .jO,4Ԡ/'yb6`M.+{,E_DI{;T- LVOou-|sv^Njox/.Zz`(8}~3{gZr"#ģ,vkLfʛS:%\ֵNwb]6i nbJRaƊk_mWX^`-)‰{p&p@DT\)l{&pp1cJe$D7} cpVU!c"VtuZlRQ`?N h޾ ,q0pf-ИB fu+x;)dCXY-B(&z)F*TFwn nH"O'>q5f3<~[$}jNby?g!cle(C'&C1@whQ)\T, EB)̜n# J;qɕHQjhK ݷ%ā?=]cڋ%Xe>&MrJ` fFbLN>25@4/C9&]kcX[7~-q%LI$sB6]CǚX2U˰0[_<`IxG#gQ Ե?N1Q0ZrSYXdA4کU]Qȡi[%H ^Z!gs򓤹NfީWtyI los!vA0l._u&aK8  PGP4xQPjAaEV8 M0؜$0YM݊rm .G9qʍO@G#Ν?Q/?H4-S_'2Ǵi2<$R~ꐑ)3D2B\vN%m&l^Lx1 ϛ?zl{ܷx(;zb*LyڃW$e5Nۀv8$r-խ~6|a6 γڏ@ -)Զ*J >K'~+9>L1Pn/^܃܈8p9e}a?뷇а/? ^mKhTwǪ ~yA@H`[Hş&,ɒ3 bu;»ui\; 5`E7V$_j'LQ4S8Y[C$|,_Ѐpsj t|N{~ Y ,A_v9\.QɢVJR[n!kb_=PR4ߪݳL{n$sFBכQǻ$qC[oL͙,, $, UoV;CQ7B*?cޤ\mx SОcT`Q/U} ` IUB*gw|Fn(~{k5~bs^&xW}摒:5*Sf=Q$ܷ'NJxHƞɶ 7+* x @1A6 0G$v %݄TqfLVjwӉHٌ&0t/i0pw:Xu" IDATz/ !ZuC Lġ@(Ś@P8k8f0 Px$"g ᰂ`KPJTQ'{t5`p?bQ),,D;ٺ>+1dH00f$\<BH%¢#RCɃ+yE6B L֎}f+& [ D4oWoz,D`>\o-FɺOV+٨n+uCĂ|N8n\I-@wf"eYcфjDvmMlY8 (~ 3yh^,W7x<г!h~~u/ ">[k݋O9Rm,61/?"RGVs4 ]Oaݠ7>9DOPS@ ի g,_# I}@6-? ;U"O5?cMP!?] iO&Ii894xuQ k s{h2C*wgZ#F8h8!',1ۼ{܃thY9W ҈1H~?i*̇>'(7w|!}A>4ƞ*u>K]ʋp1DhnɚVjV2U(%%8~~ 6 I5;oEy"Ӵ~}J~~=c>QhvώUZ @u7h.1VE j](eX:uPޅ` H<(%ZǺga2ig4rp -ۿhZUPa^ sx^Njt]SZ|GV5|{Qr, xaW-/<hJvB`0[Ec,v`Mhy.F(ǖ4-Yg`h6f!۾ѼWa~׫/#Ҩ2zj2#tZuK%fXrĶVG Sm%&ʗ'6J@. C-lyF0jGQ?P?a4 @e2!B!(4%<f_ᄐsvhDYP `|eȦlIw"id$qwd#mA(R9UD a`yX/a@>H\҅hRY/P4;nF9ClԮ=2z[c!YhDC-S вc;ZvtJa;4_vx3q1%h|jAWbF% s`B^!BL2^GU-! R"9LQx%ou KߣPv~l 0JDc Pf]MGxh p,&b3 &C^MMط^T>@͜Mѭ{2Sygm} Z2-! "QCKe ։Cc} b1JA*m|?ۉsX^J2ςGl]rFPJ`0kך]@!KT("v[}u| qկ}ID7MBlVSNs{8B6 8d8(,l"shep\v=S)fɚe`o6sf  E"|aԼLA_@tZ(YL9eO,4l\aG:%5 s|^H94 !&^hmJSW;)͒jxroVlFx mo'cK @N +l c!?XpvEjp!mu_b}K'88-h!3d[AJ &m[طbYGCkgho*Ɋתɖ5d &3q'C>BY?pf (4xڿE<>J [+hZ-_cu}dkHZ'+@.G@AF[OQ~ww8f" ?A[/"Ӥo'y 7m\ߏ>3ĭZ-roJHd c"-`%la=?7p/.6hs[mq/>CՓ%L`&}_-y SP 1ewh^񀧽c `<'0D<*eA% gv[YqT)Sb P lv~n[+h^r CKSsopUطl!oH˷`ς0xCq6 ~bP)'rbo[=h*M_Xj0A AxN7~V_mzMfcNx={s|AJ MTD6*[lF믪ϙ0 !a=GțogٕUOUN"WsTԏ!nj mj ԾRB{*8Do|R@n.@߬zL,sl} Տ%!pqiaSp^7܈2hZ@)#R[z,s ;!: L C8rTs,Ɣ@%TSHC7(@R#`ӗ`0HP=-VMY03mwUU6x"+/r:b/\ HGPIR!ZѬ@%R.ݞ^4i#, y(qv|K;`;J0aN/2 BѬޕC+G}plFP&*/D+/"g"C/V=_}"SUM+BCXO%x!#U5|}aW-C]_xz4TŞA4rxηvZ|)ޜ8uo摒Ǘw$#~@[o@ Nyڋ Ь@TQj+Y4KItUPgqOMoQPh,M60r XCp("`p <8g1'ŧzL,(D`vSYo/mU֤0YM)I* *!@,.m4BUVWk']4!ɒ/\4 ~'gAy%m yp]m:M M*j؀hjlĨ!ˆ/J2OH2( ?QYG}>|w۬+ʥѲu '=f: D+65B[sX_ Ʈ=ߡ])'MC#/)9 Zqt)=D۾SX2dCN %}%XO0q`hC<ЄwpJ($b@4E646¨c0fv练{ۀ/<Ă8@x,d&L9χ}+a$ T)|ao׫P hN$*PS{uuqAPHTL 8 oE]A* 7 SO)5I^ 2 Ʃw.u #F$0î;o]J RAT>0>]%~5CAչPSWU]"!ҡ9(K!U[チ'^XbUzO`jJ0ס;zBeW~Y6oDSi0$57ݴ0֭Uhhv /'TRO;`FEjatP8՟}8q T^m΅v/D6=,O!}v/*i0T!je]]mE]b-9|UV "A IMx5D*X,7R_|ALt 0 C!Z½h+3խ:5M8^[dMF=" jz^{Sc= S!^@Ҕ@A S#367`Q7j\H/~Gg% 8 [^Ws e6HDȜ>p #.?Xnm[PS[Nҹw/^T_dI+9`tquA fF mkEs cF;jՀ']ǺDhDG^M?KӠ7+S6{=CV "| I'CJPD zHq9l?Es u."u{O}A-VhJc@SЄp( /P0HHvQIAKɢ9uD*D,7FS }U'NFYIy bڙp ~]H\]< SarVLA`N>_OT\A j,0Ţ2X2H 5>BU+1lp;Q&/hܒ3'x<|(]]9,0Aw B!y0'O%(O lEV,A鑷÷;#qn; WbySXTGV k) D]'1Hq |\FW݄P(/hc6x{oa-c.@ 0_;<Ѽu ooUQ)H5aftd]3gAX-UXy=V~?ֹ Ͱ\?'ao|!6Yeb-JjBe?je WH!Q$|^Y]W.TBC8G·y# "S?aOmE?q6r*9'db;:O 3 \*&^wbK"߁RoHJ'on`xCW%)gizނ҄L (ᎸЬ4N %,ъ.Y6%NcR>RT0sGݛwF%?C%?{4`0d#h,DJQAxЅ<yB>DHǬ[QߑL&٘!S=۲o* P @ N*M `ٞ\>LQj/D; ?pm6 }1'"Sm0Ylǚ6_3Eo}+bȚS#3 YӲX/6u~Kr!:`?z" O?q0 ӈ6_g;lHsh{qڎP F*-$HKG4[R+J+ Pa.c @I:ᦝWn: ard?C_Ec!zeԽGAPL!ng }#D@#]yޘ_jnr"reOWhK+o?+︥~қw^S8i|/}ѣqwJPWk1# coF KU{v=>o+_4Ia'u4߼.bmݾۯ&^i8V4T>̕gMbFѲ5gF#h7|i26Ӳ&2%0Em^O:|mT\}x{M 63@T_cϢ (,t)Nd#]) RP /p/=o3t.!d VW%U8E]U[AWOѲs;~9JK;58VVU+@ 7c䏙'Bs s@f3r+&ctjL}8sTZ `rae f)-+p DB Z=BbtA $kdd47VU#0%bW.]lsگS=pO ŕpBWsq:Cy2EaDD# 8rGw@)`0dDB.#Eĥҽrg/~%klXRuFY PJ ^n xE13~LW W9E~qs+@++opܒ !fZ&M𑢐EٔiAz^ZmΛWzE dAI'#'ܙl3$S K8f u4E3@3 C{z hZJ+ ޥr.(5iYNWWUQ9 \*w2-1,c=Ss(y 2-Nw yV?K6D,?[xnZZ@4:4g}!e:Pv.]at ? K?I'._*2OfZlAW]`Imr(~!3,NJB>$XZuB0Дiyz1- l)kWIQ~sQz7#ے%ɴ(ٌwVz~3EyL˔c(oL [dm_.}* fZ ܘo^sb}ɟ"tR[Ry2 $ϴD9@<~Tsuu:HU-*-}"H$ coF KU3-J+4ro[~#i1,oLK[w~cz:u{N'Ϡ+޽6Ea΄&*Mӈ@Rpgwڥ6Sct$}N۴n>VRE(&  I@m['3EۅPԤwfr~6p 3kYkeى$ e| 8tڒV`~ @o[!:#;TBL CF( $CK_U[)!$3m/z+烣'2H3; 1<5ZX7GF7d|pZp9P+Tٹ*$Mk/x>Cppm^[#À8-;Sdt5; :Ԗq@I L[NzF lcp0Q0t䦩 BPo~gíkTZq@$R폅f KL|Jԁ :?ADgeaeF tDMJÒ#́NI{.]KH uO})›߽b2IeQ*? )K w4$п ,P/w@4NUzԴ %2f_Ui}͸xb=j8.CYY|ĂF6]Spb<؆տ K@WK+#O><ڪ +kc7۴B=R-qɼEO X6񓧶Mjgc1c1c1Ve ,~ IENDB`( w+;w+{xw;u:s>t! xwwww;u;t;t:t;v xUxwwww;u;t;t;t;tg1a;t;t;t;t;t;t=ulh;t;t;t;t;t;t;t;t;t;s6j4e4g7n;t;t;t;t;t;t;t;t;t;t;t:u0u0wwwwwwwwodqfzmuwwwwwwwwwwwwwwvrfdYh]HkkkkTtwwwwwwwwwwwwwww;u;t;t;t;t;t;t;t;t,V9o;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t6j.X,V,V,V,V0^9q;t;t;t;t;t;t;t;t;t:u0u0wwwwwwwwpecYcYcYh]pezmuwwwwwwwwwri^cYx'm_kkkkkd{nwwwwwwwwwwwwwww;u;t;t;t;t;t;t;t;t=u_1`;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t6j,V,V,V,V,V,V,V/[:r;t;t;t;t;t;t;t;t:u0u0wwwwwwwwJA|,rg]cYcYcYcYi^pezmuwwwwxkd[d[@jkkkkkkk~!rwwwwwwwwwwwwwww;u;t;t;t;t;t;t;t;t;tO/Y9o;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t:s.Y0ZrPx,V3e;t;t;t;t;t;t;t;t:u0u0wwwwwwwwMkkiZB},rg\cYcYcYdYj_sgznlacYoeWkkkkkkkkk/{wwwwwwwwwwwwwww;u;t;t;t;t;t;t;t;t;t;tn/]6j:r;t;t;t;t;t;t;t;t;t;t;t;t;t;t7l5_p/[;t;t;t;t;t;t;t;t:u0u0wwwwwwww=kkkkkkiZB},rf\cYcYcYcY4{gkkkkkkkkkk?wwwwwwwwwwwwwww;u;t;t;t;t;t;t;t;t;t;tAy.X,V-W4g;t;t;t;t;t;t;t;t;t;t;t;t;t5gRz;t;t;t;t;t;t;t;t:u0u0wwwwwwww/kkkkkkkkkkhT>{*pOkkkkkkkkkkkkOuwwwwwwwwwwwwww;u;t;t;t;t;t;t;t;t;t;t;t{i,V,V,V4e;t;t;t;t;t;t;t;t;t;t;t;tJ{;t;t;t;t;t;t;t;t:u0u0wwwwwwww {kkkkkkkkkkkkkkkkkkkkkkkkkkk^}pwwwwwwwwwwwwww;u;t;t;t;t;t;t;t;t;t;t7l2\|4^,V9o;t;t;t;t;t;t;t;t;t;t;tsQy1_/\;t;t;t;t;t;t;t;t:u0u0wwwwwwwwwakkkkkkkkkkkkkkkkkkkkkkkkkkjznwwwwwwwwwwwwww;u;t;t;t;t;t;t;t;t;t;t2aNvAk4e;t;t;t;t;t;t;t;t;t;t;t{6_,V,V~ho;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;tBy7jEv;t;t;t;t;t;t;t;t:u0u0wwwwwwwwwwwwwZkkkkkkkkkkkkkkkkkkkkkkkkkkk"rwwwwwwwwww}v;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;t;tQ3],V,V}wqgt!w+;w+????????(0` xxxD:uF:t=yw^wwxHvxwwwwwxHrvAwwwwwwwwwwwwwwwwwwwxHw;t;t;tTB{;t;t;t;twC{;t;t;t;t;t;t;t;t;tw;t;t;t;tW|rv4vƅwwwwwwwwwwwwwwwxHwЅwwwxH_' # ie. win32_20140415091256 PICARD_BUILD_VERSION_STR = "" class VersionError(Exception): pass def version_to_string(version, short=False): if len(version) != 5: raise VersionError("Length != 5") if version[3] not in ('final', 'dev'): raise VersionError("Should be either 'final' or 'dev'") _version = [] for p in version: try: n = int(p) except ValueError: n = p pass _version.append(n) version = tuple(_version) if short and version[3] == 'final': if version[2] == 0: version_str = '%d.%d' % version[:2] else: version_str = '%d.%d.%d' % version[:3] else: version_str = '%d.%d.%d%s%d' % version return version_str _version_re = re.compile(r"(\d+)[._](\d+)(?:[._](\d+)[._]?(?:(dev|final)[._]?(\d+))?)?$") def version_from_string(version_str): m = _version_re.search(version_str) if m: g = m.groups() if g[2] is None: return (int(g[0]), int(g[1]), 0, 'final', 0) if g[3] is None: return (int(g[0]), int(g[1]), int(g[2]), 'final', 0) return (int(g[0]), int(g[1]), int(g[2]), g[3], int(g[4])) raise VersionError("String '%s' does not match regex '%s'" % (version_str, _version_re.pattern)) PICARD_VERSION_STR = version_to_string(PICARD_VERSION) PICARD_VERSION_STR_SHORT = version_to_string(PICARD_VERSION, short=True) if PICARD_BUILD_VERSION_STR: __version__ = "%s_%s" % (PICARD_VERSION_STR, PICARD_BUILD_VERSION_STR) PICARD_FANCY_VERSION_STR = "%s (%s)" % (PICARD_VERSION_STR_SHORT, PICARD_BUILD_VERSION_STR) else: __version__ = PICARD_VERSION_STR_SHORT PICARD_FANCY_VERSION_STR = PICARD_VERSION_STR_SHORT # Keep those ordered api_versions = [ "0.15.0", "0.15.1", "0.16.0", "1.0.0", "1.1.0", "1.2.0", "1.3.0", "1.4.0", ] picard-release-1.4.2/picard/acoustid.py000066400000000000000000000260601310410472100200220ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2011 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from collections import deque from functools import partial from PyQt4 import QtCore from picard import config, log from picard.const import FPCALC_NAMES from picard.util import find_executable from picard.webservice import XmlNode class AcoustIDClient(QtCore.QObject): def __init__(self): QtCore.QObject.__init__(self) self._queue = deque() self._running = 0 self._max_processes = 2 if not config.setting["acoustid_fpcalc"]: fpcalc_path = find_executable(*FPCALC_NAMES) if fpcalc_path: config.setting["acoustid_fpcalc"] = fpcalc_path def init(self): pass def done(self): pass def _on_lookup_finished(self, next, file, document, http, error): def make_artist_credit_node(parent, artists): artist_credit_el = parent.append_child('artist_credit') for i, artist in enumerate(artists): name_credit_el = artist_credit_el.append_child('name_credit') artist_el = name_credit_el.append_child('artist') artist_el.append_child('id').text = artist.id[0].text artist_el.append_child('name').text = artist.name[0].text artist_el.append_child('sort_name').text = artist.name[0].text if i > 0: name_credit_el.attribs['joinphrase'] = '; ' return artist_credit_el def parse_recording(recording): if 'title' not in recording.children: # we have no metadata for this recording return recording_id = recording.id[0].text recording_el = recording_list_el.append_child('recording') recording_el.attribs['id'] = recording_id recording_el.append_child('title').text = recording.title[0].text if 'duration' in recording.children: recording_el.append_child('length').text = str(int(recording.duration[0].text) * 1000) make_artist_credit_node(recording_el, recording.artists[0].artist) release_list_el = recording_el.append_child('release_list') for release_group in recording.releasegroups[0].releasegroup: for release in release_group.releases[0].release: release_el = release_list_el.append_child('release') release_el.attribs['id'] = release.id[0].text release_group_el = release_el.append_child('release_group') release_group_el.attribs['id'] = release_group.id[0].text if 'title' in release.children: release_el.append_child('title').text = release.title[0].text else: release_el.append_child('title').text = release_group.title[0].text if 'country' in release.children: release_el.append_child('country').text = release.country[0].text medium_list_el = release_el.append_child('medium_list') medium_list_el.attribs['count'] = release.medium_count[0].text for medium in release.mediums[0].medium: medium_el = medium_list_el.append_child('medium') if 'format' in medium.children: medium_el.append_child('format').text = medium.format[0].text track_list_el = medium_el.append_child('track_list') track_list_el.attribs['count'] = medium.track_count[0].text for track in medium.tracks[0].track: track_el = track_list_el.append_child('track') track_el.append_child('position').text = track.position[0].text doc = XmlNode() metadata_el = doc.append_child('metadata') acoustid_el = metadata_el.append_child('acoustid') recording_list_el = acoustid_el.append_child('recording_list') if error: mparms = { 'error': unicode(http.errorString()), 'filename': file.filename, } log.error( "AcoustID: Lookup network error for '%(filename)s': %(error)r" % mparms) self.tagger.window.set_statusbar_message( N_("AcoustID lookup network error for '%(filename)s'!"), mparms, echo=None ) else: status = document.response[0].status[0].text if status == 'ok': results = document.response[0].results[0].children.get('result') if results: result = results[0] file.metadata['acoustid_id'] = result.id[0].text if 'recordings' in result.children: for recording in result.recordings[0].recording: parse_recording(recording) log.debug("AcoustID: Lookup successful for '%s'", file.filename) else: mparms = { 'error': document.response[0].error[0].message[0].text, 'filename': file.filename } log.error( "AcoustID: Lookup error for '%(filename)s': %(error)r" % mparms) self.tagger.window.set_statusbar_message( N_("AcoustID lookup failed for '%(filename)s'!"), mparms, echo=None ) next(doc, http, error) def _lookup_fingerprint(self, next, filename, result=None, error=None): try: file = self.tagger.files[filename] except KeyError: # The file has been removed. do nothing return mparms = { 'filename': file.filename } if not result: log.debug( "AcoustID: lookup returned no result for file '%(filename)s'" % mparms ) self.tagger.window.set_statusbar_message( N_("AcoustID lookup returned no result for file '%(filename)s'"), mparms, echo=None ) file.clear_pending() return log.debug( "AcoustID: looking up the fingerprint for file '%(filename)s'" % mparms ) self.tagger.window.set_statusbar_message( N_("Looking up the fingerprint for file '%(filename)s' ..."), mparms, echo=None ) params = dict(meta='recordings releasegroups releases tracks compress') if result[0] == 'fingerprint': type, fingerprint, length = result file.acoustid_fingerprint = fingerprint file.acoustid_length = length self.tagger.acoustidmanager.add(file, None) params['fingerprint'] = fingerprint params['duration'] = str(length) else: type, recordingid = result params['recordingid'] = recordingid self.tagger.xmlws.query_acoustid(partial(self._on_lookup_finished, next, file), **params) def _on_fpcalc_finished(self, next, file, exit_code, exit_status): process = self.sender() finished = process.property('picard_finished') if finished: return process.setProperty('picard_finished', True) result = None try: self._running -= 1 self._run_next_task() process = self.sender() if exit_code == 0 and exit_status == 0: output = str(process.readAllStandardOutput()) duration = None fingerprint = None for line in output.splitlines(): parts = line.split('=', 1) if len(parts) != 2: continue if parts[0] == 'DURATION': duration = int(parts[1]) elif parts[0] == 'FINGERPRINT': fingerprint = parts[1] if fingerprint and duration: result = 'fingerprint', fingerprint, duration else: log.error( "Fingerprint calculator failed exit code = %r, exit status = %r, error = %s", exit_code, exit_status, unicode(process.errorString())) finally: next(result) def _on_fpcalc_error(self, next, filename, error): process = self.sender() finished = process.property('picard_finished') if finished: return process.setProperty('picard_finished', True) try: self._running -= 1 self._run_next_task() log.error("Fingerprint calculator failed error = %s (%r)", unicode(process.errorString()), error) finally: next(None) def _run_next_task(self): try: file, next = self._queue.popleft() except IndexError: return fpcalc = config.setting["acoustid_fpcalc"] or "fpcalc" self._running += 1 process = QtCore.QProcess(self) process.setProperty('picard_finished', False) process.finished.connect(partial(self._on_fpcalc_finished, next, file)) process.error.connect(partial(self._on_fpcalc_error, next, file)) process.start(fpcalc, ["-length", "120", file.filename]) log.debug("Starting fingerprint calculator %r %r", fpcalc, file.filename) def analyze(self, file, next): fpcalc_next = partial(self._lookup_fingerprint, next, file.filename) if not config.setting["ignore_existing_acoustid_fingerprints"]: # use cached fingerprint fingerprints = file.metadata.getall('acoustid_fingerprint') if fingerprints: fpcalc_next(result=('fingerprint', fingerprints[0], 0)) return # calculate the fingerprint task = (file, fpcalc_next) self._queue.append(task) if self._running < self._max_processes: self._run_next_task() def stop_analyze(self, file): new_queue = deque() for task in self._queue: if task[0] != file: new_queue.appendleft(task) self._queue = new_queue picard-release-1.4.2/picard/acoustidmanager.py000066400000000000000000000101101310410472100213420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2011 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import json from functools import partial from PyQt4 import QtCore from picard import log class Submission(object): def __init__(self, fingerprint, duration, orig_recordingid=None, recordingid=None, puid=None): self.fingerprint = fingerprint self.duration = duration self.puid = puid self.orig_recordingid = orig_recordingid self.recordingid = recordingid class AcoustIDManager(QtCore.QObject): def __init__(self): QtCore.QObject.__init__(self) self._fingerprints = {} def add(self, file, recordingid): if not hasattr(file, 'acoustid_fingerprint'): return if not hasattr(file, 'acoustid_length'): return puid = file.metadata['musicip_puid'] self._fingerprints[file] = Submission(file.acoustid_fingerprint, file.acoustid_length, recordingid, recordingid, puid) self._check_unsubmitted() def update(self, file, recordingid): submission = self._fingerprints.get(file) if submission is None: return submission.recordingid = recordingid self._check_unsubmitted() def remove(self, file): if file in self._fingerprints: del self._fingerprints[file] self._check_unsubmitted() def _unsubmitted(self): for submission in self._fingerprints.itervalues(): if submission.recordingid and submission.orig_recordingid != submission.recordingid: yield submission def _check_unsubmitted(self): enabled = False for submission in self._unsubmitted(): enabled = True break self.tagger.window.enable_submit(enabled) def submit(self): fingerprints = list(self._unsubmitted()) if not fingerprints: self._check_unsubmitted() return log.debug("AcoustID: submitting ...") self.tagger.window.set_statusbar_message( N_('Submitting AcoustIDs ...'), echo=None ) self.tagger.xmlws.submit_acoustid_fingerprints(fingerprints, partial(self.__fingerprint_submission_finished, fingerprints)) def __fingerprint_submission_finished(self, fingerprints, document, http, error): if error: try: error = json.loads(document) message = error["error"]["message"] except : message = "" mparms = { 'error': unicode(http.errorString()), 'message': message } log.error( "AcoustID: submission failed with error '%(error)s': %(message)s" % mparms) self.tagger.window.set_statusbar_message( N_("AcoustID submission failed with error '%(error)s': %(message)s"), mparms, echo=None, timeout=3000 ) else: log.debug('AcoustID: successfully submitted') self.tagger.window.set_statusbar_message( N_('AcoustIDs successfully submitted.'), echo=None, timeout=3000 ) for submission in fingerprints: submission.orig_recordingid = submission.recordingid self._check_unsubmitted() picard-release-1.4.2/picard/album.py000066400000000000000000000552661310410472100173210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2004 Robert Kaye # Copyright (C) 2006-2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import traceback from PyQt4 import QtCore, QtNetwork from picard import config, log from picard.coverart import coverart from picard.metadata import (Metadata, register_album_metadata_processor, run_album_metadata_processors, run_track_metadata_processors) from picard.dataobj import DataObject from picard.file import File from picard.track import Track from picard.script import ScriptParser from picard.ui.item import Item from picard.util import format_time, mbid_validate from picard.util.imagelist import ImageList, update_metadata_images from picard.util.textencoding import asciipunct from picard.cluster import Cluster from picard.collection import add_release_to_user_collections from picard.mbxml import ( release_group_to_metadata, release_to_metadata, medium_to_metadata, track_to_metadata, ) from picard.const import VARIOUS_ARTISTS_ID register_album_metadata_processor(coverart) class AlbumArtist(DataObject): def __init__(self, id): DataObject.__init__(self, id) class Album(DataObject, Item): release_group_loaded = QtCore.pyqtSignal() def __init__(self, id, discid=None): DataObject.__init__(self, id) self.metadata = Metadata() self.orig_metadata = Metadata() self.tracks = [] self.loaded = False self.load_task = None self.release_group = None self._files = 0 self._requests = 0 self._tracks_loaded = False self._discid = discid self._after_load_callbacks = [] self.unmatched_files = Cluster(_("Unmatched Files"), special=True, related_album=self, hide_if_empty=True) self.errors = [] self.status = None self._album_artists = [] self.update_metadata_images_enabled = True def __repr__(self): return '' % (self.id, self.metadata[u"album"]) def iterfiles(self, save=False): for track in self.tracks: for file in track.iterfiles(): yield file if not save: for file in self.unmatched_files.iterfiles(): yield file def enable_update_metadata_images(self, enabled): self.update_metadata_images_enabled = enabled def append_album_artist(self, id): """Append artist id to the list of album artists and return an AlbumArtist instance""" album_artist = AlbumArtist(id) self._album_artists.append(album_artist) return album_artist def get_album_artists(self): """Returns the list of album artists (as AlbumArtist objects)""" return self._album_artists def _parse_release(self, document): log.debug("Loading release %r ...", self.id) self._tracks_loaded = False release_node = document.metadata[0].release[0] if release_node.id != self.id: self.tagger.mbid_redirects[self.id] = release_node.id album = self.tagger.albums.get(release_node.id) if album: log.debug("Release %r already loaded", release_node.id) album.match_files(self.unmatched_files.files) album.update() self.tagger.remove_album(self) return False else: del self.tagger.albums[self.id] self.tagger.albums[release_node.id] = self self.id = release_node.id # Get release metadata m = self._new_metadata m.length = 0 rg_node = release_node.release_group[0] rg = self.release_group = self.tagger.get_release_group_by_id(rg_node.id) rg.loaded_albums.add(self.id) rg.refcount += 1 release_group_to_metadata(rg_node, rg.metadata, rg) m.copy(rg.metadata) release_to_metadata(release_node, m, album=self) if self._discid: m['musicbrainz_discid'] = self._discid # Custom VA name if m['musicbrainz_albumartistid'] == VARIOUS_ARTISTS_ID: m['albumartistsort'] = m['albumartist'] = config.setting['va_name'] # Convert Unicode punctuation if config.setting['convert_punctuation']: m.apply_func(asciipunct) m['totaldiscs'] = release_node.medium_list[0].count # Add album to collections add_release_to_user_collections(release_node) # Run album metadata plugins try: run_album_metadata_processors(self, m, release_node) except: self.error_append(traceback.format_exc()) self._release_node = release_node return True def _release_request_finished(self, document, http, error): if self.load_task is None: return self.load_task = None parsed = False try: if error: self.error_append(unicode(http.errorString())) # Fix for broken NAT releases if error == QtNetwork.QNetworkReply.ContentNotFoundError: nats = False nat_name = config.setting["nat_name"] files = list(self.unmatched_files.files) for file in files: recordingid = file.metadata["musicbrainz_recordingid"] if mbid_validate(recordingid) and file.metadata["album"] == nat_name: nats = True self.tagger.move_file_to_nat(file, recordingid) self.tagger.nats.update() if nats and not self.get_num_unmatched_files(): self.tagger.remove_album(self) error = False else: try: parsed = self._parse_release(document) except: error = True self.error_append(traceback.format_exc()) finally: self._requests -= 1 if parsed or error: self._finalize_loading(error) # does http need to be set to None to free the memory used by the network response? # http://pyqt.sourceforge.net/Docs/PyQt4/qnetworkaccessmanager.html says: # After the request has finished, it is the responsibility of the user # to delete the QNetworkReply object at an appropriate time. # Do not directly delete it inside the slot connected to finished(). # You can use the deleteLater() function. def error_append(self, msg): log.error(msg) self.errors.append(msg) def _finalize_loading(self, error): if error: self.metadata.clear() self.status = _("[could not load album %s]") % self.id del self._new_metadata del self._new_tracks self.update() return if self._requests > 0: return if not self._tracks_loaded: artists = set() totalalbumtracks = 0 absolutetracknumber = 0 va = self._new_metadata['musicbrainz_albumartistid'] == VARIOUS_ARTISTS_ID djmix_ars = {} if hasattr(self._new_metadata, "_djmix_ars"): djmix_ars = self._new_metadata._djmix_ars for medium_node in self._release_node.medium_list[0].medium: mm = Metadata() mm.copy(self._new_metadata) medium_to_metadata(medium_node, mm) discpregap = False for dj in djmix_ars.get(mm["discnumber"], []): mm.add("djmixer", dj) if "pregap" in medium_node.children: discpregap = True absolutetracknumber += 1 track = self._finalize_loading_track(medium_node.pregap[0], mm, artists, va, absolutetracknumber, discpregap) track.metadata['~pregap'] = "1" tracklist_node = medium_node.track_list[0] track_count = int(tracklist_node.count) if track_count: for track_node in tracklist_node.track: absolutetracknumber += 1 track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap) if "data_track_list" in medium_node.children: for track_node in medium_node.data_track_list[0].track: absolutetracknumber += 1 track = self._finalize_loading_track(track_node, mm, artists, va, absolutetracknumber, discpregap) track.metadata['~datatrack'] = "1" totalalbumtracks = str(absolutetracknumber) for track in self._new_tracks: track.metadata["~totalalbumtracks"] = totalalbumtracks if len(artists) > 1: track.metadata["~multiartist"] = "1" del self._release_node self._tracks_loaded = True if not self._requests: self.enable_update_metadata_images(False) # Prepare parser for user's script if config.setting["enable_tagger_scripts"]: for s_pos, s_name, s_enabled, s_text in config.setting["list_of_scripts"]: if s_enabled and s_text: parser = ScriptParser() for track in self._new_tracks: # Run tagger script for each track try: parser.eval(s_text, track.metadata) except: self.error_append(traceback.format_exc()) # Strip leading/trailing whitespace track.metadata.strip_whitespace() # Run tagger script for the album itself try: parser.eval(s_text, self._new_metadata) except: self.error_append(traceback.format_exc()) self._new_metadata.strip_whitespace() for track in self.tracks: track.metadata_images_changed.connect(self.update_metadata_images) for file in list(track.linked_files): file.move(self.unmatched_files) self.metadata = self._new_metadata self.tracks = self._new_tracks del self._new_metadata del self._new_tracks self.loaded = True self.status = None self.match_files(self.unmatched_files.files) self.enable_update_metadata_images(True) self.update() self.tagger.window.set_statusbar_message( N_('Album %(id)s loaded: %(artist)s - %(album)s'), { 'id': self.id, 'artist': self.metadata['albumartist'], 'album': self.metadata['album'] }, timeout=3000 ) for func in self._after_load_callbacks: func() self._after_load_callbacks = [] def _finalize_loading_track(self, track_node, metadata, artists, va, absolutetracknumber, discpregap): track = Track(track_node.recording[0].id, self) self._new_tracks.append(track) # Get track metadata tm = track.metadata tm.copy(metadata) track_to_metadata(track_node, track) track.metadata["~absolutetracknumber"] = absolutetracknumber track._customize_metadata() self._new_metadata.length += tm.length artists.add(tm["artist"]) if va: tm["compilation"] = "1" if discpregap: tm["~discpregap"] = "1" # Run track metadata plugins try: run_track_metadata_processors(self, tm, self._release_node, track_node) except: self.error_append(traceback.format_exc()) return track def load(self, priority=False, refresh=False): if self._requests: log.info("Not reloading, some requests are still active.") return self.tagger.window.set_statusbar_message( N_('Loading album %(id)s ...'), {'id': self.id} ) self.loaded = False self.status = _("[loading album information]") if self.release_group: self.release_group.loaded = False self.release_group.folksonomy_tags.clear() self.metadata.clear() self.folksonomy_tags.clear() self.update() self._new_metadata = Metadata() self._new_tracks = [] self._requests = 1 self.errors = [] require_authentication = False inc = ['release-groups', 'media', 'recordings', 'artist-credits', 'artists', 'aliases', 'labels', 'isrcs', 'collections'] if config.setting['release_ars'] or config.setting['track_ars']: inc += ['artist-rels', 'release-rels', 'url-rels', 'recording-rels', 'work-rels'] if config.setting['track_ars']: inc += ['recording-level-rels', 'work-level-rels'] if config.setting['folksonomy_tags']: if config.setting['only_my_tags']: require_authentication = True inc += ['user-tags'] else: inc += ['tags'] if config.setting['enable_ratings']: require_authentication = True inc += ['user-ratings'] self.load_task = self.tagger.xmlws.get_release_by_id( self.id, self._release_request_finished, inc=inc, mblogin=require_authentication, priority=priority, refresh=refresh) def run_when_loaded(self, func): if self.loaded: func() else: self._after_load_callbacks.append(func) def stop_loading(self): if self.load_task: self.tagger.xmlws.remove_task(self.load_task) self.load_task = None def update(self, update_tracks=True): if self.item: self.item.update(update_tracks) def _add_file(self, track, file): self._files += 1 self.update(update_tracks=False) file.metadata_images_changed.connect(self.update_metadata_images) self.update_metadata_images() def _remove_file(self, track, file): self._files -= 1 self.update(update_tracks=False) file.metadata_images_changed.disconnect(self.update_metadata_images) self.update_metadata_images() def match_files(self, files, use_recordingid=True): """Match files to tracks on this album, based on metadata similarity or recordingid.""" for file in list(files): if file.state == File.REMOVED: continue matches = [] recordingid = file.metadata['musicbrainz_recordingid'] if use_recordingid and mbid_validate(recordingid): matches = self._get_recordingid_matches(file, recordingid) if not matches: for track in self.tracks: sim = track.metadata.compare(file.orig_metadata) if sim >= config.setting['track_matching_threshold']: matches.append((sim, track)) if matches: matches.sort(reverse=True) file.move(matches[0][1]) else: file.move(self.unmatched_files) def match_file(self, file, recordingid=None): """Match the file on a track on this album, based on recordingid or metadata similarity.""" if file.state == File.REMOVED: return if recordingid is not None: matches = self._get_recordingid_matches(file, recordingid) if matches: matches.sort(reverse=True) file.move(matches[0][1]) return self.match_files([file], use_recordingid=False) def _get_recordingid_matches(self, file, recordingid): matches = [] tracknumber = file.metadata['tracknumber'] discnumber = file.metadata['discnumber'] for track in self.tracks: tm = track.metadata if recordingid == tm['musicbrainz_recordingid']: if tracknumber == tm['tracknumber']: if discnumber == tm['discnumber']: matches.append((4.0, track)) break else: matches.append((3.0, track)) else: matches.append((2.0, track)) return matches def can_save(self): return self._files > 0 def can_remove(self): return True def can_edit_tags(self): return True def can_analyze(self): return False def can_autotag(self): return False def can_refresh(self): return True def can_view_info(self): return (self.loaded and (self.metadata.images or self.orig_metadata.images)) or self.errors def is_album_like(self): return True def get_num_matched_tracks(self): num = 0 for track in self.tracks: if track.is_linked(): num += 1 return num def get_num_unmatched_files(self): return len(self.unmatched_files.files) def get_num_total_files(self): return self._files + len(self.unmatched_files.files) def is_complete(self): if not self.tracks: return False for track in self.tracks: if not track.is_complete(): return False if self.get_num_unmatched_files(): return False else: return True def is_modified(self): if self.tracks: for track in self.tracks: for file in track.linked_files: if not file.is_saved(): return True return False def get_num_unsaved_files(self): count = 0 for track in self.tracks: for file in track.linked_files: if not file.is_saved(): count += 1 return count def column(self, column): if column == 'title': if self.status is not None: title = self.status else: title = self.metadata['album'] if self.tracks: linked_tracks = 0 for track in self.tracks: if track.is_linked(): linked_tracks += 1 text = u'%s\u200E (%d/%d' % (title, linked_tracks, len(self.tracks)) unmatched = self.get_num_unmatched_files() if unmatched: text += '; %d?' % (unmatched,) unsaved = self.get_num_unsaved_files() if unsaved: text += '; %d*' % (unsaved,) # CoverArt.set_metadata uses the orig_metadata.images if metadata.images is empty # in order to show existing cover art if there's no cover art for a release. So # we do the same here in order to show the number of images consistently. if self.metadata.images: metadata = self.metadata else: metadata = self.orig_metadata number_of_images = len(metadata.images) if getattr(metadata, 'has_common_images', True): text += ungettext("; %i image", "; %i images", number_of_images) % number_of_images else: text += ungettext("; %i image not in all tracks", "; %i different images among tracks", number_of_images) % number_of_images return text + ')' else: return title elif column == '~length': length = self.metadata.length if length: return format_time(length) else: return '' elif column == 'artist': return self.metadata['albumartist'] else: return '' def switch_release_version(self, mbid): if mbid == self.id: return for file in list(self.iterfiles(True)): file.move(self.unmatched_files) album = self.tagger.albums.get(mbid) if album: album.match_files(self.unmatched_files.files) album.update() self.tagger.remove_album(self) else: del self.tagger.albums[self.id] self.release_group.loaded_albums.discard(self.id) self.id = mbid self.tagger.albums[mbid] = self self.load(priority=True, refresh=True) def update_metadata_images(self): if not self.update_metadata_images_enabled: return update_metadata_images(self) self.update(False) def keep_original_images(self): self.enable_update_metadata_images(False) for track in self.tracks: track.keep_original_images() for file in list(self.unmatched_files.files): file.keep_original_images() self.enable_update_metadata_images(True) self.update_metadata_images() class NatAlbum(Album): def __init__(self): Album.__init__(self, id="NATS") self.loaded = True self.update() def update(self, update_tracks=True): self.enable_update_metadata_images(False) self.metadata["album"] = config.setting["nat_name"] for track in self.tracks: track.metadata["album"] = self.metadata["album"] for file in track.linked_files: track.update_file_metadata(file) self.enable_update_metadata_images(True) Album.update(self, update_tracks) def _finalize_loading(self, error): self.update() def can_refresh(self): return False def can_browser_lookup(self): return False picard-release-1.4.2/picard/browser/000077500000000000000000000000001310410472100173145ustar00rootroot00000000000000picard-release-1.4.2/picard/browser/__init__.py000066400000000000000000000000001310410472100214130ustar00rootroot00000000000000picard-release-1.4.2/picard/browser/browser.py000066400000000000000000000061721310410472100213570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtNetwork from picard import log, config class BrowserIntegration(QtNetwork.QTcpServer): """Simple HTTP server for web browser integration.""" def __init__(self, parent=None): QtNetwork.QTcpServer.__init__(self, parent) self.newConnection.connect(self._accept_connection) self.port = 0 self.host_address = None def start(self): if self.port: self.stop() if config.setting["browser_integration_localhost_only"]: self.host_address = QtNetwork.QHostAddress(QtNetwork.QHostAddress.LocalHost) else: self.host_address = QtNetwork.QHostAddress(QtNetwork.QHostAddress.Any) for port in range(config.setting["browser_integration_port"], 65535): if self.listen(self.host_address, port): log.debug("Starting the browser integration (%s:%d)", self.host_address.toString(), port) self.port = port self.tagger.listen_port_changed.emit(self.port) break def stop(self): if self.port > 0: log.debug("Stopping the browser integration") self.port = 0 self.tagger.listen_port_changed.emit(self.port) self.close() else: log.debug("Browser integration inactive, no need to stop") def _process_request(self): conn = self.sender() line = str(conn.readLine()) conn.write("HTTP/1.1 200 OK\r\nCache-Control: max-age=0\r\n\r\nNothing to see here.") conn.disconnectFromHost() line = line.split() log.debug("Browser integration request: %r", line) if line[0] == "GET" and "?" in line[1]: action, args = line[1].split("?") args = [a.split("=", 1) for a in args.split("&")] args = dict((a, unicode(QtCore.QUrl.fromPercentEncoding(b))) for (a, b) in args) self.tagger.bring_tagger_front() if action == "/openalbum": self.tagger.load_album(args["id"]) elif action == "/opennat": self.tagger.load_nat(args["id"]) else: log.error("Unknown browser integration request: %r", action) def _accept_connection(self): conn = self.nextPendingConnection() conn.readyRead.connect(self._process_request) picard-release-1.4.2/picard/browser/filelookup.py000066400000000000000000000106301310410472100220370ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (c) 2004 Robert Kaye # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore import os.path import re from picard import log from picard.const import PICARD_URLS, QUERY_LIMIT from picard.util import webbrowser2, build_qurl class FileLookup(object): def __init__(self, parent, server, port, localPort): self.server = server self.localPort = int(localPort) self.port = port def _url(self, path, params={}): if self.localPort: params['tport'] = self.localPort url = build_qurl(self.server, self.port, path=path, queryargs=params) return url.toEncoded() def _build_launch(self, path, params={}): return self.launch(self._url(path, params)) def launch(self, url): log.debug("webbrowser2: %s" % url) webbrowser2.open(url) return True def discLookup(self, url): if self.localPort: url = "%s&tport=%d" % (url, self.localPort) return self.launch(url) def _lookup(self, type_, id_): return self._build_launch("/%s/%s" % (type_, id_)) def recordingLookup(self, recording_id): return self._lookup('recording', recording_id) def albumLookup(self, album_id): return self._lookup('release', album_id) def artistLookup(self, artist_id): return self._lookup('artist', artist_id) def trackLookup(self, track_id): return self._lookup('track', track_id) def workLookup(self, work_id): return self._lookup('work', work_id) def releaseGroupLookup(self, releaseGroup_id): return self._lookup('release-group', releaseGroup_id) def acoustLookup(self, acoust_id): return self.launch(PICARD_URLS['acoustid_track'] + acoust_id) def mbidLookup(self, string, type_): """Parses string for known entity type and mbid, open browser for it If entity type is 'release', it will load corresponding release if possible. """ uuid = '[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}' entity_type = '(?:release-group|release|recording|work|artist|label|url|area|track)' regex = r"\b(%s)?\W*(%s)" % (entity_type, uuid) m = re.search(regex, string, re.IGNORECASE) if m is None: return False if m.group(1) is None: entity = type_ else: entity = m.group(1).lower() mbid = m.group(2).lower() if entity == 'release': QtCore.QObject.tagger.load_album(mbid) return True return self._lookup(entity, mbid) def _search(self, type_, query, adv=False): if self.mbidLookup(query, type_): return True params = { 'limit': QUERY_LIMIT, 'type': type_, 'query': query, } if adv: params['adv'] = 'on' return self._build_launch('/search/textsearch', params) def artistSearch(self, query, adv=False): return self._search('artist', query, adv) def albumSearch(self, query, adv=False): return self._search('release', query, adv) def trackSearch(self, query, adv=False): return self._search('recording', query, adv) def tagLookup(self, artist, release, track, trackNum, duration, filename): params = { 'artist': artist, 'release': release, 'track': track, 'tracknum': trackNum, 'duration': duration, 'filename': os.path.basename(filename), } return self._build_launch('/taglookup', params) def collectionLookup(self, userid): return self._build_launch('/user/%s/collections' % userid) picard-release-1.4.2/picard/cluster.py000066400000000000000000000405521310410472100176720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2004 Robert Kaye # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from __future__ import print_function import re import os import ntpath import sys from operator import itemgetter from heapq import heappush, heappop from PyQt4 import QtCore from picard import config from picard.metadata import Metadata from picard.similarity import similarity from picard.ui.item import Item from picard.util import format_time, album_artist_from_path from picard.util.imagelist import ImageList, update_metadata_images from picard.const import QUERY_LIMIT class Cluster(QtCore.QObject, Item): # Weights for different elements when comparing a cluster to a release comparison_weights = { 'album': 17, 'albumartist': 6, 'totaltracks': 5, 'releasecountry': 2, 'format': 2, } def __init__(self, name, artist="", special=False, related_album=None, hide_if_empty=False): QtCore.QObject.__init__(self) self.item = None self.metadata = Metadata() self.metadata['album'] = name self.metadata['albumartist'] = artist self.metadata['totaltracks'] = 0 self.special = special self.hide_if_empty = hide_if_empty self.related_album = related_album self.files = [] self.lookup_task = None def __repr__(self): return '' % self.metadata['album'] def __len__(self): return len(self.files) def _update_related_album(self): if self.related_album: self.related_album.update_metadata_images() self.related_album.update() def add_files(self, files): for file in files: self.metadata.length += file.metadata.length file._move(self) file.update(signal=False) cover = file.metadata.get_single_front_image() if cover and cover[0] not in self.metadata.images: self.metadata.append_image(cover[0]) self.files.extend(files) self.metadata['totaltracks'] = len(self.files) self.item.add_files(files) self._update_related_album() def add_file(self, file): self.metadata.length += file.metadata.length self.files.append(file) self.metadata['totaltracks'] = len(self.files) file._move(self) file.update(signal=False) cover = file.metadata.get_single_front_image() if cover and cover[0] not in self.metadata.images: self.metadata.append_image(cover[0]) self.item.add_file(file) self._update_related_album() def remove_file(self, file): self.metadata.length -= file.metadata.length self.files.remove(file) self.metadata['totaltracks'] = len(self.files) self.item.remove_file(file) if not self.special and self.get_num_files() == 0: self.tagger.remove_cluster(self) self.update_metadata_images() self._update_related_album() def update(self): if self.item: self.item.update() def get_num_files(self): return len(self.files) def iterfiles(self, save=False): for file in self.files: yield file def can_save(self): """Return if this object can be saved.""" if self.files: return True else: return False def can_remove(self): """Return if this object can be removed.""" return not self.special def can_edit_tags(self): """Return if this object supports tag editing.""" return True def can_analyze(self): """Return if this object can be fingerprinted.""" return any([_file.can_analyze() for _file in self.files]) def can_autotag(self): return True def can_refresh(self): return False def can_browser_lookup(self): return not self.special def can_view_info(self): if self.files: return True else: return False def is_album_like(self): return True def column(self, column): if column == 'title': return '%s (%d)' % (self.metadata['album'], len(self.files)) elif (column == '~length' and self.special) or column == 'album': return '' elif column == '~length': return format_time(self.metadata.length) elif column == 'artist': return self.metadata['albumartist'] return self.metadata[column] def _lookup_finished(self, document, http, error): self.lookup_task = None try: releases = document.metadata[0].release_list[0].release except (AttributeError, IndexError): releases = None mparms = { 'album': self.metadata['album'] } # no matches if not releases: self.tagger.window.set_statusbar_message( N_("No matching releases for cluster %(album)s"), mparms, timeout=3000 ) return # multiple matches -- calculate similarities to each of them match = sorted((self.metadata.compare_to_release( release, Cluster.comparison_weights) for release in releases), reverse=True, key=itemgetter(0))[0] if match[0] < config.setting['cluster_lookup_threshold']: self.tagger.window.set_statusbar_message( N_("No matching releases for cluster %(album)s"), mparms, timeout=3000 ) return self.tagger.window.set_statusbar_message( N_("Cluster %(album)s identified!"), mparms, timeout=3000 ) self.tagger.move_files_to_album(self.files, match[1].id) def lookup_metadata(self): """Try to identify the cluster using the existing metadata.""" if self.lookup_task: return self.tagger.window.set_statusbar_message( N_("Looking up the metadata for cluster %(album)s..."), {'album': self.metadata['album']} ) self.lookup_task = self.tagger.xmlws.find_releases(self._lookup_finished, artist=self.metadata['albumartist'], release=self.metadata['album'], tracks=str(len(self.files)), limit=QUERY_LIMIT) def clear_lookup_task(self): if self.lookup_task: self.tagger.xmlws.remove_task(self.lookup_task) self.lookup_task = None @staticmethod def cluster(files, threshold): artistDict = ClusterDict() albumDict = ClusterDict() tracks = [] for file in files: artist = file.metadata["albumartist"] or file.metadata["artist"] album = file.metadata["album"] # Improve clustering from directory structure if no existing tags # Only used for grouping and to provide cluster title / artist - not added to file tags. filename = file.filename if config.setting["windows_compatibility"] or sys.platform == "win32": filename = ntpath.splitdrive(filename)[1] album, artist = album_artist_from_path(filename, album, artist) # For each track, record the index of the artist and album within the clusters tracks.append((artistDict.add(artist), albumDict.add(album))) artist_cluster_engine = ClusterEngine(artistDict) artist_cluster_engine.cluster(threshold) album_cluster_engine = ClusterEngine(albumDict) album_cluster_engine.cluster(threshold) # Arrange tracks into albums albums = {} for i in xrange(len(tracks)): cluster = album_cluster_engine.getClusterFromId(tracks[i][1]) if cluster is not None: albums.setdefault(cluster, []).append(i) # Now determine the most prominent names in the cluster and build the # final cluster list for album_id, album in albums.items(): album_name = album_cluster_engine.getClusterTitle(album_id) artist_max = 0 artist_id = None artist_hist = {} for track_id in album: cluster = artist_cluster_engine.getClusterFromId( tracks[track_id][0]) if cluster is not None: cnt = artist_hist.get(cluster, 0) + 1 if cnt > artist_max: artist_max = cnt artist_id = cluster artist_hist[cluster] = cnt if artist_id is None: artist_name = u"Various Artists" else: artist_name = artist_cluster_engine.getClusterTitle(artist_id) yield album_name, artist_name, (files[i] for i in album) def update_metadata_images(self): update_metadata_images(self) class UnmatchedFiles(Cluster): """Special cluster for 'Unmatched Files' which have no PUID and have not been clustered.""" def __init__(self): super(UnmatchedFiles, self).__init__(_(u"Unmatched Files"), special=True) def add_files(self, files): Cluster.add_files(self, files) self.tagger.window.enable_cluster(self.get_num_files() > 0) def add_file(self, file): Cluster.add_file(self, file) self.tagger.window.enable_cluster(self.get_num_files() > 0) def remove_file(self, file): Cluster.remove_file(self, file) self.tagger.window.enable_cluster(self.get_num_files() > 0) def lookup_metadata(self): self.tagger.autotag(self.files) def can_edit_tags(self): return False def can_autotag(self): return len(self.files) > 0 def can_view_info(self): return False def can_remove(self): return True class ClusterList(list, Item): """A list of clusters.""" def __init__(self): super(ClusterList, self).__init__() def __hash__(self): return id(self) def iterfiles(self, save=False): for cluster in self: for file in cluster.iterfiles(save): yield file def can_save(self): return len(self) > 0 def can_analyze(self): return any([cluster.can_analyze() for cluster in self]) def can_autotag(self): return len(self) > 0 def can_browser_lookup(self): return False def lookup_metadata(self): for cluster in self: cluster.lookup_metadata() class ClusterDict(object): def __init__(self): # word -> id index self.words = {} # id -> word, token index self.ids = {} # counter for new id generation self.id = 0 self.regexp = re.compile(ur'\W', re.UNICODE) self.spaces = re.compile(ur'\s', re.UNICODE) def getSize(self): return self.id def tokenize(self, word): token = self.regexp.sub(u'', word.lower()) return token if token else self.spaces.sub(u'', word.lower()) def add(self, word): """ Add a new entry to the cluster if it does not exist. If it does exist, increment the count. Return the index of the word in the dictionary or -1 is the word is empty. """ if word == u'': return -1 token = self.tokenize(word) if token == u'': return -1 try: index, count = self.words[word] self.words[word] = (index, count + 1) except KeyError: index = self.id self.words[word] = (self.id, 1) self.ids[index] = (word, token) self.id = self.id + 1 return index def getWord(self, index): word = None try: word, token = self.ids[index] except KeyError: pass return word def getToken(self, index): token = None try: word, token = self.ids[index] except KeyError: pass return token def getWordAndCount(self, index): word = None count = 0 try: word, token = self.ids[index] index, count = self.words[word] except KeyError: pass return word, count class ClusterEngine(object): def __init__(self, clusterDict): # the cluster dictionary we're using self.clusterDict = clusterDict # keeps track of unique cluster index self.clusterCount = 0 # Keeps track of the clusters we've created self.clusterBins = {} # Index the word ids -> clusters self.idClusterIndex = {} def getClusterFromId(self, id): return self.idClusterIndex.get(id) def printCluster(self, cluster): if cluster < 0: print("[no such cluster]") return bin = self.clusterBins[cluster] print(cluster, " -> ", ", ".join([("'" + self.clusterDict.getWord(i) + "'") for i in bin])) def getClusterTitle(self, cluster): if cluster < 0: return "" max = 0 maxWord = u'' for id in self.clusterBins[cluster]: word, count = self.clusterDict.getWordAndCount(id) if count >= max: maxWord = word max = count return maxWord def cluster(self, threshold): # Keep the matches sorted in a heap heap = [] for y in xrange(self.clusterDict.getSize()): for x in xrange(y): if x != y: c = similarity(self.clusterDict.getToken(x).lower(), self.clusterDict.getToken(y).lower()) if c >= threshold: heappush(heap, ((1.0 - c), [x, y])) QtCore.QCoreApplication.processEvents() for i in xrange(self.clusterDict.getSize()): word, count = self.clusterDict.getWordAndCount(i) if word and count > 1: self.clusterBins[self.clusterCount] = [i] self.idClusterIndex[i] = self.clusterCount self.clusterCount = self.clusterCount + 1 for i in xrange(len(heap)): c, pair = heappop(heap) c = 1.0 - c try: match0 = self.idClusterIndex[pair[0]] except: match0 = -1 try: match1 = self.idClusterIndex[pair[1]] except: match1 = -1 # if neither item is in a cluster, make a new cluster if match0 == -1 and match1 == -1: self.clusterBins[self.clusterCount] = [pair[0], pair[1]] self.idClusterIndex[pair[0]] = self.clusterCount self.idClusterIndex[pair[1]] = self.clusterCount self.clusterCount = self.clusterCount + 1 continue # If cluster0 is in a bin, stick the other match into that bin if match0 >= 0 and match1 < 0: self.clusterBins[match0].append(pair[1]) self.idClusterIndex[pair[1]] = match0 continue # If cluster1 is in a bin, stick the other match into that bin if match1 >= 0 and match0 < 0: self.clusterBins[match1].append(pair[0]) self.idClusterIndex[pair[0]] = match1 continue # If both matches are already in two different clusters, merge the clusters if match1 != match0: self.clusterBins[match0].extend(self.clusterBins[match1]) for match in self.clusterBins[match1]: self.idClusterIndex[match] = match0 del self.clusterBins[match1] def can_refresh(self): return False picard-release-1.4.2/picard/collection.py000066400000000000000000000127411310410472100203430ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2013 Michael Wiencek # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from functools import partial from PyQt4 import QtCore from picard import config, log user_collections = {} class Collection(QtCore.QObject): def __init__(self, id, name, size): self.id = id self.name = name self.pending = set() self.size = int(size) self.releases = set() def __repr__(self): return '' % (self.name, self.id) def add_releases(self, ids, callback): ids = ids - self.pending if ids: self.pending.update(ids) self.tagger.xmlws.put_to_collection(self.id, list(ids), partial(self._add_finished, ids, callback)) def remove_releases(self, ids, callback): ids = ids - self.pending if ids: self.pending.update(ids) self.tagger.xmlws.delete_from_collection(self.id, list(ids), partial(self._remove_finished, ids, callback)) def _add_finished(self, ids, callback, document, reply, error): self.pending.difference_update(ids) if not error: count = len(ids) self.releases.update(ids) self.size += count callback() mparms = { 'count': count, 'name': self.name } log.debug('Added %(count)i releases to collection "%(name)s"' % mparms) self.tagger.window.set_statusbar_message( ungettext('Added %(count)i release to collection "%(name)s"', 'Added %(count)i releases to collection "%(name)s"', count), mparms, translate=None, echo=None ) def _remove_finished(self, ids, callback, document, reply, error): self.pending.difference_update(ids) if not error: count = len(ids) self.releases.difference_update(ids) self.size -= count callback() mparms = { 'count': count, 'name': self.name } log.debug('Removed %(count)i releases from collection "%(name)s"' % mparms) self.tagger.window.set_statusbar_message( ungettext('Removed %(count)i release from collection "%(name)s"', 'Removed %(count)i releases from collection "%(name)s"', count), mparms, translate=None, echo=None ) def load_user_collections(callback=None): tagger = QtCore.QObject.tagger def request_finished(document, reply, error): if error: tagger.window.set_statusbar_message( N_("Error loading collections: %(error)s"), {'error': unicode(reply.errorString())}, echo=log.error ) return collection_list = document.metadata[0].collection_list[0] if "collection" in collection_list.children: new_collections = set() for node in collection_list.collection: if node.attribs.get(u"entity_type") != u"release": continue new_collections.add(node.id) collection = user_collections.get(node.id) if collection is None: user_collections[node.id] = Collection(node.id, node.name[0].text, node.release_list[0].count) else: collection.name = node.name[0].text collection.size = int(node.release_list[0].count) for id in set(user_collections.iterkeys()) - new_collections: del user_collections[id] if callback: callback() if tagger.xmlws.oauth_manager.is_authorized(): tagger.xmlws.get_collection_list(partial(request_finished)) else: user_collections.clear() def add_release_to_user_collections(release_node): """Add album to collections""" # Check for empy collection list if ("collection_list" in release_node.children and "collection" in release_node.collection_list[0].children): username = config.persist["oauth_username"].lower() for node in release_node.collection_list[0].collection: if node.editor[0].text.lower() == username: if node.id not in user_collections: user_collections[node.id] = \ Collection(node.id, node.name[0].text, node.release_list[0].count) user_collections[node.id].releases.add(release_node.id) log.debug("Adding release %r to %r" % (release_node.id, user_collections[node.id])) picard-release-1.4.2/picard/compat.py000066400000000000000000000017721310410472100174750ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2014 Johannes Dewender # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """Python 2/3 compatibility""" import sys is_py2 = (sys.version_info[0] == 2) is_py3 = (sys.version_info[0] == 3) if is_py2: from StringIO import StringIO else: from io import StringIO picard-release-1.4.2/picard/config.py000066400000000000000000000215271310410472100174570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from __future__ import print_function from operator import itemgetter from PyQt4 import QtCore from picard import (PICARD_APP_NAME, PICARD_ORG_NAME, PICARD_VERSION, version_to_string, version_from_string) from picard.util import LockableObject class ConfigUpgradeError(Exception): pass class ConfigSection(LockableObject): """Configuration section.""" def __init__(self, config, name): LockableObject.__init__(self) self.__config = config self.__name = name def __getitem__(self, name): opt = Option.get(self.__name, name) if opt is None: return None return self.value(name, opt, opt.default) def __setitem__(self, name, value): self.lock_for_write() try: self.__config.setValue("%s/%s" % (self.__name, name), value) finally: self.unlock() def __contains__(self, key): key = "%s/%s" % (self.__name, key) return self.__config.contains(key) def remove(self, key): key = "%s/%s" % (self.__name, key) if self.__config.contains(key): self.__config.remove(key) def raw_value(self, key): """Return an option value without any type conversion.""" value = self.__config.value("%s/%s" % (self.__name, key)) # XXX QPyNullVariant does not exist in all PyQt versions, and was # removed entirely in PyQt5. See: # http://pyqt.sourceforge.net/Docs/PyQt5/pyqt_qvariant.html if str(type(value)) == "": return "" return value def value(self, name, type, default=None): """Return an option value converted to the given Option type.""" key = "%s/%s" % (self.__name, name) self.lock_for_read() try: if self.__config.contains(key): return type.convert(self.raw_value(name)) return default except: return default finally: self.unlock() class Config(QtCore.QSettings): """Configuration.""" def __init__(self): pass def __initialize(self): """Common initializer method for :meth:`from_app` and :meth:`from_file`.""" # If there are no settings, copy existing settings from old format # (registry on windows systems) if not self.allKeys(): oldFormat = QtCore.QSettings(PICARD_ORG_NAME, PICARD_APP_NAME) for k in oldFormat.allKeys(): self.setValue(k, oldFormat.value(k)) self.sync() self.application = ConfigSection(self, "application") self.setting = ConfigSection(self, "setting") self.persist = ConfigSection(self, "persist") self.profile = ConfigSection(self, "profile/default") self.current_preset = "default" TextOption("application", "version", '0.0.0dev0') self._version = version_from_string(self.application["version"]) self._upgrade_hooks = dict() @classmethod def from_app(cls, parent): """Build a Config object using the default configuration file location.""" this = cls() QtCore.QSettings.__init__(this, QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, PICARD_ORG_NAME, PICARD_APP_NAME, parent) this.__initialize() return this @classmethod def from_file(cls, parent, filename): """Build a Config object using a user-provided configuration file path.""" this = cls() QtCore.QSettings.__init__(this, filename, QtCore.QSettings.IniFormat, parent) this.__initialize() return this def switchProfile(self, profilename): """Sets the current profile.""" key = u"profile/%s" % (profilename,) if self.contains(key): self.profile.name = key else: raise KeyError("Unknown profile '%s'" % (profilename,)) def register_upgrade_hook(self, func, *args): """Register a function to upgrade from one config version to another""" to_version = version_from_string(func.__name__) assert to_version <= PICARD_VERSION, "%r > %r !!!" % (to_version, PICARD_VERSION) self._upgrade_hooks[to_version] = { 'func': func, 'args': args, 'done': False } def run_upgrade_hooks(self, outputfunc=None): """Executes registered functions to upgrade config version to the latest""" if not self._upgrade_hooks: return if self._version >= PICARD_VERSION: if self._version > PICARD_VERSION: print("Warning: config file %s was created by a more recent " "version of Picard (current is %s)" % ( version_to_string(self._version), version_to_string(PICARD_VERSION) )) return for version in sorted(self._upgrade_hooks): hook = self._upgrade_hooks[version] if self._version < version: try: if outputfunc and hook['func'].__doc__: outputfunc("Config upgrade %s -> %s: %s" % ( version_to_string(self._version), version_to_string(version), hook['func'].__doc__.strip())) hook['func'](*hook['args']) except: import traceback raise ConfigUpgradeError( "Error during config upgrade from version %s to %s " "using %s():\n%s" % ( version_to_string(self._version), version_to_string(version), hook['func'].__name__, traceback.format_exc() )) else: hook['done'] = True self._version = version self._write_version() else: # hook is not applicable, mark as done hook['done'] = True if all(map(itemgetter("done"), self._upgrade_hooks.values())): # all hooks were executed, ensure config is marked with latest version self._version = PICARD_VERSION self._write_version() def _write_version(self): self.application["version"] = version_to_string(self._version) self.sync() class Option(QtCore.QObject): """Generic option.""" registry = {} def __init__(self, section, name, default): self.section = section self.name = name self.default = default if not hasattr(self, "convert"): self.convert = type(default) self.registry[(self.section, self.name)] = self @classmethod def get(cls, section, name): return cls.registry.get((section, name)) class TextOption(Option): convert = unicode class BoolOption(Option): @staticmethod def convert(value): # The QSettings IniFormat saves boolean values as the strings "true" # and "false". Thus, explicit boolean and string comparisons are used # to determine the value. NOTE: In PyQt >= 4.8.3, QSettings.value has # an optional "type" parameter that avoids this. But we still support # PyQt >= 4.5, so that is not used. return value is True or value == "true" class IntOption(Option): convert = int class FloatOption(Option): convert = float class ListOption(Option): convert = list class IntListOption(Option): @staticmethod def convert(value): return map(int, value) config = None setting = None persist = None def _setup(app, filename=None): global config, setting, persist if filename is None: config = Config.from_app(app) else: config = Config.from_file(app, filename) setting = config.setting persist = config.persist picard-release-1.4.2/picard/config_upgrade.py000066400000000000000000000211451310410472100211620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2013-2014 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtGui import re from picard import (log, config) # TO ADD AN UPGRADE HOOK: # ---------------------- # add a function here, named after the version you want upgrade to # ie. upgrade_to_v1_0_0_dev_1() for 1.0.0dev1 # register it in upgrade_config() # and modify PICARD_VERSION to match it # def upgrade_to_v1_0_0_final_0(): """In version 1.0, the file naming formats for single and various artist releases were merged. """ _s = config.setting def remove_va_file_naming_format(merge=True): if merge: _s["file_naming_format"] = ( "$if($eq(%%compilation%%,1),\n$noop(Various Artist " "albums)\n%s,\n$noop(Single Artist Albums)\n%s)" % ( _s.value("va_file_naming_format", config.TextOption), _s["file_naming_format"] )) _s.remove("va_file_naming_format") _s.remove("use_va_format") if ("va_file_naming_format" in _s and "use_va_format" in _s): msgbox = QtGui.QMessageBox() if _s.value("use_va_format", config.BoolOption): remove_va_file_naming_format() msgbox.information(msgbox, _("Various Artists file naming scheme removal"), _("The separate file naming scheme for various artists " "albums has been removed in this version of Picard.\n" "Your file naming scheme has automatically been " "merged with that of single artist albums."), QtGui.QMessageBox.Ok) elif (_s.value("va_file_naming_format", config.TextOption) != r"$if2(%albumartist%,%artist%)/%album%/$if($gt(%totaldis" "cs%,1),%discnumber%-,)$num(%tracknumber%,2) %artist% - " "%title%"): answer = msgbox.question(msgbox, _("Various Artists file naming scheme removal"), _("The separate file naming scheme for various artists " "albums has been removed in this version of Picard.\n" "You currently do not use this option, but have a " "separate file naming scheme defined.\n" "Do you want to remove it or merge it with your file " "naming scheme for single artist albums?"), _("Merge"), _("Remove")) if answer: remove_va_file_naming_format(merge=False) else: remove_va_file_naming_format() else: # default format, disabled remove_va_file_naming_format(merge=False) def upgrade_to_v1_3_0_dev_1(): """Option "windows_compatible_filenames" was renamed "windows_compatibility" (PICARD-110). """ _s = config.setting old_opt = "windows_compatible_filenames" new_opt = "windows_compatibility" if old_opt in _s: _s[new_opt] = _s.value(old_opt, config.BoolOption, True) _s.remove(old_opt) def upgrade_to_v1_3_0_dev_2(): """Option "preserved_tags" is now using comma instead of spaces as tag separator (PICARD-536) """ _s = config.setting opt = "preserved_tags" if opt in _s: _s[opt] = re.sub(r"\s+", ",", _s[opt].strip()) def upgrade_to_v1_3_0_dev_3(): """Options were made to support lists (solving PICARD-144 and others) """ _s = config.setting option_separators = { "preferred_release_countries": " ", "preferred_release_formats": " ", "enabled_plugins": None, "caa_image_types": None, "metadata_box_sizes": None, } for (opt, sep) in option_separators.iteritems(): if opt in _s: _s[opt] = _s.raw_value(opt).split(sep) def upgrade_to_v1_3_0_dev_4(): """Option "release_type_scores" is now a list of tuples """ _s = config.setting def load_release_type_scores(setting): scores = [] values = setting.split() for i in range(0, len(values), 2): try: score = float(values[i + 1]) except IndexError: score = 0.0 scores.append((values[i], score)) return scores opt = "release_type_scores" if opt in _s: _s[opt] = load_release_type_scores(_s.raw_value(opt)) def upgrade_to_v1_4_0_dev_2(): """Options "username" and "password" are removed and replaced with OAuth tokens """ _s = config.setting opts = ["username", "password"] for opt in opts: _s.remove(opt) def upgrade_to_v1_4_0_dev_3(): """Cover art providers options were moved to a list of tuples""" _s = config.setting map = [ ('ca_provider_use_amazon', 'Amazon'), ('ca_provider_use_caa', 'Cover Art Archive'), ('ca_provider_use_whitelist', 'Whitelist'), ('ca_provider_use_caa_release_group_fallback', 'CaaReleaseGroup') ] newopts = [] for old, new in map: if old in _s: newopts.append((new, _s.value(old, config.BoolOption, True))) _s['ca_providers'] = newopts def upgrade_to_v1_4_0_dev_4(): """Adds trailing comma to default file names for scripts""" _s = config.setting _DEFAULT_FILE_NAMING_FORMAT = "$if2(%albumartist%,%artist%)/" \ "$if($ne(%albumartist%,),%album%/)" \ "$if($gt(%totaldiscs%,1),%discnumber%-,)" \ "$if($ne(%albumartist%,),$num(%tracknumber%,2) ,)" \ "$if(%_multiartist%,%artist% - ,)" \ "%title%" if _s["file_naming_format"] == _DEFAULT_FILE_NAMING_FORMAT: _DEFAULT_FILE_NAMING_FORMAT = "$if2(%albumartist%,%artist%)/" \ "$if($ne(%albumartist%,),%album%/,)" \ "$if($gt(%totaldiscs%,1),%discnumber%-,)" \ "$if($ne(%albumartist%,),$num(%tracknumber%,2) ,)" \ "$if(%_multiartist%,%artist% - ,)" \ "%title%" _s["file_naming_format"] = _DEFAULT_FILE_NAMING_FORMAT def upgrade_to_v1_4_0_dev_5(): """Using Picard.ini configuration file on all platforms""" # this is done in Config.__init__() pass def upgrade_to_v1_4_0_dev_6(): """Adds support for multiple and selective tagger scripts""" _s = config.setting DEFAULT_NUMBERED_SCRIPT_NAME = N_("My script %d") old_enabled_option = "enable_tagger_script" old_script_text_option = "tagger_script" list_of_scripts = [] if old_enabled_option in _s: _s["enable_tagger_scripts"] = _s.value(old_enabled_option, config.BoolOption, False) if old_script_text_option in _s: old_script_text = _s.value(old_script_text_option, config.TextOption, "") if old_script_text: old_script = (0, _(DEFAULT_NUMBERED_SCRIPT_NAME) % 1, _s["enable_tagger_scripts"], old_script_text) list_of_scripts.append(old_script) _s["list_of_scripts"] = list_of_scripts _s.remove(old_enabled_option) _s.remove(old_script_text_option) def upgrade_to_v1_4_0_dev_7(): """Option "save_only_front_images_to_tags" was renamed to "embed_only_one_front_image".""" _s = config.setting old_opt = "save_only_front_images_to_tags" new_opt = "embed_only_one_front_image" if old_opt in _s: _s[new_opt] = _s.value(old_opt, config.BoolOption, True) _s.remove(old_opt) def upgrade_config(): cfg = config.config cfg.register_upgrade_hook(upgrade_to_v1_0_0_final_0) cfg.register_upgrade_hook(upgrade_to_v1_3_0_dev_1) cfg.register_upgrade_hook(upgrade_to_v1_3_0_dev_2) cfg.register_upgrade_hook(upgrade_to_v1_3_0_dev_3) cfg.register_upgrade_hook(upgrade_to_v1_3_0_dev_4) cfg.register_upgrade_hook(upgrade_to_v1_4_0_dev_2) cfg.register_upgrade_hook(upgrade_to_v1_4_0_dev_3) cfg.register_upgrade_hook(upgrade_to_v1_4_0_dev_4) cfg.register_upgrade_hook(upgrade_to_v1_4_0_dev_5) cfg.register_upgrade_hook(upgrade_to_v1_4_0_dev_6) cfg.register_upgrade_hook(upgrade_to_v1_4_0_dev_7) cfg.run_upgrade_hooks(log.debug) picard-release-1.4.2/picard/const/000077500000000000000000000000001310410472100167575ustar00rootroot00000000000000picard-release-1.4.2/picard/const/__init__.py000066400000000000000000000074601310410472100210770ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import sys # Install gettext "noop" function in case const.py gets imported directly. import __builtin__ __builtin__.__dict__['N_'] = lambda a: a # Config directory if sys.platform == "win32": USER_DIR = os.environ.get("APPDATA", "~\\Application Data") else: USER_DIR = os.environ.get("XDG_CONFIG_HOME", "~/.config") USER_DIR = os.path.join( os.path.expanduser(USER_DIR), "MusicBrainz", "Picard" ) USER_PLUGIN_DIR = os.path.join(USER_DIR, "plugins") # AcoustID client API key ACOUSTID_KEY = 'v8pQ6oyB' ACOUSTID_HOST = 'api.acoustid.org' ACOUSTID_PORT = 80 FPCALC_NAMES = ['fpcalc', 'pyfpcalc'] # MB OAuth client credentials MUSICBRAINZ_OAUTH_CLIENT_ID = 'ACa9wsDX19cLp-AeEP-vVw' MUSICBRAINZ_OAUTH_CLIENT_SECRET = 'xIsvXbIuntaLuRRhzuazOA' # Cover art archive URL and port CAA_HOST = "coverartarchive.org" CAA_PORT = 443 # URLs PICARD_URLS = { 'documentation': "https://picard.musicbrainz.org/docs/", 'troubleshooting': "https://picard.musicbrainz.org/docs/troubleshooting/", 'home': "https://picard.musicbrainz.org/", 'doc_options': "https://picard.musicbrainz.org/docs/options/", 'doc_scripting': "https://picard.musicbrainz.org/docs/scripting", 'plugins': "https://picard.musicbrainz.org/plugins/", 'forum': "https://community.metabrainz.org/c/picard", 'donate': "https://metabrainz.org/donate", 'chromaprint': "https://acoustid.org/chromaprint#download", 'acoustid_apikey': "https://acoustid.org/api-key", 'doc_cover_art_types': "https://musicbrainz.org/doc/Cover_Art/Types", 'acoustid_track': "https://acoustid.org/track/", } # Various Artists MBID VARIOUS_ARTISTS_ID = '89ad4ac3-39f7-470e-963a-56509c546377' # Special purpose track titles SILENCE_TRACK_TITLE = '[silence]' DATA_TRACK_TITLE = '[data track]' # Release formats from picard.const.attributes import MB_ATTRIBUTES RELEASE_FORMATS = {} RELEASE_PRIMARY_GROUPS = {} RELEASE_SECONDARY_GROUPS = {} for k, v in MB_ATTRIBUTES.iteritems(): if k.startswith(u'DB:medium_format/name:'): RELEASE_FORMATS[v] = v elif k.startswith(u'DB:release_group_primary_type/name:'): RELEASE_PRIMARY_GROUPS[v] = v elif k.startswith(u'DB:release_group_secondary_type/name:'): RELEASE_SECONDARY_GROUPS[v] = v # Release countries from picard.const.countries import RELEASE_COUNTRIES # List of available user interface languages from picard.const.languages import UI_LANGUAGES # List of alias locales from picard.const.locales import ALIAS_LOCALES # List of official musicbrainz servers - must support SSL for mblogin requests (such as collections). MUSICBRAINZ_SERVERS = [ 'musicbrainz.org', 'beta.musicbrainz.org', ] # Plugins API PLUGINS_API = { 'host': 'picard.musicbrainz.org', 'port': 443, 'endpoint': { 'plugins': '/api/v1/plugins/', 'download': '/api/v1/download/' } } # Default query limit QUERY_LIMIT = 25 # Maximum number of covers to draw in a stack in CoverArtThumbnail MAX_COVERS_TO_STACK = 4 picard-release-1.4.2/picard/const/attributes.py000066400000000000000000000120321310410472100215150ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py update_constants` to update it. MB_ATTRIBUTES = { u'DB:cover_art_archive.art_type/name:001': u'Front', u'DB:cover_art_archive.art_type/name:002': u'Back', u'DB:cover_art_archive.art_type/name:003': u'Booklet', u'DB:cover_art_archive.art_type/name:004': u'Medium', u'DB:cover_art_archive.art_type/name:005': u'Obi', u'DB:cover_art_archive.art_type/name:006': u'Spine', u'DB:cover_art_archive.art_type/name:007': u'Track', u'DB:cover_art_archive.art_type/name:008': u'Other', u'DB:cover_art_archive.art_type/name:009': u'Tray', u'DB:cover_art_archive.art_type/name:010': u'Sticker', u'DB:cover_art_archive.art_type/name:011': u'Poster', u'DB:cover_art_archive.art_type/name:012': u'Liner', u'DB:cover_art_archive.art_type/name:013': u'Watermark', u'DB:medium_format/name:001': u'CD', u'DB:medium_format/name:002': u'DVD', u'DB:medium_format/name:003': u'SACD', u'DB:medium_format/name:004': u'DualDisc', u'DB:medium_format/name:005': u'LaserDisc', u'DB:medium_format/name:006': u'MiniDisc', u'DB:medium_format/name:007': u'Vinyl', u'DB:medium_format/name:008': u'Cassette', u'DB:medium_format/name:009': u'Cartridge', u'DB:medium_format/name:010': u'Reel-to-reel', u'DB:medium_format/name:011': u'DAT', u'DB:medium_format/name:012': u'Digital Media', u'DB:medium_format/name:013': u'Other', u'DB:medium_format/name:014': u'Wax Cylinder', u'DB:medium_format/name:015': u'Piano Roll', u'DB:medium_format/name:016': u'DCC', u'DB:medium_format/name:017': u'HD-DVD', u'DB:medium_format/name:018': u'DVD-Audio', u'DB:medium_format/name:019': u'DVD-Video', u'DB:medium_format/name:020': u'Blu-ray', u'DB:medium_format/name:021': u'VHS', u'DB:medium_format/name:022': u'VCD', u'DB:medium_format/name:023': u'SVCD', u'DB:medium_format/name:024': u'Betamax', u'DB:medium_format/name:025': u'HDCD', u'DB:medium_format/name:026': u'USB Flash Drive', u'DB:medium_format/name:027': u'slotMusic', u'DB:medium_format/name:028': u'UMD', u'DB:medium_format/name:029': u'7" Vinyl', u'DB:medium_format/name:030': u'10" Vinyl', u'DB:medium_format/name:031': u'12" Vinyl', u'DB:medium_format/name:033': u'CD-R', u'DB:medium_format/name:034': u'8cm CD', u'DB:medium_format/name:035': u'Blu-spec CD', u'DB:medium_format/name:036': u'SHM-CD', u'DB:medium_format/name:037': u'HQCD', u'DB:medium_format/name:038': u'Hybrid SACD', u'DB:medium_format/name:039': u'CD+G', u'DB:medium_format/name:040': u'8cm CD+G', u'DB:medium_format/name:041': u'CDV', u'DB:medium_format/name:042': u'Enhanced CD', u'DB:medium_format/name:043': u'Data CD', u'DB:medium_format/name:044': u'DTS CD', u'DB:medium_format/name:045': u'Playbutton', u'DB:medium_format/name:046': u'Music Card', u'DB:medium_format/name:047': u'DVDplus', u'DB:medium_format/name:048': u'VinylDisc', u'DB:medium_format/name:049': u'3.5" Floppy Disk', u'DB:medium_format/name:050': u'Edison Diamond Disc', u'DB:medium_format/name:051': u'Flexi-disc', u'DB:medium_format/name:052': u'7" Flexi-disc', u'DB:medium_format/name:053': u'Shellac', u'DB:medium_format/name:054': u'10" Shellac', u'DB:medium_format/name:055': u'12" Shellac', u'DB:medium_format/name:056': u'7" Shellac', u'DB:medium_format/name:057': u'SHM-SACD', u'DB:medium_format/name:058': u'Pathé disc', u'DB:medium_format/name:059': u'VHD', u'DB:medium_format/name:060': u'CED', u'DB:medium_format/name:061': u'Copy Control CD', u'DB:medium_format/name:062': u'SD Card', u'DB:medium_format/name:063': u'Hybrid SACD (CD layer)', u'DB:medium_format/name:064': u'Hybrid SACD (SACD layer)', u'DB:medium_format/name:065': u'DualDisc (DVD-Audio side)', u'DB:medium_format/name:066': u'DualDisc (DVD-Video side)', u'DB:medium_format/name:067': u'DualDisc (CD side)', u'DB:medium_format/name:068': u'DVDplus (DVD-Audio side)', u'DB:medium_format/name:069': u'DVDplus (DVD-Video side)', u'DB:medium_format/name:070': u'DVDplus (CD side)', u'DB:release_group_primary_type/name:001': u'Album', u'DB:release_group_primary_type/name:002': u'Single', u'DB:release_group_primary_type/name:003': u'EP', u'DB:release_group_primary_type/name:011': u'Other', u'DB:release_group_primary_type/name:012': u'Broadcast', u'DB:release_group_secondary_type/name:001': u'Compilation', u'DB:release_group_secondary_type/name:002': u'Soundtrack', u'DB:release_group_secondary_type/name:003': u'Spokenword', u'DB:release_group_secondary_type/name:004': u'Interview', u'DB:release_group_secondary_type/name:005': u'Audiobook', u'DB:release_group_secondary_type/name:006': u'Live', u'DB:release_group_secondary_type/name:007': u'Remix', u'DB:release_group_secondary_type/name:008': u'DJ-mix', u'DB:release_group_secondary_type/name:009': u'Mixtape/Street', u'DB:release_group_secondary_type/name:010': u'Demo', } picard-release-1.4.2/picard/const/countries.py000066400000000000000000000154041310410472100213500ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py update_constants` to update it. RELEASE_COUNTRIES = { u'AD': u'Andorra', u'AE': u'United Arab Emirates', u'AF': u'Afghanistan', u'AG': u'Antigua and Barbuda', u'AI': u'Anguilla', u'AL': u'Albania', u'AM': u'Armenia', u'AN': u'Netherlands Antilles', u'AO': u'Angola', u'AQ': u'Antarctica', u'AR': u'Argentina', u'AS': u'American Samoa', u'AT': u'Austria', u'AU': u'Australia', u'AW': u'Aruba', u'AX': u'Åland Islands', u'AZ': u'Azerbaijan', u'BA': u'Bosnia and Herzegovina', u'BB': u'Barbados', u'BD': u'Bangladesh', u'BE': u'Belgium', u'BF': u'Burkina Faso', u'BG': u'Bulgaria', u'BH': u'Bahrain', u'BI': u'Burundi', u'BJ': u'Benin', u'BL': u'Saint Barthélemy', u'BM': u'Bermuda', u'BN': u'Brunei', u'BO': u'Bolivia', u'BQ': u'Bonaire, Sint Eustatius and Saba', u'BR': u'Brazil', u'BS': u'Bahamas', u'BT': u'Bhutan', u'BV': u'Bouvet Island', u'BW': u'Botswana', u'BY': u'Belarus', u'BZ': u'Belize', u'CA': u'Canada', u'CC': u'Cocos (Keeling) Islands', u'CD': u'Democratic Republic of the Congo', u'CF': u'Central African Republic', u'CG': u'Congo', u'CH': u'Switzerland', u'CI': u'Côte d\'Ivoire', u'CK': u'Cook Islands', u'CL': u'Chile', u'CM': u'Cameroon', u'CN': u'China', u'CO': u'Colombia', u'CR': u'Costa Rica', u'CS': u'Serbia and Montenegro', u'CU': u'Cuba', u'CV': u'Cape Verde', u'CW': u'Curaçao', u'CX': u'Christmas Island', u'CY': u'Cyprus', u'CZ': u'Czech Republic', u'DE': u'Germany', u'DJ': u'Djibouti', u'DK': u'Denmark', u'DM': u'Dominica', u'DO': u'Dominican Republic', u'DZ': u'Algeria', u'EC': u'Ecuador', u'EE': u'Estonia', u'EG': u'Egypt', u'EH': u'Western Sahara', u'ER': u'Eritrea', u'ES': u'Spain', u'ET': u'Ethiopia', u'FI': u'Finland', u'FJ': u'Fiji', u'FK': u'Falkland Islands', u'FM': u'Federated States of Micronesia', u'FO': u'Faroe Islands', u'FR': u'France', u'GA': u'Gabon', u'GB': u'United Kingdom', u'GD': u'Grenada', u'GE': u'Georgia', u'GF': u'French Guiana', u'GG': u'Guernsey', u'GH': u'Ghana', u'GI': u'Gibraltar', u'GL': u'Greenland', u'GM': u'Gambia', u'GN': u'Guinea', u'GP': u'Guadeloupe', u'GQ': u'Equatorial Guinea', u'GR': u'Greece', u'GS': u'South Georgia and the South Sandwich Islands', u'GT': u'Guatemala', u'GU': u'Guam', u'GW': u'Guinea-Bissau', u'GY': u'Guyana', u'HK': u'Hong Kong', u'HM': u'Heard Island and McDonald Islands', u'HN': u'Honduras', u'HR': u'Croatia', u'HT': u'Haiti', u'HU': u'Hungary', u'ID': u'Indonesia', u'IE': u'Ireland', u'IL': u'Israel', u'IM': u'Isle of Man', u'IN': u'India', u'IO': u'British Indian Ocean Territory', u'IQ': u'Iraq', u'IR': u'Iran', u'IS': u'Iceland', u'IT': u'Italy', u'JE': u'Jersey', u'JM': u'Jamaica', u'JO': u'Jordan', u'JP': u'Japan', u'KE': u'Kenya', u'KG': u'Kyrgyzstan', u'KH': u'Cambodia', u'KI': u'Kiribati', u'KM': u'Comoros', u'KN': u'Saint Kitts and Nevis', u'KP': u'North Korea', u'KR': u'South Korea', u'KW': u'Kuwait', u'KY': u'Cayman Islands', u'KZ': u'Kazakhstan', u'LA': u'Laos', u'LB': u'Lebanon', u'LC': u'Saint Lucia', u'LI': u'Liechtenstein', u'LK': u'Sri Lanka', u'LR': u'Liberia', u'LS': u'Lesotho', u'LT': u'Lithuania', u'LU': u'Luxembourg', u'LV': u'Latvia', u'LY': u'Libya', u'MA': u'Morocco', u'MC': u'Monaco', u'MD': u'Moldova', u'ME': u'Montenegro', u'MF': u'Saint Martin (French part)', u'MG': u'Madagascar', u'MH': u'Marshall Islands', u'MK': u'Macedonia', u'ML': u'Mali', u'MM': u'Myanmar', u'MN': u'Mongolia', u'MO': u'Macao', u'MP': u'Northern Mariana Islands', u'MQ': u'Martinique', u'MR': u'Mauritania', u'MS': u'Montserrat', u'MT': u'Malta', u'MU': u'Mauritius', u'MV': u'Maldives', u'MW': u'Malawi', u'MX': u'Mexico', u'MY': u'Malaysia', u'MZ': u'Mozambique', u'NA': u'Namibia', u'NC': u'New Caledonia', u'NE': u'Niger', u'NF': u'Norfolk Island', u'NG': u'Nigeria', u'NI': u'Nicaragua', u'NL': u'Netherlands', u'NO': u'Norway', u'NP': u'Nepal', u'NR': u'Nauru', u'NU': u'Niue', u'NZ': u'New Zealand', u'OM': u'Oman', u'PA': u'Panama', u'PE': u'Peru', u'PF': u'French Polynesia', u'PG': u'Papua New Guinea', u'PH': u'Philippines', u'PK': u'Pakistan', u'PL': u'Poland', u'PM': u'Saint Pierre and Miquelon', u'PN': u'Pitcairn', u'PR': u'Puerto Rico', u'PS': u'Palestine', u'PT': u'Portugal', u'PW': u'Palau', u'PY': u'Paraguay', u'QA': u'Qatar', u'RE': u'Réunion', u'RO': u'Romania', u'RS': u'Serbia', u'RU': u'Russia', u'RW': u'Rwanda', u'SA': u'Saudi Arabia', u'SB': u'Solomon Islands', u'SC': u'Seychelles', u'SD': u'Sudan', u'SE': u'Sweden', u'SG': u'Singapore', u'SH': u'Saint Helena, Ascension and Tristan da Cunha', u'SI': u'Slovenia', u'SJ': u'Svalbard and Jan Mayen', u'SK': u'Slovakia', u'SL': u'Sierra Leone', u'SM': u'San Marino', u'SN': u'Senegal', u'SO': u'Somalia', u'SR': u'Suriname', u'SS': u'South Sudan', u'ST': u'Sao Tome and Principe', u'SU': u'Soviet Union', u'SV': u'El Salvador', u'SX': u'Sint Maarten (Dutch part)', u'SY': u'Syria', u'SZ': u'Swaziland', u'TC': u'Turks and Caicos Islands', u'TD': u'Chad', u'TF': u'French Southern Territories', u'TG': u'Togo', u'TH': u'Thailand', u'TJ': u'Tajikistan', u'TK': u'Tokelau', u'TL': u'Timor-Leste', u'TM': u'Turkmenistan', u'TN': u'Tunisia', u'TO': u'Tonga', u'TR': u'Turkey', u'TT': u'Trinidad and Tobago', u'TV': u'Tuvalu', u'TW': u'Taiwan', u'TZ': u'Tanzania', u'UA': u'Ukraine', u'UG': u'Uganda', u'UM': u'United States Minor Outlying Islands', u'US': u'United States', u'UY': u'Uruguay', u'UZ': u'Uzbekistan', u'VA': u'Vatican City', u'VC': u'Saint Vincent and The Grenadines', u'VE': u'Venezuela', u'VG': u'British Virgin Islands', u'VI': u'U.S. Virgin Islands', u'VN': u'Vietnam', u'VU': u'Vanuatu', u'WF': u'Wallis and Futuna', u'WS': u'Samoa', u'XC': u'Czechoslovakia', u'XE': u'Europe', u'XG': u'East Germany', u'XK': u'Kosovo', u'XW': u'[Worldwide]', u'YE': u'Yemen', u'YT': u'Mayotte', u'YU': u'Yugoslavia', u'ZA': u'South Africa', u'ZM': u'Zambia', u'ZW': u'Zimbabwe', } picard-release-1.4.2/picard/const/languages.py000066400000000000000000000063121310410472100213010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # List of available user interface languages UI_LANGUAGES = [ #(u'af', u'Afrikaans', N_(u'Afrikaans')), #(u'ar', u'العربية', N_(u'Arabic')), #(u'ast', u'Asturianu', N_(u'Asturian')), #(u'bg', u'Български', N_(u'Bulgarian')), #(u'ca', u'Català', N_(u'Catalan')), #(u'cs', u'Čeština', N_(u'Czech')), #(u'cy', u'Cymraeg', N_(u'Welsh')), (u'da', u'Dansk', N_(u'Danish')), (u'de', u'Deutsch', N_(u'German')), #(u'el', u'ελληνικά', N_(u'Greek')), (u'en', u'English', N_(u'English')), (u'en_CA', u'English (Canada)', N_(u'English (Canada)')), (u'en_GB', u'English (UK)', N_(u'English (UK)')), #(u'eo', u'Esperanto', N_(u'Esperanto')), (u'es', u'Español', N_(u'Spanish')), (u'et', u'Eesti keel', N_(u'Estonian')), #(u'fa', u'فارسی', N_(u'Persian')), (u'fi', u'Suomi', N_(u'Finnish')), #(u'fo', u'Føroyskt', N_(u'Faroese')), (u'fr', u'Français', N_(u'French')), #(u'fr_CA', u'Français canadien', N_(u'French (Canada)')), #(u'fy', u'Frysk', N_(u'Frisian')), #(u'gl', u'Galego', N_(u'Galician')), #(u'he', u'עברית', N_(u'Hebrew')), #(u'hi', u'हिन्दी', N_(u'Hindi')), #(u'hu', u'Magyar', N_(u'Hungarian')), #(u'id', u'Bahasa Indonesia', N_(u'Indonesian')), #(u'is', u'Íslenska', N_(u'Islandic')), (u'it', u'Italiano', N_(u'Italian')), #(u'ja', u'日本語', N_(u'Japanese')), #(u'kn', u'ಕನ್ನಡ', N_(u'Kannada')), #(u'ko', u'한국어', N_(u'Korean')), #(u'lt', u'Lietuvių', N_(u'Lithuanian')), (u'nb', u'Norsk bokmål', N_(u'Norwegian Bokmal')), #(u'nds', u'Plattdüütsch', N_(u'Low German')), (u'nl', u'Nederlands', N_(u'Dutch')), #(u'oc', u'Occitan', N_(u'Occitan')), (u'pl', u'Polski', N_(u'Polish')), #(u'pt', u'Português', N_(u'Portuguese')), (u'pt_BR', u'Português do Brasil', N_(u'Brazilian Portuguese')), #(u'ro', u'Română', N_(u'Romanian')), #(u'ru', u'Pyccĸий', N_(u'Russian')), #(u'sco', u'Scots leid', N_(u'Scots')), #(u'sk', u'Slovenčina', N_(u'Slovak')), #(u'sl', u'Slovenščina', N_(u'Slovenian')), #(u'sr', u'Србин', N_(u'Serbian')), (u'sv', u'Svenska', N_(u'Swedish')), #(u'ta', u'தமிழ்', N_(u'Tamil')), #(u'tr', u'Türkçe', N_(u'Turkish')), #(u'uk', u'Украї́нська мо́ва', N_(u'Ukrainian')), #(u'zh_CN', u'中文', N_(u'Chinese')), ] picard-release-1.4.2/picard/const/locales.py000066400000000000000000000401231310410472100207530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # List of alias locales ALIAS_LOCALES = { u'aa': 'Afar', u'aa_DJ': 'Afar (Djibouti)', u'aa_ER': 'Afar (Eritrea)', u'aa_ER_SAAHO': 'Afar (Eritrea) (Saho)', u'aa_ET': 'Afar (Ethiopia)', u'af': 'Afrikaans', u'af_NA': 'Afrikaans (Namibia)', u'af_ZA': 'Afrikaans (South Africa)', u'ak': 'Akan', u'ak_GH': 'Akan (Ghana)', u'sq': 'Albanian', u'sq_AL': 'Albanian (Albania)', u'am': 'Amharic', u'am_ET': 'Amharic (Ethiopia)', u'ar': 'Arabic', u'ar_DZ': 'Arabic (Algeria)', u'ar_BH': 'Arabic (Bahrain)', u'ar_EG': 'Arabic (Egypt)', u'ar_IQ': 'Arabic (Iraq)', u'ar_JO': 'Arabic (Jordan)', u'ar_KW': 'Arabic (Kuwait)', u'ar_LB': 'Arabic (Lebanon)', u'ar_LY': 'Arabic (Libya)', u'ar_MA': 'Arabic (Morocco)', u'ar_OM': 'Arabic (Oman)', u'ar_QA': 'Arabic (Qatar)', u'ar_SA': 'Arabic (Saudi Arabia)', u'ar_SD': 'Arabic (Sudan)', u'ar_SY': 'Arabic (Syria)', u'ar_TN': 'Arabic (Tunisia)', u'ar_AE': 'Arabic (United Arab Emirates)', u'ar_YE': 'Arabic (Yemen)', u'hy': 'Armenian', u'hy_AM': 'Armenian (Armenia)', u'hy_AM_REVISED': 'Armenian (Armenia) (Revised Orthography)', u'as': 'Assamese', u'as_IN': 'Assamese (India)', u'cch': 'Atsam', u'cch_NG': 'Atsam (Nigeria)', u'az': 'Azerbaijani', u'az_AZ': 'Azerbaijani (Azerbaijan)', u'az_Cyrl': 'Azerbaijani (Cyrillic)', u'az_Cyrl_AZ': 'Azerbaijani (Cyrillic) (Azerbaijan)', u'az_Latn': 'Azerbaijani (Latin)', u'az_Latn_AZ': 'Azerbaijani (Latin) (Azerbaijan)', u'eu': 'Basque', u'eu_ES': 'Basque (Spain)', u'be': 'Belarusian', u'be_BY': 'Belarusian (Belarus)', u'bn': 'Bengali', u'bn_BD': 'Bengali (Bangladesh)', u'bn_IN': 'Bengali (India)', u'byn': 'Blin', u'byn_ER': 'Blin (Eritrea)', u'bs': 'Bosnian', u'bs_BA': 'Bosnian (Bosnia and Herzegovina)', u'bg': 'Bulgarian', u'bg_BG': 'Bulgarian (Bulgaria)', u'my': 'Burmese', u'my_MM': 'Burmese (Myanmar [Burma])', u'ca': 'Catalan', u'ca_ES': 'Catalan (Spain)', u'zh': 'Chinese', u'zh_CN': 'Chinese (China)', u'zh_HK': 'Chinese (Hong Kong SAR China)', u'zh_MO': 'Chinese (Macau SAR China)', u'zh_Hans': 'Chinese (Simplified Han)', u'zh_Hans_CN': 'Chinese (Simplified Han) (China)', u'zh_Hans_HK': 'Chinese (Simplified Han) (Hong Kong SAR China)', u'zh_Hans_MO': 'Chinese (Simplified Han) (Macau SAR China)', u'zh_Hans_SG': 'Chinese (Simplified Han) (Singapore)', u'zh_SG': 'Chinese (Singapore)', u'zh_TW': 'Chinese (Taiwan)', u'zh_Hant': 'Chinese (Traditional Han)', u'zh_Hant_HK': 'Chinese (Traditional Han) (Hong Kong SAR China)', u'zh_Hant_MO': 'Chinese (Traditional Han) (Macau SAR China)', u'zh_Hant_TW': 'Chinese (Traditional Han) (Taiwan)', u'cop': 'Coptic', u'kw': 'Cornish', u'kw_GB': 'Cornish (United Kingdom)', u'hr': 'Croatian', u'hr_HR': 'Croatian (Croatia)', u'cs': 'Czech', u'cs_CZ': 'Czech (Czech Republic)', u'da': 'Danish', u'da_DK': 'Danish (Denmark)', u'dv': 'Divehi', u'dv_MV': 'Divehi (Maldives)', u'nl': 'Dutch', u'nl_BE': 'Dutch (Belgium)', u'nl_NL': 'Dutch (Netherlands)', u'dz': 'Dzongkha', u'dz_BT': 'Dzongkha (Bhutan)', u'en': 'English', u'en_AS': 'English (American Samoa)', u'en_AU': 'English (Australia)', u'en_BE': 'English (Belgium)', u'en_BZ': 'English (Belize)', u'en_BW': 'English (Botswana)', u'en_CA': 'English (Canada)', u'en_Dsrt': 'English (Deseret)', u'en_Dsrt_US': 'English (Deseret) (United States)', u'en_GU': 'English (Guam)', u'en_HK': 'English (Hong Kong SAR China)', u'en_IN': 'English (India)', u'en_IE': 'English (Ireland)', u'en_JM': 'English (Jamaica)', u'en_MT': 'English (Malta)', u'en_MH': 'English (Marshall Islands)', u'en_NA': 'English (Namibia)', u'en_NZ': 'English (New Zealand)', u'en_MP': 'English (Northern Mariana Islands)', u'en_PK': 'English (Pakistan)', u'en_PH': 'English (Philippines)', u'en_Shaw': 'English (Shavian)', u'en_SG': 'English (Singapore)', u'en_ZA': 'English (South Africa)', u'en_TT': 'English (Trinidad and Tobago)', u'en_UM': 'English (U.S. Minor Outlying Islands)', u'en_VI': 'English (U.S. Virgin Islands)', u'en_GB': 'English (United Kingdom)', u'en_US': 'English (United States)', u'en_US_POSIX': 'English (United States) (Computer)', u'en_ZW': 'English (Zimbabwe)', u'eo': 'Esperanto', u'et': 'Estonian', u'et_EE': 'Estonian (Estonia)', u'ee': 'Ewe', u'ee_GH': 'Ewe (Ghana)', u'ee_TG': 'Ewe (Togo)', u'fo': 'Faroese', u'fo_FO': 'Faroese (Faroe Islands)', u'fil': 'Filipino', u'fil_PH': 'Filipino (Philippines)', u'fi': 'Finnish', u'fi_FI': 'Finnish (Finland)', u'fr': 'French', u'fr_BE': 'French (Belgium)', u'fr_CA': 'French (Canada)', u'fr_FR': 'French (France)', u'fr_LU': 'French (Luxembourg)', u'fr_MC': 'French (Monaco)', u'fr_SN': 'French (Senegal)', u'fr_CH': 'French (Switzerland)', u'fur': 'Friulian', u'fur_IT': 'Friulian (Italy)', u'gaa': 'Ga', u'gaa_GH': 'Ga (Ghana)', u'gl': 'Galician', u'gl_ES': 'Galician (Spain)', u'gez': 'Geez', u'gez_ER': 'Geez (Eritrea)', u'gez_ET': 'Geez (Ethiopia)', u'ka': 'Georgian', u'ka_GE': 'Georgian (Georgia)', u'de': 'German', u'de_AT': 'German (Austria)', u'de_BE': 'German (Belgium)', u'de_DE': 'German (Germany)', u'de_LI': 'German (Liechtenstein)', u'de_LU': 'German (Luxembourg)', u'de_CH': 'German (Switzerland)', u'el_POLYTON': 'Greek', u'el': 'Greek', u'el_CY': 'Greek (Cyprus)', u'el_GR': 'Greek (Greece)', u'gu': 'Gujarati', u'gu_IN': 'Gujarati (India)', u'ha': 'Hausa', u'ha_Arab': 'Hausa (Arabic)', u'ha_Arab_NG': 'Hausa (Arabic) (Nigeria)', u'ha_Arab_SD': 'Hausa (Arabic) (Sudan)', u'ha_GH': 'Hausa (Ghana)', u'ha_Latn': 'Hausa (Latin)', u'ha_Latn_GH': 'Hausa (Latin) (Ghana)', u'ha_Latn_NE': 'Hausa (Latin) (Niger)', u'ha_Latn_NG': 'Hausa (Latin) (Nigeria)', u'ha_NE': 'Hausa (Niger)', u'ha_NG': 'Hausa (Nigeria)', u'ha_SD': 'Hausa (Sudan)', u'haw': 'Hawaiian', u'haw_US': 'Hawaiian (United States)', u'he': 'Hebrew', u'he_IL': 'Hebrew (Israel)', u'hi': 'Hindi', u'hi_IN': 'Hindi (India)', u'hu': 'Hungarian', u'hu_HU': 'Hungarian (Hungary)', u'is': 'Icelandic', u'is_IS': 'Icelandic (Iceland)', u'ig': 'Igbo', u'ig_NG': 'Igbo (Nigeria)', u'id': 'Indonesian', u'id_ID': 'Indonesian (Indonesia)', u'ia': 'Interlingua', u'iu': 'Inuktitut', u'ga': 'Irish', u'ga_IE': 'Irish (Ireland)', u'it': 'Italian', u'it_IT': 'Italian (Italy)', u'it_CH': 'Italian (Switzerland)', u'ja': 'Japanese', u'ja_JP': 'Japanese (Japan)', u'kaj': 'Jju', u'kaj_NG': 'Jju Nigeria', u'kl': 'Kalaallisut', u'kl_GL': 'Kalaallisut (Greenland)', u'kam': 'Kamba', u'kam_KE': 'Kamba (Kenya)', u'kn': 'Kannada', u'kn_IN': 'Kannada (India)', u'kk': 'Kazakh', u'kk_Cyrl': 'Kazakh (Cyrillic)', u'kk_Cyrl_KZ': 'Kazakh (Cyrillic) (Kazakhstan)', u'kk_KZ': 'Kazakh (Kazakhstan)', u'km': 'Khmer', u'km_KH': 'Khmer (Cambodia)', u'rw': 'Kinyarwanda', u'rw_RW': 'Kinyarwanda (Rwanda)', u'ky': 'Kirghiz', u'ky_KG': 'Kirghiz (Kyrgyzstan)', u'kok': 'Konkani', u'kok_IN': 'Konkani (India)', u'ko': 'Korean', u'ko_KR': 'Korean (South Korea)', u'kfo': 'Koro', u'kfo_CI': u'Koro (Côte d’Ivoire)', u'kpe': 'Kpelle', u'kpe_GN': 'Kpelle (Guinea)', u'kpe_LR': 'Kpelle (Liberia)', u'ku': 'Kurdish', u'ku_Arab': 'Kurdish (Arabic)', u'ku_Arab_IR': 'Kurdish (Arabic) (Iran)', u'ku_Arab_IQ': 'Kurdish (Arabic) (Iraq)', u'ku_Arab_SY': 'Kurdish (Arabic) (Syria)', u'ku_IR': 'Kurdish (Iran)', u'ku_IQ': 'Kurdish (Iraq)', u'ku_Latn': 'Kurdish (Latin)', u'ku_Latn_TR': 'Kurdish (Latin) (Turkey)', u'ku_SY': 'Kurdish (Syria)', u'ku_TR': 'Kurdish (Turkey)', u'lo': 'Lao', u'lo_LA': 'Lao (Laos)', u'lv': 'Latvian', u'lv_LV': 'Latvian (Latvia)', u'ln': 'Lingala', u'ln_CG': 'Lingala (Congo - Brazzaville)', u'ln_CD': 'Lingala (Congo - Kinshasa)', u'lt': 'Lithuanian', u'lt_LT': 'Lithuanian (Lithuania)', u'nds': 'Low German', u'nds_DE': 'Low German (Germany)', u'mk': 'Macedonian', u'mk_MK': 'Macedonian (Macedonia)', u'ms': 'Malay', u'ms_BN': 'Malay (Brunei)', u'ms_MY': 'Malay (Malaysia)', u'ml': 'Malayalam', u'ml_IN': 'Malayalam (India)', u'mt': 'Maltese', u'mt_MT': 'Maltese (Malta)', u'gv': 'Manx', u'gv_GB': 'Manx (United Kingdom)', u'mr': 'Marathi', u'mr_IN': 'Marathi (India)', u'mo': 'Moldavian', u'mn': 'Mongolian', u'mn_CN': 'Mongolian (China)', u'mn_Cyrl': 'Mongolian (Cyrillic)', u'mn_Cyrl_MN': 'Mongolian (Cyrillic) (Mongolia)', u'mn_MN': 'Mongolian (Mongolia)', u'mn_Mong': 'Mongolian (Mongolian)', u'mn_Mong_CN': 'Mongolian (Mongolian) (China)', u'ne': 'Nepali', u'ne_IN': 'Nepali (India)', u'ne_NP': 'Nepali (Nepal)', u'se': 'Northern Sami', u'se_FI': 'Northern (Sami Finland)', u'se_NO': 'Northern (Sami Norway)', u'nso': 'Northern Sotho', u'nso_ZA': 'Northern Sotho (South Africa)', u'no': 'Norwegian', u'nb': u'Norwegian Bokmål', u'nb_NO': u'Norwegian Bokmål (Norway)', u'nn': 'Norwegian Nynorsk', u'nn_NO': 'Norwegian Nynorsk (Norway)', u'ny': 'Nyanja', u'ny_MW': 'Nyanja (Malawi)', u'oc': 'Occitan', u'oc_FR': 'Occitan (France)', u'or': 'Oriya', u'or_IN': 'Oriya (India)', u'om': 'Oromo', u'om_ET': 'Oromo (Ethiopia)', u'om_KE': 'Oromo (Kenya)', u'ps': 'Pashto', u'ps_AF': 'Pashto (Afghanistan)', u'fa': 'Persian', u'fa_AF': 'Persian (Afghanistan)', u'fa_IR': 'Persian (Iran)', u'pl': 'Polish', u'pl_PL': 'Polish (Poland)', u'pt': 'Portuguese', u'pt_BR': 'Portuguese (Brazil)', u'pt_PT': 'Portuguese (Portugal)', u'pa': 'Punjabi', u'pa_Arab': 'Punjabi (Arabic)', u'pa_Arab_PK': 'Punjabi (Arabic) (Pakistan)', u'pa_Guru': 'Punjabi (Gurmukhi)', u'pa_Guru_IN': 'Punjabi (Gurmukhi) (India)', u'pa_IN': 'Punjabi (India)', u'pa_PK': 'Punjabi (Pakistan)', u'ro': 'Romanian', u'ro_MD': 'Romanian (Moldova)', u'ro_RO': 'Romanian (Romania)', u'ru': 'Russian', u'ru_RU': 'Russian (Russia)', u'ru_UA': 'Russian (Ukraine)', u'sa': 'Sanskrit', u'sa_IN': 'Sanskrit (India)', u'sr_YU': 'Serbian', u'sr': 'Serbian', u'sr_BA': 'Serbian (Bosnia and Herzegovina)', u'sr_Cyrl': 'Serbian (Cyrillic)', u'sr_Cyrl_YU': 'Serbian (Cyrillic)', u'sr_Cyrl_BA': 'Serbian (Cyrillic) (Bosnia and Herzegovina)', u'sr_Cyrl_ME': 'Serbian (Cyrillic) (Montenegro)', u'sr_Cyrl_RS': 'Serbian (Cyrillic) (Serbia)', u'sr_Cyrl_CS': 'Serbian (Cyrillic) (Serbia and Montenegro)', u'sr_Latn': 'Serbian (Latin)', u'sr_Latn_YU': 'Serbian (Latin)', u'sr_Latn_BA': 'Serbian (Latin) (Bosnia and Herzegovina)', u'sr_Latn_ME': 'Serbian (Latin) (Montenegro)', u'sr_Latn_RS': 'Serbian (Latin) (Serbia)', u'sr_Latn_CS': 'Serbian (Latin) (Serbia and Montenegro)', u'sr_ME': 'Serbian (Montenegro)', u'sr_RS': 'Serbian (Serbia)', u'sr_CS': 'Serbian (Serbia and Montenegro)', u'sh': 'Serbo-Croatian', u'sh_YU': 'Serbo-Croatian', u'sh_BA': 'Serbo-Croatian (Bosnia and Herzegovina)', u'sh_CS': 'Serbo-Croatian (Serbia and Montenegro)', u'ii': 'Sichuan Yi', u'ii_CN': 'Sichuan (Yi China)', u'sid': 'Sidamo', u'sid_ET': 'Sidamo (Ethiopia)', u'si': 'Sinhala', u'si_LK': 'Sinhala (Sri Lanka)', u'sk': 'Slovak', u'sk_SK': 'Slovak (Slovakia)', u'sl': 'Slovenian', u'sl_SI': 'Slovenian (Slovenia)', u'so': 'Somali', u'so_DJ': 'Somali (Djibouti)', u'so_ET': 'Somali (Ethiopia)', u'so_KE': 'Somali (Kenya)', u'so_SO': 'Somali (Somalia)', u'nr': 'South Ndebele', u'nr_ZA': 'South Ndebele (South Africa)', u'st': 'Southern Sotho', u'st_LS': 'Southern Sotho (Lesotho)', u'st_ZA': 'Southern Sotho (South Africa)', u'es': 'Spanish', u'es_AR': 'Spanish (Argentina)', u'es_BO': 'Spanish (Bolivia)', u'es_CL': 'Spanish (Chile)', u'es_CO': 'Spanish (Colombia)', u'es_CR': 'Spanish (Costa Rica)', u'es_DO': 'Spanish (Dominican Republic)', u'es_EC': 'Spanish (Ecuador)', u'es_SV': 'Spanish (El Salvador)', u'es_GT': 'Spanish (Guatemala)', u'es_HN': 'Spanish (Honduras)', u'es_MX': 'Spanish (Mexico)', u'es_NI': 'Spanish (Nicaragua)', u'es_PA': 'Spanish (Panama)', u'es_PY': 'Spanish (Paraguay)', u'es_PE': 'Spanish (Peru)', u'es_PR': 'Spanish (Puerto Rico)', u'es_ES': 'Spanish (Spain)', u'es_US': 'Spanish (United States)', u'es_UY': 'Spanish (Uruguay)', u'es_VE': 'Spanish (Venezuela)', u'sw': 'Swahili', u'sw_KE': 'Swahili (Kenya)', u'sw_TZ': 'Swahili (Tanzania)', u'ss': 'Swati', u'ss_ZA': 'Swati (South Africa)', u'ss_SZ': 'Swati (Swaziland)', u'sv': 'Swedish', u'sv_FI': 'Swedish (Finland)', u'sv_SE': 'Swedish (Sweden)', u'gsw': 'Swiss German', u'gsw_CH': 'Swiss German (Switzerland)', u'syr': 'Syriac', u'syr_SY': 'Syriac (Syria)', u'tl': 'Tagalog', u'tg': 'Tajik', u'tg_Cyrl': 'Tajik (Cyrillic)', u'tg_Cyrl_TJ': 'Tajik (Cyrillic) (Tajikistan)', u'tg_TJ': 'Tajik (Tajikistan)', u'ta': 'Tamil', u'ta_IN': 'Tamil (India)', u'trv': 'Taroko', u'trv_TW': 'Taroko (Taiwan)', u'tt': 'Tatar', u'tt_RU': 'Tatar (Russia)', u'te': 'Telugu', u'te_IN': 'Telugu (India)', u'th': 'Thai', u'th_TH': 'Thai (Thailand)', u'bo': 'Tibetan', u'bo_CN': 'Tibetan (China)', u'bo_IN': 'Tibetan (India)', u'tig': 'Tigre', u'tig_ER': 'Tigre (Eritrea)', u'ti': 'Tigrinya', u'ti_ER': 'Tigrinya (Eritrea)', u'ti_ET': 'Tigrinya (Ethiopia)', u'to': 'Tonga', u'to_TO': 'Tonga (Tonga)', u'ts': 'Tsonga', u'ts_ZA': 'Tsonga (South Africa)', u'tn': 'Tswana', u'tn_ZA': 'Tswana (South Africa)', u'tr': 'Turkish', u'tr_TR': 'Turkish (Turkey)', u'kcg': 'Tyap', u'kcg_NG': 'Tyap (Nigeria)', u'ug': 'Uighur', u'ug_Arab': 'Uighur (Arabic)', u'ug_Arab_CN': 'Uighur (Arabic) (China)', u'ug_CN': 'Uighur (China)', u'uk': 'Ukrainian', u'uk_UA': 'Ukrainian (Ukraine)', u'ur': 'Urdu', u'ur_IN': 'Urdu (India)', u'ur_PK': 'Urdu (Pakistan)', u'uz': 'Uzbek', u'uz_AF': 'Uzbek (Afghanistan)', u'uz_Arab': 'Uzbek (Arabic)', u'uz_Arab_AF': 'Uzbek (Arabic) (Afghanistan)', u'uz_Cyrl': 'Uzbek (Cyrillic)', u'uz_Cyrl_UZ': 'Uzbek (Cyrillic) (Uzbekistan)', u'uz_Latn': 'Uzbek (Latin)', u'uz_Latn_UZ': 'Uzbek (Latin) (Uzbekistan)', u'uz_UZ': 'Uzbek (Uzbekistan)', u've': 'Venda', u've_ZA': 'Venda (South Africa)', u'vi': 'Vietnamese', u'vi_VN': 'Vietnamese (Vietnam)', u'wal': 'Walamo', u'wal_ET': 'Walamo (Ethiopia)', u'cy': 'Welsh', u'cy_GB': 'Welsh (United Kingdom)', u'wo': 'Wolof', u'wo_Latn': 'Wolof (Latin)', u'wo_Latn_SN': 'Wolof (Latin) (Senegal)', u'wo_SN': 'Wolof (Senegal)', u'xh': 'Xhosa', u'xh_ZA': 'Xhosa (South Africa)', u'yo': 'Yoruba', u'yo_NG': 'Yoruba (Nigeria)', u'zu': 'Zulu', u'zu_ZA': 'Zulu (South Africa)', } picard-release-1.4.2/picard/coverart/000077500000000000000000000000001310410472100174565ustar00rootroot00000000000000picard-release-1.4.2/picard/coverart/__init__.py000066400000000000000000000206641310410472100215770ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Oliver Charles # Copyright (C) 2007-2011 Philipp Wolfer # Copyright (C) 2007, 2010, 2011 Lukáš Lalinský # Copyright (C) 2011 Michael Wiencek # Copyright (C) 2011-2012 Wieland Hoffmann # Copyright (C) 2013-2014 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import traceback from picard.coverart.providers import cover_art_providers, CoverArtProvider from functools import partial from picard import config, log from picard.coverart.image import (CoverArtImageIOError, CoverArtImageIdentificationError) from PyQt4.QtCore import QObject class CoverArt: def __init__(self, album, metadata, release): self._queue_new() self.album = album self.metadata = metadata self.release = release self.front_image_found = False def __repr__(self): return "CoverArt for %r" % (self.album) def retrieve(self): """Retrieve available cover art images for the release""" if (not config.setting["save_images_to_tags"] and not config.setting["save_images_to_files"]): log.debug("Cover art disabled by user options.") return self.providers = cover_art_providers() self.next_in_queue() def _set_metadata(self, coverartimage, data): try: coverartimage.set_data(data) if coverartimage.can_be_saved_to_metadata: log.debug("Cover art image stored to metadata: %r [%s]" % ( coverartimage, coverartimage.imageinfo_as_string()) ) self.metadata.append_image(coverartimage) for track in self.album._new_tracks: track.metadata.append_image(coverartimage) # If the image already was a front image, # there might still be some other non-CAA front # images in the queue - ignore them. if not self.front_image_found: self.front_image_found = coverartimage.is_front_image() else: log.debug("Thumbnail for cover art image: %r [%s]" % ( coverartimage, coverartimage.imageinfo_as_string()) ) except CoverArtImageIOError as e: self.album.error_append(unicode(e)) self.album._finalize_loading(error=True) raise e except CoverArtImageIdentificationError as e: self.album.error_append(unicode(e)) def _coverart_downloaded(self, coverartimage, data, http, error): """Handle finished download, save it to metadata""" self.album._requests -= 1 if error: self.album.error_append(u'Coverart error: %s' % (unicode(http.errorString()))) elif len(data) < 1000: log.warning("Not enough data, skipping %s" % coverartimage) else: self._message( N_("Cover art of type '%(type)s' downloaded for %(albumid)s from %(host)s"), { 'type': coverartimage.types_as_string(), 'albumid': self.album.id, 'host': coverartimage.host }, echo=None ) try: self._set_metadata(coverartimage, data) except CoverArtImageIOError: # It doesn't make sense to store/download more images if we can't # save them in the temporary folder, abort. return self.next_in_queue() def next_in_queue(self): """Downloads next item in queue. If there are none left, loading of album will be finalized. """ if self.album.id not in self.album.tagger.albums: # album removed return if (self.front_image_found and config.setting["save_images_to_tags"] and not config.setting["save_images_to_files"] and config.setting["embed_only_one_front_image"]): # no need to continue self.album._finalize_loading(None) return if self._queue_empty(): if self.providers: # requeue from next provider provider = self.providers.pop(0) ret = CoverArtProvider._STARTED try: p = provider(self) if p.enabled(): log.debug("Trying cover art provider %s ..." % provider.NAME) ret = p.queue_images() else: log.debug("Skipping cover art provider %s ..." % provider.NAME) except: log.error(traceback.format_exc()) raise finally: if ret != CoverArtProvider.WAIT: self.next_in_queue() return else: # nothing more to do self.album._finalize_loading(None) return # We still have some items to try! coverartimage = self._queue_get() if not coverartimage.support_types and self.front_image_found: # we already have one front image, no need to try other type-less # sources log.debug("Skipping %r, one front image is already available", coverartimage) self.next_in_queue() return # local files if hasattr(coverartimage, 'filepath'): try: with open(coverartimage.filepath, 'rb') as file: self._set_metadata(coverartimage, file.read()) except IOError as ioexcept: (errnum, errmsg) = ioexcept.args log.error("Failed to read %r: %s (%d)" % (coverartimage.from_file, errmsg, errnum)) except CoverArtImageIOError: # It doesn't make sense to store/download more images if we can't # save them in the temporary folder, abort. return self.next_in_queue() return # on the web self._message( N_("Downloading cover art of type '%(type)s' for %(albumid)s from %(host)s ..."), { 'type': coverartimage.types_as_string(), 'albumid': self.album.id, 'host': coverartimage.host }, echo=None ) log.debug("Downloading %r" % coverartimage) self.album.tagger.xmlws.download( coverartimage.host, coverartimage.port, coverartimage.path, partial(self._coverart_downloaded, coverartimage), priority=True, important=False ) self.album._requests += 1 def queue_put(self, coverartimage): "Add an image to queue" log.debug("Queuing cover art image %r", coverartimage) self.__queue.append(coverartimage) def _queue_get(self): "Get next image and remove it from queue" return self.__queue.pop(0) def _queue_empty(self): "Returns True if the queue is empty" return not self.__queue def _queue_new(self): "Initialize the queue" self.__queue = [] def _message(self, *args, **kwargs): """Display message to status bar""" QObject.tagger.window.set_statusbar_message(*args, **kwargs) def coverart(album, metadata, release): """Gets all cover art URLs from the metadata and then attempts to download the album art. """ coverart = CoverArt(album, metadata, release) log.debug("New %r", coverart) coverart.retrieve() picard-release-1.4.2/picard/coverart/image.py000066400000000000000000000362121310410472100211160ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Oliver Charles # Copyright (C) 2007-2011 Philipp Wolfer # Copyright (C) 2007, 2010, 2011 Lukáš Lalinský # Copyright (C) 2011 Michael Wiencek # Copyright (C) 2011-2012 Wieland Hoffmann # Copyright (C) 2013-2014 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import shutil import sys import tempfile from hashlib import md5 from PyQt4.QtCore import QUrl, QObject, QMutex from picard import config, log from picard.coverart.utils import translate_caa_type from picard.script import ScriptParser from picard.util import ( encode_filename, replace_win32_incompat, imageinfo ) from picard.util.textencoding import ( replace_non_ascii, unaccent, ) _datafiles = dict() _datafile_mutex = QMutex(QMutex.Recursive) class DataHash: def __init__(self, data, prefix='picard', suffix=''): self._filename = None _datafile_mutex.lock() try: m = md5() m.update(data) self._hash = m.hexdigest() if self._hash not in _datafiles: (fd, self._filename) = tempfile.mkstemp(prefix=prefix, suffix=suffix) QObject.tagger.register_cleanup(self.delete_file) with os.fdopen(fd, "wb") as imagefile: imagefile.write(data) _datafiles[self._hash] = self._filename log.debug("Saving image data %s to %r" % (self._hash, self._filename)) else: self._filename = _datafiles[self._hash] finally: _datafile_mutex.unlock() def __eq__(self, other): return self._hash == other._hash def hash(self): return self._hash def delete_file(self): if self._filename: try: os.unlink(self._filename) except: pass else: _datafile_mutex.lock() try: self._filename = None del _datafiles[self._hash] self._hash = None finally: _datafile_mutex.unlock() @property def data(self): if self._filename: with open(self._filename, "rb") as imagefile: return imagefile.read() return None @property def filename(self): return self._filename class CoverArtImageError(Exception): pass class CoverArtImageIOError(CoverArtImageError): pass class CoverArtImageIdentificationError(CoverArtImageError): pass class CoverArtImage: # Indicate if types are provided by the source, ie. CAA or certain file # formats may have types associated with cover art, but some other sources # don't provide such information support_types = False # `is_front` has to be explicitly set, it is used to handle CAA is_front # indicator is_front = None sourceprefix = "URL" def __init__(self, url=None, types=[], comment='', data=None): if url is not None: self.parse_url(url) else: self.url = None self.types = types self.comment = comment self.datahash = None # thumbnail is used to link to another CoverArtImage, ie. for PDFs self.thumbnail = None self.can_be_saved_to_tags = True self.can_be_saved_to_disk = True self.can_be_saved_to_metadata = True if data is not None: self.set_data(data) def parse_url(self, url): self.url = QUrl(url) self.host = str(self.url.host()) self.port = self.url.port(80) self.path = str(self.url.encodedPath()) if self.url.hasQuery(): self.path += '?' + str(self.url.encodedQuery()) @property def source(self): if self.url is not None: return u"%s: %s" % (self.sourceprefix, self.url.toString()) else: return u"%s" % self.sourceprefix def is_front_image(self): """Indicates if image is considered as a 'front' image. It depends on few things: - if `is_front` was set, it is used over anything else - if `types` was set, search for 'front' in it - if `support_types` is False, default to True for any image - if `support_types` is True, default to False for any image """ if not self.can_be_saved_to_metadata: # ignore thumbnails return False if self.is_front is not None: return self.is_front if u'front' in self.types: return True return (self.support_types is False) def imageinfo_as_string(self): if self.datahash is None: return "" return "w=%d h=%d mime=%s ext=%s datalen=%d file=%s" % (self.width, self.height, self.mimetype, self.extension, self.datalength, self.tempfile_filename) def __repr__(self): p = [] if self.url is not None: p.append("url=%r" % self.url.toString()) if self.types: p.append("types=%r" % self.types) if self.is_front is not None: p.append("is_front=%r" % self.is_front) if self.comment: p.append("comment=%r" % self.comment) return "%s(%s)" % (self.__class__.__name__, ", ".join(p)) def __unicode__(self): p = [u'Image'] if self.url is not None: p.append(u"from %s" % self.url.toString()) if self.types: p.append(u"of type %s" % u','.join(self.types)) if self.comment: p.append(u"and comment '%s'" % self.comment) return u' '.join(p) def __str__(self): return unicode(self).encode('utf-8') def __eq__(self, other): if self and other: if self.types and other.types: return (self.datahash, self.types) == (other.datahash, other.types) else: return self.datahash == other.datahash elif not self and not other: return True else: return False def __hash__(self): if self.datahash is None: return 0 return hash(self.datahash.hash()) def set_data(self, data): """Store image data in a file, if data already exists in such file it will be re-used and no file write occurs """ if self.datahash: self.datahash.delete_file() self.datahash = None try: (self.width, self.height, self.mimetype, self.extension, self.datalength) = imageinfo.identify(data) except imageinfo.IdentificationError as e: raise CoverArtImageIdentificationError(e) try: self.datahash = DataHash(data, suffix=self.extension) except (OSError, IOError) as e: raise CoverArtImageIOError(e) @property def maintype(self): """Returns one type only, even for images having more than one type set. This is mostly used when saving cover art to tags because most formats don't support multiple types for one image. Images coming from CAA can have multiple types (ie. 'front, booklet'). """ if self.is_front_image() or not self.types or u'front' in self.types: return u'front' # TODO: do something better than randomly using the first in the list return self.types[0] def _make_image_filename(self, filename, dirname, metadata): filename = ScriptParser().eval(filename, metadata) if config.setting["ascii_filenames"]: if isinstance(filename, unicode): filename = unaccent(filename) filename = replace_non_ascii(filename) if not filename: filename = "cover" if not os.path.isabs(filename): filename = os.path.join(dirname, filename) # replace incompatible characters if config.setting["windows_compatibility"] or sys.platform == "win32": filename = replace_win32_incompat(filename) # remove null characters filename = filename.replace("\x00", "") return encode_filename(filename) def save(self, dirname, metadata, counters): """Saves this image. :dirname: The name of the directory that contains the audio file :metadata: A metadata object :counters: A dictionary mapping filenames to the amount of how many images with that filename were already saved in `dirname`. """ if not self.can_be_saved_to_disk: return if (config.setting["caa_image_type_as_filename"] and not self.is_front_image()): filename = self.maintype log.debug("Make cover filename from types: %r -> %r", self.types, filename) else: filename = config.setting["cover_image_filename"] log.debug("Using default cover image filename %r", filename) filename = self._make_image_filename(filename, dirname, metadata) overwrite = config.setting["save_images_overwrite"] ext = self.extension image_filename = self._next_filename(filename, counters) while os.path.exists(image_filename + ext) and not overwrite: if not self._is_write_needed(image_filename + ext): break image_filename = self._next_filename(filename, counters) else: new_filename = image_filename + ext # Even if overwrite is enabled we don't need to write the same # image multiple times if not self._is_write_needed(new_filename): return log.debug("Saving cover image to %r", new_filename) try: new_dirname = os.path.dirname(new_filename) if not os.path.isdir(new_dirname): os.makedirs(new_dirname) shutil.copyfile(self.tempfile_filename, new_filename) except (OSError, IOError) as e: raise CoverArtImageIOError(e) def _next_filename(self, filename, counters): if counters[filename]: new_filename = "%s (%d)" % (filename, counters[filename]) else: new_filename = filename counters[filename] += 1 return new_filename def _is_write_needed(self, filename): if (os.path.exists(filename) and os.path.getsize(filename) == self.datalength): log.debug("Identical file size, not saving %r", filename) return False return True @property def data(self): """Reads the data from the temporary file created for this image. May raise CoverArtImageIOError """ try: return self.datahash.data except (OSError, IOError) as e: raise CoverArtImageIOError(e) @property def tempfile_filename(self): return self.datahash.filename def types_as_string(self, translate=True, separator=', '): if self.types: types = self.types elif self.is_front_image(): types = [u'front'] else: types = [u'-'] if translate: types = [translate_caa_type(type) for type in types] return separator.join(types) class CaaCoverArtImage(CoverArtImage): """Image from Cover Art Archive""" support_types = True sourceprefix = u"CAA" def __init__(self, url, types=[], is_front=False, comment='', data=None): CoverArtImage.__init__(self, url=url, types=types, comment=comment, data=data) self.is_front = is_front class CaaThumbnailCoverArtImage(CaaCoverArtImage): """Used for thumbnails of CaaCoverArtImage objects, together with thumbnail property""" def __init__(self, url, types=[], is_front=False, comment='', data=None): CaaCoverArtImage.__init__(self, url=url, types=types, comment=comment, data=data) self.is_front = False self.can_be_saved_to_disk = False self.can_be_saved_to_tags = False self.can_be_saved_to_metadata = False class TagCoverArtImage(CoverArtImage): """Image from file tags""" def __init__(self, file, tag=None, types=[], is_front=None, support_types=False, comment='', data=None): CoverArtImage.__init__(self, url=None, types=types, comment=comment, data=data) self.sourcefile = file self.tag = tag self.support_types = support_types if is_front is not None: self.is_front = is_front @property def source(self): if self.tag: return u'Tag %s from %s' % (self.tag, self.sourcefile) else: return u'File %s' % (self.sourcefile) def __repr__(self): p = [] p.append('%r' % self.sourcefile) if self.tag is not None: p.append("tag=%r" % self.tag) if self.types: p.append("types=%r" % self.types) if self.is_front is not None: p.append("is_front=%r" % self.is_front) p.append('support_types=%r' % self.support_types) if self.comment: p.append("comment=%r" % self.comment) return "%s(%s)" % (self.__class__.__name__, ", ".join(p)) class CoverArtImageFromFile(CoverArtImage): sourceprefix = 'LOCAL' def __init__(self, filepath, types=[], is_front=None, support_types=False, comment='', data=None): CoverArtImage.__init__(self, url=None, types=types, comment=comment, data=data) self.filepath = filepath self.support_types = support_types if is_front is not None: self.is_front = is_front @property def source(self): return u'%s %s' % (self.sourceprefix, self.filepath) def __repr__(self): p = [] p.append('%r' % self.filepath) if self.types: p.append("types=%r" % self.types) if self.is_front is not None: p.append("is_front=%r" % self.is_front) p.append('support_types=%r' % self.support_types) if self.comment: p.append("comment=%r" % self.comment) return "%s(%s)" % (self.__class__.__name__, ", ".join(p)) picard-release-1.4.2/picard/coverart/providers/000077500000000000000000000000001310410472100214735ustar00rootroot00000000000000picard-release-1.4.2/picard/coverart/providers/__init__.py000066400000000000000000000146351310410472100236150ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2014 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import traceback from picard import log, config from picard.plugin import ExtensionPoint from picard.ui.options import OptionsPage, register_options_page _cover_art_providers = ExtensionPoint() class ProviderOptions(OptionsPage): """ Template class for provider's options It works like OptionsPage for the most (options, load, save) It will append the provider's options page as a child of the main cover art's options page. The property _options_ui must be set to a valid Qt Ui class containing the layout and widgets for defined provider's options. A specific provider class (inhereting from CoverArtProvider) has to set the subclassed ProviderOptions as OPTIONS property. Options will be registered at the same time as the provider. class MyProviderOptions(ProviderOptions): _options_ui = Ui_MyProviderOptions .... class MyProvider(CoverArtProvider): OPTIONS = ProviderOptionsMyProvider .... """ PARENT = "cover" def __init__(self, parent=None): super(ProviderOptions, self).__init__(parent) self.ui = self._options_ui() self.ui.setupUi(self) def register_cover_art_provider(provider): _cover_art_providers.register(provider.__module__, provider) if hasattr(provider, 'OPTIONS') and provider.OPTIONS: provider.OPTIONS.NAME = provider.NAME provider.OPTIONS.TITLE = provider.TITLE or provider.NAME register_options_page(provider.OPTIONS) def cover_art_providers(): order = [p[0] for p in config.setting['ca_providers']] def _key_provider(p): try: return order.index(p.NAME) except ValueError: return 666 # move to the end providers = [] for p in sorted(_cover_art_providers, key=_key_provider): providers.append(p) log.debug("CA Providers order: %s", ' > '.join([p.NAME for p in providers])) return providers def is_provider_enabled(provider_name): """Test if provider with name `provider_name` was enabled by user through options""" for name, checked in config.setting['ca_providers']: if name == provider_name: return checked return False class CoverArtProvider(object): """Subclasses of this class need to reimplement at least `queue_images()`. `__init__()` does not have to do anything. `queue_images()` will be called if `enabled()` returns `True`. `queue_images()` must return `FINISHED` when it finished to queue potential cover art downloads (using `queue_put(). If `queue_images()` delegates the job of queuing downloads to another method (asynchronous) it should return `WAIT` and the other method has to explicitly call `next_in_queue()`. If `FINISHED` is returned, `next_in_queue()` will be automatically called by CoverArt object. """ # default state, internal use _STARTED = 0 # returned by queue_images(): # next_in_queue() will be automatically called FINISHED = 1 # returned by queue_images(): # next_in_queue() has to be called explicitly by provider WAIT = 2 def __init__(self, coverart): self.coverart = coverart self.release = coverart.release self.metadata = coverart.metadata self.album = coverart.album def enabled(self): """By default, return True if user enabled the provider through options. It is used when iterating through providers to decide to skip or process one. It can be subclassed to add conditions.""" enabled = is_provider_enabled(self.NAME) if not enabled: log.debug("%s disabled by user" % self.NAME) return enabled def queue_images(self): # this method has to return CoverArtProvider.FINISHED or # CoverArtProvider.WAIT old = getattr(self, 'queue_downloads') #compat with old plugins if callable(old): log.warning('CoverArtProvider: queue_downloads() was replaced by queue_images()') return old() else: raise NotImplementedError def error(self, msg): self.coverart.album.error_append(msg) def queue_put(self, what): self.coverart.queue_put(what) def next_in_queue(self): # must be called by provider if queue_images() returns WAIT self.coverart.next_in_queue() def match_url_relations(self, relation_types, func): """Execute `func` for each relation url matching type in `relation_types` """ try: if 'relation_list' in self.release.children: for relation_list in self.release.relation_list: if relation_list.target_type == 'url': for relation in relation_list.relation: if relation.type in relation_types: func(relation.target[0].text) except AttributeError: self.error(traceback.format_exc()) from picard.coverart.providers.local import CoverArtProviderLocal from picard.coverart.providers.caa import CoverArtProviderCaa from picard.coverart.providers.amazon import CoverArtProviderAmazon from picard.coverart.providers.whitelist import CoverArtProviderWhitelist from picard.coverart.providers.caa_release_group import CoverArtProviderCaaReleaseGroup __providers = [ CoverArtProviderLocal, CoverArtProviderCaa, CoverArtProviderAmazon, CoverArtProviderWhitelist, CoverArtProviderCaaReleaseGroup, ] for provider in __providers: register_cover_art_provider(provider) picard-release-1.4.2/picard/coverart/providers/amazon.py000066400000000000000000000100731310410472100233330ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Oliver Charles # Copyright (C) 2007-2011 Philipp Wolfer # Copyright (C) 2007, 2010, 2011 Lukáš Lalinský # Copyright (C) 2011 Michael Wiencek # Copyright (C) 2011-2012 Wieland Hoffmann # Copyright (C) 2013-2014 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import traceback from picard import config, log from picard.util import parse_amazon_url from picard.coverart.providers import CoverArtProvider from picard.coverart.image import CoverArtImage # amazon image file names are unique on all servers and constructed like # ..[SML]ZZZZZZZ.jpg # A release sold on amazon.de has always = 03, for example. # Releases not sold on amazon.com, don't have a "01"-version of the image, # so we need to make sure we grab an existing image. AMAZON_SERVER = { "amazon.jp": { "server": "ec1.images-amazon.com", "id": "09", }, "amazon.co.jp": { "server": "ec1.images-amazon.com", "id": "09", }, "amazon.co.uk": { "server": "ec1.images-amazon.com", "id": "02", }, "amazon.de": { "server": "ec2.images-amazon.com", "id": "03", }, "amazon.com": { "server": "ec1.images-amazon.com", "id": "01", }, "amazon.ca": { "server": "ec1.images-amazon.com", "id": "01", # .com and .ca are identical }, "amazon.fr": { "server": "ec1.images-amazon.com", "id": "08" }, } AMAZON_IMAGE_PATH = '/images/P/%(asin)s.%(serverid)s.%(size)s.jpg' # First item in the list will be tried first AMAZON_SIZES = ( # huge size option is only available for items # that have a ZOOMing picture on its amazon web page # and it doesn't work for all of the domain names #'_SCRM_', # huge size 'LZZZZZZZ', # large size, option format 1 #'_SCLZZZZZZZ_', # large size, option format 3 'MZZZZZZZ', # default image size, format 1 #'_SCMZZZZZZZ_', # medium size, option format 3 #'TZZZZZZZ', # medium image size, option format 1 #'_SCTZZZZZZZ_', # small size, option format 3 #'THUMBZZZ', # small size, option format 1 ) class CoverArtProviderAmazon(CoverArtProvider): """Use Amazon ASIN Musicbrainz relationships to get cover art""" NAME = "Amazon" TITLE = N_(u'Amazon') def enabled(self): return (super(CoverArtProviderAmazon, self).enabled() and not self.coverart.front_image_found) def queue_images(self): self.match_url_relations(('amazon asin', 'has_Amazon_ASIN'), self._queue_from_asin_relation) return CoverArtProvider.FINISHED def _queue_from_asin_relation(self, url): """Queue cover art images from Amazon""" amz = parse_amazon_url(url) if amz is None: return log.debug("Found ASIN relation : %s %s", amz['host'], amz['asin']) if amz['host'] in AMAZON_SERVER: serverInfo = AMAZON_SERVER[amz['host']] else: serverInfo = AMAZON_SERVER['amazon.com'] host = serverInfo['server'] for size in AMAZON_SIZES: path = AMAZON_IMAGE_PATH % { 'asin': amz['asin'], 'serverid': serverInfo['id'], 'size': size } self.queue_put(CoverArtImage("http://%s%s" % (host, path))) picard-release-1.4.2/picard/coverart/providers/caa.py000066400000000000000000000335541310410472100226030ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Oliver Charles # Copyright (C) 2007-2011 Philipp Wolfer # Copyright (C) 2007, 2010, 2011 Lukáš Lalinský # Copyright (C) 2011 Michael Wiencek # Copyright (C) 2011-2012 Wieland Hoffmann # Copyright (C) 2013-2014 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import json import traceback from PyQt4 import QtCore, QtGui from PyQt4.QtNetwork import QNetworkReply from picard import config, log from picard.const import CAA_HOST, CAA_PORT from picard.coverart.providers import CoverArtProvider, ProviderOptions from picard.coverart.image import CaaCoverArtImage, CaaThumbnailCoverArtImage from picard.coverart.utils import CAA_TYPES, translate_caa_type from picard.ui.ui_provider_options_caa import Ui_CaaOptions from picard.ui.util import StandardButton from picard.util import webbrowser2 from picard.ui.options import OptionsPage, OptionsCheckError, register_options_page _CAA_THUMBNAIL_SIZE_MAP = { 0: "small", 1: "large", } class CAATypesSelectorDialog(QtGui.QDialog): _columns = 4 def __init__(self, parent=None, types=[]): super(CAATypesSelectorDialog, self).__init__(parent) self.setWindowTitle(_("Cover art types")) self._items = {} self.layout = QtGui.QVBoxLayout(self) grid = QtGui.QWidget() gridlayout = QtGui.QGridLayout() grid.setLayout(gridlayout) for index, caa_type in enumerate(CAA_TYPES): row = index // self._columns column = index % self._columns name = caa_type["name"] text = translate_caa_type(name) item = QtGui.QCheckBox(text) item.setChecked(name in types) self._items[item] = caa_type gridlayout.addWidget(item, row, column) self.layout.addWidget(grid) self.buttonbox = QtGui.QDialogButtonBox(self) self.buttonbox.setOrientation(QtCore.Qt.Horizontal) self.buttonbox.addButton( StandardButton(StandardButton.OK), QtGui.QDialogButtonBox.AcceptRole) self.buttonbox.addButton(StandardButton(StandardButton.CANCEL), QtGui.QDialogButtonBox.RejectRole) self.buttonbox.addButton( StandardButton(StandardButton.HELP), QtGui.QDialogButtonBox.HelpRole) self.buttonbox.accepted.connect(self.accept) self.buttonbox.rejected.connect(self.reject) self.buttonbox.helpRequested.connect(self.help) extrabuttons = [ (N_("Chec&k all"), self.checkall), (N_("&Uncheck all"), self.uncheckall), ] for label, callback in extrabuttons: button = QtGui.QPushButton(_(label)) button.setSizePolicy( QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Expanding) self.buttonbox.addButton(button, QtGui.QDialogButtonBox.ActionRole) button.clicked.connect(callback) self.layout.addWidget(self.buttonbox) self.buttonbox.accepted.connect(self.accept) self.buttonbox.rejected.connect(self.reject) def help(self): webbrowser2.goto('doc_cover_art_types') def uncheckall(self): self._set_checked_all(False) def checkall(self): self._set_checked_all(True) def _set_checked_all(self, value): for item in self._items.keys(): item.setChecked(value) def get_selected_types(self): types = [] for item, typ in self._items.iteritems(): if item.isChecked(): types.append(typ['name']) if not types: return [u'front'] return types @staticmethod def run(parent=None, types=[]): dialog = CAATypesSelectorDialog(parent, types) result = dialog.exec_() return (dialog.get_selected_types(), result == QtGui.QDialog.Accepted) class ProviderOptionsCaa(ProviderOptions): """ Options for Cover Art Archive cover art provider """ options = [ config.BoolOption("setting", "caa_save_single_front_image", False), config.BoolOption("setting", "caa_approved_only", False), config.BoolOption("setting", "caa_image_type_as_filename", False), config.IntOption("setting", "caa_image_size", 1), config.ListOption("setting", "caa_image_types", [u"front"]), config.BoolOption("setting", "caa_restrict_image_types", True), ] _options_ui = Ui_CaaOptions def __init__(self, parent=None): super(ProviderOptionsCaa, self).__init__(parent) self.ui.restrict_images_types.clicked.connect(self.update_caa_types) self.ui.select_caa_types.clicked.connect(self.select_caa_types) def load(self): self.ui.cb_image_size.setCurrentIndex(config.setting["caa_image_size"]) self.ui.cb_save_single_front_image.setChecked(config.setting["caa_save_single_front_image"]) self.ui.cb_approved_only.setChecked(config.setting["caa_approved_only"]) self.ui.cb_type_as_filename.setChecked(config.setting["caa_image_type_as_filename"]) self.ui.restrict_images_types.setChecked( config.setting["caa_restrict_image_types"]) self.update_caa_types() def save(self): config.setting["caa_image_size"] = \ self.ui.cb_image_size.currentIndex() config.setting["caa_save_single_front_image"] = \ self.ui.cb_save_single_front_image.isChecked() config.setting["caa_approved_only"] = \ self.ui.cb_approved_only.isChecked() config.setting["caa_image_type_as_filename"] = \ self.ui.cb_type_as_filename.isChecked() config.setting["caa_restrict_image_types"] = \ self.ui.restrict_images_types.isChecked() def update_caa_types(self): enabled = self.ui.restrict_images_types.isChecked() self.ui.select_caa_types.setEnabled(enabled) def select_caa_types(self): (types, ok) = CAATypesSelectorDialog.run( self, config.setting["caa_image_types"]) if ok: config.setting["caa_image_types"] = types class CoverArtProviderCaa(CoverArtProvider): """Get cover art from Cover Art Archive using release mbid""" NAME = "Cover Art Archive" TITLE = N_(u'Cover Art Archive') OPTIONS = ProviderOptionsCaa ignore_json_not_found_error = False coverartimage_class = CaaCoverArtImage coverartimage_thumbnail_class = CaaThumbnailCoverArtImage def __init__(self, coverart): CoverArtProvider.__init__(self, coverart) self.caa_types = map(unicode.lower, config.setting["caa_image_types"]) self.len_caa_types = len(self.caa_types) self.restrict_types = config.setting["caa_restrict_image_types"] @property def _has_suitable_artwork(self): # MB web service indicates if CAA has artwork # https://tickets.metabrainz.org/browse/MBS-4536 if 'cover_art_archive' not in self.release.children: log.debug("No Cover Art Archive information for %s" % self.release.id) return False caa_node = self.release.children['cover_art_archive'][0] caa_has_suitable_artwork = caa_node.artwork[0].text == 'true' if not caa_has_suitable_artwork: log.debug("There are no images in the Cover Art Archive for %s" % self.release.id) return False if self.restrict_types: want_front = 'front' in self.caa_types want_back = 'back' in self.caa_types caa_has_front = caa_node.front[0].text == 'true' caa_has_back = caa_node.back[0].text == 'true' if self.len_caa_types == 2 and (want_front or want_back): # The OR cases are there to still download and process the CAA # JSON file if front or back is enabled but not in the CAA and # another type (that's neither front nor back) is enabled. # For example, if both front and booklet are enabled and the # CAA only has booklet images, the front element in the XML # from the webservice will be false (thus front_in_caa is False # as well) but it's still necessary to download the booklet # images by using the fact that back is enabled but there are # no back images in the CAA. front_in_caa = caa_has_front or not want_front back_in_caa = caa_has_back or not want_back caa_has_suitable_artwork = front_in_caa or back_in_caa elif self.len_caa_types == 1 and (want_front or want_back): front_in_caa = caa_has_front and want_front back_in_caa = caa_has_back and want_back caa_has_suitable_artwork = front_in_caa or back_in_caa if not caa_has_suitable_artwork: log.debug("There are no suitable images in the Cover Art Archive for %s" % self.release.id) else: log.debug("There are suitable images in the Cover Art Archive for %s" % self.release.id) return caa_has_suitable_artwork def enabled(self): """Check if CAA artwork has to be downloaded""" if not super(CoverArtProviderCaa, self).enabled() or \ self.coverart.front_image_found: return False if self.restrict_types and not self.len_caa_types: log.debug("User disabled all Cover Art Archive types") return False return self._has_suitable_artwork @property def _caa_path(self): return "/release/%s/" % self.metadata["musicbrainz_albumid"] def queue_images(self): self.album.tagger.xmlws.download( CAA_HOST, CAA_PORT, self._caa_path, self._caa_json_downloaded, priority=True, important=False ) self.album._requests += 1 # we will call next_in_queue() after json parsing return CoverArtProvider.WAIT def _caa_json_downloaded(self, data, http, error): """Parse CAA JSON file and queue CAA cover art images for download""" self.album._requests -= 1 if error: if not (error == QNetworkReply.ContentNotFoundError and self.ignore_json_not_found_error): self.error(u'CAA JSON error: %s' % (unicode(http.errorString()))) else: try: caa_data = json.loads(data) except ValueError: self.error("Invalid JSON: %s" % (http.url().toString())) else: imagesize = config.setting["caa_image_size"] thumbsize = _CAA_THUMBNAIL_SIZE_MAP.get(imagesize, None) for image in caa_data["images"]: if config.setting["caa_approved_only"] and not image["approved"]: continue is_pdf = image["image"].endswith('.pdf') if is_pdf and not config.setting["save_images_to_files"]: log.debug("Skipping pdf cover art : %s" % image["image"]) continue # if image has no type set, we still want it to match # pseudo type 'unknown' if not image["types"]: image["types"] = [u"unknown"] else: image["types"] = map(unicode.lower, image["types"]) if self.restrict_types: # only keep enabled caa types types = set(image["types"]).intersection( set(self.caa_types)) else: types = True if types: if thumbsize is None or is_pdf: url = image["image"] else: url = image["thumbnails"][thumbsize] coverartimage = self.coverartimage_class( url, types=image["types"], is_front=image['front'], comment=image["comment"], ) if is_pdf: # thumbnail will be used to "display" PDF in info # dialog thumbnail = self.coverartimage_thumbnail_class( url=image["thumbnails"]['small'], types=image["types"], is_front=image['front'], comment=image["comment"], ) self.queue_put(thumbnail) coverartimage.thumbnail = thumbnail # PDFs cannot be saved to tags (as 2014/05/29) coverartimage.can_be_saved_to_tags = False self.queue_put(coverartimage) if config.setting["caa_save_single_front_image"] and \ config.setting["save_images_to_files"] and \ image["front"]: break self.next_in_queue() picard-release-1.4.2/picard/coverart/providers/caa_release_group.py000066400000000000000000000035061310410472100255110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2014 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import traceback from picard import config, log from picard.coverart.providers import CoverArtProvider from picard.coverart.providers.caa import CoverArtProviderCaa from picard.coverart.image import CaaCoverArtImage, CaaThumbnailCoverArtImage class CaaCoverArtImageRg(CaaCoverArtImage): pass class CaaThumbnailCoverArtImageRg(CaaThumbnailCoverArtImage): pass class CoverArtProviderCaaReleaseGroup(CoverArtProviderCaa): """Use cover art from album release group""" NAME = "CaaReleaseGroup" TITLE = N_(u"CAA Release Group") # FIXME: caa release group uses the same options than caa OPTIONS = None ignore_json_not_found_error = True coverartimage_class = CaaCoverArtImageRg coverartimage_thumbnail_class = CaaThumbnailCoverArtImageRg def enabled(self): return (super(CoverArtProviderCaa, self).enabled() and not self.coverart.front_image_found) @property def _caa_path(self): return "/release-group/%s/" % self.metadata["musicbrainz_releasegroupid"] picard-release-1.4.2/picard/coverart/providers/local.py000066400000000000000000000073351310410472100231470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2015 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import re import traceback from PyQt4 import QtCore, QtGui from picard import config, log from picard.coverart.providers import CoverArtProvider, ProviderOptions from picard.coverart.image import CoverArtImageFromFile from picard.coverart.utils import CAA_TYPES from picard.ui.ui_provider_options_local import Ui_LocalOptions class ProviderOptionsLocal(ProviderOptions): """ Options for Local Files cover art provider """ _DEFAULT_LOCAL_COVER_ART_REGEX = r'^(?:cover|folder|albumart)(.*)\.(?:jpe?g|png|gif|tiff?)$' options = [ config.TextOption("setting", "local_cover_regex", _DEFAULT_LOCAL_COVER_ART_REGEX), ] _options_ui = Ui_LocalOptions def __init__(self, parent=None): super(ProviderOptionsLocal, self).__init__(parent) self.init_regex_checker(self.ui.local_cover_regex_edit, self.ui.local_cover_regex_error) self.ui.local_cover_regex_default.clicked.connect(self.set_local_cover_regex_default) def set_local_cover_regex_default(self): self.ui.local_cover_regex_edit.setText(self._DEFAULT_LOCAL_COVER_ART_REGEX) def load(self): self.ui.local_cover_regex_edit.setText(config.setting["local_cover_regex"]) def save(self): config.setting["local_cover_regex"] = unicode(self.ui.local_cover_regex_edit.text()) class CoverArtProviderLocal(CoverArtProvider): """Get cover art from local files""" NAME = "Local" TITLE = N_(u"Local Files") OPTIONS = ProviderOptionsLocal _types_split_re = re.compile('[^a-z0-9]', re.IGNORECASE) _known_types = set([t['name'] for t in CAA_TYPES]) def enabled(self): enabled = CoverArtProvider.enabled(self) return enabled and not self.coverart.front_image_found def queue_images(self): _match_re = re.compile(config.setting['local_cover_regex'], re.IGNORECASE) dirs_done = set() for file in self.album.iterfiles(): current_dir = os.path.dirname(file.filename) if current_dir in dirs_done: continue dirs_done.add(current_dir) for root, dirs, files in os.walk(current_dir): for filename in files: m = _match_re.search(filename) if not m: continue filepath = os.path.join(current_dir, root, filename) if os.path.exists(filepath): types = self.get_types(m.group(1)) or [ u'front' ] self.queue_put(CoverArtImageFromFile(filepath, types=types, support_types=True)) return CoverArtProvider.FINISHED def get_types(self, string): found = set([x.lower() for x in self._types_split_re.split(string) if x]) return list(found.intersection(self._known_types)) picard-release-1.4.2/picard/coverart/providers/whitelist.py000066400000000000000000000035261310410472100240670ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Oliver Charles # Copyright (C) 2007-2011 Philipp Wolfer # Copyright (C) 2007, 2010, 2011 Lukáš Lalinský # Copyright (C) 2011 Michael Wiencek # Copyright (C) 2011-2012 Wieland Hoffmann # Copyright (C) 2013-2014 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import traceback from picard import config, log from picard.coverart.providers import CoverArtProvider from picard.coverart.image import CoverArtImage class CoverArtProviderWhitelist(CoverArtProvider): """Use cover art link and has_cover_art_at MusicBrainz relationships to get cover art""" NAME = "Whitelist" TITLE = N_(u'Whitelist') def enabled(self): return (super(CoverArtProviderWhitelist, self).enabled() and not self.coverart.front_image_found) def queue_images(self): self.match_url_relations(('cover art link', 'has_cover_art_at'), self._queue_from_whitelist) return CoverArtProvider.FINISHED def _queue_from_whitelist(self, url): log.debug("Found cover art link in whitelist") self.queue_put(CoverArtImage(url)) picard-release-1.4.2/picard/coverart/utils.py000066400000000000000000000030441310410472100211710ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2013 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from picard.const import MB_ATTRIBUTES from picard.i18n import ugettext_attr # list of types from http://musicbrainz.org/doc/Cover_Art/Types # order of declaration is preserved in selection box CAA_TYPES = [] for k, v in sorted(MB_ATTRIBUTES.items(), key=lambda k_v: k_v[0]): if k.startswith(u'DB:cover_art_archive.art_type/name:'): CAA_TYPES.append({'name': v.lower(), 'title': v}) # pseudo type, used for the no type case CAA_TYPES.append({'name': "unknown", 'title': N_(u"Unknown")}) CAA_TYPES_TR = {} for t in CAA_TYPES: CAA_TYPES_TR[t['name']] = t['title'] def translate_caa_type(name): if name == 'unknown': return _(CAA_TYPES_TR[name]) else: return ugettext_attr(CAA_TYPES_TR[name], u"cover_art_type") picard-release-1.4.2/picard/dataobj.py000066400000000000000000000024531310410472100176130ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2004 Robert Kaye # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from picard.util import LockableObject class DataObject(LockableObject): def __init__(self, id): LockableObject.__init__(self) self.id = id self.folksonomy_tags = {} self.item = None def add_folksonomy_tag(self, name, count): self.folksonomy_tags[name] = self.folksonomy_tags.get(name, 0) + count @staticmethod def merge_folksonomy_tags(this, that): for name, count in that.iteritems(): this[name] = this.get(name, 0) + count picard-release-1.4.2/picard/disc.py000066400000000000000000000047251310410472100171350ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # Copyright (C) 2006 Matthias Friedrich # Copyright (C) 2013 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. try: # use python-libdiscid (http://pythonhosted.org/python-libdiscid/) from libdiscid.compat import discid except ImportError: try: # use python-discid (http://python-discid.readthedocs.org/en/latest/) import discid except ImportError: discid = None import traceback from PyQt4 import QtCore from picard import log from picard.ui.cdlookup import CDLookupDialog class Disc(QtCore.QObject): def __init__(self): QtCore.QObject.__init__(self) self.id = None self.submission_url = None def read(self, device=None): if device is None: device = discid.get_default_device() log.debug(u"Reading CD using device: %r", device) disc = discid.read(device) self.id = disc.id self.submission_url = disc.submission_url def lookup(self): self.tagger.xmlws.lookup_discid(self.id, self._lookup_finished) def _lookup_finished(self, document, http, error): self.tagger.restore_cursor() releases = [] if error: log.error("%r", unicode(http.errorString())) else: try: releases = document.metadata[0].disc[0].release_list[0].release except (AttributeError, IndexError): log.error(traceback.format_exc()) dialog = CDLookupDialog(releases, self, parent=self.tagger.window) dialog.exec_() if discid is not None: discid_version = "discid %s, %s" % (discid.__version__, discid.LIBDISCID_VERSION_STRING) else: discid_version = None picard-release-1.4.2/picard/file.py000066400000000000000000000632451310410472100171340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2004 Robert Kaye # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import fnmatch import os import os.path import shutil import sys import re import unicodedata from functools import partial from operator import itemgetter from collections import defaultdict from PyQt4 import QtCore from picard import config, log from picard.metadata import Metadata from picard.ui.item import Item from picard.script import ScriptParser from picard.util import ( decode_filename, encode_filename, format_time, pathcmp, replace_win32_incompat, sanitize_filename, thread, tracknum_from_filename, ) from picard.util.textencoding import ( replace_non_ascii, unaccent, ) from picard.util.filenaming import make_short_filename from picard.util.tags import PRESERVED_TAGS from picard.const import QUERY_LIMIT from picard import PICARD_APP_NAME class File(QtCore.QObject, Item): metadata_images_changed = QtCore.pyqtSignal() UNDEFINED = -1 PENDING = 0 NORMAL = 1 CHANGED = 2 ERROR = 3 REMOVED = 4 comparison_weights = { "title": 13, "artist": 4, "album": 5, "length": 10, "totaltracks": 4, "releasetype": 20, "releasecountry": 2, "format": 2, } def __init__(self, filename): super(File, self).__init__() self.filename = filename self.base_filename = os.path.basename(filename) self._state = File.UNDEFINED self.state = File.PENDING self.error = None self.orig_metadata = Metadata() self.metadata = Metadata() self.similarity = 1.0 self.parent = None self.lookup_task = None self.item = None def __repr__(self): return '' % self.base_filename @property def new_metadata(self): return self.metadata def load(self, callback): thread.run_task( partial(self._load_check, self.filename), partial(self._loading_finished, callback), priority=1) def _load_check(self, filename): # Check that file has not been removed since thread was queued # Don't load if we are stopping. if self.state != File.PENDING: log.debug("File not loaded because it was removed: %r", self.filename) return None if self.tagger.stopping: log.debug("File not loaded because %s is stopping: %r", PICARD_APP_NAME, self.filename) return None return self._load(filename) def _load(self, filename): """Load metadata from the file.""" raise NotImplementedError def _loading_finished(self, callback, result=None, error=None): if self.state != File.PENDING or self.tagger.stopping: return if error is not None: self.error = str(error) self.state = self.ERROR from picard.formats import supported_extensions file_name, file_extension = os.path.splitext(self.base_filename) if file_extension not in supported_extensions(): self.remove() log.error('Unsupported media file %r wrongly loaded. Removing ...',self) return else: self.error = None self.state = self.NORMAL self._copy_loaded_metadata(result) self.update() callback(self) def _copy_loaded_metadata(self, metadata): filename, _ = os.path.splitext(self.base_filename) metadata['~length'] = format_time(metadata.length) if 'title' not in metadata: metadata['title'] = filename if 'tracknumber' not in metadata: tracknumber = tracknum_from_filename(self.base_filename) if tracknumber != -1: tracknumber = str(tracknumber) metadata['tracknumber'] = tracknumber if metadata['title'] == filename: stripped_filename = filename.lstrip('0') tnlen = len(tracknumber) if stripped_filename[:tnlen] == tracknumber: metadata['title'] = stripped_filename[tnlen:].lstrip() self.orig_metadata = metadata self.metadata.copy(metadata) def copy_metadata(self, metadata): acoustid = self.metadata["acoustid_id"] preserve = config.setting["preserved_tags"].strip() saved_metadata = {} for tag in re.split(r"\s*,\s*", preserve) + PRESERVED_TAGS: values = self.orig_metadata.getall(tag) if values: saved_metadata[tag] = values deleted_tags = self.metadata.deleted_tags self.metadata.copy(metadata) self.metadata.deleted_tags = deleted_tags for tag, values in saved_metadata.iteritems(): self.metadata.set(tag, values) if acoustid: self.metadata["acoustid_id"] = acoustid self.metadata_images_changed.emit() def keep_original_images(self): self.metadata.images = self.orig_metadata.images[:] self.update() self.metadata_images_changed.emit() def has_error(self): return self.state == File.ERROR def save(self): self.set_pending() metadata = Metadata() metadata.copy(self.metadata) thread.run_task( partial(self._save_and_rename, self.filename, metadata), self._saving_finished, priority=2, thread_pool=self.tagger.save_thread_pool) def _save_and_rename(self, old_filename, metadata): """Save the metadata.""" # Check that file has not been removed since thread was queued # Also don't save if we are stopping. if self.state == File.REMOVED: log.debug("File not saved because it was removed: %r", self.filename) return None if self.tagger.stopping: log.debug("File not saved because %s is stopping: %r", PICARD_APP_NAME, self.filename) return None new_filename = old_filename if not config.setting["dont_write_tags"]: encoded_old_filename = encode_filename(old_filename) info = os.stat(encoded_old_filename) self._save(old_filename, metadata) if config.setting["preserve_timestamps"]: try: os.utime(encoded_old_filename, (info.st_atime, info.st_mtime)) except OSError: log.warning("Couldn't preserve timestamp for %r", old_filename) # Rename files if config.setting["rename_files"] or config.setting["move_files"]: new_filename = self._rename(old_filename, metadata) # Move extra files (images, playlists, etc.) if config.setting["move_files"] and config.setting["move_additional_files"]: self._move_additional_files(old_filename, new_filename) # Delete empty directories if config.setting["delete_empty_dirs"]: dirname = encode_filename(os.path.dirname(old_filename)) try: self._rmdir(dirname) head, tail = os.path.split(dirname) if not tail: head, tail = os.path.split(head) while head and tail: try: self._rmdir(head) except: break head, tail = os.path.split(head) except EnvironmentError: pass # Save cover art images if config.setting["save_images_to_files"]: self._save_images(os.path.dirname(new_filename), metadata) return new_filename @staticmethod def _rmdir(dir): junk_files = (".DS_Store", "desktop.ini", "Desktop.ini", "Thumbs.db") if not set(os.listdir(dir)) - set(junk_files): shutil.rmtree(dir, False) else: raise OSError def _saving_finished(self, result=None, error=None): # Handle file removed before save # Result is None if save was skipped if ((self.state == File.REMOVED or self.tagger.stopping) and result is None): return old_filename = new_filename = self.filename if error is not None: self.error = str(error) self.set_state(File.ERROR, update=True) else: self.filename = new_filename = result self.base_filename = os.path.basename(new_filename) length = self.orig_metadata.length temp_info = {} for info in ('~bitrate', '~sample_rate', '~channels', '~bits_per_sample', '~format'): temp_info[info] = self.orig_metadata[info] # Data is copied from New to Original because New may be a subclass to handle id3v23 if config.setting["clear_existing_tags"]: self.orig_metadata.copy(self.new_metadata) else: self.orig_metadata.update(self.new_metadata) self.orig_metadata.length = length self.orig_metadata['~length'] = format_time(length) for k, v in temp_info.items(): self.orig_metadata[k] = v self.error = None # Force update to ensure file status icon changes immediately after save self.clear_pending(force_update=True) self._add_path_to_metadata(self.orig_metadata) self.metadata_images_changed.emit() if self.state != File.REMOVED: del self.tagger.files[old_filename] self.tagger.files[new_filename] = self if self.tagger.stopping: log.debug("Save of %r completed before stopping Picard", self.filename) def _save(self, filename, metadata): """Save the metadata.""" raise NotImplementedError def _script_to_filename(self, format, file_metadata, settings=None): if settings is None: settings = config.setting metadata = Metadata() if config.setting["clear_existing_tags"]: metadata.copy(file_metadata) else: metadata.copy(self.orig_metadata) metadata.update(file_metadata) # make sure every metadata can safely be used in a path name for name in metadata.keys(): if isinstance(metadata[name], basestring): metadata[name] = sanitize_filename(metadata[name]) format = format.replace("\t", "").replace("\n", "") filename = ScriptParser().eval(format, metadata, self) if settings["ascii_filenames"]: if isinstance(filename, unicode): filename = unaccent(filename) filename = replace_non_ascii(filename) # replace incompatible characters if settings["windows_compatibility"] or sys.platform == "win32": filename = replace_win32_incompat(filename) # remove null characters filename = filename.replace("\x00", "") return filename def _fixed_splitext(self, filename): # In case the filename is blank and only has the extension # the real extension is in new_filename and ext is blank new_filename, ext = os.path.splitext(filename) if ext == '' and new_filename.lower() in map(unicode, self.EXTENSIONS): ext = new_filename new_filename = '' return new_filename, ext def _make_filename(self, filename, metadata, settings=None): """Constructs file name based on metadata and file naming formats.""" if settings is None: settings = config.setting if settings["move_files"]: new_dirname = settings["move_files_to"] if not os.path.isabs(new_dirname): new_dirname = os.path.normpath(os.path.join(os.path.dirname(filename), new_dirname)) else: new_dirname = os.path.dirname(filename) new_filename = os.path.basename(filename) if settings["rename_files"]: new_filename, ext = self._fixed_splitext(new_filename) ext = ext.lower() new_filename = new_filename + ext # expand the naming format format = settings['file_naming_format'] if len(format) > 0: new_filename = self._script_to_filename(format, metadata, settings) # NOTE: the _script_to_filename strips the extension away new_filename = new_filename + ext if not settings['move_files']: new_filename = os.path.basename(new_filename) new_filename = make_short_filename(new_dirname, new_filename, config.setting['windows_compatibility'], config.setting['windows_compatibility_drive_root']) # TODO: move following logic under util.filenaming # (and reconsider its necessity) # win32 compatibility fixes if settings['windows_compatibility'] or sys.platform == 'win32': new_filename = new_filename.replace('./', '_/').replace('.\\', '_\\') # replace . at the beginning of file and directory names new_filename = new_filename.replace('/.', '/_').replace('\\.', '\\_') if new_filename and new_filename[0] == '.': new_filename = '_' + new_filename[1:] # Fix for precomposed characters on OSX if sys.platform == "darwin": new_filename = unicodedata.normalize("NFD", unicode(new_filename)) return os.path.realpath(os.path.join(new_dirname, new_filename)) def _rename(self, old_filename, metadata): new_filename, ext = os.path.splitext( self._make_filename(old_filename, metadata)) if old_filename == new_filename + ext: return old_filename new_dirname = os.path.dirname(new_filename) if not os.path.isdir(encode_filename(new_dirname)): os.makedirs(new_dirname) tmp_filename = new_filename i = 1 while (not pathcmp(old_filename, new_filename + ext) and os.path.exists(encode_filename(new_filename + ext))): new_filename = "%s (%d)" % (tmp_filename, i) i += 1 new_filename = new_filename + ext log.debug("Moving file %r => %r", old_filename, new_filename) shutil.move(encode_filename(old_filename), encode_filename(new_filename)) return new_filename def _save_images(self, dirname, metadata): """Save the cover images to disk.""" if not metadata.images: return counters = defaultdict(lambda: 0) images = [] if config.setting["caa_save_single_front_image"]: images = metadata.get_single_front_image() if not images: images = metadata.images for image in images: image.save(dirname, metadata, counters) def _move_additional_files(self, old_filename, new_filename): """Move extra files, like playlists...""" old_path = encode_filename(os.path.dirname(old_filename)) new_path = encode_filename(os.path.dirname(new_filename)) patterns = encode_filename(config.setting["move_additional_files_pattern"]) patterns = filter(bool, [p.strip() for p in patterns.split()]) try: names = os.listdir(old_path) except os.error: log.error("Error: {} directory not found".format(old_path)) return filtered_names = filter(lambda x: x[0] != '.', names) for pattern in patterns: pattern_regex = re.compile(fnmatch.translate(pattern), re.IGNORECASE) file_names = names if pattern[0] != '.': file_names = filtered_names for old_file in file_names: if pattern_regex.match(old_file): new_file = os.path.join(new_path, old_file) old_file = os.path.join(old_path, old_file) # FIXME we shouldn't do this from a thread! if self.tagger.files.get(decode_filename(old_file)): log.debug("File loaded in the tagger, not moving %r", old_file) continue log.debug("Moving %r to %r", old_file, new_file) shutil.move(old_file, new_file) def remove(self, from_parent=True): if from_parent and self.parent: log.debug("Removing %r from %r", self, self.parent) self.parent.remove_file(self) self.tagger.acoustidmanager.remove(self) self.state = File.REMOVED def move(self, parent): if parent != self.parent: log.debug("Moving %r from %r to %r", self, self.parent, parent) self.clear_lookup_task() self.tagger._acoustid.stop_analyze(self) if self.parent: self.clear_pending() self.parent.remove_file(self) self.parent = parent self.parent.add_file(self) self.tagger.acoustidmanager.update(self, self.metadata['musicbrainz_recordingid']) def _move(self, parent): if parent != self.parent: log.debug("Moving %r from %r to %r", self, self.parent, parent) if self.parent: self.parent.remove_file(self) self.parent = parent self.tagger.acoustidmanager.update(self, self.metadata['musicbrainz_recordingid']) def supports_tag(self, name): """Returns whether tag ``name`` can be saved to the file.""" return True def is_saved(self): return self.similarity == 1.0 and self.state == File.NORMAL def update(self, signal=True): new_metadata = self.new_metadata names = set(new_metadata.keys()) names.update(self.orig_metadata.keys()) clear_existing_tags = config.setting["clear_existing_tags"] for name in names: if not name.startswith('~') and self.supports_tag(name): new_values = new_metadata.getall(name) if not (new_values or clear_existing_tags): continue orig_values = self.orig_metadata.getall(name) if orig_values != new_values: self.similarity = self.orig_metadata.compare(new_metadata) if self.state in (File.CHANGED, File.NORMAL): self.state = File.CHANGED break else: if (self.metadata.images and self.orig_metadata.images != self.metadata.images): self.state = File.CHANGED else: self.similarity = 1.0 if self.state in (File.CHANGED, File.NORMAL): self.state = File.NORMAL if signal: log.debug("Updating file %r", self) if self.item: self.item.update() def can_save(self): """Return if this object can be saved.""" return True def can_remove(self): """Return if this object can be removed.""" return True def can_edit_tags(self): """Return if this object supports tag editing.""" return True def can_analyze(self): """Return if this object can be fingerprinted.""" return True def can_autotag(self): return True def can_refresh(self): return False def can_view_info(self): return True def _info(self, metadata, file): if hasattr(file.info, 'length'): metadata.length = int(file.info.length * 1000) if hasattr(file.info, 'bitrate') and file.info.bitrate: metadata['~bitrate'] = file.info.bitrate / 1000.0 if hasattr(file.info, 'sample_rate') and file.info.sample_rate: metadata['~sample_rate'] = file.info.sample_rate if hasattr(file.info, 'channels') and file.info.channels: metadata['~channels'] = file.info.channels if hasattr(file.info, 'bits_per_sample') and file.info.bits_per_sample: metadata['~bits_per_sample'] = file.info.bits_per_sample metadata['~format'] = self.__class__.__name__.replace('File', '') self._add_path_to_metadata(metadata) def _add_path_to_metadata(self, metadata): metadata['~dirname'] = os.path.dirname(self.filename) filename, extension = os.path.splitext(os.path.basename(self.filename)) metadata['~filename'] = filename metadata['~extension'] = extension.lower()[1:] def get_state(self): return self._state # in order to significantly speed up performance, the number of pending # files is cached num_pending_files = 0 def set_state(self, state, update=False): if state != self._state: if state == File.PENDING: File.num_pending_files += 1 elif self._state == File.PENDING: File.num_pending_files -= 1 self._state = state if update: self.update() self.tagger.tagger_stats_changed.emit() state = property(get_state, set_state) def column(self, column): m = self.metadata if column == "title" and not m["title"]: return self.base_filename return m[column] def _lookup_finished(self, lookuptype, document, http, error): self.lookup_task = None if self.state == File.REMOVED: return try: m = document.metadata[0] if lookuptype == "metadata": tracks = m.recording_list[0].recording elif lookuptype == "acoustid": tracks = m.acoustid[0].recording_list[0].recording except (AttributeError, IndexError): tracks = None # no matches if not tracks: self.tagger.window.set_statusbar_message( N_("No matching tracks for file '%(filename)s'"), {'filename': self.filename}, timeout=3000 ) self.clear_pending() return # multiple matches -- calculate similarities to each of them match = sorted((self.metadata.compare_to_track( track, self.comparison_weights) for track in tracks), reverse=True, key=itemgetter(0))[0] if lookuptype != 'acoustid': threshold = config.setting['file_lookup_threshold'] if match[0] < threshold: self.tagger.window.set_statusbar_message( N_("No matching tracks above the threshold for file '%(filename)s'"), {'filename': self.filename}, timeout=3000 ) self.clear_pending() return self.tagger.window.set_statusbar_message( N_("File '%(filename)s' identified!"), {'filename': self.filename}, timeout=3000 ) self.clear_pending() rg, release, track = match[1:] if lookuptype == 'acoustid': self.tagger.acoustidmanager.add(self, track.id) if release: self.tagger.get_release_group_by_id(rg.id).loaded_albums.add(release.id) self.tagger.move_file_to_track(self, release.id, track.id) else: self.tagger.move_file_to_nat(self, track.id, node=track) def lookup_metadata(self): """Try to identify the file using the existing metadata.""" if self.lookup_task: return self.tagger.window.set_statusbar_message( N_("Looking up the metadata for file %(filename)s ..."), {'filename': self.filename} ) self.clear_lookup_task() metadata = self.metadata self.set_pending() self.lookup_task = self.tagger.xmlws.find_tracks(partial(self._lookup_finished, 'metadata'), track=metadata['title'], artist=metadata['artist'], release=metadata['album'], tnum=metadata['tracknumber'], tracks=metadata['totaltracks'], qdur=str(metadata.length / 2000), isrc=metadata['isrc'], limit=QUERY_LIMIT) def clear_lookup_task(self): if self.lookup_task: self.tagger.xmlws.remove_task(self.lookup_task) self.lookup_task = None def set_pending(self): if self.state != File.REMOVED: self.state = File.PENDING self.update() def clear_pending(self, force_update=False): if self.state == File.PENDING: self.state = File.NORMAL self.update() elif force_update: self.update() def iterfiles(self, save=False): yield self def _get_tracknumber(self): try: return int(self.metadata["tracknumber"]) except: return 0 tracknumber = property(_get_tracknumber, doc="The track number as an int.") def _get_discnumber(self): try: return int(self.metadata["discnumber"]) except: return 0 discnumber = property(_get_discnumber, doc="The disc number as an int.") picard-release-1.4.2/picard/formats/000077500000000000000000000000001310410472100173045ustar00rootroot00000000000000picard-release-1.4.2/picard/formats/__init__.py000066400000000000000000000103371310410472100214210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import sys from mutagen import _util from picard import log from picard.plugin import ExtensionPoint _formats = ExtensionPoint() _extensions = {} def register_format(format): _formats.register(format.__module__, format) for ext in format.EXTENSIONS: _extensions[ext[1:]] = format def supported_formats(): """Returns list of supported formats.""" return [(format.EXTENSIONS, format.NAME) for format in _formats] def supported_extensions(): """Returns list of supported extensions.""" return [ext for exts, name in supported_formats() for ext in exts] def guess_format(filename, options=_formats): """Select the best matching file type amongst supported formats.""" results = [] # Since we are reading only 128 bytes and then immediately closing the file, # use unbuffered mode. with file(filename, "rb", 0) as fileobj: header = fileobj.read(128) # Calls the score method of a particular format's associated filetype # and assigns a positive score depending on how closely the fileobj's header matches # the header for a particular file format. results = [(option._File.score(filename, fileobj, header), option.__name__, option) for option in options if getattr(option, "_File", None)] if results: results.sort() if results[-1][0] > 0: # return the format with the highest matching score return results[-1][2](filename) # No positive score i.e. the fileobj's header did not match any supported format return None def open(filename): """Open the specified file and return a File instance with the appropriate format handler, or None.""" try: # First try to guess the format on the basis of file headers audio_file = guess_format(filename) if not audio_file: i = filename.rfind(".") if i < 0: return None ext = filename[i+1:].lower() # Switch to extension based opening if guess_format fails audio_file = _extensions[ext](filename) return audio_file except KeyError: # None is returned if both the methods fail return None except Exception as error: log.error("Error occured:\n{}".format(error.message)) return None from picard.formats.id3 import ( AiffFile, MP3File, TrueAudioFile, ) if AiffFile: register_format(AiffFile) register_format(MP3File) register_format(TrueAudioFile) from picard.formats.apev2 import ( MonkeysAudioFile, MusepackFile, OptimFROGFile, WavPackFile, TAKFile, ) register_format(MusepackFile) register_format(WavPackFile) register_format(OptimFROGFile) register_format(MonkeysAudioFile) register_format(TAKFile) from picard.formats.vorbis import ( FLACFile, OggFLACFile, OggSpeexFile, OggVorbisFile, OggAudioFile, OggVideoFile, OggOpusFile, with_opus, ) register_format(FLACFile) register_format(OggFLACFile) register_format(OggSpeexFile) register_format(OggVorbisFile) if with_opus: register_format(OggOpusFile) register_format(OggAudioFile) register_format(OggVideoFile) try: from picard.formats.mp4 import MP4File register_format(MP4File) except ImportError: pass try: from picard.formats.asf import ASFFile register_format(ASFFile) except ImportError: pass from picard.formats.wav import WAVFile register_format(WAVFile) picard-release-1.4.2/picard/formats/apev2.py000066400000000000000000000243621310410472100207020ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from __future__ import absolute_import import re import mutagen.apev2 import mutagen.monkeysaudio import mutagen.musepack import mutagen.wavpack import mutagen.optimfrog from .mutagenext import tak from picard import config, log from picard.coverart.image import TagCoverArtImage, CoverArtImageError from picard.file import File from picard.metadata import Metadata from picard.util import encode_filename, sanitize_date from os.path import isfile class APEv2File(File): """Generic APEv2-based file.""" _File = None __translate = { "Album Artist": "albumartist", "MixArtist": "remixer", "Weblink": "website", "DiscSubtitle": "discsubtitle", "BPM": "bpm", "ISRC": "isrc", "CatalogNumber": "catalognumber", "Barcode": "barcode", "EncodedBy": "encodedby", "Language": "language", "MUSICBRAINZ_ALBUMSTATUS": "releasestatus", "MUSICBRAINZ_ALBUMTYPE": "releasetype", "musicbrainz_trackid": "musicbrainz_recordingid", "musicbrainz_releasetrackid": "musicbrainz_trackid", } __rtranslate = dict([(v, k) for k, v in __translate.iteritems()]) def _load(self, filename): log.debug("Loading file %r", filename) file = self._File(encode_filename(filename)) metadata = Metadata() if file.tags: for origname, values in file.tags.items(): if origname.lower().startswith("cover art") and values.kind == mutagen.apev2.BINARY: if '\0' in values.value: descr, data = values.value.split('\0', 1) try: coverartimage = TagCoverArtImage( file=filename, tag=origname, data=data, ) except CoverArtImageError as e: log.error('Cannot load image from %r: %s' % (filename, e)) else: metadata.append_image(coverartimage) # skip EXTERNAL and BINARY values if values.kind != mutagen.apev2.TEXT: continue for value in values: name = origname if name == "Year": name = "date" value = sanitize_date(value) elif name == "Track": name = "tracknumber" track = value.split("/") if len(track) > 1: metadata["totaltracks"] = track[1] value = track[0] elif name == "Disc": name = "discnumber" disc = value.split("/") if len(disc) > 1: metadata["totaldiscs"] = disc[1] value = disc[0] elif name == 'Performer' or name == 'Comment': name = name.lower() + ':' if value.endswith(')'): start = value.rfind(' (') if start > 0: name += value[start + 2:-1] value = value[:start] elif name in self.__translate: name = self.__translate[name] else: name = name.lower() metadata.add(name, value) self._info(metadata, file) return metadata def _save(self, filename, metadata): """Save metadata to the file.""" log.debug("Saving file %r", filename) try: tags = mutagen.apev2.APEv2(encode_filename(filename)) except mutagen.apev2.APENoHeaderError: tags = mutagen.apev2.APEv2() if config.setting["clear_existing_tags"]: tags.clear() elif metadata.images_to_be_saved_to_tags: for name, value in tags.items(): if name.lower().startswith('cover art') and value.kind == mutagen.apev2.BINARY: del tags[name] temp = {} for name, value in metadata.items(): if name.startswith("~"): continue if name.startswith('lyrics:'): name = 'Lyrics' elif name == "date": name = "Year" # tracknumber/totaltracks => Track elif name == 'tracknumber': name = 'Track' if 'totaltracks' in metadata: value = '%s/%s' % (value, metadata['totaltracks']) # discnumber/totaldiscs => Disc elif name == 'discnumber': name = 'Disc' if 'totaldiscs' in metadata: value = '%s/%s' % (value, metadata['totaldiscs']) elif name in ('totaltracks', 'totaldiscs'): continue # "performer:Piano=Joe Barr" => "Performer=Joe Barr (Piano)" elif name.startswith('performer:') or name.startswith('comment:'): name, desc = name.split(':', 1) name = name.title() if desc: value += ' (%s)' % desc elif name in self.__rtranslate: name = self.__rtranslate[name] else: name = name.title() temp.setdefault(name, []).append(value) for name, values in temp.items(): tags[str(name)] = values for image in metadata.images_to_be_saved_to_tags: cover_filename = 'Cover Art (Front)' cover_filename += image.extension tags['Cover Art (Front)'] = mutagen.apev2.APEValue(cover_filename + '\0' + image.data, mutagen.apev2.BINARY) break # can't save more than one item with the same name # (mp3tags does this, but it's against the specs) self._remove_deleted_tags(metadata, tags) tags.save(encode_filename(filename)) def _remove_deleted_tags(self, metadata, tags): """Remove the tags from the file that were deleted in the UI""" for tag in metadata.deleted_tags: real_name = str(self._get_tag_name(tag)) if real_name in ('Lyrics', 'Comment', 'Performer'): tag_type = r"\(%s\)" % tag.split(':', 1)[1] for item in tags.get(real_name): if re.search(tag_type, item): tags.get(real_name).remove(item) elif tag in ('totaltracks', 'totaldiscs'): tagstr = real_name.lower() + 'number' try: tags[real_name] = metadata[tagstr] except KeyError: pass else: del tags[real_name] def _get_tag_name(self, name): if name.startswith('lyrics:'): return 'Lyrics' elif name == 'date': return 'Year' elif name in ('tracknumber', 'totaltracks'): return 'Track' elif name in ('discnumber', 'totaldiscs'): return 'Disc' elif name.startswith('performer:') or name.startswith('comment:'): return name.split(':', 1)[0].title() elif name in self.__rtranslate: return self.__rtranslate[name] else: return name.title() def supports_tag(self, name): return bool(name) class MusepackFile(APEv2File): """Musepack file.""" EXTENSIONS = [".mpc", ".mp+"] NAME = "Musepack" _File = mutagen.musepack.Musepack def _info(self, metadata, file): super(MusepackFile, self)._info(metadata, file) metadata['~format'] = "Musepack, SV%d" % file.info.version class WavPackFile(APEv2File): """WavPack file.""" EXTENSIONS = [".wv"] NAME = "WavPack" _File = mutagen.wavpack.WavPack def _info(self, metadata, file): super(WavPackFile, self)._info(metadata, file) metadata['~format'] = self.NAME def _save_and_rename(self, old_filename, metadata): """Includes an additional check for WavPack correction files""" wvc_filename = old_filename.replace(".wv", ".wvc") if isfile(wvc_filename): if config.setting["rename_files"] or config.setting["move_files"]: self._rename(wvc_filename, metadata) return File._save_and_rename(self, old_filename, metadata) class OptimFROGFile(APEv2File): """OptimFROG file.""" EXTENSIONS = [".ofr", ".ofs"] NAME = "OptimFROG" _File = mutagen.optimfrog.OptimFROG def _info(self, metadata, file): super(OptimFROGFile, self)._info(metadata, file) if file.filename.lower().endswith(".ofs"): metadata['~format'] = "OptimFROG DualStream Audio" else: metadata['~format'] = "OptimFROG Lossless Audio" class MonkeysAudioFile(APEv2File): """Monkey's Audio file.""" EXTENSIONS = [".ape"] NAME = "Monkey's Audio" _File = mutagen.monkeysaudio.MonkeysAudio def _info(self, metadata, file): super(MonkeysAudioFile, self)._info(metadata, file) metadata['~format'] = self.NAME class TAKFile(APEv2File): """TAK file.""" EXTENSIONS = [".tak"] NAME = "Tom's lossless Audio Kompressor" _File = tak.TAK def _info(self, metadata, file): super(TAKFile, self)._info(metadata, file) metadata['~format'] = self.NAME picard-release-1.4.2/picard/formats/asf.py000066400000000000000000000220421310410472100204270ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006-2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from picard import config, log from picard.coverart.image import TagCoverArtImage, CoverArtImageError from picard.file import File from picard.formats.id3 import types_from_id3, image_type_as_id3_num from picard.util import encode_filename from picard.metadata import Metadata from mutagen.asf import ASF, ASFByteArrayAttribute import struct def unpack_image(data): """ Helper function to unpack image data from a WM/Picture tag. The data has the following format: 1 byte: Picture type (0-20), see ID3 APIC frame specification at http://www.id3.org/id3v2.4.0-frames 4 bytes: Picture data length in LE format MIME type, null terminated UTF-16-LE string Description, null terminated UTF-16-LE string The image data in the given length """ (type, size) = struct.unpack_from(" 1: metadata["totaldiscs"] = disc[1] values[0] = disc[0] name = self.__RTRANS[name] values = filter(bool, map(unicode, values)) if values: metadata[name] = values self._info(metadata, file) return metadata def _save(self, filename, metadata): log.debug("Saving file %r", filename) file = ASF(encode_filename(filename)) tags = file.tags if config.setting['clear_existing_tags']: tags.clear() cover = [] for image in metadata.images_to_be_saved_to_tags: tag_data = pack_image(image.mimetype, image.data, image_type_as_id3_num(image.maintype), image.comment) cover.append(ASFByteArrayAttribute(tag_data)) if cover: tags['WM/Picture'] = cover for name, values in metadata.rawitems(): if name.startswith('lyrics:'): name = 'lyrics' elif name == '~rating': values[0] = int(values[0]) * 99 / (config.setting['rating_steps'] - 1) elif name == 'discnumber' and 'totaldiscs' in metadata: values[0] = '%s/%s' % (metadata['discnumber'], metadata['totaldiscs']) if name not in self.__TRANS: continue name = self.__TRANS[name] tags[name] = map(unicode, values) self._remove_deleted_tags(metadata, tags) file.save() def _remove_deleted_tags(self, metadata, tags): """Remove the tags from the file that were deleted in the UI""" for tag in metadata.deleted_tags: real_name = self._get_tag_name(tag) if real_name and real_name in tags: if tag == 'totaldiscs': tags[real_name] = map(unicode, metadata['discnumber']) else: del tags[real_name] def supports_tag(self, name): return (name in self.__TRANS or name in ('~rating', '~length', 'totaldiscs') or name.startswith('lyrics')) def _get_tag_name(self, name): if name.startswith('lyrics'): return 'lyrics' elif name == 'totaldiscs': return self.__TRANS['discnumber'] elif name in self.__TRANS: return self.__TRANS[name] else: return None picard-release-1.4.2/picard/formats/id3.py000066400000000000000000000610701310410472100203410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006-2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import mutagen.apev2 import mutagen.mp3 import mutagen.trueaudio try: import mutagen.aiff except ImportError: mutagen.aiff = None import re from collections import defaultdict from mutagen import id3 from picard import config, log from picard.coverart.image import TagCoverArtImage, CoverArtImageError from picard.metadata import Metadata from picard.file import File from picard.formats.mutagenext import compatid3 from picard.util import encode_filename, sanitize_date from urlparse import urlparse id3.TCMP = compatid3.TCMP id3.TSO2 = compatid3.TSO2 id3.TSOC = compatid3.TSOC __ID3_IMAGE_TYPE_MAP = { "other": 0, "obi": 0, "tray": 0, "spine": 0, "sticker": 0, "front": 3, "back": 4, "booklet": 5, "medium": 6, "track": 6, } __ID3_REVERSE_IMAGE_TYPE_MAP = dict([(v, k) for k, v in __ID3_IMAGE_TYPE_MAP.iteritems()]) def id3text(text, encoding): """Returns a string which only contains code points which can be encododed with the given numeric id3 encoding. """ if encoding == 0: return text.encode("latin1", "replace").decode("latin1") return text def image_type_from_id3_num(id3type): return __ID3_REVERSE_IMAGE_TYPE_MAP.get(id3type, "other") def image_type_as_id3_num(texttype): return __ID3_IMAGE_TYPE_MAP.get(texttype, 0) def types_from_id3(id3type): return [unicode(image_type_from_id3_num(id3type))] class ID3File(File): """Generic ID3-based file.""" _IsMP3 = False __upgrade = { 'XSOP': 'TSOP', 'TXXX:ALBUMARTISTSORT': 'TSO2', 'TXXX:COMPOSERSORT': 'TSOC', 'TXXX:mood': 'TMOO', } __translate = { # In same sequence as defined at http://id3.org/id3v2.4.0-frames 'TIT1': 'grouping', 'TIT2': 'title', 'TIT3': 'subtitle', 'TALB': 'album', 'TSST': 'discsubtitle', 'TSRC': 'isrc', 'TPE1': 'artist', 'TPE2': 'albumartist', 'TPE3': 'conductor', 'TPE4': 'remixer', 'TEXT': 'lyricist', 'TCOM': 'composer', 'TENC': 'encodedby', 'TBPM': 'bpm', 'TKEY': 'key', 'TLAN': 'language', 'TCON': 'genre', 'TMED': 'media', 'TMOO': 'mood', 'TCOP': 'copyright', 'TPUB': 'label', 'TDOR': 'originaldate', 'TDRC': 'date', 'TSSE': 'encodersettings', 'TSOA': 'albumsort', 'TSOP': 'artistsort', 'TSOT': 'titlesort', 'WCOP': 'license', 'WOAR': 'website', 'COMM': 'comment', 'TOAL': 'originalalbum', 'TOPE': 'originalartist', # The following are informal iTunes extensions to id3v2: 'TCMP': 'compilation', 'TSOC': 'composersort', 'TSO2': 'albumartistsort', } __rtranslate = dict([(v, k) for k, v in __translate.iteritems()]) __translate_freetext = { 'MusicBrainz Artist Id': 'musicbrainz_artistid', 'MusicBrainz Album Id': 'musicbrainz_albumid', 'MusicBrainz Album Artist Id': 'musicbrainz_albumartistid', 'MusicBrainz Album Type': 'releasetype', 'MusicBrainz Album Status': 'releasestatus', 'MusicBrainz TRM Id': 'musicbrainz_trmid', 'MusicBrainz Release Track Id': 'musicbrainz_trackid', 'MusicBrainz Disc Id': 'musicbrainz_discid', 'MusicBrainz Work Id': 'musicbrainz_workid', 'MusicBrainz Release Group Id': 'musicbrainz_releasegroupid', 'MusicBrainz Original Album Id': 'musicbrainz_originalalbumid', 'MusicBrainz Original Artist Id': 'musicbrainz_originalartistid', 'MusicBrainz Album Release Country': 'releasecountry', 'MusicIP PUID': 'musicip_puid', 'Acoustid Fingerprint': 'acoustid_fingerprint', 'Acoustid Id': 'acoustid_id', 'SCRIPT': 'script', 'LICENSE': 'license', 'CATALOGNUMBER': 'catalognumber', 'BARCODE': 'barcode', 'ASIN': 'asin', 'MusicMagic Fingerprint': 'musicip_fingerprint', 'Artists': 'artists', 'Work': 'work', 'Writer': 'writer', } __rtranslate_freetext = dict([(v, k) for k, v in __translate_freetext.iteritems()]) __translate_freetext['writer'] = 'writer' # For backward compatibility of case _tipl_roles = { 'engineer': 'engineer', 'arranger': 'arranger', 'producer': 'producer', 'DJ-mix': 'djmixer', 'mix': 'mixer', } _rtipl_roles = dict([(v, k) for k, v in _tipl_roles.iteritems()]) __other_supported_tags = ("discnumber", "tracknumber", "totaldiscs", "totaltracks") __tag_re_parse = { 'TRCK': re.compile(r'^(?P\d+)(?:/(?P\d+))?$'), 'TPOS': re.compile(r'^(?P\d+)(?:/(?P\d+))?$') } def build_TXXX(self, encoding, desc, values): """Construct and return a TXXX frame.""" # This is here so that plugins can customize the behavior of TXXX # frames in particular via subclassing. # discussion: https://github.com/metabrainz/picard/pull/634 # discussion: https://github.com/metabrainz/picard/pull/635 # Used in the plugin "Compatible TXXX frames" # PR: https://github.com/metabrainz/picard-plugins/pull/83 return id3.TXXX(encoding=encoding, desc=desc, text=values) def _load(self, filename): log.debug("Loading file %r", filename) file = self._get_file(encode_filename(filename)) tags = file.tags or {} # upgrade custom 2.3 frames to 2.4 for old, new in self.__upgrade.items(): if old in tags and new not in tags: f = tags.pop(old) tags.add(getattr(id3, new)(encoding=f.encoding, text=f.text)) metadata = Metadata() for frame in tags.values(): frameid = frame.FrameID if frameid in self.__translate: name = self.__translate[frameid] if frameid.startswith('T'): for text in frame.text: if text: metadata.add(name, unicode(text)) elif frameid == 'COMM': for text in frame.text: if text: metadata.add('%s:%s' % (name, frame.desc), unicode(text)) else: metadata.add(name, unicode(frame)) elif frameid == "TMCL": for role, name in frame.people: if role or name: metadata.add('performer:%s' % role, name) elif frameid == "TIPL": # If file is ID3v2.3, TIPL tag could contain TMCL # so we will test for TMCL values and add to TIPL if not TMCL for role, name in frame.people: if role in self._tipl_roles and name: metadata.add(self._tipl_roles[role], name) else: metadata.add('performer:%s' % role, name) elif frameid == 'TXXX': name = frame.desc if name in self.__translate_freetext: name = self.__translate_freetext[name] elif ((name in self.__rtranslate) != (name in self.__rtranslate_freetext)): # If the desc of a TXXX frame conflicts with the name of a # Picard tag, load it into ~id3:TXXX:desc rather than desc. # # This basically performs an XOR, making sure that 'name' # is in __rtranslate or __rtranslate_freetext, but not # both. (Being in both implies we support reading it both # ways.) Currently, the only tag in both is license. name = '~id3:TXXX:' + name for text in frame.text: metadata.add(name, unicode(text)) elif frameid == 'USLT': name = 'lyrics' if frame.desc: name += ':%s' % frame.desc metadata.add(name, unicode(frame.text)) elif frameid == 'UFID' and frame.owner == 'http://musicbrainz.org': metadata['musicbrainz_recordingid'] = frame.data.decode('ascii', 'ignore') elif frameid in self.__tag_re_parse.keys(): m = self.__tag_re_parse[frameid].search(frame.text[0]) if m: for name, value in m.groupdict().iteritems(): if value is not None: metadata[name] = value else: log.error("Invalid %s value '%s' dropped in %r", frameid, frame.text[0], filename) elif frameid == 'APIC': try: coverartimage = TagCoverArtImage( file=filename, tag=frameid, types=types_from_id3(frame.type), comment=frame.desc, support_types=True, data=frame.data, ) except CoverArtImageError as e: log.error('Cannot load image from %r: %s' % (filename, e)) else: metadata.append_image(coverartimage) elif frameid == 'POPM': # Rating in ID3 ranges from 0 to 255, normalize this to the range 0 to 5 if frame.email == config.setting['rating_user_email']: rating = unicode(int(round(frame.rating / 255.0 * (config.setting['rating_steps'] - 1)))) metadata.add('~rating', rating) if 'date' in metadata: sanitized = sanitize_date(metadata.getall('date')[0]) if sanitized: metadata['date'] = sanitized self._info(metadata, file) return metadata def _save(self, filename, metadata): """Save metadata to the file.""" log.debug("Saving file %r", filename) tags = self._get_tags(filename) if config.setting['clear_existing_tags']: tags.clear() if metadata.images_to_be_saved_to_tags: tags.delall('APIC') encoding = {'utf-8': 3, 'utf-16': 1}.get(config.setting['id3v2_encoding'], 0) if 'tracknumber' in metadata: if 'totaltracks' in metadata: text = '%s/%s' % (metadata['tracknumber'], metadata['totaltracks']) else: text = metadata['tracknumber'] tags.add(id3.TRCK(encoding=0, text=id3text(text, 0))) if 'discnumber' in metadata: if 'totaldiscs' in metadata: text = '%s/%s' % (metadata['discnumber'], metadata['totaldiscs']) else: text = metadata['discnumber'] tags.add(id3.TPOS(encoding=0, text=id3text(text, 0))) # This is necessary because mutagens HashKey for APIC frames only # includes the FrameID (APIC) and description - it's basically # impossible to save two images, even of different types, without # any description. counters = defaultdict(lambda: 0) for image in metadata.images_to_be_saved_to_tags: desc = desctag = image.comment if counters[desc] > 0: if desc: desctag = "%s (%i)" % (desc, counters[desc]) else: desctag = "(%i)" % counters[desc] counters[desc] += 1 tags.add(id3.APIC(encoding=0, mime=image.mimetype, type=image_type_as_id3_num(image.maintype), desc=id3text(desctag, 0), data=image.data)) tmcl = mutagen.id3.TMCL(encoding=encoding, people=[]) tipl = mutagen.id3.TIPL(encoding=encoding, people=[]) tags.delall('TCMP') for name, values in metadata.rawitems(): values = [id3text(v, encoding) for v in values] name = id3text(name, encoding) if name.startswith('performer:'): role = name.split(':', 1)[1] for value in values: tmcl.people.append([role, value]) elif name.startswith('comment:'): desc = name.split(':', 1)[1] if desc.lower()[:4] == "itun": tags.delall('COMM:' + desc) tags.add(id3.COMM(encoding=0, desc=desc, lang='eng', text=[v + u'\x00' for v in values])) else: tags.add(id3.COMM(encoding=encoding, desc=desc, lang='eng', text=values)) elif name.startswith('lyrics:') or name == 'lyrics': if ':' in name: desc = name.split(':', 1)[1] else: desc = '' for value in values: tags.add(id3.USLT(encoding=encoding, desc=desc, text=value)) elif name in self._rtipl_roles: for value in values: tipl.people.append([self._rtipl_roles[name], value]) elif name == 'musicbrainz_recordingid': tags.add(id3.UFID(owner='http://musicbrainz.org', data=str(values[0]))) elif name == '~rating': # Search for an existing POPM frame to get the current playcount for frame in tags.values(): if frame.FrameID == 'POPM' and frame.email == config.setting['rating_user_email']: count = getattr(frame, 'count', 0) break else: count = 0 # Convert rating to range between 0 and 255 rating = int(round(float(values[0]) * 255 / (config.setting['rating_steps'] - 1))) tags.add(id3.POPM(email=config.setting['rating_user_email'], rating=rating, count=count)) elif name in self.__rtranslate: frameid = self.__rtranslate[name] if frameid.startswith('W'): valid_urls = all([all(urlparse(v)[:2]) for v in values]) if frameid == 'WCOP': # Only add WCOP if there is only one license URL, otherwise use TXXX:LICENSE if len(values) > 1 or not valid_urls: tags.add(self.build_TXXX(encoding, self.__rtranslate_freetext[name], values)) else: tags.add(id3.WCOP(url=values[0])) elif frameid == 'WOAR' and valid_urls: for url in values: tags.add(id3.WOAR(url=url)) elif frameid.startswith('T'): if config.setting['write_id3v23']: if frameid == 'TMOO': tags.add(self.build_TXXX(encoding, 'mood', values)) # No need to care about the TMOO tag being added again as it is # automatically deleted by Mutagen if id2v23 is selected tags.add(getattr(id3, frameid)(encoding=encoding, text=values)) if frameid == 'TSOA': tags.delall('XSOA') elif frameid == 'TSOP': tags.delall('XSOP') elif frameid == 'TSO2': tags.delall('TXXX:ALBUMARTISTSORT') elif name in self.__rtranslate_freetext: tags.add(self.build_TXXX(encoding, self.__rtranslate_freetext[name], values)) elif name.startswith('~id3:'): name = name[5:] if name.startswith('TXXX:'): tags.add(self.build_TXXX(encoding, name[5:], values)) else: frameclass = getattr(id3, name[:4], None) if frameclass: tags.add(frameclass(encoding=encoding, text=values)) # don't save private / already stored tags elif not name.startswith("~") and name not in self.__other_supported_tags: tags.add(self.build_TXXX(encoding, name, values)) tags.add(tmcl) tags.add(tipl) self._remove_deleted_tags(metadata, tags) self._save_tags(tags, encode_filename(filename)) if self._IsMP3 and config.setting["remove_ape_from_mp3"]: try: mutagen.apev2.delete(encode_filename(filename)) except: pass def _remove_deleted_tags(self, metadata, tags): """Remove the tags from the file that were deleted in the UI""" for name in metadata.deleted_tags: real_name = self._get_tag_name(name) try: if name.startswith('performer:'): role = name.split(':', 1)[1] for key, frame in tags.items(): if frame.FrameID in ('TMCL', 'TIPL', 'IPLS'): for people in frame.people: if people[0] == role: frame.people.remove(people) elif name.startswith('comment:'): desc = name.split(':', 1)[1] if desc.lower()[:4] != 'itun': for key, frame in tags.items(): if frame.FrameID == 'COMM' and frame.desc == desc: del tags[key] elif name.startswith('lyrics:') or name == 'lyrics': if ':' in name: desc = name.split(':', 1)[1] else: desc = '' for key, frame in tags.items(): if frame.FrameID == desc: del tags[key] elif name in self._rtipl_roles: role = self._rtipl_roles[name] for key, frame in tags.items(): if frame.FrameID in ('TIPL', 'IPLS'): for people in frame.people: if people[0] == role: frame.people.remove(people) elif name == 'musicbrainz_recordingid': for key, frame in tags.items(): if frame.FrameID == 'UFID' and frame.owner == 'http://musicbrainz.org': del tags[key] elif real_name == 'POPM': for key, frame in tags.items(): if frame.FrameID == 'POPM' and frame.email == config.setting['rating_user_email']: del tags[key] elif real_name in self.__translate: del tags[real_name] elif real_name in self.__translate_freetext: for key, frame in tags.items(): if frame.FrameID == 'TXXX' and frame.desc == real_name: del tags[key] elif not name.startswith("~") and name not in self.__other_supported_tags: for key, frame in tags.items(): if frame.FrameID == 'TXXX' and frame.desc == name: del tags[key] elif name.startswith("~"): name = name[1:] for key, frame in tags.items(): if frame.FrameID == 'TXXX' and frame.desc == name: del tags[key] elif name in self.__other_supported_tags: del tags[real_name] except KeyError: pass def supports_tag(self, name): return ((name and not name.startswith("~")) or name in ("~rating", "~length") or name.startswith("~id3")) def _get_tag_name(self, name): if name in self.__rtranslate: return self.__rtranslate[name] elif name in self.__rtranslate_freetext: return self.__rtranslate_freetext[name] elif name == '~rating': return 'POPM' elif name == 'tracknumber': return 'TRCK' elif name == 'discnumber': return 'TPOS' else: return None def _get_file(self, filename): raise NotImplementedError() def _get_tags(self, filename): try: return compatid3.CompatID3(encode_filename(filename)) except mutagen.id3.ID3NoHeaderError: return compatid3.CompatID3() def _save_tags(self, tags, filename): if config.setting['write_id3v1']: v1 = 2 else: v1 = 0 if config.setting['write_id3v23']: tags.update_to_v23() separator = config.setting['id3v23_join_with'] tags.save(filename, v2_version=3, v1=v1, v23_sep=separator) else: tags.update_to_v24() tags.save(filename, v2_version=4, v1=v1) @property def new_metadata(self): if not config.setting["write_id3v23"]: return self.metadata copy = Metadata() copy.copy(self.metadata) join_with = config.setting["id3v23_join_with"] copy.multi_valued_joiner = join_with for name, values in copy.rawitems(): # ID3v23 can only save TDOR dates in YYYY format. Mutagen cannot # handle ID3v23 dates which are YYYY-MM rather than YYYY or # YYYY-MM-DD. if name == "originaldate": values = [v[:4] for v in values] elif name == "date": values = [(v[:4] if len(v) < 10 else v) for v in values] # If this is a multi-valued field, then it needs to be flattened, # unless it's TIPL or TMCL which can still be multi-valued. if (len(values) > 1 and name not in ID3File._rtipl_roles and not name.startswith("performer:")): values = [join_with.join(values)] copy[name] = values return copy class MP3File(ID3File): """MP3 file.""" EXTENSIONS = [".mp3", ".mp2", ".m2a"] NAME = "MPEG-1 Audio" _IsMP3 = True _File = mutagen.mp3.MP3 def _get_file(self, filename): return mutagen.mp3.MP3(filename, ID3=compatid3.CompatID3) def _info(self, metadata, file): super(MP3File, self)._info(metadata, file) id3version = '' if file.tags is not None and file.info.layer == 3: id3version = ' - ID3v%d.%d' % (file.tags.version[0], file.tags.version[1]) metadata['~format'] = 'MPEG-1 Layer %d%s' % (file.info.layer, id3version) class TrueAudioFile(ID3File): """TTA file.""" EXTENSIONS = [".tta"] NAME = "The True Audio" _File = mutagen.trueaudio.TrueAudio def _get_file(self, filename): return mutagen.trueaudio.TrueAudio(filename, ID3=compatid3.CompatID3) def _info(self, metadata, file): super(TrueAudioFile, self)._info(metadata, file) metadata['~format'] = self.NAME if mutagen.aiff: class AiffFile(ID3File): """AIFF file.""" EXTENSIONS = [".aiff", ".aif", ".aifc"] NAME = "Audio Interchange File Format (AIFF)" _File = mutagen.aiff.AIFF def _get_file(self, filename): return mutagen.aiff.AIFF(filename) def _get_tags(self, filename): file = self._get_file(filename) if file.tags is None: file.add_tags() return file.tags def _save_tags(self, tags, filename): if config.setting['write_id3v23']: tags.update_to_v23() separator = config.setting['id3v23_join_with'] tags.save(filename, v2_version=3, v23_sep=separator) else: tags.update_to_v24() tags.save(filename, v2_version=4) def _info(self, metadata, file): super(AiffFile, self)._info(metadata, file) metadata['~format'] = self.NAME else: AiffFile = None picard-release-1.4.2/picard/formats/mp4.py000066400000000000000000000252571310410472100203710ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from mutagen.mp4 import MP4, MP4Cover from picard import config, log from picard.coverart.image import TagCoverArtImage, CoverArtImageError from picard.file import File from picard.metadata import Metadata from picard.util import encode_filename class MP4File(File): EXTENSIONS = [".m4a", ".m4b", ".m4p", ".m4v", ".mp4"] NAME = "MPEG-4 Audio" _File = MP4 __text_tags = { "\xa9ART": "artist", "\xa9nam": "title", "\xa9alb": "album", "\xa9wrt": "composer", "aART": "albumartist", "\xa9grp": "grouping", "\xa9day": "date", "\xa9gen": "genre", "\xa9lyr": "lyrics", "\xa9cmt": "comment:", "\xa9too": "encodedby", "cprt": "copyright", "soal": "albumsort", "soaa": "albumartistsort", "soar": "artistsort", "sonm": "titlesort", "soco": "composersort", "sosn": "showsort", "tvsh": "show", "purl": "podcasturl", } __r_text_tags = dict([(v, k) for k, v in __text_tags.iteritems()]) __bool_tags = { "pcst": "podcast", "cpil": "compilation", "pgap": "gapless", } __r_bool_tags = dict([(v, k) for k, v in __bool_tags.iteritems()]) __int_tags = { "tmpo": "bpm", } __r_int_tags = dict([(v, k) for k, v in __int_tags.iteritems()]) __freeform_tags = { "----:com.apple.iTunes:MusicBrainz Track Id": "musicbrainz_recordingid", "----:com.apple.iTunes:MusicBrainz Artist Id": "musicbrainz_artistid", "----:com.apple.iTunes:MusicBrainz Album Id": "musicbrainz_albumid", "----:com.apple.iTunes:MusicBrainz Album Artist Id": "musicbrainz_albumartistid", "----:com.apple.iTunes:MusicIP PUID": "musicip_puid", "----:com.apple.iTunes:MusicBrainz Album Status": "releasestatus", "----:com.apple.iTunes:MusicBrainz Album Release Country": "releasecountry", "----:com.apple.iTunes:MusicBrainz Album Type": "releasetype", "----:com.apple.iTunes:MusicBrainz Disc Id": "musicbrainz_discid", "----:com.apple.iTunes:MusicBrainz TRM Id": "musicbrainz_trmid", "----:com.apple.iTunes:MusicBrainz Work Id": "musicbrainz_workid", "----:com.apple.iTunes:MusicBrainz Release Group Id": "musicbrainz_releasegroupid", "----:com.apple.iTunes:MusicBrainz Release Track Id": "musicbrainz_trackid", "----:com.apple.iTunes:Acoustid Fingerprint": "acoustid_fingerprint", "----:com.apple.iTunes:Acoustid Id": "acoustid_id", "----:com.apple.iTunes:ASIN": "asin", "----:com.apple.iTunes:BARCODE": "barcode", "----:com.apple.iTunes:PRODUCER": "producer", "----:com.apple.iTunes:LYRICIST": "lyricist", "----:com.apple.iTunes:CONDUCTOR": "conductor", "----:com.apple.iTunes:ENGINEER": "engineer", "----:com.apple.iTunes:MIXER": "mixer", "----:com.apple.iTunes:DJMIXER": "djmixer", "----:com.apple.iTunes:REMIXER": "remixer", "----:com.apple.iTunes:ISRC": "isrc", "----:com.apple.iTunes:MEDIA": "media", "----:com.apple.iTunes:LABEL": "label", "----:com.apple.iTunes:LICENSE": "license", "----:com.apple.iTunes:CATALOGNUMBER": "catalognumber", "----:com.apple.iTunes:SUBTITLE": "subtitle", "----:com.apple.iTunes:DISCSUBTITLE": "discsubtitle", "----:com.apple.iTunes:MOOD": "mood", "----:com.apple.iTunes:SCRIPT": "script", "----:com.apple.iTunes:LANGUAGE": "language", "----:com.apple.iTunes:ARTISTS": "artists", "----:com.apple.iTunes:WORK": "work", "----:com.apple.iTunes:initialkey": "key", } __r_freeform_tags = dict([(v, k) for k, v in __freeform_tags.iteritems()]) __other_supported_tags = ("discnumber", "tracknumber", "totaldiscs", "totaltracks") def _load(self, filename): log.debug("Loading file %r", filename) file = MP4(encode_filename(filename)) tags = file.tags if tags is None: file.add_tags() metadata = Metadata() for name, values in tags.items(): if name in self.__text_tags: for value in values: metadata.add(self.__text_tags[name], value) elif name in self.__bool_tags: metadata.add(self.__bool_tags[name], values and '1' or '0') elif name in self.__int_tags: for value in values: metadata.add(self.__int_tags[name], unicode(value)) elif name in self.__freeform_tags: for value in values: value = value.strip("\x00").decode("utf-8", "replace") metadata.add(self.__freeform_tags[name], value) elif name == "----:com.apple.iTunes:fingerprint": for value in values: value = value.strip("\x00").decode("utf-8", "replace") if value.startswith("MusicMagic Fingerprint"): metadata.add("musicip_fingerprint", value[22:]) elif name == "trkn": metadata["tracknumber"] = str(values[0][0]) metadata["totaltracks"] = str(values[0][1]) elif name == "disk": metadata["discnumber"] = str(values[0][0]) metadata["totaldiscs"] = str(values[0][1]) elif name == "covr": for value in values: if value.imageformat not in (value.FORMAT_JPEG, value.FORMAT_PNG): continue try: coverartimage = TagCoverArtImage( file=filename, tag=name, data=value, ) except CoverArtImageError as e: log.error('Cannot load image from %r: %s' % (filename, e)) else: metadata.append_image(coverartimage) self._info(metadata, file) return metadata def _save(self, filename, metadata): log.debug("Saving file %r", filename) file = MP4(encode_filename(self.filename)) tags = file.tags if tags is None: file.add_tags() if config.setting["clear_existing_tags"]: tags.clear() for name, values in metadata.rawitems(): if name.startswith('lyrics:'): name = 'lyrics' if name in self.__r_text_tags: tags[self.__r_text_tags[name]] = values elif name in self.__r_bool_tags: tags[self.__r_bool_tags[name]] = (values[0] == '1') elif name in self.__r_int_tags: try: tags[self.__r_int_tags[name]] = [int(value) for value in values] except ValueError: pass elif name in self.__r_freeform_tags: values = [v.encode("utf-8") for v in values] tags[self.__r_freeform_tags[name]] = values elif name == "musicip_fingerprint": tags["----:com.apple.iTunes:fingerprint"] = ["MusicMagic Fingerprint%s" % str(v) for v in values] if "tracknumber" in metadata: if "totaltracks" in metadata: tags["trkn"] = [(int(metadata["tracknumber"]), int(metadata["totaltracks"]))] else: tags["trkn"] = [(int(metadata["tracknumber"]), 0)] if "discnumber" in metadata: if "totaldiscs" in metadata: tags["disk"] = [(int(metadata["discnumber"]), int(metadata["totaldiscs"]))] else: tags["disk"] = [(int(metadata["discnumber"]), 0)] covr = [] for image in metadata.images_to_be_saved_to_tags: if image.mimetype == "image/jpeg": covr.append(MP4Cover(image.data, MP4Cover.FORMAT_JPEG)) elif image.mimetype == "image/png": covr.append(MP4Cover(image.data, MP4Cover.FORMAT_PNG)) if covr: tags["covr"] = covr self._remove_deleted_tags(metadata, tags) file.save() def _remove_deleted_tags(self, metadata, tags): """Remove the tags from the file that were deleted in the UI""" for tag in metadata.deleted_tags: real_name = self._get_tag_name(tag) if real_name and real_name in tags: if tag not in ("totaltracks", "totaldiscs"): del tags[real_name] def supports_tag(self, name): return (name in self.__r_text_tags or name in self.__r_bool_tags or name in self.__r_freeform_tags or name in self.__other_supported_tags or name.startswith('lyrics:') or name in ('~length', 'musicip_fingerprint')) def _get_tag_name(self, name): if name.startswith('lyrics:'): return 'lyrics' if name in self.__r_text_tags: return self.__r_text_tags[name] elif name in self.__r_bool_tags: return self.__r_bool_tags[name] elif name in self.__r_int_tags: return self.__r_int_tags[name] elif name in self.__r_freeform_tags: return self.__r_freeform_tags[name] elif name == "musicip_fingerprint": return "----:com.apple.iTunes:fingerprint" elif name in ("tracknumber", "totaltracks"): return "trkn" elif name in ("discnumber", "totaldiscs"): return "disk" else: return None def _info(self, metadata, file): super(MP4File, self)._info(metadata, file) if hasattr(file.info, 'codec_description') and file.info.codec_description: metadata['~format'] = "%s (%s)" % (metadata['~format'], file.info.codec_description) picard-release-1.4.2/picard/formats/mutagenext/000077500000000000000000000000001310410472100214655ustar00rootroot00000000000000picard-release-1.4.2/picard/formats/mutagenext/__init__.py000066400000000000000000000000001310410472100235640ustar00rootroot00000000000000picard-release-1.4.2/picard/formats/mutagenext/compatid3.py000066400000000000000000000041151310410472100237230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # Copyright (C) 2005 Michael Urman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from mutagen.id3 import ID3, Frames, Frames_2_2, TextFrame class TCMP(TextFrame): pass class TSO2(TextFrame): pass class TSOC(TextFrame): pass class XDOR(TextFrame): pass class XSOP(TextFrame): pass class CompatID3(ID3): """ Additional features over mutagen.id3.ID3: * iTunes' TCMP frame * Allow some v2.4 frames also in v2.3 """ PEDANTIC = False def __init__(self, *args, **kwargs): if args: known_frames = dict(Frames) known_frames.update(dict(Frames_2_2)) known_frames["TCMP"] = TCMP known_frames["TSO2"] = TSO2 known_frames["TSOC"] = TSOC known_frames["XDOR"] = XDOR known_frames["XSOP"] = XSOP kwargs["known_frames"] = known_frames super(CompatID3, self).__init__(*args, **kwargs) def update_to_v23(self): # leave TSOP, TSOA and TSOT even though they are officially defined # only in ID3v2.4, because most applications use them also in ID3v2.3 frames = [] for key in ["TSOP", "TSOA", "TSOT", "TSST"]: frames.extend(self.getall(key)) super(CompatID3, self).update_to_v23() for frame in frames: self.add(frame) picard-release-1.4.2/picard/formats/mutagenext/tak.py000066400000000000000000000024061310410472100226200ustar00rootroot00000000000000# Tom's lossless Audio Kompressor (TAK) reader/tagger # # Copyright 2008 Lukas Lalinsky # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # $$ """Tom's lossless Audio Kompressor streams with APEv2 tags. TAK is a lossless audio compressor developed by Thomas Becker. For more information, see http://wiki.hydrogenaudio.org/index.php?title=TAK and http://en.wikipedia.org/wiki/TAK_(audio_codec) """ __all__ = ["TAK", "Open", "delete"] from mutagen.apev2 import APEv2File, error, delete class TAKHeaderError(error): pass class TAKInfo(object): """TAK stream information. Attributes: (none at the moment) """ def __init__(self, fileobj): header = fileobj.read(4) if len(header) != 4 or not header.startswith("tBaK"): raise TAKHeaderError("not a TAK file") def pprint(self): return "Tom's lossless Audio Kompressor" class TAK(APEv2File): _Info = TAKInfo _mimes = ["audio/x-tak"] def score(filename, fileobj, header): return header.startswith("tBaK") + filename.lower().endswith(".tak") score = staticmethod(score) Open = TAK picard-release-1.4.2/picard/formats/vorbis.py000066400000000000000000000313641310410472100211710ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import base64 import re import mutagen.flac import mutagen.ogg import mutagen.oggflac import mutagen.oggspeex import mutagen.oggtheora import mutagen.oggvorbis try: from mutagen.oggopus import OggOpus with_opus = True except ImportError: OggOpus = None with_opus = False from picard import config, log from picard.coverart.image import TagCoverArtImage, CoverArtImageError from picard.file import File from picard.formats.id3 import types_from_id3, image_type_as_id3_num from picard.metadata import Metadata from picard.util import encode_filename, sanitize_date from picard.formats import guess_format class VCommentFile(File): """Generic VComment-based file.""" _File = None __translate = { "musicbrainz_trackid": "musicbrainz_recordingid", "musicbrainz_releasetrackid": "musicbrainz_trackid", } __rtranslate = dict([(v, k) for k, v in __translate.iteritems()]) def _load(self, filename): log.debug("Loading file %r", filename) file = self._File(encode_filename(filename)) file.tags = file.tags or {} metadata = Metadata() for origname, values in file.tags.items(): for value in values: name = origname if name == "date" or name == "originaldate": # YYYY-00-00 => YYYY value = sanitize_date(value) elif name == 'performer' or name == 'comment': # transform "performer=Joe Barr (Piano)" to "performer:Piano=Joe Barr" name += ':' if value.endswith(')'): start = len(value) - 2 count = 1 while count > 0 and start > 0: if value[start] == ')': count += 1 elif value[start] == '(': count -= 1 start -= 1 if start > 0: name += value[start + 2:-1] value = value[:start] elif name.startswith('rating'): try: name, email = name.split(':', 1) except ValueError: email = '' if email != config.setting['rating_user_email']: continue name = '~rating' value = unicode(int(round((float(value) * (config.setting['rating_steps'] - 1))))) elif name == "fingerprint" and value.startswith("MusicMagic Fingerprint"): name = "musicip_fingerprint" value = value[22:] elif name == "tracktotal": if "totaltracks" in file.tags: continue name = "totaltracks" elif name == "disctotal": if "totaldiscs" in file.tags: continue name = "totaldiscs" elif name == "metadata_block_picture": image = mutagen.flac.Picture(base64.standard_b64decode(value)) try: coverartimage = TagCoverArtImage( file=filename, tag=name, types=types_from_id3(image.type), comment=image.desc, support_types=True, data=image.data, ) except CoverArtImageError as e: log.error('Cannot load image from %r: %s' % (filename, e)) else: metadata.append_image(coverartimage) continue elif name in self.__translate: name = self.__translate[name] metadata.add(name, value) if self._File == mutagen.flac.FLAC: for image in file.pictures: try: coverartimage = TagCoverArtImage( file=filename, tag='FLAC/PICTURE', types=types_from_id3(image.type), comment=image.desc, support_types=True, data=image.data, ) except CoverArtImageError as e: log.error('Cannot load image from %r: %s' % (filename, e)) else: metadata.append_image(coverartimage) # Read the unofficial COVERART tags, for backward compatibillity only if "metadata_block_picture" not in file.tags: try: for data in file["COVERART"]: try: coverartimage = TagCoverArtImage( file=filename, tag='COVERART', data=base64.standard_b64decode(data) ) except CoverArtImageError as e: log.error('Cannot load image from %r: %s' % (filename, e)) else: metadata.append_image(coverartimage) except KeyError: pass self._info(metadata, file) return metadata def _save(self, filename, metadata): """Save metadata to the file.""" log.debug("Saving file %r", filename) is_flac = self._File == mutagen.flac.FLAC file = self._File(encode_filename(filename)) if file.tags is None: file.add_tags() if config.setting["clear_existing_tags"]: file.tags.clear() if (is_flac and (config.setting["clear_existing_tags"] or metadata.images_to_be_saved_to_tags)): file.clear_pictures() tags = {} for name, value in metadata.items(): if name == '~rating': # Save rating according to http://code.google.com/p/quodlibet/wiki/Specs_VorbisComments if config.setting['rating_user_email']: name = 'rating:%s' % config.setting['rating_user_email'] else: name = 'rating' value = unicode(float(value) / (config.setting['rating_steps'] - 1)) # don't save private tags elif name.startswith("~"): continue elif name.startswith('lyrics:'): name = 'lyrics' elif name == "date" or name == "originaldate": # YYYY-00-00 => YYYY value = sanitize_date(value) elif name.startswith('performer:') or name.startswith('comment:'): # transform "performer:Piano=Joe Barr" to "performer=Joe Barr (Piano)" name, desc = name.split(':', 1) if desc: value += ' (%s)' % desc elif name == "musicip_fingerprint": name = "fingerprint" value = "MusicMagic Fingerprint%s" % value elif name in self.__rtranslate: name = self.__rtranslate[name] tags.setdefault(name.upper().encode('utf-8'), []).append(value) if "totaltracks" in metadata: tags.setdefault(u"TRACKTOTAL", []).append(metadata["totaltracks"]) if "totaldiscs" in metadata: tags.setdefault(u"DISCTOTAL", []).append(metadata["totaldiscs"]) for image in metadata.images_to_be_saved_to_tags: picture = mutagen.flac.Picture() picture.data = image.data picture.mime = image.mimetype picture.desc = image.comment picture.type = image_type_as_id3_num(image.maintype) if self._File == mutagen.flac.FLAC: file.add_picture(picture) else: tags.setdefault(u"METADATA_BLOCK_PICTURE", []).append( base64.standard_b64encode(picture.write())) file.tags.update(tags) self._remove_deleted_tags(metadata, file.tags) kwargs = {} if is_flac and config.setting["remove_id3_from_flac"]: kwargs["deleteid3"] = True try: file.save(**kwargs) except TypeError: file.save() def _remove_deleted_tags(self, metadata, tags): """Remove the tags from the file that were deleted in the UI""" for tag in metadata.deleted_tags: real_name = self._get_tag_name(tag) if real_name and real_name in tags: if real_name in ('performer', 'comment'): tag_type = r"\(%s\)" % tag.split(':', 1)[1] for item in tags.get(real_name): if re.search(tag_type, item): tags.get(real_name).remove(item) else: if tag in ('totaldiscs', 'totaltracks'): # both tag and real_name are to be deleted in this case del tags[tag] del tags[real_name] def _get_tag_name(self, name): if name == '~rating': if config.setting['rating_user_email']: return 'rating:%s' % config.setting['rating_user_email'] else: return 'rating' elif name.startswith("~"): return None elif name.startswith('lyrics:'): return 'lyrics' elif name.startswith('performer:') or name.startswith('comment:'): return name.split(':', 1)[0] elif name == 'musicip_fingerprint': return 'fingerprint' elif name == 'totaltracks': return 'tracktotal' elif name == 'totaldiscs': return 'disctotal' elif name in self.__rtranslate: return self.__rtranslate[name] else: return name def supports_tag(self, name): return bool(name) class FLACFile(VCommentFile): """FLAC file.""" EXTENSIONS = [".flac"] NAME = "FLAC" _File = mutagen.flac.FLAC def _info(self, metadata, file): super(FLACFile, self)._info(metadata, file) metadata['~format'] = self.NAME class OggFLACFile(VCommentFile): """FLAC file.""" EXTENSIONS = [".oggflac"] NAME = "Ogg FLAC" _File = mutagen.oggflac.OggFLAC def _info(self, metadata, file): super(OggFLACFile, self)._info(metadata, file) metadata['~format'] = self.NAME class OggSpeexFile(VCommentFile): """Ogg Speex file.""" EXTENSIONS = [".spx"] NAME = "Speex" _File = mutagen.oggspeex.OggSpeex def _info(self, metadata, file): super(OggSpeexFile, self)._info(metadata, file) metadata['~format'] = self.NAME class OggTheoraFile(VCommentFile): """Ogg Theora file.""" EXTENSIONS = [".oggtheora"] NAME = "Ogg Theora" _File = mutagen.oggtheora.OggTheora def _info(self, metadata, file): super(OggTheoraFile, self)._info(metadata, file) metadata['~format'] = self.NAME class OggVorbisFile(VCommentFile): """Ogg Vorbis file.""" EXTENSIONS = [".ogg"] NAME = "Ogg Vorbis" _File = mutagen.oggvorbis.OggVorbis def _info(self, metadata, file): super(OggVorbisFile, self)._info(metadata, file) metadata['~format'] = self.NAME class OggOpusFile(VCommentFile): """Ogg Opus file.""" EXTENSIONS = [".opus"] NAME = "Ogg Opus" _File = OggOpus def _info(self, metadata, file): super(OggOpusFile, self)._info(metadata, file) metadata['~format'] = self.NAME def OggAudioFile(filename): """Generic Ogg audio file.""" options = [OggFLACFile, OggSpeexFile, OggVorbisFile] return guess_format(filename, options) OggAudioFile.EXTENSIONS = [".oga"] OggAudioFile.NAME = "Ogg Audio" def OggVideoFile(filename): """Generic Ogg video file.""" options = [OggTheoraFile] return guess_format(filename, options) OggVideoFile.EXTENSIONS = [".ogv"] OggVideoFile.NAME = "Ogg Video" picard-release-1.4.2/picard/formats/wav.py000066400000000000000000000032021310410472100204500ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import wave from picard import log from picard.file import File from picard.metadata import Metadata from picard.util import encode_filename class WAVFile(File): EXTENSIONS = [".wav"] NAME = "Microsoft WAVE" _File = None def _load(self, filename): log.debug("Loading file %r", filename) f = wave.open(encode_filename(filename), "rb") metadata = Metadata() metadata['~channels'] = f.getnchannels() metadata['~bits_per_sample'] = f.getsampwidth() * 8 metadata['~sample_rate'] = f.getframerate() metadata.length = 1000 * f.getnframes() / f.getframerate() metadata['~format'] = 'Microsoft WAVE' self._add_path_to_metadata(metadata) return metadata def _save(self, filename, metadata): log.debug("Saving file %r", filename) pass picard-release-1.4.2/picard/i18n.py000066400000000000000000000110341310410472100167610ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2013 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import gettext import locale import os.path import sys import __builtin__ __builtin__.__dict__['N_'] = lambda a: a def setup_gettext(localedir, ui_language=None, logger=None): """Setup locales, load translations, install gettext functions.""" if not logger: logger = lambda *a, **b: None # noop current_locale = '' if ui_language: os.environ['LANGUAGE'] = '' os.environ['LANG'] = ui_language try: current_locale = locale.normalize(ui_language + '.' + locale.getpreferredencoding()) locale.setlocale(locale.LC_ALL, current_locale) except: pass if sys.platform == "win32": from ctypes import windll try: current_locale = locale.windows_locale[windll.kernel32.GetUserDefaultUILanguage()] locale.setlocale(locale.LC_ALL, current_locale) except KeyError: os.environ["LANG"] = locale.getdefaultlocale()[0] try: current_locale = locale.setlocale(locale.LC_ALL, "") except: pass except: pass elif not ui_language: if sys.platform == "darwin": try: import Foundation defaults = Foundation.NSUserDefaults.standardUserDefaults() os.environ["LANG"] = \ defaults.objectForKey_("AppleLanguages")[0] except: pass try: current_locale = locale.setlocale(locale.LC_ALL, "") except: pass logger("Using locale %r", current_locale) try: logger("Loading gettext translation, localedir=%r", localedir) trans = gettext.translation("picard", localedir) trans.install(True) _ungettext = trans.ungettext logger("Loading gettext translation (picard-countries), localedir=%r", localedir) trans_countries = gettext.translation("picard-countries", localedir) _ugettext_countries = trans_countries.ugettext logger("Loading gettext translation (picard-attributes), localedir=%r", localedir) trans_attributes = gettext.translation("picard-attributes", localedir) _ugettext_attributes = trans_attributes.ugettext except IOError as e: logger(e) __builtin__.__dict__['_'] = lambda a: a def _ungettext(a, b, c): if c == 1: return a else: return b def _ugettext_countries(msg): return msg def _ugettext_attributes(msg): return msg __builtin__.__dict__['ungettext'] = _ungettext __builtin__.__dict__['ugettext_countries'] = _ugettext_countries __builtin__.__dict__['ugettext_attributes'] = _ugettext_attributes logger("_ = %r", _) logger("N_ = %r", N_) logger("ungettext = %r", ungettext) logger("ugettext_countries = %r", ugettext_countries) logger("ugettext_attributes = %r", ugettext_attributes) # Workaround for po files with msgctxt which isn't supported by current python # gettext # msgctxt are used within attributes.po, and ugettext is failing to translate # strings due to that # This workaround is a hack until we get proper msgctxt support _CONTEXT_SEPARATOR = "\x04" def ugettext_ctxt(ugettext_, message, context=None): if context is None: return ugettext_(message) msg_with_ctxt = u"%s%s%s" % (context, _CONTEXT_SEPARATOR, message) translated = ugettext_(msg_with_ctxt) if _CONTEXT_SEPARATOR in translated: # no translation found, return original message return message return translated def ugettext_attr(message, context=None): """Translate MB attributes, depending on context""" return ugettext_ctxt(ugettext_attributes, message, context) picard-release-1.4.2/picard/log.py000066400000000000000000000067301310410472100167720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from __future__ import print_function import sys import os from collections import deque from PyQt4 import QtCore from picard.util import thread LOG_INFO = 1 LOG_WARNING = 2 LOG_ERROR = 4 LOG_DEBUG = 8 class Logger(object): def __init__(self, maxlen=0): self._receivers = [] self.maxlen = maxlen self.reset() def reset(self): if self.maxlen > 0: self.entries = deque(maxlen=self.maxlen) else: self.entries = [] def register_receiver(self, receiver): self._receivers.append(receiver) def unregister_receiver(self, receiver): self._receivers.remove(receiver) def message(self, level, message, *args): if not self.log_level(level): return if not (isinstance(message, str) or isinstance(message, unicode)): message = repr(message) if args: message = message % args time = QtCore.QTime.currentTime() message = "%s" % (message,) self.entries.append((level, time, message)) for func in self._receivers: try: thread.to_main(func, level, time, message) except: import traceback traceback.print_exc() def log_level(self, level): return True # main logger log_levels = LOG_INFO | LOG_WARNING | LOG_ERROR main_logger = Logger(50000) main_logger.log_level = lambda level: log_levels & level def debug(message, *args): main_logger.message(LOG_DEBUG, message, *args) def info(message, *args): main_logger.message(LOG_INFO, message, *args) def warning(message, *args): main_logger.message(LOG_WARNING, message, *args) def error(message, *args): main_logger.message(LOG_ERROR, message, *args) _log_prefixes = { LOG_INFO: 'I', LOG_WARNING: 'W', LOG_ERROR: 'E', LOG_DEBUG: 'D', } def formatted_log_line(level, time, message, timefmt='hh:mm:ss', level_prefixes=_log_prefixes, format='%s %s'): msg = format % (time.toString(timefmt), message) if level_prefixes: return "%s: %s" % (level_prefixes[level], msg) else: return msg def _stderr_receiver(level, time, msg): try: sys.stderr.write(formatted_log_line(level, time, msg) + os.linesep) except (UnicodeDecodeError, UnicodeEncodeError): sys.stderr.write(formatted_log_line(level, time, msg, format='%s %r') + os.linesep) main_logger.register_receiver(_stderr_receiver) # history of status messages history_logger = Logger(50000) history_logger.log_level = lambda level: log_levels & level def history_info(message, *args): history_logger.message(LOG_INFO, message, *args) picard-release-1.4.2/picard/mbxml.py000066400000000000000000000450051310410472100173260ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import re from picard import config from picard.util import (format_time, translate_from_sortname, parse_amazon_url, linear_combination_of_weights) from picard.const import RELEASE_FORMATS _artist_rel_types = { "composer": "composer", "writer": "writer", "conductor": "conductor", "chorus master": "conductor", "performing orchestra": "performer:orchestra", "arranger": "arranger", "orchestrator": "arranger", "instrumentator": "arranger", "lyricist": "lyricist", "librettist": "lyricist", "remixer": "remixer", "producer": "producer", "engineer": "engineer", "audio": "engineer", #"Mastering": "engineer", "sound": "engineer", "live sound": "engineer", "mix": "mixer", #"Recording": "engineer", "mix-DJ": "djmixer", } def _decamelcase(text): return re.sub(r'([A-Z])', r' \1', text).strip() _REPLACE_MAP = {} _EXTRA_ATTRS = ['guest', 'additional', 'minor'] _BLANK_SPECIAL_RELTYPES = {'vocal': 'vocals'} def _parse_attributes(attrs, reltype): attrs = [_decamelcase(_REPLACE_MAP.get(a, a)) for a in attrs] prefix = ' '.join([a for a in attrs if a in _EXTRA_ATTRS]) attrs = [a for a in attrs if a not in _EXTRA_ATTRS] if len(attrs) > 1: attrs = '%s and %s' % (', '.join(attrs[:-1]), attrs[-1:][0]) elif len(attrs) == 1: attrs = attrs[0] else: attrs = _BLANK_SPECIAL_RELTYPES.get(reltype, '') return ' '.join([prefix, attrs]).strip().lower() def _relations_to_metadata(relation_lists, m): use_credited_as = not config.setting["standardize_artists"] for relation_list in relation_lists: if relation_list.target_type == 'artist': for relation in relation_list.relation: artist = relation.artist[0] value, valuesort = _translate_artist_node(artist) has_translation = (value != artist.name[0].text) if not has_translation and use_credited_as and 'target_credit' in relation.children: credited_as = relation.target_credit[0].text if credited_as: value, valuesort = credited_as, credited_as reltype = relation.type attribs = [] if 'attribute_list' in relation.children: attribs = [a.text for a in relation.attribute_list[0].attribute] if reltype in ('vocal', 'instrument', 'performer'): name = 'performer:' + _parse_attributes(attribs, reltype) elif reltype == 'mix-DJ' and len(attribs) > 0: if not hasattr(m, "_djmix_ars"): m._djmix_ars = {} for attr in attribs: m._djmix_ars.setdefault(attr.split()[1], []).append(value) continue else: try: name = _artist_rel_types[reltype] except KeyError: continue if value not in m[name]: m.add(name, value) if name == 'composer' and valuesort not in m['composersort']: m.add('composersort', valuesort) elif relation_list.target_type == 'work': for relation in relation_list.relation: if relation.type == 'performance': performance_to_metadata(relation, m) work_to_metadata(relation.work[0], m) elif relation_list.target_type == 'url': for relation in relation_list.relation: if relation.type == 'amazon asin' and 'asin' not in m: amz = parse_amazon_url(relation.target[0].text) if amz is not None: m['asin'] = amz['asin'] elif relation.type == 'license': url = relation.target[0].text m.add('license', url) def _translate_artist_node(node): transl, translsort = None, None if config.setting['translate_artist_names']: locale = config.setting["artist_locale"] lang = locale.split("_")[0] if "alias_list" in node.children: result = (-1, (None, None)) for alias in node.alias_list[0].alias: if alias.attribs.get("primary") != "primary": continue if "locale" not in alias.attribs: continue parts = [] if alias.locale == locale: score = 0.8 elif alias.locale == lang: score = 0.6 elif alias.locale.split("_")[0] == lang: score = 0.4 else: continue parts.append((score, 5)) if alias.attribs.get("type") == u"Artist name": score = 0.8 elif alias.attribs.get("type") == u"Legal Name": score = 0.5 else: # as 2014/09/19, only Artist or Legal names should have the # Primary flag score = 0.0 parts.append((score, 5)) comb = linear_combination_of_weights(parts) if comb > result[0]: result = (comb, (alias.text, alias.attribs["sort_name"])) transl, translsort = result[1] if not transl: translsort = node.sort_name[0].text transl = translate_from_sortname(node.name[0].text, translsort) else: transl, translsort = node.name[0].text, node.sort_name[0].text return (transl, translsort) def artist_credit_from_node(node): artist = "" artistsort = "" artists = [] artistssort = [] use_credited_as = not config.setting["standardize_artists"] for credit in node.name_credit: a = credit.artist[0] translated, translated_sort = _translate_artist_node(a) has_translation = (translated != a.name[0].text) if has_translation: name = translated elif use_credited_as and 'name' in credit.children: name = credit.name[0].text else: name = a.name[0].text artist += name artistsort += translated_sort artists.append(name) artistssort.append(translated_sort) if 'joinphrase' in credit.attribs: artist += credit.joinphrase artistsort += credit.joinphrase return (artist, artistsort, artists, artistssort) def artist_credit_to_metadata(node, m, release=False): ids = [n.artist[0].id for n in node.name_credit] artist, artistsort, artists, artistssort = artist_credit_from_node(node) if release: m["musicbrainz_albumartistid"] = ids m["albumartist"] = artist m["albumartistsort"] = artistsort m["~albumartists"] = artists m["~albumartists_sort"] = artistssort else: m["musicbrainz_artistid"] = ids m["artist"] = artist m["artistsort"] = artistsort m["artists"] = artists m["~artists_sort"] = artistssort def country_list_from_node(node): """Extract list of country codes from `release_event_list` node. This is in contrast with `country` element, which has single release event information. """ if "release_event_list" in node.children: country = [] for release_event in node.release_event_list[0].release_event: try: country.append( release_event.area[0].iso_3166_1_code_list[0].iso_3166_1_code[0].text) except AttributeError: pass return country def label_info_from_node(node): labels = [] catalog_numbers = [] if node.children: for label_info in node.label_info: if 'label' in label_info.children and label_info.label[0].children: label = label_info.label[0].name[0].text if label not in labels: labels.append(label) if 'catalog_number' in label_info.children: cat_num = label_info.catalog_number[0].text if cat_num not in catalog_numbers: catalog_numbers.append(cat_num) return (labels, catalog_numbers) def media_formats_from_node(node): formats_count = {} formats_order = [] for medium in node.medium: if "format" in medium.children: text = medium.format[0].text else: text = "(unknown)" if text in formats_count: formats_count[text] += 1 else: formats_count[text] = 1 formats_order.append(text) formats = [] for format in formats_order: count = formats_count[format] format = RELEASE_FORMATS.get(format, format) if count > 1: format = str(count) + u"×" + format formats.append(format) return " + ".join(formats) def track_to_metadata(node, track): m = track.metadata recording_to_metadata(node.recording[0], m, track) m.add_unique('musicbrainz_trackid', node.id) # overwrite with data we have on the track for name, nodes in node.children.iteritems(): if not nodes: continue if name == 'title': m['title'] = nodes[0].text elif name == 'position': m['tracknumber'] = nodes[0].text elif name == 'number': m['~musicbrainz_tracknumber'] = nodes[0].text elif name == 'length' and nodes[0].text: m.length = int(nodes[0].text) elif name == 'artist_credit': artist_credit_to_metadata(nodes[0], m) m['~length'] = format_time(m.length) def recording_to_metadata(node, m, track=None): m.length = 0 m.add_unique('musicbrainz_recordingid', node.id) for name, nodes in node.children.iteritems(): if not nodes: continue if name == 'title': m['title'] = nodes[0].text m['~recordingtitle'] = nodes[0].text elif name == 'length' and nodes[0].text: m.length = int(nodes[0].text) elif name == 'disambiguation': m['~recordingcomment'] = nodes[0].text elif name == 'artist_credit': artist_credit_to_metadata(nodes[0], m) if 'name_credit' in nodes[0].children and track: for name_credit in nodes[0].name_credit: if 'artist' in name_credit.children: for artist in name_credit.artist: trackartist = track.append_track_artist(artist.id) if 'tag_list' in artist.children: add_folksonomy_tags(artist.tag_list[0], trackartist) if 'user_tag_list' in artist.children: add_user_folksonomy_tags(artist.user_tag_list[0], trackartist) elif name == 'relation_list': _relations_to_metadata(nodes, m) elif name == 'tag_list': add_folksonomy_tags(nodes[0], track) elif name == 'user_tag_list': add_user_folksonomy_tags(nodes[0], track) elif name == 'isrc_list': add_isrcs_to_metadata(nodes[0], m) elif name == 'user_rating': m['~rating'] = nodes[0].text elif name == 'video' and nodes[0].text == 'true': m['~video'] = '1' m['~length'] = format_time(m.length) def performance_to_metadata(relation, m): if 'attribute_list' in relation.children: if 'attribute' in relation.attribute_list[0].children: for attribute in relation.attribute_list[0].attribute: m.add_unique("~performance_attributes", attribute.text) def work_to_metadata(work, m): m.add_unique("musicbrainz_workid", work.id) if 'language' in work.children: m.add_unique("language", work.language[0].text) if 'title' in work.children: m.add_unique("work", work.title[0].text) if 'relation_list' in work.children: _relations_to_metadata(work.relation_list, m) def medium_to_metadata(node, m): for name, nodes in node.children.iteritems(): if not nodes: continue if name == 'position': m['discnumber'] = nodes[0].text elif name == 'track_list': m['totaltracks'] = nodes[0].count elif name == 'title': m['discsubtitle'] = nodes[0].text elif name == 'format': m['media'] = nodes[0].text def artist_to_metadata(node, m): """Make meatadata dict from a XML 'artist' node.""" m.add_unique("musicbrainz_artistid", node.id) for name, nodes in node.children.iteritems(): if not nodes: continue if name == "name": m["name"] = nodes[0].text elif name == "area": m["area"] = nodes[0].name[0].text elif name == "gender": m["gender"] = nodes[0].text elif name == "life_span": if "begin" in nodes[0].children: m["begindate"] = nodes[0].begin[0].text if "ended" in nodes[0].children: ended = nodes[0].ended[0].text if ended == "true" and "end" in nodes[0].children: m["enddate"] = nodes[0].end[0].text elif name == "begin_area": m["beginarea"] = nodes[0].name[0].text elif name == "end_area": m["endarea"] = nodes[0].name[0].text try: m["type"] = node.type except AttributeError: pass def release_to_metadata(node, m, album=None): """Make metadata dict from a XML 'release' node.""" m.add_unique('musicbrainz_albumid', node.id) for name, nodes in node.children.iteritems(): if not nodes: continue if name == 'status': m['releasestatus'] = nodes[0].text.lower() elif name == 'title': m['album'] = nodes[0].text elif name == 'disambiguation': m['~releasecomment'] = nodes[0].text elif name == 'asin': m['asin'] = nodes[0].text elif name == 'artist_credit': artist_credit_to_metadata(nodes[0], m, release=True) # set tags from artists if album is not None: if 'name_credit' in nodes[0].children: for name_credit in nodes[0].name_credit: if 'artist' in name_credit.children: for artist in name_credit.artist: albumartist = album.append_album_artist(artist.id) if 'tag_list' in artist.children: add_folksonomy_tags(artist.tag_list[0], albumartist) if 'user_tag_list' in artist.children: add_user_folksonomy_tags(artist.user_tag_list[0], albumartist) elif name == 'date': m['date'] = nodes[0].text elif name == 'country': m['releasecountry'] = nodes[0].text elif name == 'barcode': m['barcode'] = nodes[0].text elif name == 'relation_list': _relations_to_metadata(nodes, m) elif name == 'label_info_list' and nodes[0].children: m['label'], m['catalognumber'] = label_info_from_node(nodes[0]) elif name == 'text_representation': if 'language' in nodes[0].children: m['~releaselanguage'] = nodes[0].language[0].text if 'script' in nodes[0].children: m['script'] = nodes[0].script[0].text elif name == 'tag_list': add_folksonomy_tags(nodes[0], album) elif name == 'user_tag_list': add_user_folksonomy_tags(nodes[0], album) def release_group_to_metadata(node, m, release_group=None): """Make metadata dict from a XML 'release-group' node taken from inside a 'release' node.""" m.add_unique('musicbrainz_releasegroupid', node.id) for name, nodes in node.children.iteritems(): if not nodes: continue if name == 'title': m['~releasegroup'] = nodes[0].text elif name == 'disambiguation': m['~releasegroupcomment'] = nodes[0].text elif name == 'first_release_date': m['originaldate'] = nodes[0].text if m['originaldate']: m['originalyear'] = m['originaldate'][:4] elif name == 'tag_list': add_folksonomy_tags(nodes[0], release_group) elif name == 'user_tag_list': add_user_folksonomy_tags(nodes[0], release_group) elif name == 'primary_type': m['~primaryreleasetype'] = nodes[0].text.lower() elif name == 'secondary_type_list': add_secondary_release_types(nodes[0], m) m['releasetype'] = m.getall('~primaryreleasetype') + m.getall('~secondaryreleasetype') def add_secondary_release_types(node, m): if 'secondary_type' in node.children: for secondary_type in node.secondary_type: m.add_unique('~secondaryreleasetype', secondary_type.text.lower()) def add_folksonomy_tags(node, obj): if obj and 'tag' in node.children: for tag in node.tag: name = tag.name[0].text count = int(tag.attribs['count']) obj.add_folksonomy_tag(name, count) def add_user_folksonomy_tags(node, obj): if obj and 'user_tag' in node.children: for tag in node.user_tag: name = tag.name[0].text obj.add_folksonomy_tag(name, 1) def add_isrcs_to_metadata(node, metadata): if 'isrc' in node.children: for isrc in node.isrc: metadata.add('isrc', isrc.id) picard-release-1.4.2/picard/metadata.py000066400000000000000000000267431310410472100177770ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006-2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. from PyQt4.QtCore import QObject from picard import config, log from picard.plugin import PluginFunctions, PluginPriority from picard.similarity import similarity2 from picard.util import ( linear_combination_of_weights, ) from picard.util.tags import PRESERVED_TAGS from picard.mbxml import artist_credit_from_node from picard.util.imagelist import ImageList MULTI_VALUED_JOINER = '; ' class Metadata(dict): """List of metadata items with dict-like access.""" __weights = [ ('title', 22), ('artist', 6), ('album', 12), ('tracknumber', 6), ('totaltracks', 5), ] multi_valued_joiner = MULTI_VALUED_JOINER def __init__(self): super(Metadata, self).__init__() self.images = ImageList() self.deleted_tags = set() self.length = 0 def __nonzero__(self): return len(self) or len(self.images) def append_image(self, coverartimage): self.images.append(coverartimage) def set_front_image(self, coverartimage): # First remove all front images self.images[:] = [img for img in self.images if not img.is_front_image()] self.images.append(coverartimage) @property def images_to_be_saved_to_tags(self): if not config.setting["save_images_to_tags"]: return () images = [img for img in self.images if img.can_be_saved_to_tags] if config.setting["embed_only_one_front_image"]: front_image = self.get_single_front_image(images) if front_image: return front_image return images def get_single_front_image(self, images=None): if not images: images = self.images for img in images: if img.is_front_image(): return [img] return [] def remove_image(self, index): self.images.pop(index) def compare(self, other): parts = [] if self.length and other.length: score = 1.0 - min(abs(self.length - other.length), 30000) / 30000.0 parts.append((score, 8)) for name, weight in self.__weights: a = self[name] b = other[name] if a and b: if name in ('tracknumber', 'totaltracks'): try: ia = int(a) ib = int(b) except ValueError: ia = a ib = b score = 1.0 - abs(cmp(ia, ib)) else: score = similarity2(a, b) parts.append((score, weight)) return linear_combination_of_weights(parts) def compare_to_release(self, release, weights): """ Compare metadata to a MusicBrainz release. Produces a probability as a linear combination of weights that the metadata matches a certain album. """ parts = self.compare_to_release_parts(release, weights) return (linear_combination_of_weights(parts), release) def compare_to_release_parts(self, release, weights): parts = [] if "album" in self: b = release.title[0].text parts.append((similarity2(self["album"], b), weights["album"])) if "albumartist" in self and "albumartist" in weights: a = self["albumartist"] b = artist_credit_from_node(release.artist_credit[0])[0] parts.append((similarity2(a, b), weights["albumartist"])) if "totaltracks" in self: try: a = int(self["totaltracks"]) except ValueError: pass else: if "title" in weights: b = int(release.medium_list[0].medium[0].track_list[0].count) else: b = int(release.medium_list[0].track_count[0].text) score = 0.0 if a > b else 0.3 if a < b else 1.0 parts.append((score, weights["totaltracks"])) preferred_countries = config.setting["preferred_release_countries"] preferred_formats = config.setting["preferred_release_formats"] total_countries = len(preferred_countries) if total_countries: score = 0.0 if "country" in release.children: try: i = preferred_countries.index(release.country[0].text) score = float(total_countries - i) / float(total_countries) except ValueError: pass parts.append((score, weights["releasecountry"])) total_formats = len(preferred_formats) if total_formats: score = 0.0 subtotal = 0 for medium in release.medium_list[0].medium: if "format" in medium.children: try: i = preferred_formats.index(medium.format[0].text) score += float(total_formats - i) / float(total_formats) except ValueError: pass subtotal += 1 if subtotal > 0: score /= subtotal parts.append((score, weights["format"])) if "releasetype" in weights: type_scores = dict(config.setting["release_type_scores"]) if 'release_group' in release.children and 'type' in release.release_group[0].attribs: release_type = release.release_group[0].type score = type_scores.get(release_type, type_scores.get('Other', 0.5)) else: score = 0.0 parts.append((score, weights["releasetype"])) rg = QObject.tagger.get_release_group_by_id(release.release_group[0].id) if release.id in rg.loaded_albums: parts.append((1.0, 6)) return parts def compare_to_track(self, track, weights): parts = [] if 'title' in self: a = self['title'] b = track.title[0].text parts.append((similarity2(a, b), weights["title"])) if 'artist' in self: a = self['artist'] b = artist_credit_from_node(track.artist_credit[0])[0] parts.append((similarity2(a, b), weights["artist"])) a = self.length if a > 0 and 'length' in track.children: b = int(track.length[0].text) score = 1.0 - min(abs(a - b), 30000) / 30000.0 parts.append((score, weights["length"])) releases = [] if "release_list" in track.children and "release" in track.release_list[0].children: releases = track.release_list[0].release if not releases: sim = linear_combination_of_weights(parts) return (sim, None, None, track) result = (-1,) for release in releases: release_parts = self.compare_to_release_parts(release, weights) sim = linear_combination_of_weights(parts + release_parts) if sim > result[0]: rg = release.release_group[0] if "release_group" in release.children else None result = (sim, rg, release, track) return result def copy(self, other): self.clear() self.update(other) def update(self, other): for key in other.iterkeys(): self.set(key, other.getall(key)[:]) if other.images: self.images = other.images[:] if other.length: self.length = other.length self.deleted_tags.update(other.deleted_tags) # Remove deleted tags from UI on save for tag in other.deleted_tags: self.pop(tag, None) def clear(self): dict.clear(self) self.images = ImageList() self.length = 0 self.deleted_tags = set() def getall(self, name): return dict.get(self, name, []) def get(self, name, default=None): values = dict.get(self, name, None) if values: return self.multi_valued_joiner.join(values) else: return default def __getitem__(self, name): return self.get(name, u'') def set(self, name, values): dict.__setitem__(self, name, values) if name in self.deleted_tags: self.deleted_tags.remove(name) def __setitem__(self, name, values): if not isinstance(values, list): values = [values] values = filter(None, map(unicode, values)) if len(values): self.set(name, values) else: self.delete(name) def add(self, name, value): if value or value == 0: self.setdefault(name, []).append(value) if name in self.deleted_tags: self.deleted_tags.remove(name) def add_unique(self, name, value): if value not in self.getall(name): self.add(name, value) def delete(self, name): if name in self: self.pop(name, None) self.deleted_tags.add(name) def iteritems(self): for name, values in dict.iteritems(self): for value in values: yield name, value def items(self): """Returns the metadata items. >>> m.items() [("key1", "value1"), ("key1", "value2"), ("key2", "value3")] """ return list(self.iteritems()) def rawitems(self): """Returns the metadata items. >>> m.rawitems() [("key1", ["value1", "value2"]), ("key2", ["value3"])] """ return dict.items(self) def apply_func(self, func): for key, values in self.rawitems(): if key not in PRESERVED_TAGS: self[key] = [func(value) for value in values] def strip_whitespace(self): """Strip leading/trailing whitespace. >>> m = Metadata() >>> m["foo"] = " bar " >>> m["foo"] " bar " >>> m.strip_whitespace() >>> m["foo"] "bar" """ self.apply_func(lambda s: s.strip()) _album_metadata_processors = PluginFunctions() _track_metadata_processors = PluginFunctions() def register_album_metadata_processor(function, priority=PluginPriority.NORMAL): """Registers new album-level metadata processor.""" _album_metadata_processors.register(function.__module__, function, priority) def register_track_metadata_processor(function, priority=PluginPriority.NORMAL): """Registers new track-level metadata processor.""" _track_metadata_processors.register(function.__module__, function, priority) def run_album_metadata_processors(tagger, metadata, release): _album_metadata_processors.run(tagger, metadata, release) def run_track_metadata_processors(tagger, metadata, release, track): _track_metadata_processors.run(tagger, metadata, track, release) picard-release-1.4.2/picard/oauth.py000066400000000000000000000164331310410472100173320ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2014 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import json import time from functools import partial from PyQt4.QtNetwork import QNetworkRequest from PyQt4.QtCore import QUrl from picard import config, log from picard.const import ( MUSICBRAINZ_SERVERS, MUSICBRAINZ_OAUTH_CLIENT_ID, MUSICBRAINZ_OAUTH_CLIENT_SECRET, ) from picard.util import build_qurl class OAuthManager(object): def __init__(self, xmlws): self.xmlws = xmlws def is_authorized(self): return bool(config.persist["oauth_refresh_token"] and config.persist["oauth_refresh_token_scopes"]) def is_logged_in(self): return self.is_authorized() and bool(config.persist["oauth_username"]) def revoke_tokens(self): # TODO actually revoke the tokens on MB (I think it's not implementented there) self.forget_refresh_token() self.forget_access_token() def forget_refresh_token(self): config.persist.remove("oauth_refresh_token") config.persist.remove("oauth_refresh_token_scopes") def forget_access_token(self): config.persist.remove("oauth_access_token") config.persist.remove("oauth_access_token_expires") def get_access_token(self, callback): if not self.is_authorized(): callback(None) else: access_token = config.persist["oauth_access_token"] access_token_expires = config.persist["oauth_access_token_expires"] if access_token and time.time() < access_token_expires: callback(access_token) else: self.forget_access_token() self.refresh_access_token(callback) def get_authorization_url(self, scopes): host, port = config.setting['server_host'], config.setting['server_port'] params = {"response_type": "code", "client_id": MUSICBRAINZ_OAUTH_CLIENT_ID, "redirect_uri": "urn:ietf:wg:oauth:2.0:oob", "scope": scopes} url = build_qurl(host, port, path="/oauth2/authorize", queryargs=params) return str(url.toEncoded()) def set_refresh_token(self, refresh_token, scopes): log.debug("OAuth: got refresh_token %s with scopes %s", refresh_token, scopes) config.persist["oauth_refresh_token"] = refresh_token config.persist["oauth_refresh_token_scopes"] = scopes def set_access_token(self, access_token, expires_in): log.debug("OAuth: got access_token %s that expires in %s seconds", access_token, expires_in) config.persist["oauth_access_token"] = access_token config.persist["oauth_access_token_expires"] = int(time.time() + expires_in - 60) def set_username(self, username): log.debug("OAuth: got username %s", username) config.persist["oauth_username"] = username def refresh_access_token(self, callback): refresh_token = config.persist["oauth_refresh_token"] log.debug("OAuth: refreshing access_token with a refresh_token %s", refresh_token) host, port = config.setting['server_host'], config.setting['server_port'] path = "/oauth2/token" url = QUrl() url.addQueryItem("grant_type", "refresh_token") url.addQueryItem("refresh_token", refresh_token) url.addQueryItem("client_id", MUSICBRAINZ_OAUTH_CLIENT_ID) url.addQueryItem("client_secret", MUSICBRAINZ_OAUTH_CLIENT_SECRET) data = str(url.encodedQuery()) self.xmlws.post(host, port, path, data, partial(self.on_refresh_access_token_finished, callback), xml=False, mblogin=True, priority=True, important=True) def on_refresh_access_token_finished(self, callback, data, http, error): access_token = None try: if error: log.error("OAuth: access_token refresh failed: %s", data) if http.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 400: response = json.loads(data) if response["error"] == "invalid_grant": self.forget_refresh_token() else: response = json.loads(data) self.set_access_token(response["access_token"], response["expires_in"]) access_token = response["access_token"] finally: callback(access_token) def exchange_authorization_code(self, authorization_code, scopes, callback): log.debug("OAuth: exchanging authorization_code %s for an access_token", authorization_code) host, port = config.setting['server_host'], config.setting['server_port'] path = "/oauth2/token" url = QUrl() url.addQueryItem("grant_type", "authorization_code") url.addQueryItem("code", authorization_code) url.addQueryItem("client_id", MUSICBRAINZ_OAUTH_CLIENT_ID) url.addQueryItem("client_secret", MUSICBRAINZ_OAUTH_CLIENT_SECRET) url.addQueryItem("redirect_uri", "urn:ietf:wg:oauth:2.0:oob") data = str(url.encodedQuery()) self.xmlws.post(host, port, path, data, partial(self.on_exchange_authorization_code_finished, scopes, callback), xml=False, mblogin=True, priority=True, important=True) def on_exchange_authorization_code_finished(self, scopes, callback, data, http, error): successful = False try: if error: log.error("OAuth: authorization_code exchange failed: %s", data) else: response = json.loads(data) self.set_refresh_token(response["refresh_token"], scopes) self.set_access_token(response["access_token"], response["expires_in"]) successful = True finally: callback(successful) def fetch_username(self, callback): log.debug("OAuth: fetching username") host, port = config.setting['server_host'], config.setting['server_port'] path = "/oauth2/userinfo" self.xmlws.get(host, port, path, partial(self.on_fetch_username_finished, callback), xml=False, mblogin=True, priority=True, important=True) def on_fetch_username_finished(self, callback, data, http, error): successful = False try: if error: log.error("OAuth: username fetching failed: %s", data) else: response = json.loads(data) self.set_username(response["sub"]) successful = True finally: callback(successful) picard-release-1.4.2/picard/plugin.py000066400000000000000000000401771310410472100175120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # Copyright (C) 2014 Shadab Zafar # Copyright (C) 2015 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore from collections import defaultdict from functools import partial import imp import json import os.path import shutil import picard.plugins import tempfile import traceback import zipimport from picard import (config, log, version_from_string, version_to_string, VersionError) from picard.const import USER_PLUGIN_DIR, PLUGINS_API _suffixes = [s[0] for s in imp.get_suffixes()] _package_entries = ["__init__.py", "__init__.pyc", "__init__.pyo"] _extension_points = [] _PLUGIN_MODULE_PREFIX = "picard.plugins." _PLUGIN_MODULE_PREFIX_LEN = len(_PLUGIN_MODULE_PREFIX) def _plugin_name_from_path(path): path = os.path.normpath(path) file = os.path.basename(path) if os.path.isdir(path): for entry in _package_entries: if os.path.isfile(os.path.join(path, entry)): return file else: if file in _package_entries: return None name, ext = os.path.splitext(file) if ext in _suffixes: return name return None def is_zip(path): if os.path.splitext(path)[1] == '.zip': return os.path.basename(path) return False def zip_import(path): splitext = os.path.splitext(path) if (not os.path.isfile(path) or not splitext[1] == '.zip'): return (None, None) try: importer = zipimport.zipimporter(path) basename = os.path.basename(splitext[0]) return (importer, basename) except zipimport.ZipImportError: return (None, None) def _unregister_module_extensions(module): for ep in _extension_points: ep.unregister_module(module) class ExtensionPoint(object): def __init__(self): self.__items = [] _extension_points.append(self) def register(self, module, item): if module.startswith(_PLUGIN_MODULE_PREFIX): module = module[_PLUGIN_MODULE_PREFIX_LEN:] else: module = None self.__items.append((module, item)) def unregister_module(self, name): self.__items = filter(lambda i: i[0] != name, self.__items) def __iter__(self): enabled_plugins = config.setting["enabled_plugins"] for module, item in self.__items: if module is None or module in enabled_plugins: yield item class PluginShared(object): def __init__(self): super(PluginShared, self).__init__() self.new_version = "" self.enabled = False self.can_be_updated = False self.can_be_downloaded = False self.marked_for_update = False class PluginWrapper(PluginShared): def __init__(self, module, plugindir, file=None): super(PluginWrapper, self).__init__() self.module = module self.compatible = False self.dir = plugindir self._file = file @property def name(self): try: return self.module.PLUGIN_NAME except AttributeError: return self.module_name @property def module_name(self): name = self.module.__name__ if name.startswith(_PLUGIN_MODULE_PREFIX): name = name[_PLUGIN_MODULE_PREFIX_LEN:] return name @property def author(self): try: return self.module.PLUGIN_AUTHOR except AttributeError: return "" @property def description(self): try: return self.module.PLUGIN_DESCRIPTION except AttributeError: return "" @property def version(self): try: return self.module.PLUGIN_VERSION except AttributeError: return "" @property def api_versions(self): try: return self.module.PLUGIN_API_VERSIONS except AttributeError: return [] @property def file(self): if not self._file: return self.module.__file__ else: return self._file @property def license(self): try: return self.module.PLUGIN_LICENSE except AttributeError: return "" @property def license_url(self): try: return self.module.PLUGIN_LICENSE_URL except AttributeError: return "" @property def files_list(self): return self.file[len(self.dir)+1:] class PluginData(PluginShared): """Used to store plugin data from JSON API""" def __init__(self, d, module_name): self.__dict__ = d super(PluginData, self).__init__() self.module_name = module_name def __getattribute__(self, name): try: return PluginShared.__getattribute__(self, name) except AttributeError: log.debug('Attribute %r not found for plugin %r', name, self.module_name) return None @property def files_list(self): return ", ".join(self.files.keys()) class PluginManager(QtCore.QObject): plugin_installed = QtCore.pyqtSignal(PluginWrapper, bool) plugin_updated = QtCore.pyqtSignal(unicode, bool) def __init__(self): QtCore.QObject.__init__(self) self.plugins = [] self._api_versions = set([version_from_string(v) for v in picard.api_versions]) self._available_plugins = {} @property def available_plugins(self): return self._available_plugins def load_plugindir(self, plugindir): plugindir = os.path.normpath(plugindir) if not os.path.isdir(plugindir): log.info("Plugin directory %r doesn't exist", plugindir) return # first, handle eventual plugin updates for updatepath in [os.path.join(plugindir, file) for file in os.listdir(plugindir) if file.endswith('.update')]: path = os.path.splitext(updatepath)[0] name = is_zip(path) if not name: name = _plugin_name_from_path(path) if name: self.remove_plugin(name) os.rename(updatepath, path) log.debug('Updating plugin %r (%r))', name, path) else: log.error('Cannot get plugin name from %r', updatepath) # now load found plugins names = set() for path in [os.path.join(plugindir, file) for file in os.listdir(plugindir)]: name = is_zip(path) if not name: name = _plugin_name_from_path(path) if name: names.add(name) log.debug("Looking for plugins in directory %r, %d names found", plugindir, len(names)) for name in sorted(names): self.load_plugin(name, plugindir) def load_plugin(self, name, plugindir): module_file = None (importer, module_name) = zip_import(os.path.join(plugindir, name)) if importer: name = module_name if not importer.find_module(name): log.error("Failed loading zipped plugin %r", name) return None module_pathname = importer.get_filename(name) else: try: info = imp.find_module(name, [plugindir]) module_file = info[0] module_pathname = info[1] except ImportError: log.error("Failed loading plugin %r", name) return None plugin = None try: index = None for i, p in enumerate(self.plugins): if name == p.module_name: log.warning("Module %r conflict: unregistering previously" \ " loaded %r version %s from %r", p.module_name, p.name, p.version, p.file) _unregister_module_extensions(name) index = i break if not importer: plugin_module = imp.load_module(_PLUGIN_MODULE_PREFIX + name, *info) else: plugin_module = importer.load_module(_PLUGIN_MODULE_PREFIX + name) plugin = PluginWrapper(plugin_module, plugindir, file=module_pathname) versions = [version_from_string(v) for v in list(plugin.api_versions)] compatible_versions = list(set(versions) & self._api_versions) if compatible_versions: log.debug("Loading plugin %r version %s, compatible with API: %s", plugin.name, plugin.version, ", ".join([version_to_string(v, short=True) for v in sorted(compatible_versions)])) plugin.compatible = True setattr(picard.plugins, name, plugin_module) if index is not None: self.plugins[index] = plugin else: self.plugins.append(plugin) else: log.warning("Plugin '%s' from '%s' is not compatible" " with this version of Picard." % (plugin.name, plugin.file)) except VersionError as e: log.error("Plugin %r has an invalid API version string : %s", name, e) except: log.error("Plugin %r : %s", name, traceback.format_exc()) if module_file is not None: module_file.close() return plugin def _get_existing_paths(self, plugin_name): dirpath = os.path.join(USER_PLUGIN_DIR, plugin_name) if not os.path.isdir(dirpath): dirpath = None fileexts = ['.py', '.pyc', '.pyo', '.zip'] filepaths = [ os.path.join(USER_PLUGIN_DIR, f) for f in os.listdir(USER_PLUGIN_DIR) if f in [plugin_name + ext for ext in fileexts] ] return (dirpath, filepaths) def remove_plugin(self, plugin_name): if plugin_name.endswith('.zip'): plugin_name = os.path.splitext(plugin_name)[0] log.debug("Remove plugin files and dirs : %r", plugin_name) dirpath, filepaths = self._get_existing_paths(plugin_name) if dirpath: log.debug("Removing directory %r", dirpath) shutil.rmtree(dirpath) if filepaths: for filepath in filepaths: log.debug("Removing file %r", filepath) os.remove(filepath) def install_plugin(self, path, overwrite_confirm=None, plugin_name=None, plugin_data=None): """ path is either: 1) /some/dir/name.py 2) /some/dir/name (directory containing __init__.py) 3) /some/dir/name.zip (containing either 1 or 2) """ zip_plugin = False if not plugin_name: zip_plugin = is_zip(path) if not zip_plugin: plugin_name = _plugin_name_from_path(path) else: plugin_name = os.path.splitext(zip_plugin)[0] if plugin_name: try: dirpath, filepaths = self._get_existing_paths(plugin_name) update = dirpath or filepaths if plugin_data and plugin_name: # zipped module from download zip_plugin = plugin_name + '.zip' dst = os.path.join(USER_PLUGIN_DIR, zip_plugin) if update: dst += '.update' if os.path.isfile(dst): os.remove(dst) ziptmp = tempfile.NamedTemporaryFile(delete=False, dir=USER_PLUGIN_DIR).name try: with open(ziptmp, "wb") as zipfile: zipfile.write(plugin_data) zipfile.flush() os.fsync(zipfile.fileno()) os.rename(ziptmp, dst) log.debug("Plugin saved to %r", dst) except: try: os.remove(ziptmp) except (IOError, OSError): pass raise elif os.path.isfile(path): dst = os.path.join(USER_PLUGIN_DIR, os.path.basename(path)) if update: dst += '.update' if os.path.isfile(dst): os.remove(dst) shutil.copy2(path, dst) elif os.path.isdir(path): dst = os.path.join(USER_PLUGIN_DIR, plugin_name) if update: dst += '.update' if os.path.isdir(dst): shutil.rmtree(dst) shutil.copytree(path, dst) if not update: installed_plugin = self.load_plugin(zip_plugin or plugin_name, USER_PLUGIN_DIR) if installed_plugin is not None: self.plugin_installed.emit(installed_plugin, False) else: self.plugin_updated.emit(plugin_name, False) except (OSError, IOError): log.warning("Unable to copy %s to plugin folder %s" % (path, USER_PLUGIN_DIR)) def query_available_plugins(self, callback=None): self.tagger.xmlws.get( PLUGINS_API['host'], PLUGINS_API['port'], PLUGINS_API['endpoint']['plugins'], partial(self._plugins_json_loaded, callback=callback), xml=False, priority=True, important=True ) def _plugins_json_loaded(self, response, reply, error, callback=None): if error: self.tagger.window.set_statusbar_message( N_("Error loading plugins list: %(error)s"), {'error': unicode(reply.errorString())}, echo=log.error ) else: self._available_plugins = [PluginData(data, key) for key, data in json.loads(response)['plugins'].items()] if callback: callback() def enabled(self, name): return True class PluginPriority: """ Define few priority values for plugin functions execution order Those with higher values are executed first Default priority is PluginPriority.NORMAL """ HIGH = 100 NORMAL = 0 LOW = -100 class PluginFunctions: """ Store ExtensionPoint in a defaultdict with priority as key run() method will execute entries with higher priority value first """ def __init__(self): self.functions = defaultdict(ExtensionPoint) def register(self, module, item, priority=PluginPriority.NORMAL): self.functions[priority].register(module, item) def run(self, *args, **kwargs): "Execute registered functions with passed parameters honouring priority" for priority, functions in sorted(self.functions.iteritems(), key=lambda i: i[0], reverse=True): for function in functions: function(*args, **kwargs) picard-release-1.4.2/picard/plugins/000077500000000000000000000000001310410472100173125ustar00rootroot00000000000000picard-release-1.4.2/picard/plugins/__init__.py000066400000000000000000000000001310410472100214110ustar00rootroot00000000000000picard-release-1.4.2/picard/releasegroup.py000066400000000000000000000141561310410472100207070ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2012 Michael Wiencek # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import traceback from collections import defaultdict from functools import partial from itertools import combinations from picard import log from picard.metadata import Metadata from picard.dataobj import DataObject from picard.mbxml import media_formats_from_node, label_info_from_node from picard.util import uniqify class ReleaseGroup(DataObject): def __init__(self, id): DataObject.__init__(self, id) self.metadata = Metadata() self.loaded = False self.versions = [] self.version_headings = '' self.loaded_albums = set() self.refcount = 0 def load_versions(self, callback): kwargs = {"release-group": self.id, "limit": 100} self.tagger.xmlws.browse_releases(partial(self._request_finished, callback), **kwargs) def _parse_versions(self, document): """Parse document and return a list of releases""" del self.versions[:] data = [] namekeys = ("tracks", "year", "country", "format", "label", "catnum") headings = { "tracks": N_('Tracks'), "year": N_('Year'), "country": N_('Country'), "format": N_('Format'), "label": N_('Label'), "catnum": N_('Cat No'), } extrakeys = ("packaging", "barcode", "disambiguation") for node in document.metadata[0].release_list[0].release: labels, catnums = label_info_from_node(node.label_info_list[0]) countries = [] if 'release_event_list' in node.children: for release_event in node.release_event_list[0].release_event: if "area" in release_event.children: countries.append(release_event.area[0].iso_3166_1_code_list[0].iso_3166_1_code[0].text) formats = [] for medium in node.medium_list[0].medium: if "format" in medium.children: formats.append(medium.format[0].text) release = { "id": node.id, "year": node.date[0].text[:4] if "date" in node.children else "????", "country": "+".join(countries) if countries else node.country[0].text if "country" in node.children else "??", "format": media_formats_from_node(node.medium_list[0]), "label": ", ".join([' '.join(x.split(' ')[:2]) for x in set(labels)]), "catnum": ", ".join(set(catnums)), "tracks": "+".join([m.track_list[0].count for m in node.medium_list[0].medium]), "barcode": node.barcode[0].text if "barcode" in node.children and node.barcode[0].text != "" else _("[no barcode]"), "packaging": node.packaging[0].text if "packaging" in node.children else None, "disambiguation": node.disambiguation[0].text if "disambiguation" in node.children else None, "_disambiguate_name": list(), "totaltracks": sum([int(m.track_list[0].count) for m in node.medium_list[0].medium]), "countries": countries, "formats": formats, } data.append(release) versions = defaultdict(list) for release in data: name = " / ".join([release[k] for k in namekeys]).replace("&", "&&") if name == release["tracks"]: name = "%s / %s" % (_('[no release info]'), name) versions[name].append(release) # de-duplicate names if possible for name, releases in versions.iteritems(): for a, b in combinations(releases, 2): for key in extrakeys: (value1, value2) = (a[key], b[key]) if value1 != value2: a['_disambiguate_name'].append(value1) b['_disambiguate_name'].append(value2) for name, releases in versions.iteritems(): for release in releases: dis = " / ".join(filter(None, uniqify(release['_disambiguate_name']))).replace("&", "&&") disname = name if not dis else name + ' / ' + dis version = { 'id': release['id'], 'name': disname, 'totaltracks': release['totaltracks'], 'countries': release['countries'], 'formats': release['formats'], } self.versions.append(version) self.version_headings = " / ".join(_(headings[k]) for k in namekeys) def _request_finished(self, callback, document, http, error): try: if error: log.error("%r", unicode(http.errorString())) else: try: self._parse_versions(document) except: error = True log.error(traceback.format_exc()) finally: self.loaded = True callback() def remove_album(self, id): self.loaded_albums.discard(id) self.refcount -= 1 if self.refcount == 0: del self.tagger.release_groups[self.id] picard-release-1.4.2/picard/resources.py000066400000000000000000012012271310410472100202220ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Resource object code # # Created by: The Resource Compiler for PyQt4 (Qt v4.8.7) # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore qt_resource_data = "\ \x00\x00\x01\x18\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x04\x00\x00\x00\xb5\xfa\x37\xea\ \x00\x00\x00\xdf\x49\x44\x41\x54\x78\x01\x75\x8e\x43\x7a\x05\x51\ \x10\x85\x7b\x05\x59\x55\x3c\x8a\xb3\x83\xcc\x63\xa3\x27\xb1\x6d\ \xdb\xe6\x28\xb6\xb5\x80\x58\x8d\x68\xfa\xe7\xa5\x9e\x75\xce\xf5\ \xf9\xeb\xab\xab\xb4\x8c\xab\xf8\x71\x9c\xf2\x2f\x15\x7f\x52\xb1\ \x03\xbc\xdb\xfc\x61\xb3\x86\xe6\x0d\xd8\x03\xcd\x17\x60\xaf\xb3\ \x43\xba\x37\x20\xb1\x2d\xd2\xc5\xde\x80\x44\x76\x1b\xee\x80\x66\ \xb3\x44\x3c\xd1\xc7\x10\xa3\x54\x52\xb7\x53\x30\x2c\x80\x6b\xbd\ \xc1\x2d\xbd\x9c\xb2\xcd\x8e\x65\x6d\xc4\x05\x30\x24\xd6\xb8\x62\ \x98\x11\x86\x28\x63\xe4\xbb\xeb\xd7\xde\x42\x42\x5d\x4e\xb7\x74\ \xb3\x08\xa8\x14\x2d\x74\xe0\xf2\x07\x03\x43\xf6\x7b\x7a\x59\x16\ \xa0\x7a\xc6\x05\x90\xfe\x52\x3f\xc2\x30\x2b\x40\x2a\xd5\x57\xdd\ \x02\xb8\x68\x84\x56\x8e\x58\x63\x95\x59\xcb\x7a\x48\xb3\xa9\x34\ \xcc\xab\xb8\x7a\x94\x0d\x32\xc8\xa4\x82\x6d\x86\x88\x4f\x56\xdc\ \x95\x1a\x9e\x33\x9e\xb3\x13\x19\xaa\x28\x79\x09\xea\x4e\x52\x7b\ \x50\xc0\x1f\x12\x3f\x99\x1d\x7d\x4c\x0b\x7e\x00\x00\x00\x00\x49\ \x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x4c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\x13\x49\x44\x41\x54\x78\x01\x8d\x93\x03\xb2\x1d\x41\ \x18\x85\x93\xb7\x8d\x70\x0d\xd1\x3a\x52\x0c\x96\x90\x42\x6c\xdb\ \x7e\xb6\x6d\xdb\xb6\x6d\x63\x6c\xdc\x3b\x27\x3d\x9d\xa4\x63\x4c\ \xd5\x37\x3e\x5f\xf3\xdf\x05\x80\x42\x8e\xbd\x84\xc3\x84\x63\xff\ \xe0\x20\x21\x84\xe5\xbe\x13\x1c\xb6\x2c\x4b\x72\x5d\x37\x18\x08\ \x04\xf0\x3b\xc8\x77\x84\x86\x86\xbe\x67\x92\x9f\x04\xc7\xfc\xb0\ \xa6\x69\xd0\x75\x9d\x21\xcb\x32\x44\x51\xc4\xd6\xd6\x16\x95\x6c\ \x6c\x6c\x20\x2c\x2c\x8c\x49\xbe\x86\x77\x5f\x7f\x5b\x70\xb3\x61\ \x4c\x43\x76\xbb\x80\xd4\x26\x0e\x85\x9d\x3b\x18\x99\x17\xa1\xaa\ \x2a\x24\x49\xc2\xf6\xf6\x36\x15\x18\x86\x01\xbf\x91\xa8\xa8\xa8\ \xcf\x92\xfc\x3e\x33\x24\xab\x4b\x7b\x9a\xda\x2a\x7b\x4b\x9c\x83\ \xe9\x2d\x1b\xdb\xba\x8b\x85\x2d\x13\x4f\xb3\xe7\x50\xd9\xb3\x4e\ \x7a\xa1\x60\x67\x67\xe7\x97\x21\xd1\x39\x21\xe1\x93\xa9\xad\x92\ \xa7\x98\x41\x48\xba\x87\x8a\x11\x0d\x9b\xa6\x07\xcb\x03\x2c\x27\ \x88\x1b\xf1\x23\xe8\x1c\xdb\xa4\x43\xe1\x38\x8e\xf6\x64\x7d\x7d\ \x1d\x4c\x40\xc2\x95\x0b\xdb\x36\x09\x4a\x58\x15\x02\x90\x34\x97\ \xa2\x39\x1e\x88\x07\x13\xcb\x0a\x42\xf3\x27\xe8\x7c\x28\x8a\x42\ \xe7\x83\xe7\x79\x30\x41\x7c\x3d\xb7\x3c\xb6\x6a\xa0\xa0\x8f\x87\ \x48\x82\x2f\x73\xfb\x11\x53\x31\x0e\x41\x75\x30\xb8\x6a\x61\x62\ \xcd\xc0\xd5\xa8\x5e\xba\x02\xfe\xf8\xc9\x9c\x50\x11\x13\x44\x54\ \x6d\x2e\x0f\x2f\xeb\x4c\x10\x5e\x32\x82\xb4\xfa\x19\xf0\x8a\x8d\ \x86\x29\x1d\x23\xcb\x1a\x2e\x7c\xec\x84\xe3\x38\xb0\x6d\x9b\x4a\ \x7c\x98\xe0\x7d\xc9\x4a\xe5\xcc\xba\x8e\x59\xde\x05\x67\x78\x7e\ \xcb\x34\x3c\xb9\x69\xa3\x79\xd6\x44\xff\x34\x8f\x27\x29\x03\x70\ \x5d\x97\x0a\x08\xb4\x37\x4c\xf0\x22\x6f\xe1\xe4\xa3\x8c\x69\x4f\ \xb6\x82\x98\xe2\x81\x9e\x55\x0f\x2d\xf3\x0e\x9a\xa6\x4d\x2c\xec\ \x58\x38\x7d\xaf\x0e\x4d\x03\xab\xb4\x07\x04\x1a\xfe\x41\xf0\x30\ \x63\x3a\xe4\x76\xd2\xf8\xd3\xab\x31\x43\xde\xd0\xbc\x84\x89\x75\ \x0b\x23\x2b\x3a\x7a\x26\x39\x9c\xba\x57\x8b\xe4\x8a\x49\xda\x2a\ \x09\x7f\x0f\x13\xb0\x8d\x74\xfc\xcc\x87\x9b\xa1\x05\x13\xb8\x1c\ \xd1\x83\x73\x1f\x3a\xf0\x2c\x75\x00\xcd\x03\x6b\xb4\xeb\xbf\xe3\ \x17\x81\xff\x40\x5e\x7a\x04\xfc\x27\x41\x26\xf8\x22\x39\x42\xba\ \xaa\xfb\x1f\xfe\x27\x4c\xfe\x55\x69\xf5\x7e\x27\xd8\xe7\x4b\xa8\ \xf5\xff\x38\xec\x67\x3e\x01\x60\xbc\xed\xe2\x06\xed\xe2\xf6\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x39\x2c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x80\x00\x00\x00\x80\x08\x06\x00\x00\x00\xc3\x3e\x61\xcb\ \x00\x00\x38\xf3\x49\x44\x41\x54\x78\x01\xec\x92\x47\x76\xdc\x40\ \x0c\x44\x7f\x35\x9b\xca\x61\xe9\x6c\xef\x7d\x05\x5f\xd8\x3e\x89\ \xcf\xa0\xb5\xb4\x73\xb6\xb2\x86\x24\x94\xc8\x57\x8f\xad\x76\xce\ \x01\x0a\x53\x0f\x53\x28\x14\x80\xd6\xa3\x07\x8f\x77\xf8\x27\xe2\ \x7f\xa8\xd1\x13\xe0\x25\x0e\x32\xc0\xe9\xfb\x23\x9a\x84\x92\x00\ \xc9\x05\xe0\x90\x33\x86\x25\x4f\xa0\x7a\xad\x9c\x71\x08\x15\x42\ \x45\x9d\xfb\x55\xd2\x80\xca\x9c\x85\x15\x54\x1b\x6a\x9e\x70\xad\ \x9c\xfc\xf4\xcc\xf5\xbc\x05\x3f\xad\x85\x81\xaa\xcb\x0c\x95\x2b\ \x0a\x20\x61\x62\x54\x77\x2d\x4a\x9b\x7b\x7b\x6f\xba\x95\xed\x75\ \x6a\x91\x01\x62\x18\x58\x5e\x6e\xb5\xb4\xdc\x08\x40\x16\xb9\xc6\ \xd2\x88\xc1\x58\x13\x74\xde\xd8\x66\x84\x35\x11\x75\x3e\xd6\xac\ \xf5\x9a\x0e\x37\xff\x4e\x05\xaf\xda\xc3\xba\x61\x4f\xce\xe3\xde\ \xce\x23\xec\x03\x9c\x37\x9e\xf8\x32\xa6\xd6\x17\x44\x9d\x0f\xd5\ \xde\x75\x0f\x35\x7d\x8d\xd8\x05\xd6\x08\x8c\xc5\xd3\x67\xcf\xf7\ \xa1\x1e\x19\x0f\xf6\xfb\x1e\x1e\x41\xc9\x37\xe6\xd3\x87\x8f\xdf\ \xe8\xf0\x55\xfe\x77\x3e\xbc\xb0\xa6\xa3\xef\xbb\xb4\xbb\xbb\x33\ \x25\x63\xfe\x00\x92\x50\x61\xd8\x70\xca\x87\xca\x65\x22\x8c\xcb\ \xc5\x86\x44\x14\x2f\x12\xcc\xc1\x3a\x50\x0e\x15\xa3\x78\xcc\x35\ \x6b\x8b\xc5\x0b\x47\x60\x1c\x20\x81\x57\x52\xed\x1d\x04\x75\x1f\ \xf5\x23\x69\xea\x41\x40\xa9\x6a\x54\x99\x39\xc0\xbe\x2b\x7b\xb4\ \xed\x9b\x7e\xec\xe3\xa6\xb7\xb0\xa9\xba\x9f\x04\x70\x72\x72\xb8\ \x0a\x2c\x03\xfd\xf8\x37\x64\xc6\x48\xf2\x52\x25\x04\x91\x62\x2c\ \xf4\x83\xf1\x40\x10\x9a\x9b\x31\xe7\x1a\xc6\x34\x09\x81\xbd\x85\ \x97\x30\xe6\xbd\x7c\x7c\x6c\x0f\x06\x4c\xfb\x08\x02\x70\xed\xf4\ \xb8\x82\x60\x1a\xbb\xc9\xad\xb6\xb7\x37\xf3\xe6\xd6\x46\x6e\x97\ \xdb\xd4\xb6\x17\x7f\x4b\x2d\x39\x37\x29\xb7\x39\x5d\x7c\xd2\x77\ \x43\xf4\x7d\x17\x8b\xae\x8b\xee\xe2\xaf\x5f\xf4\xc3\x62\xb1\x88\ \xc3\x83\xc3\x6e\x7f\xff\xb0\x1b\xba\xde\x3d\x88\x11\xc7\xe8\xc9\ \x53\xda\x37\x02\xcc\xa9\xf0\x31\x7f\xaa\x18\x39\xe1\x97\x1b\x1e\ \xde\x55\xe9\x1a\x0b\x8d\xcc\x8a\xb7\xf9\x23\x8b\xb9\x07\x05\x91\ \x23\x82\xe3\xe3\x83\xdb\xc0\x00\xec\x03\x47\xc0\x22\x63\x51\xa5\ \x24\xdc\x55\x59\xd0\x88\xb0\xf3\xe2\xd0\x44\x14\x8f\xc3\x50\x9e\ \x1c\x09\x11\x5e\xce\x35\x8e\x92\xe3\x05\x4c\xe9\x73\x5e\xcd\xc2\ \xb9\x71\xe4\xd9\xe3\x3d\x23\xc9\x18\xf0\x05\x0d\x61\xe6\xbd\x24\ \xcb\xbb\x8f\xfe\xff\x47\x07\xcb\xcc\x5b\xe1\x0b\x27\xb6\x35\xcf\ \x3d\xd5\xa5\x6f\xf5\x46\xde\xe7\xd4\x2f\xbf\x53\x95\xe2\xc9\x68\ \x66\x04\x9f\x66\x49\x00\xf3\x78\x6e\x25\x73\x45\xe0\xf3\x85\xac\ \x19\xae\x0c\x65\xfa\x87\xfa\xc2\xae\x9e\xee\xa0\xbb\xa7\x18\xe6\ \xf3\x79\xab\x03\x3b\x11\x2c\x69\x8b\xb1\x80\x50\xa6\x04\x7f\x67\ \x67\xe7\xf1\xc9\xd1\x49\x93\xf7\xfd\xbd\x83\xfa\xce\xd6\x6e\xe3\ \xf2\xe2\x32\x76\x14\x27\x86\xdc\x39\x97\x68\x2d\xb7\x45\x79\x70\ \xe1\xd2\x9f\x08\x2c\xee\xdf\x70\x1b\x34\x25\xa0\x93\x39\x10\x74\ \x2b\x6b\xfb\x73\x92\x20\xc7\x5f\xd1\x41\x6d\xf9\x4c\xc2\x47\xac\ \x9a\x3c\x6a\x97\x75\xad\xad\x5e\xbf\x9c\x11\xab\xff\x85\xe1\x13\ \x51\x03\x02\x60\x61\x01\x62\x17\x5b\x63\x28\x20\x47\x81\x13\xe7\ \x90\xf8\xd2\x1f\x25\x3f\x91\x46\x03\x69\x57\x3e\x10\xc2\x81\x5f\ \x8f\x5f\xcf\x91\xeb\xd7\xd2\xac\xdd\x4d\x18\x45\xa6\x52\x1b\x8e\ \x86\xca\x03\xe1\x50\x79\x28\xea\xee\x2d\x5a\x85\xb0\x2d\x78\xa3\ \xc0\xbb\x54\xf0\xd0\xae\x42\x21\x6f\x79\x1f\x2c\x0f\x46\x13\x44\ \x39\xbe\xc3\xa3\xc3\xe3\xe6\xee\xce\x5e\x63\x77\x7b\xaf\xc1\x02\ \xd1\x6c\x34\x04\x14\x66\x5f\xb5\x10\x04\x9d\x30\x0a\x61\x32\xc2\ \xb9\xc4\xbd\x22\xce\x4f\xb3\x76\xc9\x7a\x98\xad\xd7\x4e\x49\x30\ \x5c\x72\xfe\xd0\xaf\xe8\x9a\x55\xd1\xfc\xbf\x88\x68\x9f\x87\xc1\ \x05\x58\x2b\xe6\xdf\x19\xa6\xcf\x84\x9c\x21\x6b\x8d\x33\x89\xb9\ \x4f\xf1\xfb\xda\xe3\xa4\x07\x51\x90\x4c\xb4\x53\xa2\x55\x8c\x87\ \xc9\xf4\x82\x59\xad\x56\xa2\x89\x99\xb1\xa8\x52\x2b\x47\x61\x18\ \x88\x98\xdc\x3c\x78\x7d\x80\x47\xc2\xb7\xf6\xf4\x76\x07\x3d\xa5\ \xee\x60\x72\x66\x3c\xdb\x6c\x36\xdd\xd6\xd7\xad\xc6\xe7\x8f\x5f\ \xeb\x3b\xdb\xbb\x0d\x58\x97\xf4\x9c\xce\xa0\xf3\xca\x61\x08\xba\ \xfd\x49\x6c\x04\xd4\x49\x7f\x7a\x4c\x82\xb3\x39\x35\xc6\x12\x79\ \x23\x24\x31\x00\x85\xdc\xa7\x05\xc0\x30\x6d\xa2\xc4\x58\x32\x78\ \x6f\xda\x40\xcc\x74\x02\xfe\x67\x41\x21\xba\xdb\x80\x47\x7f\x6f\ \xa9\x27\x98\x99\x9f\xca\x8c\x4d\xd4\xa2\x6c\x3e\x6b\xb5\x09\x37\ \xff\x2f\xf8\x20\x0c\xa8\xab\xab\x8b\xa2\x28\xa2\x4c\x26\xe3\x7f\ \xc3\x28\xa4\x30\x08\x89\xb5\xb7\xde\xa8\x53\xa3\xde\xfa\xad\xd7\ \xe9\xf2\xf2\x92\x4e\x4e\x4f\x28\x8e\x61\x6e\x21\x04\x58\x5e\xda\ \xc4\x42\x58\x1b\xab\x46\xbc\x9f\x9f\x5f\xb8\x6f\x9f\xbf\xd5\x3f\ \xbe\xfb\x74\x79\x7a\x7a\xe6\x0c\xb5\xcf\x36\x64\xd3\xfd\xed\xc0\ \xb7\xcb\x92\x52\xc0\x43\xc8\xd2\xc6\x28\xa3\x60\x65\x37\x6d\xd3\ \x40\xf1\x69\x06\xe6\xe7\xef\x05\xdf\x37\x50\x0a\x97\x57\x17\xb3\ \xd5\xd1\x4a\xc4\xda\xaf\xc1\xa7\x17\x83\xfa\x07\xfa\x68\x70\x70\ \x90\xba\xbb\xbb\xa9\x58\x2c\x52\x21\x5f\xa0\x4c\x36\x43\x52\xd9\ \xc2\x78\x87\x79\xfa\xbc\x8e\xf8\x96\x2f\xce\xcf\xe8\xec\xfc\x9c\ \x4e\x4f\x4e\xe8\xf8\xf8\x98\xb6\xb6\xb6\x68\xff\x60\x5f\xc0\xa7\ \x6b\x70\x3e\x9f\x35\x53\xb3\x13\x99\xc9\x99\x89\xcc\xb7\xaf\xdf\ \xeb\x6f\x5f\xbe\xbb\x3c\x39\x3a\x8d\xf5\xfd\xe2\x5c\xe8\xb7\x68\ \xff\x93\xc0\x4b\xf3\xa7\x9b\x4e\x03\xe1\x7c\x25\x8a\x4f\xcb\xe5\ \xff\x39\xe0\x87\x86\x07\xc2\xa5\xb5\x85\x6c\xb9\x3a\x1c\x41\x93\ \xd3\xc1\x67\x73\x39\xaa\x56\xaa\x54\x2e\x0f\xd3\x2f\x7d\xbf\x50\ \x26\xca\x60\x4d\x0c\x06\x70\x69\xe3\xb6\x70\x9f\xf0\xc1\x0c\x33\ \x4f\xf9\x42\x9e\xfa\x5a\x6b\xf2\xf9\x17\x17\x17\xe8\xb2\x5e\xa7\ \xfd\xbd\x3d\x2f\x0c\xdf\xbe\x7d\xa3\xf3\xf3\x73\x7d\x4d\x28\xea\ \x50\x6d\xb4\x65\x15\x46\x2a\xd1\xd6\xf7\xed\xc6\xdb\x57\xef\x2e\ \x0f\x0f\x8e\x9a\x3f\x07\x9f\x56\x37\x20\x55\x37\xf8\x47\xc0\x0b\ \xa8\xce\x04\x20\xb0\x72\x5e\x59\xc0\xc5\xce\x07\x99\x96\x34\xf8\ \x54\xd3\x86\x0b\xbc\x36\xf8\xae\xee\xa2\xdd\xb8\x7b\x2b\x5f\x1d\ \xa9\x08\xf8\x84\xba\x62\x99\xcb\xe7\x68\x6c\x74\x8c\x6a\xb5\x1a\ \xb5\x52\x3c\xb2\xc6\xaa\x75\x62\x17\x43\x43\xa5\x5f\xaa\x9c\xfa\ \xbc\x10\x70\x65\x45\x74\x2e\x83\x74\x2a\x93\x89\x68\xb8\x25\x68\ \xe5\x72\x99\xd6\xd6\x56\xe9\xaf\xc3\x43\xfa\xfa\xe5\x0b\x7d\xf8\ \xf0\xc1\xbb\x0f\x5d\xb2\x76\x3e\x9f\x1e\xae\x0e\x85\xe5\xca\x50\ \xf8\xfd\xeb\x56\xfd\xf9\x93\x97\x17\x97\x17\x17\xce\x08\xbd\xce\ \xc1\xa7\x02\xc5\x5c\xd2\x63\xd2\xc1\x77\x2e\x00\x12\x70\x41\x8d\ \x58\x52\xe3\x6b\x82\x47\x5b\xd6\x50\xfd\x1a\x42\x10\x5a\x5a\x58\ \x9e\xcf\x2d\xad\xce\x65\x83\x30\x30\x1a\x3c\x02\xd3\x89\x89\x09\ \xaa\x8d\xd4\xa8\x54\x2a\x91\x4d\xd1\xf2\x98\x62\x32\x1a\x9b\xff\ \xdf\x39\xf4\x3b\x51\x7b\xe7\x00\xde\x1a\x93\x68\xbf\x34\x78\x96\ \xca\xb0\x70\x32\x47\x41\x60\x89\xb7\x5f\x4a\x25\x2a\xf5\xf6\xd2\ \xc2\xe2\x22\x1d\xec\xef\xd3\xa7\x4f\x9f\xe9\xe3\xa7\x0f\x08\xf1\ \x1c\x1e\x5a\xa5\x36\x14\x0d\x0e\xf7\x87\x6f\x5e\xbe\xbf\xfc\xf4\ \xfe\xd3\xa5\xac\x7d\xe3\xe0\xb5\x75\x40\x3f\x28\x74\x20\x00\xd6\ \x9a\xa4\x7a\xe7\x18\xbd\x3c\x24\x76\x8e\x37\x09\x9e\xf7\xe1\xca\ \x60\xb4\x79\xff\xd7\x7c\x4b\x93\xad\xae\xe4\x21\x7b\x98\x9a\x9a\ \xa4\xa9\xe9\x69\xca\xe5\x72\xea\x41\x08\x4b\x06\x0f\x2d\x17\x70\ \xcd\x66\x93\xe2\xd6\xde\x8c\x63\xdf\x66\xed\x27\xf0\xbd\xe2\x73\ \x0d\x67\x3e\x72\xef\x2c\x6c\x36\x08\x28\xb0\x96\x82\x20\x90\xac\ \x48\x04\x26\x25\xa8\xb2\xce\x51\x7f\x7f\x1f\xf5\xf5\xf5\xd1\xec\ \xdc\x0c\xbd\x79\xf5\x9a\x3e\x7e\xfc\x28\x99\x86\x4b\x62\xae\x30\ \x32\x66\x61\x79\x36\x3b\x32\x56\x09\x9f\xfe\xf1\xe2\xe2\xf0\xe0\ \xb0\x79\x33\xe0\xa5\xb7\x3d\x78\xde\xae\x13\x03\x30\x6e\x83\xb2\ \x62\x0c\x53\x72\x53\xe0\x79\x5b\x5c\x99\xcb\xad\x6e\x2c\xe5\x92\ \xb8\xc2\x48\x96\x2b\x1a\x3f\x36\x36\x46\xb3\xb3\xb3\x1e\x3c\xfd\ \xe8\xbf\x49\x70\xcb\xff\x71\x1c\xb3\x19\x96\xfd\x52\x15\xa1\x74\ \xdc\xa2\x8a\xc1\x58\xcb\xc5\xb2\x8e\xf1\x6b\x99\x66\x93\x1a\xc6\ \x24\xc2\x1f\x86\x21\x0b\x05\xc7\x18\x52\xec\xc1\x05\xf9\x61\x36\ \xf0\x97\x5f\x2c\x14\x69\x6d\x6d\x8d\x66\x66\x5b\x82\xf0\xfa\x0d\ \x79\x41\x20\xe5\xce\xd8\x6d\x05\x77\x1e\x6c\x14\x5e\x3c\x79\x71\ \xfe\xf5\xf3\xf7\xc6\xdf\x00\x1e\x63\x3a\xb5\x00\x5e\xe3\x21\xbc\ \x46\x92\xc2\x9b\x00\xcf\x5a\x65\xee\x3c\x5c\xcf\x8f\x4f\x8d\x66\ \x34\x78\x09\x02\x87\x06\x69\x79\x79\xc5\xa7\x6e\x4e\x2a\x85\x84\ \x0a\x91\x68\xb9\x63\x50\x9c\xb6\xf1\xee\xc1\x9b\x2b\xc1\x92\x81\ \xc0\x18\x06\x25\xf3\xc5\xef\xab\x3a\x95\xc3\x31\x23\x1d\x1e\xb4\ \xe8\x70\xbd\xd1\x60\xa1\xe0\x79\x5c\x84\xf2\xcf\x28\x60\xe8\x46\ \xe6\x8a\x69\x09\x24\xdb\x28\x16\xbb\x68\x65\x75\x85\x26\x27\x27\ \xe8\xcf\x27\x4f\x68\x77\x77\x57\xa5\x8f\x36\x30\xd4\x0a\x74\x73\ \xdd\x3d\xdd\xf5\x57\xcf\xdf\x5c\xfc\x1d\xe0\x3b\x8e\x01\x2c\x4e\ \xc2\x70\x44\x4d\x4c\x6a\x64\xaf\xff\x6f\x07\x1e\xf2\xcf\x25\xdb\ \x87\xff\x7e\xbf\x38\x30\xd8\x1f\xfc\x08\xbe\xd8\x55\x6c\x81\x5f\ \xa6\xc1\x81\x41\x29\x5d\xc6\x24\xab\x24\x5a\xd4\x68\x36\x39\x3d\ \x63\xe8\x9c\xc3\xe3\xad\x1a\xe0\xa2\x76\x2e\x34\x21\x40\xaa\x48\ \x83\xea\x1d\x34\x5a\x2c\x81\x21\x87\xeb\x16\x57\xe3\x92\x0c\xc9\ \xc5\xec\x5a\x88\x9a\xae\x41\x56\x5c\x84\x65\xf0\x0e\xa9\xa4\xf5\ \x90\x03\x9f\x8e\xde\xbb\x7b\x97\xb6\xb6\xb6\xe9\xc9\xd3\x3f\x39\ \x73\x50\x99\xc7\xd8\xe4\x48\x54\xec\x2e\xda\x27\xbf\x3d\x3d\x6b\ \x36\x9a\xed\x23\xfb\x7f\x10\x7c\xa7\x2e\x40\xa0\x42\xc3\x63\x5d\ \xd6\xec\x3c\xb2\xd7\xe0\x7d\xb3\xd4\x57\xb2\x8f\xff\xe3\x7e\xb1\ \xd0\x95\x87\xbf\x97\x6d\x7a\x66\x9a\xe6\x66\xe7\xfc\xc3\x24\x79\ \xa9\x83\x8d\xb5\xdd\xd1\xe9\xe9\x29\xef\x58\x3f\x39\x8a\xb5\xb4\ \x46\x63\x15\x43\xd0\x7c\xd1\x73\x04\x7d\x10\x1c\x5c\x91\x93\x1e\ \xad\x95\x57\xd2\xc7\x38\x6e\x7a\x81\x88\x0d\xc7\x0d\x36\xb1\x0a\ \xaa\xaa\xca\xd9\x40\x79\x88\xfa\x07\xfe\x95\x9e\x3d\x7d\x4a\x9f\ \xbf\x7c\x51\x42\xd0\x3f\xd8\x17\xdc\x79\xb8\x51\xf8\xfd\xbf\xfe\ \x3c\x3f\x3b\x3d\x8f\x3b\x4e\xe9\x90\x96\x1b\x35\x26\x5d\x29\x3b\ \xb5\x00\x1c\x04\xc9\x82\xdc\x36\x92\x06\x4a\xbb\x03\xf0\xe8\x27\ \xdc\xe7\xe8\xf8\x48\x74\xf7\xd1\x46\x3e\x88\x42\x19\x81\x07\xb4\ \x76\x6b\x8d\xaa\xd5\xaa\x84\x70\x0e\x39\xbb\x44\xed\x27\x27\xa7\ \x5c\x9d\x93\x7e\xbc\x61\x74\x5a\xda\x65\x2e\xae\x11\xe1\x3e\xa4\ \x02\xb0\x05\xb8\x44\xf6\x32\x0e\x6b\x26\x32\xe4\x7c\x8b\xfb\x43\ \x23\x46\xcb\xff\x2f\x4b\x22\xbb\xe0\xa0\xd3\xbb\xa5\x40\x02\x48\ \x82\xf0\x79\xc1\xb0\x91\xa1\x95\xd5\x55\xea\xee\xe9\xa1\x17\xcf\ \x9f\xab\x54\xb3\x50\x2c\xd8\x3b\x0f\x36\xf3\x4f\x7e\x7b\x76\xbe\ \xbf\x7b\xd0\xec\x14\x7c\xfb\x31\xe6\x2a\xa7\x4e\x2d\x00\xaa\x80\ \x94\x44\xc8\xb1\x6f\xbb\x6b\x80\xc7\x5a\x5c\xd4\x59\xfe\x75\x31\ \xf7\x63\x11\x27\x93\xc9\xd2\xe6\xe6\x26\x95\x4a\xbd\x02\x40\x83\ \x3f\x3b\x3b\xa3\xe3\x93\x63\x8a\x9b\x31\x81\x21\x37\xa0\xbd\xd8\ \x0c\x7c\xf7\x95\x1a\xbf\x13\x98\x57\xf3\x7f\x27\x82\x92\xaa\xf9\ \x82\x88\xbb\x58\x10\x6c\x64\x21\x38\x64\x13\x53\xe0\xd4\x9a\x2c\ \x08\x0d\x1f\x4c\x86\x41\x20\xc5\x1e\x94\x7a\xc3\xc0\xd2\xe4\xe4\ \x24\x15\x8b\x05\xfa\xed\xb7\xdf\x7c\x86\x22\x9f\x56\x70\x79\xda\ \xac\x6d\xae\xe6\xdf\xbe\x7c\x7b\xf1\xe5\xe3\xb7\xfa\x0d\x83\xef\ \xc0\x02\x60\x51\xf9\x95\x34\x90\x48\xb4\xdf\x5c\x0b\x3c\xff\x77\ \xef\xf1\xed\xfc\xf8\xe4\x68\xe6\xc7\xb2\x6d\x4f\x6f\x2f\x6d\xac\ \xaf\xfb\x08\xdf\x25\xf0\x8d\x2f\xe2\xb4\xaa\x25\x74\x74\x78\x44\ \x0d\xf1\xf1\xbc\x23\x48\xc3\xa6\xe3\x3c\xf7\xa3\x85\x80\xc6\x0b\ \x60\x65\xea\xf9\x78\x9a\x35\xc1\x5a\xfa\x44\x5e\x11\x12\xe7\x21\ \x99\x00\x86\xc8\x2c\x25\x48\x0d\x86\x6b\x62\x6f\x0d\x18\xbe\x9c\ \xcc\xc7\x58\x43\x43\x43\x74\xff\xde\x7d\xfa\x9f\xff\xf9\x1f\x16\ \x74\xb8\x30\x4b\x34\xbb\x30\x9d\x65\x8b\xf0\xfa\xc5\x9b\x8b\x1b\ \x03\xdf\x69\x29\x98\xa1\x5b\x63\xd5\x0b\x15\x28\x4e\x27\xe0\xd1\ \xbf\xba\xbe\x94\x13\xf8\xaa\xd2\x56\xa9\x56\x68\x65\x65\x99\xcd\ \x22\x8a\x35\xe2\xf7\x19\x3c\x6b\x7d\x92\xa6\x31\x1c\x20\xd4\x80\ \xf5\xb5\x80\x19\xd2\x2e\x98\x71\x7c\x34\x21\xfd\xa0\x86\xb8\x21\ \xd6\x02\xe3\x64\x3e\xc5\x64\x6d\xc0\x2d\x58\x93\x64\x6d\x59\x00\ \x62\x28\x47\x61\xc9\x9a\x31\xcf\x27\x0f\x1e\x81\xb6\xa5\x9e\x9e\ \x9e\x56\x80\x78\x8f\xfe\xf8\xe3\x0f\xda\xdb\xdf\x53\xef\x6f\x5b\ \xb5\x82\x88\xbf\x3b\xf8\xfc\xf1\x4b\xfd\x26\xc0\xe3\x45\x53\xfb\ \xcd\x02\x22\xe9\x74\xaa\x4d\x80\x67\x8d\x49\xce\x6d\x2d\xc6\x73\ \x7b\x6c\x6a\x34\x5a\x58\x99\xcb\xca\xf4\xc4\x0f\xcf\xcf\xcf\xd1\ \xaf\xb7\x6e\xf9\x37\x71\x44\xf0\xdd\x1c\x67\xec\xed\xee\xf9\x17\ \x2f\xe4\x7c\x3d\x57\xe0\x27\x77\xcb\x6d\xd9\xa5\x95\x64\x60\x2e\ \x09\xee\x78\x27\x87\x63\xea\x1c\x44\xa8\x00\x2a\xf7\x80\xa0\x53\ \x9f\x3b\x16\xbf\x61\xa5\x50\x84\xb5\xf5\x7b\x06\xf4\x2b\x61\x74\ \x38\x47\x1c\xfb\xc2\x94\x5c\x30\x9e\x23\x97\xb5\xd7\xd7\xd7\xb9\ \xb4\x2d\xd9\x09\xb4\x69\x72\x76\x3c\xdb\x7a\xb9\x15\xca\x33\xc5\ \x97\x5e\xfa\x59\xa3\xcd\xbf\xa8\xe4\xca\x63\xb3\x52\xc8\xf2\xfd\ \x1d\x09\x80\x98\xbb\xa4\xe0\x63\x2c\x84\x42\x16\x33\x22\x80\xe8\ \xc7\x4e\x3e\xaa\xbd\xfd\x60\x3d\x0f\xf0\xe4\xd7\xdb\x58\xdf\xe0\ \x68\xdf\x8f\x73\x78\x3a\x6c\xea\xf9\x05\x0b\x9b\x42\x49\xfd\x1c\ \x77\x03\x34\x00\xb3\x96\x02\x42\x1a\x60\x75\x00\xe3\x75\x81\x18\ \x82\x23\xeb\x0b\x74\x9d\x2d\x42\x62\x62\x6f\xad\xd4\x86\xeb\x24\ \x21\x86\xf1\xea\x5c\xb2\x3e\x9f\x22\x76\xec\xf3\xb1\xb8\xc0\x8a\ \xa2\xd0\x2b\xc6\xdc\xdc\x1c\x1e\xaa\x58\x09\x56\xa2\x42\x57\x41\ \xc8\xea\x67\xdd\x31\x78\x08\x50\x27\x02\x80\x2f\x4d\xad\x45\x1b\ \xe0\x13\x01\xd4\x12\x28\x07\x0a\xc5\xbc\x79\xf8\x6f\xf7\x0a\x61\ \x10\x70\x4f\xe2\x46\x16\x16\x16\xd9\xf4\xa3\x46\x2f\xe6\xa9\x7e\ \x59\xa7\x9d\xdd\x1d\x6a\x48\x21\x47\x53\x34\xb2\x43\x47\xc9\x18\ \xb0\xd1\x80\xe1\x1a\x30\x5a\x59\x0e\x81\x93\x08\x92\x1f\x05\xe2\ \x84\xf5\xa4\x6d\xe0\x46\xac\xf8\x7f\xf4\x00\xb2\xda\x70\x0d\xfa\ \xeb\x13\x07\xc1\xf1\x42\xe0\x92\x83\xfc\xdc\x38\x73\x60\x2b\xc0\ \xd5\x4f\xd4\x00\x0c\xb1\x70\x98\xe5\x5b\x0b\xb9\x4c\x26\x82\xf2\ \x69\x45\x4c\xfa\x04\xbc\xec\xe8\x97\xb6\xa8\x72\xfb\xcd\xe2\x63\ \x10\x63\x94\xc6\x8b\xed\x49\x03\x0f\xa9\x33\xfe\xe3\x88\x47\xad\ \x3c\xbf\x65\xd6\xac\x07\x2f\xd5\xac\x91\xd1\x11\x5f\xcf\x77\xa4\ \x3f\xeb\x3a\x3d\x39\xe5\x0a\x19\x9b\x46\x58\x05\xa5\xa7\xb1\x68\ \x1a\xe0\xca\xc3\x55\x10\x52\x5c\x03\x40\x68\xed\x07\x74\x27\x2f\ \x6b\x1c\x84\x01\xae\xc6\xc9\x82\x7e\xb4\xd4\x26\x30\x5f\xfb\x79\ \xac\x2c\x69\x86\x76\x25\x29\x71\x09\x39\xb6\x04\x0d\x14\x8e\xa4\ \xc0\x14\x84\x01\xcd\x4c\xcf\x50\x7f\x7f\x3f\xe2\x12\x43\xfe\xb3\ \xb4\x85\x95\xf9\x9c\xb5\x1a\xbc\x05\x78\x9e\x9f\x80\x07\x1b\x12\ \x96\x32\x88\x3a\x74\x01\x00\xcf\x2d\x98\x98\x36\xe0\x93\xfe\x56\ \x31\x23\x57\xea\xeb\x0d\x04\x3c\xff\xf1\xef\xd3\x57\x56\x56\xb8\ \xad\x02\x94\xa3\xc3\x43\x3a\x3c\xfc\x4b\xd6\x85\x64\x6b\x9d\x32\ \x80\x0b\x29\x80\xf9\xd6\x1a\xad\x7d\xbf\xd2\x7e\x07\x41\x02\x58\ \xa5\xe5\xbc\x61\x0d\x95\x5d\x48\x95\x4f\xfb\x78\x11\x24\xad\xf1\ \xc9\x75\xd1\x15\xa1\x53\xf6\x57\x58\x34\x63\x5f\x37\x50\xb1\x55\ \x18\x05\xb4\xb4\xbc\xc8\x75\x01\x32\x06\x96\xe0\x97\xbe\x52\x30\ \x35\x3b\x99\x69\x0f\xde\xb4\x03\x2f\x68\x4d\x87\x31\x00\x5e\x9e\ \x48\xdb\xc9\xe2\xd4\x16\x3c\xaf\xbb\xb4\x36\x9f\x1d\x1d\xaf\x49\ \x6d\x5f\x3e\xd6\xc8\x66\x69\x63\x73\xd3\x9b\xb7\xc4\x89\xf9\xc2\ \xce\x09\x57\xf4\x14\x78\xfd\xfd\x80\x80\x80\x69\x57\x70\x0c\x69\ \xb8\xe8\x85\xa9\xe7\x5d\xf9\x69\xe9\x82\x19\x87\x69\xd6\x02\x23\ \x53\x30\x97\xd3\xb8\x74\x1f\x6f\x9c\x6e\xc6\x5a\x78\x84\xb4\x16\ \x04\xbd\xb1\x00\x88\x10\xc8\xfd\x92\xa1\x6c\x36\x47\xab\xab\xab\ \xfc\x02\x4a\x09\x41\x65\xa4\x1c\x95\x6b\xe5\xf0\x9a\xe0\xa1\xd0\ \x9d\x08\x00\x59\x64\x9c\x80\xd2\x16\xbc\x7c\xfd\x52\x89\x96\x56\ \x17\x72\xea\x13\x26\x6b\xe9\xde\xbd\x7b\xf2\x0a\xd7\x24\xb9\xb7\ \xff\xe6\xee\xf8\xf8\x2a\x78\xec\x0a\xa6\xd6\x54\xe9\x13\xb8\x86\ \x9c\xce\xf9\x53\x32\x06\xf1\x24\xb2\x26\xaa\x7a\xce\x08\x35\x83\ \xbc\x1f\xb2\xa2\x8b\x49\x36\x08\xa4\x95\xae\xf1\xd8\x8c\xee\xc3\ \xd8\xa4\x4f\x55\x2b\x65\x77\x71\x2c\xc7\x8d\x20\xe0\x97\x49\xfe\ \xbd\x88\x68\x3a\xc6\x4f\xcd\x8d\x67\x7b\x4b\xbd\xc1\x75\xc1\x1b\ \x23\x96\xac\xa3\x18\x40\xa7\x19\x29\xd1\x27\x25\x7b\x14\x86\xe6\ \xd7\x3b\x6b\xb9\x04\x90\x8c\xdd\xd8\xd8\xa0\xde\xde\x5e\x02\x20\ \xe3\x83\x9f\xc3\xc3\x43\x11\x26\xe4\xa9\x36\x4d\x08\x90\xd2\xc9\ \x0e\xa7\x20\x42\x26\xcc\x70\x31\xa2\x53\x44\xc9\x18\x43\x50\x74\ \x97\x66\xea\xe5\xc7\x08\x74\x99\x0b\xeb\xcd\xd1\x7f\xaa\x8f\x97\ \xea\x9d\x6c\x68\xea\xf4\x32\x6e\xfb\x81\xb0\xde\x0c\x57\x3b\x11\ \x38\x1a\x7f\x5e\xff\x89\xdb\xf4\xcc\x0c\xd6\x64\x1e\xc6\xd2\xf4\ \xfc\x64\xc6\x06\xf6\x5a\xe0\x3b\xae\x03\x08\x00\x23\xf3\x7d\x5b\ \x4b\x1a\x3e\xa4\xe0\x8b\x59\x6c\x95\x79\xf3\x85\x9c\x45\x0e\x6b\ \x68\x6e\x7e\x8e\xaa\xb5\xaa\xfe\x30\xdd\x39\x0f\x5f\x32\x00\x1e\ \xa7\xc1\xe3\x1c\x49\xbf\x43\x50\x82\x6c\x1d\xd0\x64\x1d\x07\x04\ \xc2\x04\xd6\x5f\x44\x44\x63\x02\x22\x98\x88\x44\x60\xa0\xdb\x0e\ \xda\x8f\x40\x52\x6f\x2e\xad\x2b\x25\x45\x4d\x9d\xe8\x94\x7b\xc0\ \xa7\x6c\x98\xc7\xa5\xe4\x5a\xb5\x42\x95\x6a\xf5\xff\x18\x37\x0b\ \x1d\xc9\x79\x20\x08\xdb\xc3\x3f\x0f\x1e\x33\xf3\xfb\x3f\xd1\x3e\ \xc0\xc1\x26\x19\x48\x7e\xbb\x94\xd2\x7c\x5a\x47\xa7\x78\x15\x5d\ \xce\x13\xae\x76\x77\x35\xe9\x5d\x49\x0a\x73\xd9\x9c\x81\xf7\x16\ \x23\x37\x2d\x02\x03\x6f\xed\x3d\x42\x03\x08\xe0\xce\xd2\x16\xc3\ \x15\xa0\x08\xe0\x35\xf7\xf7\x7f\x7f\xe7\x72\xed\xa5\x81\xcf\x7f\ \x9b\xcd\x26\xbc\x7f\xfb\x8e\x6f\xad\x87\xfb\xf1\xf3\x87\x34\x40\ \xa1\xf6\x43\x84\x3b\x83\x79\x4b\x60\xd7\x6a\xe3\x2a\xf2\x3b\xd3\ \x8f\xef\x7a\x5f\x5e\x8f\x0e\x4d\x20\x3d\x52\xa8\x6c\xab\x13\xcf\ \x21\x40\x04\x79\xb2\xfb\xa7\x07\x03\x35\x81\x4e\x86\x86\x2b\xbc\ \x80\x62\x40\xeb\xe0\x20\xa4\xb2\x25\xb8\xe4\x18\x72\x0f\xe5\x41\ \xc9\x94\x9a\x0f\x28\x52\xf8\x78\xbe\x58\x2e\xf5\x1f\x70\x29\xe1\ \x67\xe0\x6d\xa6\x8d\xe1\x78\x12\x08\x7f\x13\xb6\x5e\xc0\x93\x7d\ \x66\xd5\x2f\x55\x84\xbc\xd8\xb7\x44\x5e\x24\x37\x60\xb6\x55\x22\ \x7c\x2a\xd8\x20\xc0\x88\xaf\x7b\x7e\x32\x68\x66\x18\xba\x05\x29\ \x94\x50\x14\xe9\x5e\xe8\x09\x13\x37\xac\x7a\x2a\x68\x9a\x15\xcf\ \x21\x65\x6c\x33\x22\x17\xd0\x02\xc2\x24\x52\x18\x06\x98\x23\xa2\ \xaf\x6d\x98\x16\xe2\xe9\x6d\x88\x3a\xf1\x01\x66\x4b\x45\x06\x93\ \x29\x40\xc0\x29\xe6\xd4\x73\x7c\xf1\xfa\xf9\xe2\xb7\xc0\x13\x4b\ \x2d\xdc\x71\x1c\x20\x0e\x00\xaf\x39\x46\x81\x72\x23\x44\x2e\xdf\ \xc6\x4b\xe5\x20\x86\x0a\x36\xd1\xbd\xaa\xc4\x4e\x5d\x57\x64\xf9\ \x50\xf9\x85\x40\x00\x78\x0b\xc4\xc4\x2b\x8d\x00\xfb\x43\x20\x50\ \x88\x06\x4a\xed\xde\x21\x8f\xd2\x12\xf6\x2e\xae\xe0\x68\x1e\x50\ \x10\x2e\xd9\xd1\x22\x86\x18\x8a\xd8\xbf\x35\x20\xad\x3f\xa3\x5a\ \x98\x2b\x87\xe6\x5a\x9a\x04\x91\x42\x9d\xa1\x84\xd2\x59\x5a\x61\ \xb3\xde\xa4\x6d\x0b\x77\x3a\x86\xed\x7e\x33\xfd\x6f\xfd\xcf\x64\ \x04\xf0\xe3\x43\xc1\x05\x30\x81\xc0\x47\x5f\x3c\xbe\x4f\xc5\x8d\ \x90\x66\x49\xe9\xc7\x4f\x9f\x02\x38\x99\x3e\x78\x5d\x55\x65\xf8\ \xd2\x94\x65\x40\x20\x02\x4d\x83\x2a\x6d\x58\x58\x52\x86\x6d\xad\ \xca\x11\x75\x2f\x91\x04\xbc\x6d\x0e\xc2\x74\x6d\x38\x5d\xce\xda\ \x2e\x97\xde\x0d\x63\x04\x51\xbb\xca\xe1\xeb\x78\x17\x8d\x88\xf8\ \xf5\xab\x14\x43\x9a\x03\x0e\x27\x45\x75\x90\x23\x40\xfb\x03\xf8\ \xc8\x49\x69\xcc\x5c\xf1\xd4\x5e\xae\xf1\x86\x57\xaf\x5e\x49\x0b\ \x53\x8d\x3c\x4a\x0b\x71\x04\xf0\xa3\xdc\x40\xad\x66\x7a\x00\x5d\ \xa4\x9a\x8c\x66\xf8\xf1\xe1\xc3\xc3\x74\x9d\xda\xb5\x18\x18\xf9\ \xf0\xe1\x43\x58\x2d\x97\x7c\x45\xb9\x7c\x2d\xca\xad\x00\x94\x07\ \x0a\x32\xf4\xb0\xe6\xcd\xb0\xa7\x64\xe5\x79\xde\x65\xda\xb6\xff\ \x93\x10\x79\x6d\xde\x22\xaa\x3a\x58\xb9\x86\xf4\xaf\x57\x3b\x02\ \x52\x85\x16\x52\xd6\x2f\x79\x36\x6a\x1f\xd3\x47\x0b\x2c\x1a\x8d\ \xd4\x08\x28\x0e\xc5\xda\x67\x74\x0f\xc7\x72\x9f\xc0\x77\x7a\x6f\ \x0a\x68\xab\xe7\x4d\xd7\xd6\xb3\x5c\x33\xb3\xea\x87\x50\x38\xfd\ \xe6\xe6\x26\xb0\x6d\x2e\xf7\x53\xfc\xfa\x55\xb5\x45\xb7\x16\xed\ \x7e\xd4\xfc\xe8\x50\xb0\xae\x43\xc9\xd1\x7e\xdf\xff\xfb\xee\xf3\ \xdb\x15\x2e\xae\x3e\xbb\xe7\x2f\x9e\xeb\xc6\x6c\x93\xae\x9b\x86\ \x6a\xbf\x8c\x29\x18\x0c\x1f\x83\xdf\x26\x34\x05\x41\xf2\x8c\xfa\ \x3e\x34\x3e\x16\xe1\xe3\xa8\xcc\x5b\x55\xdd\x86\x9f\x3f\x7e\xa8\ \x9a\x48\x82\x28\x5f\xdb\x7d\x02\x3a\x8b\x5e\x83\x37\x85\xa5\x9b\ \x63\x1d\x6e\xab\x2a\x9c\xfa\x7e\x41\xf5\x15\x00\x74\x8a\x6f\x17\ \xe8\x56\xb2\x8e\xd0\x8a\x84\x98\xc3\x34\x99\xf8\x82\x9c\xa6\x7b\ \x29\x37\xd2\x4a\xc3\xa9\x3a\x99\xab\x5d\xe7\x3e\x7e\xf4\x58\x98\ \xd0\xf4\xaa\x75\xce\x34\x7c\x98\x67\x69\x8c\x36\x01\x81\x84\x2c\ \x0d\x86\x82\xef\x3d\x38\x4c\x53\x8a\x72\x4a\xf6\xfa\xe1\xfd\xfb\ \xb0\x48\x2b\x86\x74\xa6\x69\x1a\x7d\x98\xa1\x5c\x35\xeb\xf1\xb9\ \xa9\x2b\x96\xc2\x20\x84\xa2\xe6\x8d\xbf\x76\x8a\x10\xab\x40\x95\ \x2f\x5d\x55\xb9\x97\xef\x97\xc0\xd3\x87\x64\x86\x20\x72\xb5\x9a\ \xb8\x73\x45\x5b\x7b\x89\x78\x69\x35\xe6\xa2\x8e\xba\xc9\x95\xc7\ \xe7\x42\x68\xe8\xce\x75\x3e\xbf\xa8\x3a\xb2\xa0\x94\xae\x9f\xe6\ \x94\x0d\xcd\x42\xd7\xe8\x5e\x16\x6d\x9f\x2b\x21\x00\x59\x9c\x2f\ \x66\xe1\xe1\xc3\x87\x21\xa2\x26\x60\xbd\x59\x4f\xff\xfa\xfb\xcf\ \x09\xb4\xb7\x4c\x81\xc0\xd7\x18\xcf\x01\xb4\xfa\x0d\x76\xbf\x1b\ \xe8\xa7\xbf\x7e\xf7\x52\x6e\x9f\x25\x78\x31\x9f\xa9\x63\x07\x2f\ \xad\x22\xc9\xe3\xe9\x58\x00\x0c\x7f\x14\x20\x87\xd2\x77\x85\x5f\ \x6b\xe0\xa5\x07\x58\xad\x4c\x12\x98\xab\x88\x9a\x4a\x21\xe6\x0c\ \x7c\x20\xe0\x1e\x66\xd8\x45\x68\x98\xb1\x85\x68\x42\x99\x13\x5b\ \x8c\xd1\x2b\x6e\x5f\xd7\x75\x06\x0b\x7c\x01\xe0\x72\xa5\xd2\x21\ \x00\xf0\x9e\x02\xb8\x5a\x28\xd2\x32\x2d\xac\x1a\x62\x1b\xee\x52\ \x26\xa1\x7c\x94\x04\xc0\x04\xd5\x42\x70\x78\xb0\x9f\xdd\x05\x5e\ \x58\x92\x50\x87\xb1\x6e\x20\x56\x2c\xa5\x6a\xb9\x5a\xc4\xfb\xa9\ \xcf\x2d\xa2\x6e\xe0\xfd\x87\xf7\xb9\x4e\x1e\x2d\xb7\x41\x2f\x85\ \xd4\x32\x36\xb8\x23\x65\xc1\x89\x7f\x2f\xd3\x9b\x85\x19\xf1\xbd\ \x94\x55\xcb\x15\x44\x5a\xa1\x77\x6a\xbe\xb5\x91\x49\xa8\xc2\x27\ \x6f\x44\x4b\xf3\x6c\xf2\xe8\x7a\x52\x3b\xf7\x6f\x57\x75\xae\xd5\ \x7a\x92\x70\xb7\x1d\x4a\x84\xbd\x4f\xf0\xe9\xad\xa0\x6f\xa0\x83\ \xba\x6f\x9a\x44\xf4\x4c\x68\xe9\x25\x74\x38\x57\x42\xa0\x1a\x02\ \x68\x81\x45\xb8\x7f\xff\x7e\x20\x49\xdf\xee\x36\xd3\x3e\xff\x5f\ \x02\xef\x58\xc1\xb8\x50\x30\x13\x34\x91\x42\xa0\xe0\x43\xf6\xfb\ \x03\xbb\x60\x1f\x3f\x09\x31\xc2\x96\xf5\x1d\x3a\x00\x8d\x00\x6b\ \x8e\xe7\xc7\x41\xf0\x91\xd7\x0e\x03\x61\x62\xd4\x12\xe4\x22\x92\ \xe0\xef\x8c\x3c\x00\x71\xed\x90\xa8\x89\x50\xf5\x3e\xd6\x65\xe8\ \xc2\x52\x3e\xf6\x54\x9b\x6d\xb8\x41\xd4\xae\x38\x46\xce\x69\xd4\ \x32\x39\x1d\x8b\x8f\x61\x16\x3a\xc4\x00\xf0\x93\x98\x7d\x55\x37\ \xe1\x64\x4d\x82\x01\xe0\x3d\xe1\x84\x91\x76\x51\x87\xac\x16\x78\ \xb8\x84\x39\x8b\x18\x53\xd9\xbd\xb4\xc0\x20\xf0\x71\xbc\x09\x30\ \xf9\x8b\x5c\x89\x2a\xed\x7e\xf1\x78\x1e\x51\x00\xb3\xdf\xed\x52\ \xff\xfd\x8a\xaa\x52\x2d\xd4\x5c\xf1\x3e\x9e\x2b\x9b\x82\x51\xce\ \x8b\x88\x18\xe3\x41\xe0\xa3\xb4\xcc\x51\x9a\x06\x25\x02\xa5\xc7\ \xae\x1f\x3d\x83\x85\xa8\xbf\x32\x32\x27\xcc\xd4\xfc\x39\xd3\x1c\ \x88\x02\xb8\x86\x67\xf5\xae\x32\x09\x0c\x1d\x4b\xc3\x14\xa1\x2b\ \x81\xa8\x86\x96\xe6\x78\x72\xe4\x92\xf4\x15\x9a\xab\xa8\x6d\xf2\ \xf9\xe4\x2f\x4a\x16\xa5\xe8\x20\x9e\x6d\x12\x76\x87\xcd\xb4\x04\ \x7e\xbc\x1b\x88\x50\xb0\x46\xc6\xc1\x60\xc5\xdc\xcf\x96\x73\xd2\ \x06\x40\x81\x9f\xe7\xcf\xb5\xc3\xe0\xc8\x19\xb6\xbf\x5c\xf9\x14\ \x0c\x19\x03\x44\x0d\x3b\x93\xc0\x22\x39\x14\x28\x20\xf9\xc3\x37\ \x47\xb9\x4a\xe9\x78\xa8\x78\xc3\x6e\x9f\x5d\x60\x40\x38\xb4\xef\ \x87\xd7\xb9\xee\xea\xc9\x9b\x5e\xbf\xd3\x0f\x72\x01\x11\xf4\xa3\ \x70\xa1\x68\x54\x43\x26\x48\xcf\x22\xe0\xd5\x82\x06\x42\xa9\xd0\ \xae\x84\xb5\xae\x1b\xc4\x1b\x3c\x2c\x08\x06\x9e\xd9\x49\x66\x0e\ \xff\x67\xe5\x2a\x97\x24\xf9\x6d\xf8\x4f\x76\xf7\xc0\xd2\xf1\x85\ \x99\x39\x9f\x83\xaf\x91\x7c\xcd\xeb\xfd\xdf\x22\xcc\xcc\xc7\xcc\ \x3b\xb3\x43\x3d\xd3\xdd\x96\xb2\x2d\xab\xa2\x72\x1d\xd4\x91\xef\ \x6e\x7b\xbd\xd3\x5b\x67\x5b\xb2\xf8\x27\x47\x46\xf9\x90\x2c\x05\ \x60\x52\x52\x2b\xad\x8f\x62\xa5\x6b\x2f\x09\xef\x79\x9c\xd7\x8a\ \x03\x04\x7f\xd1\x0e\x9e\x13\xab\xab\x51\x56\xa4\x06\xc5\xc9\x1b\ \xb6\xcf\xca\xa0\xb9\x28\xb5\xb6\x53\xf4\xc5\x97\xfa\xb0\xb0\x6e\ \x3d\x22\x10\xf2\xfb\xff\xdf\xbb\x7d\x6f\x44\x6d\xfb\xce\x54\x4c\ \xc8\xc7\x62\xa5\xe4\xe4\x12\xd7\xdf\xd7\xa9\x58\x94\x2c\x22\x56\ \x46\x6c\x78\xe4\x0c\x20\xd8\xd3\x6e\x5a\x42\x5d\x8f\x0a\x7f\x5e\ \x7c\x3f\x2e\x9c\x6d\x0e\x21\x83\xa8\x65\x1c\x00\xc4\x45\x64\xdf\ \x25\x05\xaa\x0a\x00\xc7\x5a\x1a\x31\xcb\xe0\x8f\x5b\xff\x45\xc8\ \x19\x8e\x30\x16\x2f\x45\xb7\x9f\xe0\xc2\xf9\x0b\xb8\x77\xf7\x1e\ \x2c\x12\x69\x1e\xc1\x51\x3c\x7e\x36\x4f\xaf\x6c\x82\xf9\x6a\x06\ \x70\x11\x2d\x02\x0b\xc8\xd1\x50\xe8\x59\x81\xe0\x00\xce\x53\xee\ \x9b\x8c\x47\xee\x17\x9b\xa5\x5c\xb6\xfc\x15\x0f\x96\x38\xad\x0b\ \x86\xf0\x61\xa2\xd3\xc1\x9c\xf0\xbe\x86\xe6\x2e\xa5\xa4\x62\x9f\ \x82\xdd\x66\x87\x6c\x03\xe2\x8c\x64\x87\x06\x0a\x51\xab\x8f\x3d\ \xa5\x1d\xf2\xe7\x56\x92\x25\xa2\xe2\x5e\x19\x43\x8b\x56\x88\x30\ \xaa\xc7\xda\x52\x06\x40\x7e\x87\x8b\x12\x30\xe3\x03\x81\xc0\x03\ \x50\x02\x65\x82\x7c\x79\x28\x68\x74\xb1\x6d\x77\x10\x66\x20\x4b\ \x38\x63\x18\x97\x46\x20\x3f\x1b\x1f\x8e\x46\x12\x8f\x98\x1a\x23\ \x30\x44\x02\xe0\xb7\x10\xa3\xf1\x18\xfb\x07\x07\xda\xc6\xc6\x6a\ \x2f\x31\xb4\xc8\x9b\x1d\x9f\xa4\x17\x13\xfe\xf5\x24\x00\xb9\xf8\ \x05\x58\x44\x31\x6f\x17\x4e\xad\x4c\xcf\xc6\x69\xb7\xae\x32\x96\ \x07\xb1\xda\x3e\xbf\x04\x10\x2a\x32\x67\xde\xf5\x2e\x42\x8a\xf8\ \xb7\x5b\xd2\x20\x29\x7c\x61\x2f\xd0\xe1\x0c\xac\x24\x93\x10\xf6\ \x95\xc4\xa5\x8c\x31\x8e\x12\x41\x09\x1b\xbd\xcd\x5a\xbb\x6d\xb1\ \x35\x50\xa9\xc0\x2d\x64\x10\x0c\xb1\x13\x55\xa7\x9e\x16\x5b\x98\ \x36\x22\xf3\x04\xa0\xc4\x95\x42\x17\x84\x42\xe7\x93\x6d\xb5\x55\ \xfd\xae\x8c\xe3\x87\x2f\xe2\x4c\x09\x2a\x7f\x8f\xca\x88\xa2\x3d\ \x51\x4a\x00\xb6\x06\x91\x59\xd2\x45\x25\x93\x2b\xa1\xb3\x67\x8e\ \xd0\x6c\xd6\x00\x02\x0c\x7a\xae\xdb\x2a\x09\xff\xfa\x3d\x82\xdc\ \x58\x20\xc7\x06\x0e\xd6\x65\xac\x0a\xfa\xe3\xe2\xc5\x8b\x05\x68\ \x54\x58\x4c\x14\x97\xdd\x1d\x11\x02\x20\x6c\x9c\x4d\x2e\x01\x40\ \x28\x81\x96\xae\x2c\xc2\xf3\xa1\x72\xd5\xfb\x3a\x71\x88\x98\x8b\ \x48\x11\x38\x80\x3d\x63\xf9\x6d\xf3\xd8\x6d\x77\xda\xca\x45\x04\ \x0a\xcb\x0a\x31\x7f\x46\x10\x67\x84\x98\x3d\x8e\x5d\xdb\x62\xb9\ \x5c\xe9\x19\xec\xed\xed\xa3\x36\x06\xa8\x47\xb5\xea\x79\x66\x43\ \x14\x95\x9d\x1d\x6d\x7d\x3d\x36\xcd\x46\xe3\x07\x93\xf1\xc4\xd5\ \x87\xb8\x4d\x52\x34\x8d\x24\xb7\x4b\x4a\xe2\xa0\x10\xf5\x90\x00\ \x29\xd3\xce\x05\x23\x0d\xe0\x92\x07\x0f\x1f\x82\x90\xc7\x68\x5c\ \xd3\x50\x94\x3b\x80\x4a\xca\x76\xb5\xaf\xad\x02\x00\x0a\x65\x2c\ \xfb\xd2\xe5\x0b\x35\x08\xfe\x9f\x8c\x46\x03\xec\xd9\x17\x6b\xe2\ \x32\x90\x1b\x5d\xbe\x68\x36\xee\x64\x6f\x7e\x08\x76\x71\x4f\xa5\ \xb5\x6c\x62\xd6\x8d\x39\x48\x8e\xe7\xa7\x1e\x30\xe6\x60\x01\xec\ \x33\x3f\x60\x04\x0b\x63\x9b\x2e\x17\x45\x19\xa9\xca\x18\x18\x02\ \xb0\xbc\x40\xb7\x85\x87\x6d\x01\xbf\x21\xd9\xf8\x63\x11\xb5\xec\ \xd7\x9b\x06\xe7\x4e\xa5\xc1\x64\x3a\x05\x21\xa8\x34\x48\x29\xe9\ \x3f\x1b\xb6\x6f\xd6\xfa\xc6\x61\x7d\x01\xa4\x81\xa8\x51\x2d\x08\ \xd1\xf2\x14\x14\xca\x32\x11\xb1\xa0\x5a\x71\xf3\x1d\xe4\xea\x4c\ \x60\x97\xd0\x75\x00\x84\xac\xcf\x51\x08\xc8\x83\xb4\x78\x34\x04\ \xf2\x92\x32\x64\x29\x70\xfc\x74\xc6\xcf\xf7\x16\x78\x8d\x16\x31\ \x8f\x1e\x2d\xd2\xe5\x4b\x47\x55\x3d\xaa\x32\x35\xad\x5d\x1b\xc1\ \xfd\xf7\x8f\x7e\xec\x63\x76\xf5\x39\x3f\x04\x90\xc4\x0e\x02\x29\ \x2a\xe8\x1d\x64\x89\x22\xa9\xe3\xef\xb0\xbd\xe9\x5c\xe3\xb1\x33\ \xc9\x49\xa5\xa2\x4d\x41\xb0\xdf\x81\x2b\x10\x04\x2b\x9e\x10\x41\ \xbe\xc9\x8b\x05\x10\x54\x1a\xe4\xcc\x5a\x62\x10\x89\x1b\x9c\xc4\ \xce\x72\x76\xdb\xba\xbe\xd3\x7f\x83\xd4\xd0\x80\xcf\xae\x1d\x92\ \x2d\x38\x73\x74\x84\x58\x55\xce\x48\x66\xcd\x6f\x9a\x2d\x76\xbb\ \x2d\xa4\xe8\x0a\x2a\xba\xde\xc9\x64\x0c\x21\xbf\x0d\xa5\x41\x59\ \x74\xfe\x2c\x40\xa8\xce\x08\x36\x29\xeb\x10\xc0\x22\xa5\xc9\x4c\ \x01\x87\x07\x87\x58\x2c\x97\x20\x82\x35\xdb\x3a\x08\xb3\xa7\xf3\ \x82\xf0\xaf\xdd\x1f\xe0\xc9\xd3\x65\xfa\xc5\x2f\xfe\xdb\x7c\xfb\ \xdb\x9f\x1a\x9f\x3b\xbf\x5f\x41\x80\xfd\xc3\x83\x48\x6e\x55\xa8\ \x01\x68\x37\xd4\x7c\x5a\x5f\x98\x7f\x29\x44\xbd\x33\x84\xab\x09\ \x7f\xa7\x20\xbc\xb8\x7d\x0c\x42\xea\x12\x52\xef\xde\x05\xc4\x98\ \x00\x00\xbb\xf7\x6c\xc4\x21\x6c\x77\x5b\x9c\x9c\xcc\x51\x57\x23\ \xb0\xb0\xba\x5f\xb9\xa6\x41\xd0\x6e\x37\x32\x7b\xfa\xb4\x9f\x1d\ \xcf\x53\xbb\x6d\xb5\x39\x74\x3d\xaa\xe9\x94\x58\x74\xee\xc2\xf9\ \x78\xfe\xd2\x85\x2a\x8e\xa6\x94\x58\x34\x9f\x30\x99\xa6\x7c\x5b\ \x15\xc7\x77\x68\x76\x45\x8d\xb6\xdd\x28\x84\x8d\x59\xe0\xa9\x6f\ \xd8\x33\x33\x52\xcd\x95\x32\xa4\x5f\x87\x92\x11\x5c\xee\xf9\xbc\ \x40\x32\xeb\x8c\xdc\x23\x32\x5b\x45\xcc\x8b\x12\xe7\x5b\x95\xc6\ \x43\xc5\x95\xfd\xd6\x60\xcb\x04\xef\xa0\xfe\x26\xe0\x50\x1b\xcd\ \xb6\x93\x5f\xff\xe6\xea\xf6\x8b\x9f\xff\xf0\xf8\x8b\x5f\xf9\x68\ \x7d\x7a\x40\x01\x9e\x93\x1f\x3a\x79\x38\x42\xd6\xb2\x6f\xe4\xf1\ \x61\x00\x5c\xdc\x66\xdf\xbf\x6f\xb8\x70\xab\x08\x36\xfc\x58\xec\ \xf2\xe8\xed\x05\x91\x1f\xa0\x52\xde\x19\x45\x71\xfb\xa6\xd7\x13\ \x33\x96\xcb\x25\xaa\x7a\x84\xbe\x57\xb5\x91\x09\x24\x09\xf7\x6f\ \xdf\xea\xee\xdc\xbc\xd7\x9a\xd8\x75\x3f\xbe\x4f\xb2\x6d\xb6\x98\ \xcf\x4e\xd2\x69\xf7\xae\xee\x53\x9f\xfb\xe4\xe8\xf2\x47\x3e\x52\ \x31\xd5\xca\x3c\x29\x71\x4e\xcb\x06\xed\x36\xaa\xeb\x61\x16\xfd\ \xe7\x1b\x73\x63\xd3\x6e\xb5\x06\x89\x26\x56\x50\xea\xcb\x2f\x2b\ \x80\x5c\x18\x38\xf1\x81\x60\x04\xf6\x3e\x09\x2e\x01\xb8\x74\x23\ \x6d\x8c\x27\x63\x9f\x83\x86\x39\xd9\x80\xbf\xf6\xda\x0d\x22\x7c\ \x61\xff\xb9\xf2\xb0\x43\x3d\x4e\x3f\xec\x05\x75\xed\x04\xde\x9b\ \x4c\x3d\xf9\x63\xa9\x50\x8f\x07\x38\xb6\x1e\x62\x2c\x43\x05\x2a\ \xc6\x7d\x74\x4f\xe3\x3b\xdf\x0b\xe0\x1e\x93\x20\x31\x3f\x27\x3e\ \x29\x38\x72\x57\x43\xd7\x31\x80\x04\x56\x71\x1c\x20\xcc\x6a\x93\ \x10\x80\xb6\x59\xf0\xd5\x7f\x5d\xdd\x2d\x17\x2b\x36\xcb\xff\xa5\ \xc8\x59\x66\x96\x9b\x57\x6f\xed\xe6\xcf\xe6\xfd\x67\xbe\xf8\xd9\ \x71\x35\xde\xa7\x41\xa7\x2f\x16\x2b\x80\x82\x7a\x11\x07\x07\x87\ \xa0\x10\x06\x63\x4b\x03\x3c\x59\x1c\x09\x80\xc2\xa8\x53\xc6\xc9\ \x43\x5c\x9f\xbb\x71\x0c\x21\x17\xe4\xf0\xd0\x74\x59\xf6\x56\x48\ \x4d\x2a\x92\x58\xe4\x07\x87\xf1\x68\x5c\x30\x15\x05\x1a\x30\x19\ \xd4\x76\xad\x38\xe1\x5f\x2b\x14\xfc\xfc\xa0\x38\x8a\xb7\xef\xad\ \xb0\x5a\xf7\x20\x01\xea\x58\xa9\xff\x49\x1e\xbd\x2b\x4a\xa5\x0a\ \x17\x84\x50\xcc\x8b\xcf\x0a\x02\x78\x78\x16\xf0\x7c\x81\xba\x95\ \x65\x1a\xb9\x88\xf5\x9b\xc5\xaf\x44\xdd\x98\x8b\x17\x22\x65\xa9\ \x01\x60\xbb\x9a\xf3\x5f\x7e\xf7\x97\xed\x6a\xb9\xe6\x37\x01\x50\ \x2e\x4e\x16\xfc\xb7\x3f\xfe\xbd\x69\x9b\x25\x03\xc8\x65\x6d\xaa\ \xef\x73\x22\x08\x0a\xde\xd8\x83\xa7\x81\x9d\xf8\x00\x2c\x97\x2f\ \x96\xde\xa5\xa2\x52\x99\x99\xd0\x33\x21\x25\x02\x33\x9e\x57\x04\ \x62\x4e\xae\x38\x26\x12\x05\x3a\xca\x43\xdc\x92\x9f\x6a\x94\x13\ \xa1\xc8\x0d\x8c\x26\x23\xf2\x44\xda\x1b\x54\x04\x95\x43\x21\x49\ \x75\x62\xc1\xfd\x47\x1b\x4c\xa7\x15\xbe\xf4\xf9\x8f\x22\x10\xdc\ \xff\xcd\x2b\x29\xf4\xba\xeb\x28\x2a\x2c\x58\x13\x69\x65\x04\xd0\ \xdc\x22\xf7\x31\x5d\x23\x76\x29\xc1\xae\x26\x50\x4a\x8d\xec\x2e\ \x46\xdd\x98\x5a\xe6\x2b\x15\xfd\xb5\x65\x22\x09\x24\xad\x0c\x37\ \x1f\x02\xa1\x57\x60\xe5\xcb\x68\x99\xbf\xc4\x89\x71\xf3\xca\xcd\ \xf6\x4b\xdf\xfc\xda\x84\x31\xd2\x16\x35\x43\x45\xce\x66\xdd\xe4\ \x6a\x21\xaa\x80\x30\xc1\x7a\xd5\xa0\xe7\xbc\xc4\x3e\x01\x69\x78\ \xb2\xe8\x7c\x3a\x19\xd4\xe5\xa4\x44\xf0\x06\x94\xd8\x0b\x08\xa2\ \xee\x43\x50\x85\xe1\x09\xfb\x5e\x50\x47\x41\x15\x13\xaa\x28\xae\ \x32\x58\x20\xa1\x0c\x9e\xc5\x18\x35\x7a\xd9\x77\x9d\x9d\x7b\x56\ \x03\xc3\x5a\xa9\x94\x78\x6f\xce\x00\xd3\xbd\x49\x84\x8d\xa6\xe9\ \x91\x70\x06\x1c\xce\x83\x64\x0d\xc8\xae\x6c\xf0\x6f\x84\x77\x71\ \x0f\x9f\x3b\x63\x38\x28\x92\x4d\x9b\x11\x5c\xfb\x0b\x59\x94\x4d\ \x72\x88\xd5\x7c\x53\x13\x93\x6e\xfc\x09\x10\x0d\x1c\xd1\x34\x8d\ \x17\x64\x98\x78\xbc\x7b\xfd\x56\xd7\x6c\x1a\x76\xe3\xb5\xe8\xcd\ \xfb\x12\xc2\x97\xba\x72\xb5\x5c\xf1\xcd\x2b\xd7\xbb\xa3\x0f\x7f\ \xb2\x5e\x37\xc0\xe3\x59\xc0\xde\xe1\x79\xec\x2d\x09\x07\x07\xfb\ \xe8\xda\x1a\x0f\x1f\xae\xcd\xf5\x94\x4c\x58\x82\xbb\x79\xe8\x30\ \xd9\x13\xe8\xc7\x46\x78\x08\x81\x21\x00\x3b\x33\x30\xe7\x1b\xcf\ \xec\xb8\xbf\x2e\x10\x1a\xef\xdc\x8e\x2a\x32\xaa\x68\x0c\x21\x09\ \x31\x16\xa6\xa3\x4a\xe5\xde\xce\x0b\xa4\x52\xa1\x48\xe6\x01\x78\ \x3b\x09\x30\x58\xc9\xf0\xa1\x4d\x94\x41\x35\x18\x67\x40\xd4\x03\ \xbc\x06\xa8\x2d\x5c\xbf\x32\x98\xe1\x06\x4c\x81\xa3\x2f\xeb\xea\ \x6d\x1e\xc0\xb0\xb8\x82\xfd\xcc\x4d\xec\xe0\x0c\x06\xf1\xaa\x5d\ \x0a\xd9\xd7\x8f\x15\x5a\x13\xfd\xcd\xf2\x84\x1f\xdc\x7b\xd4\x11\ \xd1\x2b\x3a\x69\xb8\x5f\xdc\x75\x49\x66\xf3\xf5\xe9\xbf\x95\xcc\ \x8f\xd7\x7c\x3c\x5b\xc9\x6c\xb6\x94\xf9\x6c\xcd\x0c\x69\x7e\xfc\ \x93\x1f\x9d\x9d\x9c\xfb\x48\xac\xeb\xe6\xb4\xfe\xe1\x02\xa8\xd9\ \xe2\xf0\xf0\x08\xf5\x68\xac\xeb\x4a\x2c\x08\xb6\xad\x04\x20\x28\ \x21\xa1\x31\x01\x31\xe9\xc7\x2c\x00\x19\xd1\x61\x12\x91\x05\x6c\ \xb7\x39\x98\xbe\x64\x91\xcc\xe4\xec\x90\x74\x66\xa0\x93\x80\xc4\ \x84\xb6\xcf\xc9\x1e\xa6\x0a\x91\x5a\xd4\xb1\x45\x00\x6b\x24\xd3\ \x85\x68\x2e\x1b\x2f\xf6\xff\xd6\x0c\x50\xd7\x01\x3e\x14\xf0\x09\ \xc0\x02\x18\x15\x18\x87\xba\x38\x92\x1d\x48\x5a\x10\x75\x00\xc1\ \x5d\x17\x57\x07\x80\xc0\x7d\x66\x9b\x90\x11\x5e\xc0\xb0\x8b\x02\ \xb6\x77\x8b\x05\x8b\x43\xea\x1d\xed\x12\xc0\x29\x97\x6d\xc5\xaa\ \x46\xda\xed\x40\x00\x9e\x3e\x7e\xd2\x03\x02\x0a\x4e\x78\x66\xc1\ \x72\xd9\xc8\xfc\x64\x23\x8b\xd3\x7f\xf3\xf9\x4a\x8e\xe7\x6b\x9e\ \x1d\x2f\x65\xb5\xda\x0a\x5e\x31\xae\xfc\xf3\x3f\xbb\x6f\x7e\xf7\ \x43\x7b\x5d\xd7\xa2\xdd\x35\x88\x01\x80\xb0\x8a\xde\xbd\xe9\x54\ \x5d\x46\x26\x82\x04\x93\x5d\xee\xa2\xea\xda\x2a\x44\x04\x42\x66\ \x6e\x86\x6d\xd2\x6a\xf9\x05\x60\x91\x2c\x21\xf4\x33\xc7\x26\x64\ \x26\xf0\xf8\x0b\xb3\xe4\x39\x80\xc4\x01\x4c\x53\x74\x3c\x45\x0c\ \x09\xcf\xe6\x1d\xb8\xeb\x31\x99\xd6\x80\xa9\x05\xdd\x7f\xd9\xe1\ \xfd\xcd\x19\x60\x34\xaa\x03\x4a\x86\x28\xe2\xd6\x99\x48\x11\x8c\ \x29\x04\x93\x1c\xa9\x42\x07\x42\x8b\x88\x16\xa0\x04\x08\x3c\x62\ \x97\x9f\xce\x09\x9e\x30\x72\x41\x00\x78\xe1\x64\x29\x4d\xdc\x83\ \x34\x9c\x5c\xc7\xbd\x7b\x17\x02\xad\x0c\xfa\xc7\xdf\xae\xb7\x0f\ \x1f\x3c\xe3\x93\x93\xb5\xcc\x17\x1b\x9e\xcf\xd7\xb2\x5c\x6c\x84\ \x59\xf0\x36\xe3\xe6\xb5\x5b\xdd\xb7\xbf\x97\xc0\x88\x8a\x71\xd8\ \xdb\x9b\x82\x39\xa9\x0a\xaa\xaa\x60\x48\xaa\x6c\xe0\x21\x00\x62\ \x4a\x2c\x10\x0d\xd2\x45\xd7\x89\xe0\x70\x38\x5d\x87\x31\x01\x82\ \x58\x04\x35\x33\x48\x60\x40\x02\x8a\xbd\x03\x70\x55\xa9\xd3\x32\ \x91\xc4\x52\xe1\xc9\x71\x8b\xbf\xff\xfd\x0a\xce\x9d\xdd\xc3\xe7\ \x3e\x7d\x11\xe3\x2a\x92\x13\xde\x6d\x80\x77\x55\x01\x66\x71\xba\ \x98\x7f\x5e\xb4\x04\x88\x8c\x90\xb8\x42\x27\x53\x40\x12\x08\x0c\ \xa2\x04\x91\x1e\x90\x5e\xe7\xca\x18\x46\x58\x77\xa2\xc4\x23\x87\ \x82\x82\xd1\x58\xcc\xd0\x32\x63\x4b\xdf\xeb\x03\xe6\x73\xc6\xcd\ \x3b\x0d\xba\x44\x78\xf8\x78\x8e\x90\x1a\x7c\xf0\xc1\xcf\x76\x22\ \x82\xf7\x35\xe6\xb3\x45\x8a\x99\x3c\xe0\xd4\x99\x49\x92\x10\x42\ \x8d\xba\x8a\x16\x98\x30\x10\x8b\x00\x6c\xae\x21\x38\x47\x0d\xd9\ \x8c\xb7\xe0\x37\x1f\x46\x70\x67\x02\xc0\x5c\x66\xd8\xcd\x17\x64\ \x61\x41\x9e\x94\x03\x01\xf0\xc4\x91\x88\x14\x17\x13\x00\x66\xf3\ \x0d\x7e\xff\xe7\xdb\xe8\x77\x1b\xde\xaf\x7b\xfe\xd8\x47\x2f\x85\ \x77\xb2\x01\xaa\xba\x7a\x4e\x02\xd8\x78\xa5\x6e\x71\xdf\x3d\x82\ \x25\x82\xb9\x02\x73\x0d\x5b\x38\x98\x25\xc3\xa2\xc1\x79\x6e\x3f\ \xb7\x32\x2b\x2d\xf0\x5c\xad\x73\xcd\x5c\xd7\x09\xfa\x64\xef\x28\ \x23\x68\xbf\x5e\x4d\xd2\x9c\xba\xec\x58\x35\xd6\x1d\x5c\x44\x37\ \xfe\x3e\x89\x0f\x68\x22\x4a\x98\x3b\xa1\x18\x29\xa5\x4e\x93\x4a\ \x90\x84\x40\x40\x5d\x47\x50\x01\x02\x21\x8b\xd8\x91\xe5\x15\x18\ \xcc\xc5\x2d\x77\xa2\x3b\xc1\x6d\x0e\xb0\xc0\xde\x25\x87\xb9\x98\ \x3d\x60\x4c\x00\xe6\x92\xf8\x22\x62\x74\xf1\xf1\xec\x78\x85\xff\ \x71\x77\x96\xcd\xad\xec\x5a\x1a\x56\xda\x76\x1b\xc3\x97\x99\x99\ \x99\x99\xf9\xde\xcf\xf3\x17\xe6\xd7\x0c\x7e\x1b\x66\x38\x4c\x1b\ \x0f\xc3\x66\x66\x66\xc6\xe4\x24\x3b\xb1\xdd\xa3\xa7\x8e\xde\x52\ \x2d\xb9\xc7\x75\x53\xd7\x1b\x55\xa5\x92\x5b\xdd\x76\x62\xaf\x57\ \x8b\xb5\xf4\xb7\xff\xf4\xc8\xc2\x97\xbf\xfc\xb1\xea\x2f\x7f\xf9\ \xf5\x7c\x72\xaa\x33\x46\x5b\x31\x00\x7a\x50\xc0\xa6\x26\x89\xb8\ \x56\x0c\xd8\x36\x38\x17\x59\x56\x74\x9b\xea\x87\x72\x81\xf8\x74\ \x56\x19\xc5\x94\x0a\xcf\x41\xba\x78\xf7\x98\x57\x6c\x3c\x7a\xce\ \xe4\x04\x22\xba\x57\x38\x59\x0a\x00\x36\xe7\xb1\x51\x36\xe4\x29\ \x1d\x1e\xc6\x8a\x87\xf0\xb0\xff\xc0\xce\x99\xed\x3b\xe9\xfd\x46\ \xd1\x09\x29\xcd\x22\xb0\xb3\x8a\x9d\x9d\x2b\xc6\x8c\x08\xe0\xb6\ \x56\x7c\x61\x95\x42\x89\x54\xc3\x81\xb1\x00\xd4\xe4\xe1\xe4\xfe\ \x86\x0d\x7b\xbb\x5b\xb7\x1e\xe8\x7e\xef\x7b\x9f\xaf\xfd\xf8\x47\ \x5f\xce\x57\x0c\x80\xe5\x25\xc2\x8a\x6a\x36\xe3\x57\x63\x4a\xec\ \x92\x44\x07\x73\x6d\x2b\x62\x4b\x61\xb4\x7e\x51\x5c\xaf\x31\x67\ \x50\x0e\x34\x9b\x21\x8d\x12\x98\x07\x4d\x9c\x2a\x1e\x4c\x67\xd5\ \xfa\x18\x36\xb0\x0f\xe8\x8c\x8c\x0d\xb0\xf3\x46\x27\x98\xb2\xe2\ \xf1\x3f\xd4\xaa\x4c\x15\x88\x04\x3f\x04\x9b\x2e\x00\xc1\xcf\x06\ \xf7\x70\x3f\xb0\x6f\x1e\x95\xf9\x17\x4c\x42\x67\x81\xa1\x39\x59\ \x0c\xe0\x59\xa2\x51\xbf\x8d\x54\x07\xfd\xe6\x02\x80\x76\x61\xc5\ \xc6\xf5\xb2\x6e\xa2\x87\xb8\xd5\xab\x37\x2d\xbf\xfc\xf2\xee\xee\ \x0a\x3d\x81\x00\xa0\x6b\x00\xa0\x8c\xdf\x84\xb8\x43\xbb\x2b\x9b\ \x1b\xfe\x3e\xe2\xea\xf6\xbd\xb2\xcf\x15\x24\x8b\x29\xd2\x21\x01\ \xa4\x12\xa2\x10\x99\xf3\xa5\x6a\x6a\x6e\x84\xed\x5d\xef\x7d\x47\ \x8d\x0f\x07\x00\xcd\x46\x9d\xc0\x12\xff\x9f\x1f\x43\xf2\xc7\x58\ \x2c\x68\xa5\x3d\xc6\x3c\x0b\x10\x58\xb5\xdc\xeb\x07\x4e\xe6\x87\ \xf0\x3a\xee\x47\xe0\x75\x98\x03\x14\x66\xbe\x50\x4f\x56\x7e\x2a\ \x02\x52\x00\x2c\x03\x80\xa4\xf9\xed\x63\xc5\xca\x01\xa0\x0f\xb2\ \x48\x1b\x4e\xf4\x21\xc4\x76\x22\xa4\xbd\xd6\x6b\x11\x1b\x0e\x10\ \x4d\xc1\x92\x8e\xcc\x67\xc4\x2f\x01\x08\x70\x09\x43\x94\x82\x02\ \x15\x1f\xff\x50\xdd\x8d\xb0\x7d\xf8\x13\xfe\xf3\x60\xfb\x9e\x98\ \x33\x33\x53\xa4\xc2\xf1\x9a\x55\xea\xad\x82\xd7\x21\xb8\x88\x1e\ \x40\x10\x6b\x1a\xf0\x1c\xd7\x51\xd7\x71\x81\xa5\x8b\xe8\x2e\xee\ \x3b\x08\xb2\x5d\xa3\xe6\x01\x45\x24\x7c\x84\xff\x1f\xcb\x01\x6c\ \x5b\x19\x00\x70\xae\xa4\x22\xc0\x10\x75\x45\xdd\x31\x0e\x4f\x0f\ \x97\xd3\x9f\x15\xed\xd2\x7b\x1a\x83\xad\x0d\x08\xc6\x42\xc9\x55\ \x7e\x70\xbc\x73\xb4\xf7\x7e\xf4\x23\xb9\x3f\x8c\xa2\x36\x12\xe2\ \x7f\xfc\x83\xf9\xbb\xde\xf7\xae\x1a\xc4\x9d\x99\x9e\x44\xf6\x13\ \xeb\x87\xf8\x5e\xce\x2e\x39\x5f\xdf\x1d\x12\x45\xe2\x2b\xf0\xc5\ \xea\x0f\x4e\x2b\xb3\xf7\xbf\x9f\xac\xf2\x7e\x20\x34\xa3\xea\x95\ \xd8\xd5\xaf\x04\x11\x33\xa7\xa6\x6b\x38\xb3\x5d\xb8\xa3\x02\xc0\ \x2d\xab\x03\x5c\xbd\x7a\x35\xd5\x01\xd4\x57\x0e\x0c\x37\xec\x7d\ \xc8\xf7\x3c\x9e\x52\x52\xc2\x25\xf0\xb4\xf1\x2d\xa9\x4a\x42\x9b\ \xf4\xf9\x71\xac\xb8\xae\xab\xba\xef\xff\xec\x7b\x9d\x7a\xe3\x4f\ \x52\x08\x29\x7a\x99\x7d\xef\x27\xdf\xee\xf0\xcb\x40\xc0\x77\xfb\ \x7a\x87\x18\x00\xad\x16\xfe\x7d\xe7\x1d\x4b\x37\x20\xbc\x88\x6f\ \x38\x00\xcf\xa3\x2f\xf8\xd7\xb1\x6e\x91\x76\x33\x05\x10\x58\x31\ \x20\x51\xa0\xb9\x31\x5d\x8b\xf0\xca\x74\x32\x84\x57\x23\x3f\x21\ \x34\x9d\x79\x3c\x1a\x00\x5c\xbf\x7a\xc3\x28\x0e\xe7\xcf\x9f\x17\ \xc1\xec\xa1\x08\x2b\x17\x09\xe9\xf6\xb0\x14\x14\xc6\xeb\x68\x1c\ \x1a\x01\x14\x00\x80\x79\x4a\xaf\x53\x8f\x90\x5b\x02\x43\x7d\x62\ \x3a\xfb\xee\x8f\xbf\xd5\x76\x7f\x42\xfb\xe1\xcf\xbf\xdb\x6e\x74\ \x88\xad\x93\x83\x3f\xeb\xdd\xbf\x6d\xcf\x65\x5a\x28\x80\xd8\xe9\ \xde\x07\x71\x45\x75\x63\x20\x7e\x60\x5e\xb1\xd8\x14\xd5\x3d\x63\ \xb9\x5a\xe6\x04\x14\x99\xb3\x74\x11\xd8\xe8\x03\xa6\x82\x49\x11\ \xf5\x03\xfd\xde\x46\x17\xc0\x02\x20\x14\x6e\x7c\x17\x3e\xe9\x65\ \x24\x00\xb8\x72\xf9\xaa\xe1\x2d\xe4\xbf\x2d\xd8\xa2\x0f\x43\x01\ \x50\x98\x52\xa6\x66\x8c\xdd\xc5\x79\x75\x9a\xea\xe2\xc4\x58\xb9\ \x7d\x2f\x3f\x0a\x5b\xb9\x61\xcb\xd4\xd1\xe1\x80\xa9\xb7\xbc\xe5\ \x4d\xbc\xcf\x41\x8e\x0f\x7e\xf2\xe3\xf5\x1f\xfc\xec\x3b\x6d\xca\ \xad\xba\x15\x34\x38\xc7\x4f\x7f\xfd\x83\xce\xfb\x3f\xf2\x9e\x7c\ \x2c\x2b\x3c\x10\x73\x5f\xc1\xf3\x03\x6c\x84\x05\x6c\xb0\x76\x37\ \xe7\x57\x3f\x5b\xd0\xe5\xf8\x2d\xe2\xca\xd7\x5c\xe0\x00\xfe\xb5\ \xd1\x0f\x62\x21\x6a\xad\x7e\x81\xc0\x0f\x22\xba\xae\x19\x35\x25\ \x1d\xc0\x34\xe6\x48\x82\x61\x34\x9c\xfa\xf2\xca\x01\x50\x2d\x05\ \xc0\x25\x00\x60\x1b\x7f\x10\xe5\x8b\x56\xb6\xb2\xd5\xcc\xb5\x42\ \xc1\x9a\xb3\x1e\x61\xbb\x75\x3c\x98\x78\xa4\x60\xe1\xf0\xe9\x2f\ \x75\x4d\x61\x07\x99\x82\x59\x48\xe4\xe4\xb8\x36\x92\x96\x61\xcd\ \x38\x68\xd0\xcc\x8f\x1d\x3b\xe2\xc7\xc2\x7d\xf4\xf3\x9f\x6e\xbc\ \xcb\x6f\x69\x5b\xfd\xf8\xfa\xb9\xb3\xe1\x84\xae\x61\x0d\xdd\xe1\ \x87\x3f\xff\x4e\x67\x7c\xba\x95\xf1\x8f\x00\xee\x4f\x7f\xea\x13\ \x1e\x04\x35\x37\xc1\x21\x95\xc1\x6e\x3d\x7b\xf6\x74\x90\xf9\xaa\ \x5d\xc0\xbb\xa3\xf5\xce\xff\x85\x38\x0a\xfe\xbc\xb8\x2f\xc0\xc9\ \x62\xf0\x73\xf2\x20\x28\xaf\x12\x20\x64\x5c\x0c\x38\x89\xf4\x7d\ \xb9\x95\x7a\x61\xcd\xea\xd7\x3d\xbc\x97\x23\x01\xc0\xfc\xdc\xeb\ \x3d\xf4\x80\xbc\x9e\x67\x9a\xbb\x7e\xfd\x3a\xbb\x53\xf5\xc7\xf8\ \x91\x54\x80\xa1\x1c\x00\xd1\xaf\x1f\xe7\xa4\xef\xe9\x1e\xbd\x10\ \x18\x0a\x71\x05\x80\xe6\x59\xfd\xdc\x00\xe1\xdb\xb0\xe3\xf1\x71\ \x08\x42\xd8\xd8\x03\xa0\x46\x8d\x02\xf4\x00\xff\x3c\xff\x5b\xdf\ \xef\x9a\x39\xe1\x75\x98\x45\xd7\x9e\x99\xa9\xfc\xe1\xcf\x7e\x3b\ \xb9\x67\xeb\x9e\xc5\xfd\xbb\x0f\xde\x3a\x73\xfa\x5c\x57\xcb\x4a\ \x19\x34\x6c\x7c\xfd\xf8\xa7\x3e\x5c\xff\xd8\x67\x3e\x54\xc7\x00\ \xe1\x9f\xe1\xf3\x3e\xe1\x8b\x35\xc2\xf6\xdb\xad\x26\x2b\x9a\xff\ \x8f\xca\xe6\x9c\x2d\x1c\x5c\xbf\x4a\x5f\xca\xb8\x27\xc7\x0d\x5c\ \x23\xc8\x7d\xcd\xc9\x37\xa0\x3f\xac\x52\x33\x59\x88\x21\xc8\xc5\ \x1b\xd2\x1f\x32\x53\x6d\x44\x79\x00\x21\xfe\x6f\x5d\xf1\x29\x00\ \x48\x80\x41\x41\x1e\x05\x00\xa4\x07\xf4\xfc\xfe\xf3\x4c\xd7\xa7\ \x4f\x9f\xa6\xb4\xf9\x50\x87\x90\x9a\x89\xc7\x97\x02\x20\x16\x97\ \x0c\xd3\xe6\x90\xc6\x56\xab\x45\x8a\xb6\xeb\x2f\xf7\x98\x27\xe5\ \xd9\x13\xa3\x13\x22\x88\xaa\xbe\xdd\xf5\xdd\x5b\x0e\x9e\x60\xe4\ \xed\xa1\xa1\xf3\x23\xb5\x9b\x75\x5f\x4e\xe5\xb4\x57\x5c\x2f\xbb\ \xc2\x53\xf5\x13\x5f\xfa\x54\xe3\x93\x5f\xfc\x64\xa3\xe7\x7d\xcb\ \x0b\xbe\xa4\x0a\x2e\xde\x7a\x33\xcf\xa8\x70\x5e\xad\x65\xc8\x7a\ \x9d\xdd\x8f\x2e\xc1\x77\x44\x11\xf5\x20\x6c\x04\xc5\x8f\x1f\xfb\ \x9a\x3b\x73\xfa\x24\x7f\x3f\xc8\xf3\x0c\xe2\x9a\xcc\x1e\x52\xc8\ \x01\x40\x4c\x89\x2f\xe2\x2e\x22\x39\x8d\xc2\xd3\x8c\x72\x14\x29\ \x48\x54\x09\xe2\xcd\x99\xc8\xa1\xb8\xa5\xdc\xca\x71\xb1\x5d\xbc\ \x78\xd1\xb2\x7f\xc9\xff\x51\x01\xc0\x1f\x80\xbc\xe4\x01\x50\xd3\ \xf5\x3e\x7f\xe0\xd1\x0f\x7e\xf0\x03\xe3\xf5\x93\x62\xb2\x72\x00\ \xd0\x8a\x14\x00\xb1\x87\xb4\x67\xb6\x53\x37\x5b\x6d\xae\x83\x6c\ \x8c\x20\x19\x0b\x1b\x47\xaa\x21\x3a\xd7\x61\xe7\xac\x27\xc0\xf4\ \xe4\xb8\x27\xe4\xa4\xaf\x48\x7e\xd1\x27\x6e\x9c\xf5\xa9\x5e\xd7\ \x1c\x44\xa8\xd6\xab\x63\x13\xf5\xf1\x8a\x88\x2d\xe2\xc0\x8d\xa8\ \x72\xf6\x9e\xf7\xbc\x9b\x11\x27\x0e\x39\x80\x70\x02\xb8\x04\x91\ \x40\x7f\x4e\xf0\x51\x88\x10\x08\x69\x8f\x8a\x2b\xc2\x4e\x9e\x26\ \xbb\x76\xb9\x61\x89\xee\x5f\x46\x8e\xc0\x9c\x38\x87\x8b\x71\x02\ \x1e\x97\xdb\x57\xd9\x52\xb2\x0a\xe0\x02\x70\xa1\x01\x33\x90\x9a\ \x41\x6a\xb4\xb3\xa7\xce\x2f\x8f\x14\x00\x27\x8f\x9f\x5e\xf2\x87\ \x3e\x49\xa3\xa6\xc4\x3b\x7a\x00\xe9\xc8\xa9\x18\x18\x0a\x00\xb3\ \x19\x42\xc4\x4e\x75\x81\x30\x8a\x35\x37\x6a\x55\xcf\xea\x27\xbc\ \xf2\xb9\x40\x6c\x1d\xc2\x27\x95\xfd\x04\x20\x07\x81\xb0\xd1\x51\ \xd6\xd0\xc0\xfd\x38\xe5\xaf\x73\xe4\x37\xa3\x7f\xf6\x3d\xde\x5c\ \xba\x49\x19\x19\x76\x31\x93\xfb\x4f\x3d\x7e\xc0\x42\x88\xd7\xcd\ \x7a\x5d\x82\x12\x2c\x7c\x26\x1e\x3f\x14\x3e\x81\x15\xa5\xef\xf0\ \x91\xc3\xac\x52\x7b\x76\x1f\x84\x74\x72\x03\xb3\xc5\x8c\xbf\x5d\ \x65\xde\x70\x89\x58\x3a\x56\x62\xc3\x9e\x51\x80\x07\x33\x4b\x81\ \x50\x38\xe9\x02\x12\x01\xe8\x3a\x66\x2b\x19\x66\x39\xbe\x99\x84\ \x5e\xa3\x05\xc0\x29\x0f\x80\x54\xbe\x9f\x3a\x75\xca\x1f\xa9\xfe\ \x71\xcd\x0d\x8f\x0a\x16\x5a\x61\x46\xd6\xd3\x8c\xcc\x97\x8c\x17\ \xe1\x49\xef\x96\x3d\x0c\x2b\xee\x2d\x2c\xf2\x88\x9a\xa9\xf6\x25\ \xe0\xb0\x51\xa3\xe8\xd7\x38\xcb\x5f\xa9\xdc\x94\x8f\xf1\xbd\x8d\ \xdc\xe6\x84\x32\xbc\x79\x28\x68\x74\xfe\x2f\xc7\x90\x85\x6b\x2c\ \x88\x86\x7f\x2f\x95\xc2\x22\x8b\x3d\xcf\xf7\xe5\x7f\x61\x4e\x1b\ \xd8\x62\xe8\x82\x99\xe0\xfc\xf1\xb5\x7a\x06\xd8\x3d\x0f\xea\xfb\ \xc7\x95\x1f\xf3\x13\x55\xaa\x86\x5b\xfd\x20\x02\x0a\x67\x75\x81\ \x22\x16\xeb\x30\x9c\x17\xb3\x3c\x75\x00\x9d\x3f\x73\xa1\x3b\x52\ \x00\x2c\x2e\xdc\xea\x5f\xbe\x78\xa5\xfb\xa6\xb7\xcc\xea\x19\x77\ \xe8\xd0\x21\x00\x60\x08\x9f\x70\x81\x28\xa7\x44\x6c\x11\x4d\x3b\ \xc5\x02\xd1\x04\x0a\x28\x51\xf7\x84\xc7\x0b\x58\x84\x2c\xdb\x9e\ \xef\xda\xb6\xd5\x0b\x7b\xed\xc7\xe2\x2f\x1f\xc5\x40\x94\xc2\x8e\ \xcc\x1d\x82\x34\x10\x32\xf7\x2c\x93\xcf\x7b\x33\x7b\x19\x5d\xff\ \x8d\x7b\xfd\xae\x2b\xbc\xde\xa0\x93\xc0\xd1\x17\xc4\x35\x98\x53\ \xf2\x26\x55\xcd\x39\xe4\x91\x84\xd3\x58\x29\x54\x66\xab\x1c\x32\ \x3a\x7a\x2e\xa3\x68\x03\x9f\x27\x85\x4f\xca\x4c\xb8\x16\x6a\x33\ \xbd\xcf\xc5\x1c\xf8\x54\x17\xd0\xff\x00\x10\xcc\xea\x8f\xef\x8d\ \xfa\x98\xa1\x15\xa9\x70\xd0\x60\xa4\x00\xa0\xf9\x73\xec\x96\x04\ \x00\xda\xde\xbd\x7b\xdd\x2f\x7e\xf1\x0b\x7e\xdc\x34\x28\x94\xae\ \xd0\x04\x00\x96\x03\x48\xfe\xc3\x36\x51\x9e\xfa\xc1\x4f\xae\xc0\ \x47\xdc\x75\xc3\x33\x39\x35\x08\x48\x7d\x62\x8e\x66\x74\x07\x2d\ \x2d\xa5\x96\x50\xa4\xf2\xfa\xb5\x05\x2c\x01\x58\xbc\xff\xfc\x31\ \xd8\xbd\x7f\x2c\x97\x9b\x16\xe5\x4a\x1e\x3a\x95\xb7\x73\x37\x3c\ \xbb\xbf\x7c\xe9\x12\x07\x5a\x38\x17\x9e\x51\xc5\x31\xa8\x6d\x58\ \x7b\x38\xe2\x96\xcd\x19\x88\x1f\x2b\xdf\x43\xb6\x9f\x61\xf7\x4c\ \x86\xf7\x1a\x70\xe8\xb3\xc2\xac\x22\x9e\x24\x8c\xf2\xf7\x89\xf9\ \x07\xf6\xaf\xef\x8d\xff\xff\xdc\xb9\x73\x96\x5b\x9f\x38\x03\xb2\ \x47\x0f\x80\x23\x3e\x45\xfa\xb3\x5f\xfa\x54\x4b\xd7\xc8\x1d\x4e\ \xc6\xe6\x10\x44\x35\x29\x82\x09\x00\x24\x22\xf9\xf2\x96\x60\xb2\ \x97\x2b\xd5\x10\x37\x2f\x04\x0e\x71\x0f\xf9\xd2\x05\x02\x58\xb4\ \x7f\xce\x5b\x05\x45\x4f\x5a\x74\x10\x33\x1a\x15\x63\xe0\x16\xde\ \xba\x6b\x5e\x7f\x98\x73\xee\x52\x1f\x02\x79\xd9\x9e\x13\xd3\xa7\ \xa6\x0e\x2b\x8a\x78\xbe\x23\xc9\x03\x7d\x80\x6d\xd6\xc8\x7a\x08\ \x48\xd7\x2a\x17\x47\xe8\x85\xad\x60\x10\x2d\x96\x78\xc8\xf0\x55\ \x60\x26\x02\x0a\x11\x3c\x59\xe5\x7d\x71\x09\x5b\xbe\xc6\xa5\xba\ \x00\x9f\xec\xe2\xfb\x75\x2d\x9f\x47\x35\x33\xf2\xff\xe4\xc9\x93\ \x86\xe3\x8a\x4e\xb7\x05\x00\xa7\x4e\x9e\x59\x66\x77\x8d\xce\xf9\ \xa7\xed\xd8\xb1\x03\x00\xa4\x44\x37\xec\x3f\xa6\xf9\x73\x2f\x2a\ \x50\xd5\x37\x92\x2c\x8c\x0b\x54\x84\x34\xe6\x65\xe1\x2c\x91\x05\ \x82\x85\xc5\x50\x53\x37\x82\x20\xb5\x26\xfa\xe1\xf4\x51\x7d\x1e\ \xfa\x01\xd1\xbb\xe8\xaf\x67\xf5\x41\xcc\x70\x5d\x14\xd6\x5a\x11\ \x11\x33\xa5\x74\x31\xf6\x4d\xa1\x09\xac\x8e\xf1\xf1\x4e\xb2\xca\ \x25\xdf\xca\x08\x2e\xe8\xe8\x59\x5b\x1a\x26\xfe\x1e\xce\x55\xc2\ \x3f\xc3\xeb\x46\xc5\x19\xe2\xd3\x8e\x1c\x39\x92\x5a\x6b\x5d\xe3\ \x01\x1c\x81\x2b\xd8\x64\xe5\x1e\xd8\x73\xc8\xa8\x9b\xbb\x77\xef\ \x86\x13\xac\x24\x2e\x40\xc8\x16\x1b\x19\xed\x5b\x73\xe9\x59\x84\ \x76\x3e\x2b\x79\x26\xcb\x70\x10\x71\xc8\x92\x8d\x13\x24\xef\x65\ \xbf\x3e\xcd\xbe\xdf\x3e\x67\x0f\x60\x62\xa4\xc7\x4a\xa5\x41\x44\ \x84\x79\x89\xf0\x3e\x3d\x78\x07\xc7\xb9\x6f\x82\x40\x22\xaf\x4b\ \x23\x77\x8e\xeb\xd0\x55\x20\x43\x9e\x41\x51\x3e\xee\x22\x32\x41\ \xa0\x66\xbd\x62\x02\x40\x00\x3b\x55\x00\xf7\xef\x16\x7d\x46\x0c\ \x00\xb5\x7d\xbb\x0e\x9a\x3f\x80\xb7\x09\x65\xd0\xb6\xf2\xb2\xb0\ \xac\xf6\x3c\xc4\xed\x05\x12\xda\x40\x35\x8b\x40\x80\x72\xc2\xc5\ \xd5\x29\x4e\x80\x08\x89\x3c\x5f\xa3\x63\xf4\x4a\xdc\xbc\x8d\x35\ \x48\x85\xd3\x63\xe5\xd1\x48\xe9\x03\x68\xdc\xc1\x42\x88\x3d\x00\ \x45\x66\xa2\xa9\xf9\x1f\x89\x5b\x94\x9c\x5d\x68\xe2\x04\x7a\x76\ \x08\x40\x62\x4c\x00\xab\xb2\x92\x59\x57\xef\xb1\x63\xc7\x06\xaa\ \x88\xed\xdf\x73\x7b\x01\x40\x60\xa8\x77\xf1\xfc\x25\xa3\x64\x3c\ \xff\xfc\xf3\xa9\x03\x28\x0d\x14\x61\x1b\x03\x80\xf4\xc7\x4e\x09\ \x6d\xee\x67\x76\x85\x9a\x51\x00\x52\x96\x32\x51\x43\xe6\xd4\xfc\ \x4d\x58\xb5\x6a\x15\x0f\xe9\xf2\x3f\x24\x20\x70\x36\x5f\x41\x0a\ \x23\xd7\xb0\xfc\x09\xef\xff\x80\x03\xc5\x55\xcb\x68\xbd\x7d\x74\ \xad\xf2\x84\xb8\xc2\x8b\x79\x2f\x5d\xc4\x4d\xe3\xfe\x9d\xc6\x60\ \xf4\x0f\x67\x9c\x1a\x8d\x9d\xcf\x7e\x27\x54\x71\x5b\x01\x40\xdb\ \xb9\x75\xef\x22\xa3\xda\x25\xaf\x2d\x1f\x38\x70\xc0\xa5\x0d\x82\ \x40\x74\x11\x9e\x66\x47\xeb\x15\xb4\x73\x96\x00\x29\x27\x70\x49\ \xed\x41\xb8\x4a\xb3\xd1\xa4\xb8\x93\x4a\xa8\x06\xf6\x4f\xb3\x11\ \x46\x4b\x64\x80\xa4\x7b\xe5\x79\x09\xf2\xef\x23\xba\xc6\x3b\x6d\ \x9f\x77\x3f\x85\x08\x0b\xf3\x34\x11\x9c\x31\x3d\xc4\x72\xa8\x18\ \xb0\x67\x19\x08\x44\x02\x4c\x88\x06\x36\x73\x00\x68\x57\xff\xe1\ \xc3\x87\xd9\xa9\xec\x8c\x38\xde\xbe\x4f\xab\xff\xf6\x02\x60\xef\ \xae\xfd\xb7\x08\x36\xe8\x9a\xb6\x7e\xfd\x7a\x63\x9e\xa8\xf6\x5e\ \xd9\xca\xd6\x98\x99\x39\x37\x3c\xb2\x98\x3e\xaf\x7b\x38\x51\x5c\ \x64\xfd\x6c\x90\x04\x08\xa4\x8b\xe3\x39\xb4\x72\x5e\x59\xba\xbe\ \x8b\xf8\x65\xe1\x68\x03\x98\x8c\xca\x20\xae\xd5\xea\xe0\x1a\xe6\ \x38\xb7\xb4\xf6\xa1\xd8\xbd\x48\x3b\x44\x0c\x84\x1e\x09\xa9\x6e\ \xe6\xf4\x19\xaa\x1c\xe2\xd3\x11\xd2\xd5\x8f\x09\x3e\xc0\x99\x0f\ \x1f\x3c\xba\x74\x47\x00\x40\xde\xfe\xb6\x8d\x3b\x17\x52\x2e\x20\ \x5d\x60\x78\x36\x70\x42\xd8\x64\xaf\x5e\x66\xae\x2d\xb1\x0d\x70\ \x32\x23\x12\xd2\xba\xf8\xc1\x03\x38\xee\x89\x36\xed\x5a\xed\x36\ \xfe\x85\xe4\xb9\xf2\x1c\x03\x19\x76\x3c\xdf\x6a\xb6\x21\xba\xef\ \x33\xae\xd9\x6a\xb1\x4f\x3e\xcd\x4a\xd2\x7b\xca\x08\x59\x4e\xe0\ \x08\x84\x74\xf5\x9b\x33\x93\xf5\xbe\x4e\xc3\x7a\xfe\x24\xfb\xd3\ \xd5\xbf\xf9\xd5\x6d\x0b\x06\x6f\xa3\x37\x03\x6d\xdb\xb9\x6d\xcf\ \xad\x2f\x7e\xed\x73\x4d\x4e\x0b\xd3\xdc\xea\xd5\xab\xdd\x07\x3f\ \xf8\x41\x43\x0c\x99\x6e\x61\x63\x9c\x8c\x79\x63\x22\x46\xeb\x38\ \xfa\xc0\x21\x72\xcf\xc9\x75\xcc\x7d\xc7\x75\xd8\x1c\x81\x73\xc4\ \xa9\xf2\xb8\x39\x11\x44\xcf\xe3\x20\x11\x47\xc0\xc3\xe7\xbc\xb2\ \x86\xf7\xaf\xd7\x5d\x66\x7c\xa3\xfe\x5f\xb8\xc6\x0b\x08\x1c\x32\ \x3f\xf2\x71\x52\xfa\xa4\xd5\x47\xe7\x8d\xba\x29\x60\x15\x4d\xce\ \xbe\xa9\x0c\x9e\x7a\xfb\xf4\x9e\x00\x88\xd8\xe3\x16\x7b\x5b\x52\ \x1e\xa7\x5f\xab\x6e\x1d\x3f\xe4\xfd\x61\x79\x99\x48\xed\xb5\x1b\ \x3d\x29\x7f\x77\x84\x03\xd0\xba\xcb\xdd\x62\xc7\x96\x5d\x06\x86\ \x57\xae\x5c\x71\x9b\x37\x6f\x4e\x51\xaf\x66\xd9\xbf\xd9\xb4\x58\ \xae\x13\x64\xd1\x24\xb4\x9c\x42\x3d\x86\x0e\xd2\x23\xe6\xc9\x88\ \x4d\xd9\xba\x1f\x2a\xae\x0a\x20\xf2\x86\x5f\xdd\x2d\xaf\xc1\x77\ \xd8\xe1\x4b\x39\x38\xd7\xf4\xbd\x9e\x37\x00\x4c\x38\x24\x52\xf6\ \x1e\xca\x57\x14\x1d\x88\x0d\x46\x71\x02\x33\xef\x6c\xb9\xf9\x72\ \x8e\x50\x32\xe7\x6c\x91\x69\xcd\x8f\x37\xf9\xfe\x96\xfd\xef\xd9\ \xb3\x67\xc0\xec\xde\xf2\xda\xf6\x05\x7c\x14\x77\x14\x00\xb4\xad\ \x1b\x76\x2c\xce\xdd\x98\x33\xba\xc0\xda\xb5\x6b\x49\x4e\x2c\xf3\ \x06\x96\x2b\x58\xe9\x7c\xe9\x6b\x67\x6c\x75\xfb\x99\x7a\x56\x44\ \x08\x95\x39\xba\xbd\x24\xc7\x30\x2b\x97\xf5\x22\x68\x04\x8f\xd1\ \x13\xec\xa8\x3e\xa6\x51\x7f\x03\xa2\x1b\x07\x0d\x83\xfa\x1f\x09\ \x04\xb3\x31\xb6\x99\x3b\xd7\x96\xec\x8f\x49\x1f\x12\xb3\x26\xee\ \xbf\x67\xe7\x7e\x10\x71\xc7\x01\x40\x42\x66\xf1\xc2\xba\x57\xe6\ \x75\x4d\x43\x41\x59\xb7\x6e\x9d\xbe\x58\xa9\xa6\x5f\x6a\x01\x68\ \x74\x72\xbc\x84\x16\x89\x3f\x78\x8c\x5d\x16\x5f\xeb\x1d\x8c\x94\ \x90\x17\x20\x44\x4c\xab\xdc\x0d\x9f\x77\x71\x65\x9b\x91\x5e\xf0\ \x3a\x76\x55\xf0\x44\xe1\x55\xfa\x7a\x09\x71\xdd\x50\x7b\x3f\x7d\ \x96\xbd\xa6\xb3\xe3\x15\xcd\x69\x74\xdb\xb7\x6f\x1f\xe0\xac\xcf\ \xad\x7a\x69\x1e\x9d\xec\x8e\x03\x40\xed\xd0\xfe\xa3\x4b\xc7\x8f\ \x9c\x34\xda\x27\xee\xe1\xfd\xfb\xf7\x0f\xfc\xb3\x59\xaa\xd4\x65\ \x65\xca\x9e\x51\x08\xed\xa9\xe5\xd1\x04\xe4\xb5\xf9\x3c\x9a\x88\ \x49\x91\x88\x54\xb9\x93\xd6\x4f\xb7\x4e\x21\xcd\x69\xde\x80\x40\ \x23\xbd\x8c\x0b\x18\xe0\xf0\x19\x02\xc6\xe0\xca\x57\xb7\xd9\xbe\ \x29\x47\xe0\x3b\xcf\x76\x2a\xdc\x30\xc4\xc7\xe5\x9b\x66\xfd\x1c\ \xdc\x7b\xf8\x96\xe2\xfe\x77\x07\x00\x42\xe1\xea\x97\xe6\x3d\xcb\ \x35\xd4\x7e\xe8\xa1\x87\xc8\x9b\x4b\x41\x20\x02\x5a\x20\xa4\x26\ \x5e\x49\x59\x93\x2c\xb3\xc4\x4f\xdf\xa7\xd2\x2b\x78\x26\x11\x01\ \x22\xb2\x08\x2a\x30\xc4\x11\xc2\x57\xe2\x9c\x79\xce\x12\xdc\xbe\ \x16\xb1\xed\x6b\x59\x14\xe2\x02\x8c\x7d\x11\xbd\x3f\x04\x08\xce\ \x7a\x0f\x67\x3c\xf1\xe5\xf1\xd3\x3c\x84\xdf\xb5\x6b\x57\xba\x57\ \x03\xee\x2b\x47\xc7\x5d\x05\x00\x5a\x68\x7f\xcb\x6b\xdb\x16\x52\ \x51\xf0\xef\xff\xfe\xef\xe5\xfa\x40\x24\x6c\x1c\x63\x06\x90\x51\ \xf0\xfc\xa5\x05\x42\xb8\x21\x11\x20\x77\xad\xde\xb4\xdc\x25\x4c\ \x3c\x28\xdb\xe3\xca\xd6\x28\x8f\xbd\xe6\xec\x4a\x4e\x89\x9f\x82\ \xa8\x90\xf8\x90\x42\x68\xde\x1b\xf7\x30\x6a\x97\x8f\x4d\xef\x2e\ \xd7\x0f\x26\x9b\x99\xcb\xab\x56\xee\xb3\x45\x7e\xe3\xc6\x8d\x03\ \x11\xbf\x57\x5f\xd8\xf4\xba\x4f\xd6\xed\xdf\x13\x00\xa0\x6d\x78\ \x69\xcb\xc2\x89\xa3\xa7\x8c\x28\x40\x69\xf9\x9f\xff\xf9\x1f\x4c\ \x97\x41\x10\x58\xb1\xa0\xd7\xa9\x5d\x6f\xad\x01\xe7\x74\x9d\xba\ \x87\x75\x9f\x5d\xb0\xe1\xe0\x44\xb3\xda\xcd\x6e\xa2\x28\xbf\x75\ \x3f\x05\x81\xde\x23\x90\x58\x20\x14\xba\xb6\xdd\x38\x8f\x04\xc2\ \x58\x36\x2e\x09\xf0\x38\x7b\x5c\x5d\x2b\xcf\x5c\xbb\x51\x35\x22\ \x01\x53\x76\xc3\x86\x0d\x03\x5b\xbe\x30\xf9\xb6\x6f\x96\x05\x76\ \x8f\x00\x80\x7f\xf8\xa9\x47\xd6\xcc\x5d\xbb\x72\xdd\x84\x22\xc9\ \x17\x78\xe6\x99\x67\x0c\xaa\x65\x06\x8a\xa0\x66\x65\x1b\x6f\x9f\ \x00\x52\xe8\xe4\x52\x03\x06\x89\x05\x8d\x4a\x4b\x0f\x77\xa3\x6c\ \x8f\x84\x11\xe1\x05\x04\xee\x9b\x7b\xe5\xa3\x9e\x31\x62\xc2\xcc\ \x33\x4a\x2c\xa4\x1b\x5f\x87\xe9\x03\xcc\xd6\x89\x2d\xb4\xaa\x03\ \x62\x61\xdb\xb6\x6d\xe4\x5d\x3a\xb3\x2b\xeb\xec\xc5\xee\xda\xa7\ \x9e\x93\xe2\x7d\xef\x00\x40\x72\xe9\xb1\xff\x7e\xfa\x66\xba\x2f\ \x7f\xeb\xd6\xad\xb0\x31\x7d\xb9\x14\x04\xa5\x66\x5f\x16\x95\xc4\ \x18\x9a\x95\x34\x08\x44\xd7\x3d\x01\x85\x84\x0e\xc6\xa2\x44\xde\ \xd3\xcb\xe5\xbd\x40\x00\xa1\xe2\xf3\x1a\x6d\x17\xd1\xcd\x9c\x15\ \x25\xd2\x2f\x2c\x57\x90\x3e\x40\x37\x40\x68\xd6\x32\x37\xdd\xa9\ \x0d\x98\x84\x28\xd1\x69\xa8\x17\x96\xff\xc4\xff\x3e\x73\x93\xc2\ \x0f\xf7\x24\x00\x68\xd4\xdb\x7d\xfa\xd1\xb5\x37\xd3\x62\x4c\xab\ \x56\xad\x72\x47\x8f\x1e\x4d\x65\x99\xc2\xad\xa9\xa5\x50\xae\x33\ \x64\x91\x0b\x98\x73\x87\xc5\x01\x7a\xbd\xc0\x0d\xd2\x15\x9d\x2a\ \x76\x02\x89\x95\xfd\xe5\x66\x9f\x9e\x97\x76\x2f\x60\x58\x8e\xa0\ \x51\xfa\x43\x54\x08\xe3\x33\xb0\x7e\xba\xb6\x7a\x4f\x34\x6b\x6e\ \xb2\x9d\x0f\x58\x02\xa4\x78\x13\xec\xa1\xa9\xa1\x64\x3f\xf1\xbf\ \xab\x6e\x4a\xee\xdf\xb3\x00\xa0\x79\x5d\x60\xf9\xc5\xf5\xd1\x3f\ \x20\x64\xff\xf7\x7f\xff\x37\x31\x83\xff\x0f\x04\x46\x0c\x64\x22\ \x74\xbc\xe6\x96\xba\x88\x2e\x30\x84\x62\xd5\x45\x52\x81\x93\x51\ \x84\x29\x91\xf7\x76\x65\xdb\x31\x5d\xd9\x74\xfb\xbc\x80\x65\xbb\ \xe4\x7e\xaa\x10\xd2\x8b\x58\xb2\x6e\xca\x13\xbe\x55\xaf\x1a\xae\ \xc8\xef\x42\x8a\xf7\xce\x9d\x3b\x5d\xda\xd6\x3f\xfd\xfc\xfc\xf9\ \xb3\xca\xf4\xbd\xc7\x01\x40\xdb\xbe\x69\xd7\xe2\x9e\x1d\xfb\x16\ \xd3\xba\x02\x7f\xff\xf7\x7f\xef\x37\x56\x1c\x2f\x05\x41\x45\xc9\ \x16\xa9\xeb\xd8\x10\x3e\xf4\xc8\x01\x78\x6f\x5c\xfd\x51\x69\xb4\ \x84\x8e\xfa\x40\xb9\xd2\x17\xd9\x7f\xc2\x15\x86\x8a\x80\x72\x2e\ \x60\xf5\x03\x03\x06\xfe\xaf\x99\x4e\x83\xcc\x67\x88\x6f\x3a\x66\ \xf3\xa6\x4d\x9b\xcc\x6f\x23\x57\xef\xbe\xdd\x4a\xc4\xb9\x1f\x00\ \x20\xd4\xae\x7a\x71\xfe\xcc\xa9\x73\x46\x85\x25\x8a\xf5\xcf\xff\ \xfc\xcf\x6e\xcb\x96\x2d\xe9\x17\x0d\xa7\x6e\xc5\xe8\x57\x58\xf5\ \x71\x45\x87\x6b\x79\x0b\x03\x36\xc4\x01\xa4\x2b\xf0\xbc\xde\x23\ \x42\x18\xa2\x0a\x04\x43\x88\x6c\x41\x92\xcc\xa7\xf2\xbf\x1c\x14\ \x51\x11\x95\x38\x80\xe8\xb3\x13\x14\x75\xb4\xa6\x9e\x1c\x3d\x28\ \x7d\xe9\x7e\xbe\x63\x47\x4e\x2c\xbd\xf4\xdc\x6b\xb2\xf7\xef\x2f\ \x00\xe0\xa2\x7c\xf2\xa1\x55\x37\x6f\x5c\xbb\xd9\x4b\xc5\xc1\x93\ \x4f\x3e\x89\x75\x60\x4c\x44\x35\x40\x40\xf7\xcd\x10\xdd\xc8\xfd\ \xc8\x39\x20\xbe\xee\x71\x6d\x36\x79\x58\x62\xcb\x34\x1b\xe2\xf1\ \x2b\x77\x02\xe9\x7d\xe6\x3a\x05\x88\xf5\x0b\xd8\xbc\x83\x4e\xb3\ \xee\x95\x3d\x88\x9f\xf1\x3f\x99\xd8\x3e\x2c\x5f\x3e\xfe\x64\x57\ \x76\xef\x99\x47\xd7\xcd\xb9\xc2\xdd\xb1\x56\x99\x9a\x7c\xf3\x9f\ \x77\x17\x97\x22\x51\xe2\x37\xac\x46\x68\x0f\x6f\x49\xd4\xd0\x1d\ \xdc\x77\x78\xe9\x1d\xef\x7a\x5b\x8d\x63\x4c\x34\x2f\x65\x87\x4d\ \x0d\x1f\xfa\xd0\x87\xcc\x21\x4f\x52\x06\x2b\x21\x46\x1c\x73\x64\ \xec\x28\x0b\x91\xd3\x39\xe2\x6a\x32\xbe\x61\x73\xc0\xb3\xe6\x53\ \xb7\xa4\xad\x5a\x1a\x23\x7a\xf1\x19\x75\x23\x8c\xec\x73\xc9\x9c\ \x9e\x6f\xe4\x35\x37\xe5\x59\x7e\x8e\x83\x3f\x59\x04\x88\x44\x56\ \x3d\x51\xd4\xb4\xe1\xe2\x7d\xf4\xbf\x9e\xba\x81\x65\xe5\x46\xda\ \xc2\x26\x9b\x66\xee\xe6\xe6\xaf\x6d\xf0\x8b\xe7\x32\xe9\x1c\xe8\ \xef\x18\x72\x23\x07\x80\x82\x46\x64\xab\x72\xf2\xa8\x36\x96\xa8\ \x91\xb3\x4f\x6e\xdb\x07\x3e\xf0\x01\x53\x6f\x40\xa3\x0a\x31\xc9\ \x66\x36\x1b\xc1\x0a\xa7\x32\x76\xe9\xc9\x9a\x5c\x96\xb4\x18\x3f\ \x66\xd4\x75\x04\x4d\x04\x84\x99\xa3\x6b\x4e\xd7\x9a\xb3\xc4\xd7\ \x36\x17\xca\xb4\x7a\x0d\xbf\xe9\x9a\xf5\x9a\x4b\x9a\x9c\x64\x98\ \xc7\x4a\x59\x37\x0d\x27\xcf\x2a\x5f\xcb\x80\xc5\x43\xbb\xef\x01\ \x20\xc4\xb3\x61\xc1\xd7\xb3\x2f\xfc\xf9\xc3\xb9\x88\x2c\xbd\x80\ \x00\x12\xa7\x90\xaa\xbc\x4b\xb8\x6f\x80\x80\x54\xe8\xc7\x32\xf4\ \x12\x33\x28\x80\x2a\x22\xcd\x18\x9b\x7c\x48\xa2\xbb\x45\x46\xcc\ \x0a\xb6\x00\x11\x98\x0c\xa1\x2d\x57\x70\xa5\xab\x9d\x31\x87\xf0\ \xad\x86\xd7\xf0\x6b\x32\x69\x53\x11\xc7\x4e\x1e\xbe\x6f\x5a\xd8\ \x11\xf9\x5f\x3c\xeb\xf5\xa6\x4d\xaf\x6c\x55\x76\xcf\x03\x02\x00\ \xbb\x6f\xad\x7b\xe1\xdc\xa5\xee\xfb\xfd\xb9\x3c\x95\x20\xe8\x95\ \x5e\x4e\xc0\x83\xd8\xc1\x3b\xfd\x89\x64\x88\x84\x72\x20\x38\x29\ \x51\xcc\xe0\xfb\x17\x93\x17\xd1\x2c\x07\xd0\x5c\x94\x00\x86\x68\ \x65\x5c\xa1\x90\x48\x88\xc4\xa7\x5b\xae\xa0\x39\xed\x5b\xac\x54\ \xdc\x78\x33\x77\x9d\x46\x9e\x9e\xcf\x6b\x80\xce\x77\xc4\x1f\x92\ \x82\x82\x73\x0d\x70\xa2\xf9\x45\x22\xa5\xf9\x81\x04\x80\x9c\x45\ \x7d\xb8\xc1\x7b\xdf\xff\xee\x5a\xa3\x59\xcf\x0c\x40\xce\x9e\xc5\ \x42\xa0\x28\x84\xaf\xf5\xf3\x16\xad\xa0\x24\x65\x5b\x8a\xa2\x83\ \x05\x88\x10\xf4\x34\xdc\x98\xb2\x7b\x35\xa3\x17\x98\xf7\x69\xd4\ \x9c\xd9\xe6\x41\x8b\xb6\x7c\xcd\xa3\x91\x95\x3e\xd1\xcc\x19\xa9\ \x4d\x90\x14\xcf\x8e\xe0\x86\xe8\xc8\x7b\x00\x6e\x1b\xa5\xed\x2f\ \x77\x1f\xfa\xb7\xc7\x6f\xf8\xcd\xb7\x52\x96\x1f\x68\x00\xd0\x38\ \x7f\xa7\xc0\xb6\x7d\xeb\xdb\xdf\x52\x9d\x98\x1a\xaf\xa4\x9b\x4d\ \x0e\x1e\x3c\xe8\xe8\xb3\xb3\xb3\x24\x66\x4a\x6b\x36\x2e\x63\xae\ \x21\x4f\xad\x82\x89\x05\x77\x08\xa7\xcb\x18\x4d\x30\x65\xdb\x85\ \xa5\xb3\xe5\x04\x7a\x50\xf7\x0c\x48\x60\xe9\x8d\xbc\xea\xda\xf5\ \xaa\xc3\x83\xd7\xf4\xaf\x6b\xd5\xb2\x73\x78\x6c\xe1\x06\x00\x0d\ \xdb\xe7\x3a\x6d\x87\x7d\x3e\xc5\x63\xff\xf3\xf4\x4d\x7e\x0f\xae\ \x1f\x7c\x00\xd8\x62\xc6\x3e\xb2\x75\xf0\x96\x67\xf7\x63\x6f\x7d\ \xc7\x5b\x6a\xe9\x0f\xc9\x6a\x41\x56\x62\x22\x4d\x4e\x4e\x1a\x20\ \xd0\x52\x3f\x02\x9c\x37\xaf\x66\x9e\x48\x99\x07\x85\x43\x67\x08\ \x22\x43\xd9\xc6\x03\x67\xb4\x05\xe2\xc6\x0b\x25\xa0\x22\x66\x00\ \x16\x81\x9a\x7a\x2d\xf3\xec\xdd\x13\xbd\x55\x73\x8d\x5a\x85\x95\ \x9e\x16\x69\x32\xd7\x8c\x58\x37\xe4\x47\xe2\xf4\x52\x34\x2f\x95\ \xf7\x1b\x5e\xdc\xbc\xf0\xdc\x9a\x97\xe6\xf5\x3d\xee\x05\x00\x8c\ \xbd\xf7\xdd\x9f\xd8\xbf\x78\x6d\x0e\xe5\x4a\xcf\x53\x9e\x00\xb7\ \x55\xbd\x10\x08\x46\xdf\x38\x99\xbc\xf2\xbd\x9f\x7c\xab\x8d\xb9\ \xa8\xb9\xb4\x51\x94\xea\x5b\xdf\xfa\x16\x99\xc7\x6c\x36\x49\x9d\ \x29\x00\x22\xbd\x4e\x01\x13\x3a\x85\x1a\x29\x3f\xcf\xc8\x3a\xa7\ \xf7\x43\x4c\xc2\x7e\xa6\xba\x88\x94\xcc\x99\x6b\x08\x4d\x11\x09\ \x02\x39\x96\xd5\xdb\x76\xe2\xd8\xa9\x65\x52\xb9\x88\x9b\x68\xee\ \x4e\x03\xa0\x39\xdd\xf1\x67\x2b\x1c\xfb\xcb\xe5\xff\x6b\xef\x1c\ \x98\xec\x88\x82\x28\xfc\x8c\xb5\x6d\xdb\xf6\xff\x8e\x6d\xdb\xb6\ \x9d\x79\xa3\xf4\x57\xa9\xd5\xcd\xce\x1a\x2f\x53\xf7\x54\x9d\x64\ \xad\x3e\x57\x3d\x7d\xfa\x9a\x06\x8e\x9e\x5b\xc2\xc7\xc2\x6f\xbb\ \x2e\x00\x15\xed\xdd\x2d\xf1\x89\xb9\x51\xd9\x02\x24\x3d\xbf\x0f\ \x36\xb0\xae\xae\xae\x40\x47\x47\x47\xa0\xa2\xa2\x42\x0d\x8c\x27\ \x81\x12\xb4\xf5\xd2\xf3\xf3\x58\xae\x48\xe1\x52\xab\x4f\xf0\x79\ \xdd\x0b\x98\x69\x8e\x1f\x3c\xfd\x83\xbc\x48\x40\x45\x9a\x08\x80\ \x00\xef\x29\x6e\x5d\xbb\x6b\xb0\x41\x1c\x1c\xed\x4b\x76\xf7\x77\ \x24\x62\xf1\x7f\x5b\xbd\x92\x40\x61\x7a\x85\xf4\x28\x42\x0c\x35\ \x35\x35\x81\xf2\xf2\x72\xf5\xd2\x04\xd5\x3c\xb9\xae\xb6\xf6\x6a\ \xf1\x8a\xfa\x31\x14\x6a\xf0\xa8\x96\x75\x9d\x7a\x87\x45\x93\x86\ \x67\x77\x15\x4a\xe8\x7f\x5d\x94\x2a\x6a\xb5\x7f\x6f\xba\x61\xcf\ \x67\x00\xb5\x5b\x67\xdf\x50\x77\xa2\x67\xb0\x2b\xc9\xdd\xbe\xeb\ \x51\x36\x47\xc8\xba\xba\x3a\x96\x0b\xf6\x0d\xf4\x07\x9a\x5f\x2e\ \xd4\x25\xc2\x6b\x99\x58\xb1\x05\x2b\xa4\x36\x8f\x53\x0a\x4f\x34\ \xd7\x01\xee\x17\x74\x70\x50\x5d\xbd\x74\xc3\xf0\x0c\xbc\x9e\x01\ \xbc\x61\xfc\x4e\xb9\x67\x64\xa3\x84\xff\xa0\x7b\xa0\x33\xd1\xd1\ \xdd\x1a\xcf\x2b\xc8\x0d\xaf\x96\x6c\x62\x1a\x86\x4b\x0d\xaa\x6c\ \x1e\x39\x4d\x30\x5b\x30\x43\xe0\x26\xe6\x7f\x72\x0d\x88\x83\x20\ \x33\xaa\x97\x92\x80\x93\xa5\xe4\xff\x8d\x5e\x3f\x43\x2f\x25\x8c\ \x9a\xd7\xaf\xdc\x32\x94\xe2\x8d\xb4\x47\xda\x08\x40\xed\x7b\x8f\ \xf7\x0d\x96\x55\x94\x44\xda\xba\x5a\xe2\xcd\x6d\x0d\xb1\x44\x72\ \xd1\x96\xb6\xda\xb4\x4f\xae\x1d\xee\x24\x18\xed\x94\x69\xdf\xba\ \x7e\xd7\x10\xfb\xfc\xb6\x6d\xee\xb4\x00\x14\xbc\x96\xf6\x67\xf0\ \xe8\xfe\x93\x3f\xea\x24\x9b\xd8\xd0\x5c\x1b\xad\xac\xa9\x88\xe6\ \xe4\x92\x4b\xd8\x5d\xb0\x8b\x97\xc6\x59\x26\x7b\x96\x27\x8f\x9e\ \x99\x8a\x3d\x4b\x0b\x60\x27\xc1\xc8\xe6\x0f\x0f\x79\x5d\x04\x10\ \x92\x67\x0c\xd1\x2a\xb9\x2a\xa6\x4c\x92\x4b\xd9\xb9\x59\x5c\x9a\ \xb7\x8d\xdf\xcf\xe5\x42\x69\x9b\xfe\x7b\xcf\x9e\xbc\x34\x9f\xc9\ \x51\x4e\xb5\xc8\x03\x2d\x80\xbd\x81\x04\xe7\x9b\xc3\xba\x0b\xe7\ \x6f\xf9\x62\xbf\x90\x5f\x98\x17\x2e\x10\xe6\x15\xe4\x85\x39\x5a\ \xc6\x24\x65\x18\x8d\x0a\x63\x7f\x49\xd3\x07\xcb\xb4\x39\xc3\xbb\ \x6c\xd4\x52\x29\x98\x72\xe9\x23\x4c\xb3\x25\xbc\x77\x1f\xe5\x7f\ \x46\xbb\x87\x05\x4b\x0b\x20\x1d\xc1\x79\x9c\xdc\x3a\x0c\x68\x6c\ \x7f\x45\x90\x86\x16\x80\x86\x16\x80\x86\x16\x80\x86\xff\x36\x81\ \xf3\x66\x06\x9f\x42\x43\x8d\xad\x2a\x80\x44\x6e\xa6\x9a\x62\x85\ \xae\x72\x7b\xef\xff\x0d\x0d\x57\xb8\xd8\xab\x76\x5e\x00\xc1\x70\ \x70\x72\xf1\x28\x65\x85\x7e\xff\xfe\x91\xfc\xf5\xeb\x7b\x99\x69\ \xa6\x9a\x5c\xd7\xae\x10\x19\x24\x7d\xb1\x54\x68\x38\xa6\x69\x90\ \x1f\x37\x08\xf5\xbc\x08\xd4\x91\xcd\xeb\x71\x61\xb1\xb0\x46\x58\ \x22\x4c\xe8\x19\xc0\x37\x33\x80\x21\xfc\x20\x7c\x2e\x7c\x23\xfc\ \xb5\x92\x00\x58\x16\x32\x85\x79\xc2\x2c\x61\x34\xa0\xe1\x17\x58\ \xc2\x9f\xc2\xcf\xc2\xef\x42\x33\xe8\x71\x32\x08\x0b\x63\x8b\x75\ \x81\x1a\x3e\x9a\x05\x10\x81\x09\x79\xdd\x6b\x6a\x0f\x2a\xd4\xf0\ \x8f\x00\x80\x33\xff\xfa\x1f\x73\x9e\xf3\xe9\x17\x01\x41\x89\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x1f\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\xe6\x49\x44\x41\x54\x78\x01\xa5\x52\x03\x8c\x1c\x51\ \x18\x6e\xcc\xba\x8d\x5d\xdb\xb6\x6d\xdb\x66\x50\xdb\xb6\x6d\xdb\ \x88\xce\xd6\x5a\x67\x73\xec\x79\xdd\xaf\x67\x7b\x93\xfc\x36\x1a\ \x00\xf0\x08\xaa\x35\x86\xbc\xec\x32\x2c\x0f\xea\x9d\x80\xb6\x1f\ \x33\xd2\xb6\x63\x86\x7a\x25\x88\x7c\xdb\x7d\x92\x9b\xf9\x4b\xdc\ \xcc\x0f\x92\xc7\xd7\x39\x01\x65\x3d\x68\x05\xf5\x16\x6e\xea\x05\ \xb2\xcc\x7b\x2c\xb5\x4a\xf0\xf3\x6a\xeb\x96\xbe\xb7\xda\x0f\x0f\ \x7b\xd3\xeb\x2a\xc9\x7e\x4e\x48\xc6\x65\x90\xb4\xb3\xd0\xd2\xef\ \x90\x88\x37\x3d\xae\x78\xdf\xee\x30\xf4\xfb\xcd\x2e\x4d\xcb\x24\ \xf0\xbe\xd1\xa6\x43\x6a\xf8\x36\xbb\x9c\x74\x2b\x4b\x4b\xb9\x27\ \xff\xcb\x79\x0a\x50\xcf\xa1\xa5\x9e\x82\x9a\xb4\x1f\x4a\xe2\x4e\ \x28\x49\x3b\x41\xd2\xcf\x43\x4b\x39\x09\x29\xee\xa0\xc0\xbb\xf6\ \xa6\x24\xf9\x2f\x36\xe6\xc5\xe6\x67\xb1\x7f\x1b\xbe\x5e\xcf\x78\ \x20\xba\x33\x2f\xe5\x56\x3c\x9e\x1b\xb8\x17\x72\xdc\x36\x48\x31\ \xeb\x20\x3a\x57\x40\x72\x2d\x83\xe8\x98\x01\xc5\x39\x0d\x24\x6e\ \x2e\x94\xb8\x7d\x9c\xfd\xcb\xc0\x0d\x65\x46\xb0\x7f\xee\x37\x5b\ \x4e\x3c\xc7\x90\xe4\x9d\xb9\x0e\x1b\x21\xba\x96\x83\xb3\xce\x01\ \x63\x9e\x02\x3a\x6a\x10\x98\xc8\xfe\x10\x2c\x43\xc1\xda\x56\xe7\ \x44\xbd\xed\x39\xaf\xd2\x1d\x44\xbc\xe8\x32\x5a\x8c\x3b\x90\xa3\ \xc4\x2c\x81\xe0\x98\x05\xda\x38\x16\xd9\xa1\x7d\x90\x93\x0b\x6c\ \x54\x7f\xb0\xd6\x55\x59\xa6\xd7\xdd\xc6\x56\x7b\x05\xca\xb0\x2e\ \x46\xb4\x8e\x06\x6b\x18\x08\x3a\xa2\x2f\xa8\xb0\xbe\xb9\xd5\xfb\ \x81\xcb\x95\x33\x42\xe6\x38\x6b\xba\x42\x23\xc6\xb8\x90\x95\xad\ \x43\x21\x18\x07\x41\x30\x0f\xcb\x85\x02\x5e\xb4\x0c\x01\x65\x98\ \x4b\xe7\xf9\x54\x99\x20\xf8\x71\xe7\x49\x5a\xdc\x2a\x68\xae\xf1\ \x90\x1c\x4b\xe4\x64\xbf\x19\xbe\x49\xfe\x33\x03\x24\xc7\x32\x45\ \x73\x8d\x85\x12\xb3\x14\xc1\x0f\x3b\x8d\xab\x32\x81\xfd\xe7\xe0\ \xd3\x24\xe9\x80\x9a\x1a\x3c\xd7\x3b\xfc\x69\xa7\x91\x45\xfa\xd0\ \x17\x5d\x26\xa6\x04\xcd\x0b\xd4\x93\x0f\x6b\x96\x8f\x7d\x8f\x57\ \x99\xc0\xf0\xae\xd7\x46\xe3\xdb\x1e\xc3\xaa\x7c\xef\x57\xdd\x27\ \x45\xbe\xec\xbe\xbd\x7c\x02\x8f\xe0\x3f\x2b\x26\xec\x3d\x58\x33\ \xa3\x57\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\xb1\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\xc2\x00\x00\x00\x40\x08\x04\x00\x00\x00\xe2\x90\x60\x69\ \x00\x00\x02\x78\x49\x44\x41\x54\x78\x01\xed\x9a\x03\x8c\x5c\x51\ \x18\x46\x4f\x6d\xdb\xb6\x6d\x2b\x6c\x54\x2b\xac\x11\x67\x82\x32\ \x9c\xa0\x8e\x36\x4e\x1a\xd4\x8d\xcb\xa0\xb6\x1b\xd4\xb6\x6d\xf7\ \x66\x75\xbb\x9a\x3b\xf3\xfe\xe2\x3b\x7f\x34\x7e\xef\x9d\xc5\xc9\ \x9f\x41\x64\x62\x0f\x7d\x30\x46\x7c\xe6\x0b\xeb\x69\x86\x29\x92\ \xf0\xf5\xfb\x7c\x20\x8d\x5a\x08\x3b\x09\x6e\x5e\x11\xa7\x02\xc2\ \x4e\x82\x9b\x07\xcc\xa3\x38\xc2\x4e\x82\x9b\xeb\x4c\xa3\x08\xc2\ \x4e\x82\x9b\x13\x8c\x40\x58\x4a\x70\xb3\x2f\xfa\x78\x95\x84\x35\ \x9e\x90\x2f\xac\xa3\x29\x11\x22\x09\x85\x69\xcd\xfa\xcc\x12\x2c\ \xe2\x55\x12\x00\x7a\xb1\xc7\x13\x11\x65\xbc\x4a\x82\x63\x28\x67\ \x3c\x11\x8f\x88\x51\x92\xc8\x90\x04\x28\xca\x34\x6e\x1b\xc6\xab\ \x24\x38\x4a\x13\xe3\xa9\x27\xe2\x1c\xa3\x89\x06\x49\x70\x54\x26\ \xce\x5b\xc3\x78\x95\x04\x47\x3d\xd2\xf8\xe4\xc5\x6b\x74\x9b\x57\ \x49\x70\x28\x5e\xed\x25\xfc\x29\xf1\x2a\x09\x8a\x57\x7b\x09\x8a\ \x57\x63\x09\xa9\x88\xd7\x91\x8c\x76\xa3\xf9\x92\x80\x04\x80\xaa\ \x2c\xe3\x9d\x27\x62\x17\x5d\xc9\x27\x8f\xbd\x17\x6b\x0a\x28\x21\ \x3c\x5e\x9f\x04\x1c\xaa\x24\x04\xc7\x6b\xb8\x04\x49\xf0\x19\xc8\ \x21\xef\xdd\x5e\xb2\x84\x72\xe4\x0c\xcf\x02\x0e\x55\x12\x02\xe3\ \xd5\x97\xb0\x85\xf5\x6e\x34\x85\x08\xa3\x28\xd3\xb9\xe3\x89\xb8\ \xcc\x84\xdf\xeb\x7d\x91\xfe\x94\xf2\x04\x20\x42\xe2\xf5\x65\xfa\ \xc3\xe5\x08\x42\x24\xbe\x79\x7d\x95\xfe\x50\x59\x44\x08\x01\xf1\ \xfa\x3a\xfd\x81\x32\x88\x10\x02\xe2\xf5\x4d\xfa\x9d\xa5\x11\x41\ \x24\xbe\x79\x95\x84\xa8\x28\xc6\x1c\x7f\xb9\xc1\x05\x46\x90\xe9\ \xee\x92\x88\x54\x51\x88\xd1\x5c\xf4\x04\x3c\x24\x46\x09\x90\x84\ \x28\x18\xca\x31\x4f\xc0\x73\x16\x50\x06\xc7\x87\xf4\xbb\x8b\x23\ \x92\x4f\xdb\x1c\xfe\x25\xd7\x20\x13\x1f\xd3\x1f\x2a\x86\x48\x2e\ \xf5\x49\xe3\xb3\x1f\xa7\x34\xc1\xe1\x4b\x18\xc7\x68\x37\x9a\x42\ \x84\x52\x9d\xd5\xbc\xf7\x7e\x07\xb6\xd1\x11\x1f\xcf\x54\xf8\x68\ \x81\x57\x86\x18\xcf\xbd\xf7\x3c\xc2\x60\x72\xe0\x4b\x52\x0f\x5e\ \x12\x8a\x31\x8d\xbb\xde\xfb\x5d\x63\x0a\x85\x20\xf5\x12\x24\xa1\ \x10\xe3\xb8\xe4\xbd\xd7\x5d\x66\x52\x8c\x5c\x09\x3f\x60\x49\xc8\ \x67\x88\xe6\xcc\x7a\x4d\xfa\x7c\x49\x58\x42\x67\x76\x78\x02\xde\ \xb3\x82\x6a\x88\xf0\xaf\xbc\x04\x86\x68\x04\x48\x42\x55\xe2\xfe\ \x3e\x88\x9d\x74\x26\x1c\x49\x48\x38\x44\x8f\x32\x98\x14\x20\x09\ \x01\x21\x1a\x80\x24\x24\xbe\x11\x4d\x31\x92\x90\x43\x88\xbe\x24\ \x4e\x79\x22\x41\x12\x72\xde\x88\x46\x80\x24\x18\x85\xa8\x24\xd8\ \x87\xa8\x24\xd8\x87\xa8\x24\xd8\x87\xa8\x24\xd8\x87\xa8\x24\x0c\ \xcf\x79\x23\x1a\x11\x92\x60\x1e\xa2\x92\xf0\xa7\x85\xa8\x24\x28\ \x44\x8d\x25\x28\x44\x8d\x25\x5c\x60\x8c\x42\xd4\x52\x82\x42\xd4\ \x58\xc2\x2b\xdb\x8d\xa8\x78\x67\x13\xa2\xdf\x00\x72\x7f\x9a\x9e\ \xe8\x28\xba\x6c\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \ \x00\x00\x00\x63\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x00\x00\x00\x00\x3a\x98\xa0\xbd\ \x00\x00\x00\x02\x74\x52\x4e\x53\x00\xff\x5b\x91\x22\xb5\x00\x00\ \x00\x1c\x49\x44\x41\x54\x78\x01\x63\xf8\x8f\x06\x70\x09\xb8\x41\ \x01\x42\x60\x32\x18\x0c\x01\x01\x84\xd3\x09\xfb\x16\x00\xa1\x6c\ \xcb\xad\xf1\x4f\xdb\xdd\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x01\x0c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\xd3\x49\x44\x41\x54\x78\x01\x63\xa0\x09\x28\xad\x35\ \xed\x2f\xad\xd7\xfa\x4f\x08\x97\x37\x1a\xed\xc1\x6a\x00\x48\xf2\ \xc4\xc5\x6a\x82\x18\xa4\x0e\xab\x01\x25\x75\xda\x5f\xa9\x61\x00\ \x4e\xa7\x97\xd4\x6a\xfd\x25\xda\x00\x1c\xf2\xdf\x29\x34\x00\x21\ \x8f\x37\x10\x41\x34\x2e\x79\x8a\x0d\xa0\xae\x17\xb2\x0a\x2d\x24\ \x73\x4b\x4d\xca\x52\x73\xb4\x77\xc0\x42\xfa\xd0\xd9\x3c\x30\x4d\ \x8c\x3c\x43\x6c\xb2\xd6\xf4\xe6\x5e\x83\xff\xdb\x0f\x07\xfc\x3f\ \x76\xa1\xfc\xff\xd5\xdb\xb3\xfe\x6f\x3e\xe0\xfe\xbf\x7b\x9a\xf1\ \x7f\x62\xe4\x19\xc0\x26\x9e\xc9\xfe\x7f\xe0\x54\xfa\xff\x93\x97\ \xeb\xfe\x6f\xda\xef\xf6\xbf\xa5\xcf\xf0\x5f\x41\xa5\xc1\x66\x7c\ \xf2\x79\x65\xc6\x75\x70\x03\x16\xac\xb5\xfa\xbf\x62\xab\x1d\xd0\ \x96\x10\xb0\xc9\x45\xd5\x7a\xe7\xad\xed\xe5\x6c\x09\xca\xc3\x40\ \x62\xa6\xda\x6d\x98\xff\x02\xc2\x94\x17\x42\x85\x71\xca\xe7\x97\ \x5b\x4b\x52\x25\xe7\x02\x00\xda\xe3\x49\x7a\xab\x12\x64\x73\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\xf3\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\xba\x49\x44\x41\x54\x78\x01\xa5\x92\x43\xa2\x1d\x41\ \x18\x85\xb3\x81\x38\x59\x44\x16\x11\x3b\x19\x46\x4b\x88\x6d\xdb\ \xce\xe8\x2e\xe1\xd9\x6e\x55\xf7\x45\xdb\x78\xf6\xbb\x93\xd8\x4e\ \x4e\xaa\x6a\x12\x7b\x50\xae\xf3\xfd\x9c\x04\xe0\xbf\xc6\x2f\x1f\ \x8f\x1f\x3f\x3e\x9f\x8d\x7f\x06\x68\x9a\x16\xa9\xaa\x1a\xfe\x13\ \xe0\xd2\xa5\x4b\xab\x27\x26\x26\xde\xb3\xc1\xf6\x7f\x0d\x20\x84\ \x64\xe3\xe3\xe3\x18\x1b\x1b\x83\x20\x08\xe9\x1f\x01\xb6\x6f\xdf\ \x3e\x7b\xdf\xbe\x7d\x0b\xce\x9f\x3f\x7f\x67\x78\x78\xf8\x7d\x6f\ \x6f\x2f\x7a\x7a\x7a\xd0\xd7\xd7\xf7\xfe\xe2\xc5\x8b\xb7\xf7\xee\ \xdd\x3b\xef\xd0\xa1\x43\xd3\xbf\x02\xec\xda\xb5\x6b\x4e\x6b\x6b\ \x6b\x4f\x1c\xc7\xf7\xb2\x2c\x7b\x35\x3c\x32\xcc\xad\xa6\x69\x8a\ \x30\x0a\xe1\xfb\x3e\xc2\x30\x64\x10\x24\x49\x02\xd7\x75\x9f\xeb\ \x25\xbd\x5c\x5d\x5d\x1d\x31\x2d\xa7\xdc\xbe\x7d\x7b\x53\xff\x40\ \xff\x8b\x81\x81\x01\x50\x08\x17\xd8\xb6\x0d\xdd\xd0\x51\x2c\x16\ \x51\x2a\x95\x50\x28\x14\xf8\xde\x71\x1c\x06\x7d\x7a\xe7\xce\x9d\ \xcd\x5f\x85\x70\xf3\xe6\xcd\xb5\x54\xf8\x38\x4a\x22\x26\xe6\x22\ \xa2\x10\x88\x92\x88\xf6\x8e\x76\xb4\xb7\xb7\x43\x51\x14\xd0\xca\ \x3c\xb8\x72\xe5\xca\x86\x1f\xe6\xe0\xec\xd9\xb3\x4b\xa8\xf8\x81\ \x69\x9a\xd0\x0a\x1a\x4b\x1e\x9a\x1a\x1b\xd1\xd4\xd4\x84\xce\x8e\ \x4e\x10\x42\xee\x51\xf1\xb2\x5f\x56\xa1\x4b\xe8\x1a\xa4\x1f\xd1\ \xd9\xd9\x89\x96\xb6\x16\x34\xb7\x36\x73\xeb\x5d\x5d\x5d\x0c\xd4\ \xf7\xbb\x2a\x4c\xa1\x56\x9f\x30\x80\x28\x8a\x90\x65\x99\x0f\x41\ \x12\xb8\xfb\x14\xfe\x88\xfd\xf9\x29\xe0\xc8\x91\x23\xab\x5d\xcf\ \x85\xae\xf3\xe4\xbd\xaa\xac\xac\xcc\x57\x55\x55\x95\xe8\xfe\xb5\ \x61\x18\xb0\x2c\x0b\xf4\xcf\xf2\x9f\x02\x72\xb9\xdc\xa5\x24\x4d\ \xde\xd4\xd5\xd5\xa9\xa7\x4e\x9d\x5a\xf4\x45\x6e\x56\xd5\xd6\xd6\ \xea\xb4\xb4\x6f\x69\xb2\xcf\xfd\x14\x70\xed\xda\xb5\x2d\x34\x49\ \xf3\x7f\xd5\xde\x97\x2f\x5f\xde\xf5\x2d\xe0\xbf\xc6\x27\xf2\xfb\ \xf2\xce\x07\xb1\x08\x0f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x00\x67\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x2e\x49\x44\x41\x54\x78\x01\x63\xf8\xff\xff\x3f\x45\ \x98\x76\x06\x54\x02\xa5\xf0\x61\xa2\x0c\xc0\x05\x20\x72\xa3\x06\ \xd0\x27\x16\xde\xca\xa8\x60\xc3\xc3\xdc\x00\xca\x03\x91\xfe\xb9\ \x11\x00\x0b\x2f\xc7\x97\x23\x83\x18\xbc\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\x5e\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x04\x00\x00\x00\xb5\xfa\x37\xea\ \x00\x00\x00\x25\x49\x44\x41\x54\x78\x01\x63\xf8\x4f\x00\x92\xa6\ \x40\xea\x3f\x2a\xc4\xa2\x00\x09\x0c\x5a\x05\x84\x7d\xe1\x89\x80\ \x74\x53\x40\xd8\x91\x94\xc7\x26\x00\xd4\x5f\x28\x0e\xfb\x3f\xa6\ \x9b\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\x76\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x3d\x49\x44\x41\x54\x78\x01\x63\xf8\xff\xff\x3f\x45\ \x98\x76\x06\x54\xeb\x31\xfc\xc7\x87\x89\x32\x00\x0e\xfe\xfe\xfd\ \xff\xff\xdf\x3f\x08\xfe\xfb\x87\x44\x03\x30\xc1\x10\x32\x80\xf2\ \x40\xfc\xd6\x17\x86\x15\x57\x8f\x1a\x40\xdb\x58\xa0\x7f\x6e\x04\ \x00\x4b\x04\xd8\xd0\x27\xa7\x67\xc3\x00\x00\x00\x00\x49\x45\x4e\ \x44\xae\x42\x60\x82\ \x00\x00\x00\x68\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x04\x00\x00\x00\xb5\xfa\x37\xea\ \x00\x00\x00\x2f\x49\x44\x41\x54\x78\x01\x63\xf8\x4f\x00\x92\xa6\ \xc0\xfa\x3f\x2a\xc4\xa2\x00\x04\xfe\xfe\xff\x07\x84\x7f\x71\x28\ \x40\x00\x1a\x29\x20\xec\xc8\x05\x08\x38\x78\x15\x60\xfa\x82\xf2\ \xd8\x04\x00\xaf\x43\x33\x60\x30\xf1\x3c\xd9\x00\x00\x00\x00\x49\ \x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\xa1\ \x47\ \x49\x46\x38\x39\x61\x10\x00\x10\x00\xf2\x00\x00\xff\xff\xff\x00\ \x00\x00\xc2\xc2\xc2\x42\x42\x42\x00\x00\x00\x62\x62\x62\x82\x82\ \x82\x92\x92\x92\x21\xff\x0b\x4e\x45\x54\x53\x43\x41\x50\x45\x32\ \x2e\x30\x03\x01\x00\x00\x00\x21\xfe\x1a\x43\x72\x65\x61\x74\x65\ \x64\x20\x77\x69\x74\x68\x20\x61\x6a\x61\x78\x6c\x6f\x61\x64\x2e\ \x69\x6e\x66\x6f\x00\x21\xf9\x04\x09\x0a\x00\x00\x00\x2c\x00\x00\ \x00\x00\x10\x00\x10\x00\x00\x03\x33\x08\xba\xdc\xfe\x30\xca\x49\ \x6b\x13\x63\x08\x3a\x08\x19\x9c\x07\x4e\x98\x66\x09\x45\xb1\x31\ \xc2\xba\x14\x99\xc1\xb6\x2e\x60\xc4\xc2\x71\xd0\x2d\x5b\x18\x39\ \xdd\xa6\x07\x39\x18\x0c\x07\x4a\x6b\xe7\x48\x00\x00\x21\xf9\x04\ \x09\x0a\x00\x00\x00\x2c\x00\x00\x00\x00\x10\x00\x10\x00\x00\x03\ \x34\x08\xba\xdc\xfe\x4e\x8c\x21\x20\x1b\x84\x0c\xbb\xb0\xe6\x8a\ \x44\x71\x42\x51\x54\x60\x31\x19\x20\x60\x4c\x45\x5b\x1a\xa8\x7c\ \x1c\xb5\x75\xdf\xed\x61\x18\x07\x80\x20\xd7\x18\xe2\x86\x43\x19\ \xb2\x25\x24\x2a\x12\x00\x21\xf9\x04\x09\x0a\x00\x00\x00\x2c\x00\ \x00\x00\x00\x10\x00\x10\x00\x00\x03\x36\x08\xba\x32\x23\x2b\xca\ \x41\xc8\x90\xcc\x94\x56\x2f\x06\x85\x63\x1c\x0e\xf4\x19\x4e\xf1\ \x49\x42\x61\x98\xab\x70\x1c\xf0\x0a\xcc\xb3\xbd\x1c\xc6\xa8\x2b\ \x02\x59\xed\x17\xfc\x01\x83\xc3\x0f\x32\xa9\x64\x1a\x9f\xbf\x04\ \x00\x21\xf9\x04\x09\x0a\x00\x00\x00\x2c\x00\x00\x00\x00\x10\x00\ \x10\x00\x00\x03\x33\x08\xba\x62\x25\x2b\xca\x32\x86\x91\xec\x9c\ \x56\x5f\x85\x8b\xa6\x09\x85\x21\x0c\x04\x31\x44\x87\x61\x1c\x11\ \xaa\x46\x82\xb0\xd1\x1f\x03\x62\x52\x5d\xf3\x3d\x1f\x30\x38\x2c\ \x1a\x8f\xc8\xa4\x72\x39\x4c\x00\x00\x21\xf9\x04\x09\x0a\x00\x00\ \x00\x2c\x00\x00\x00\x00\x10\x00\x10\x00\x00\x03\x32\x08\xba\x72\ \x27\x2b\x4a\xe7\x64\x14\xf0\x18\xf3\x4c\x81\x0c\x26\x76\xc3\x60\ \x5c\x62\x54\x94\x85\x84\xb9\x1e\x68\x59\x42\x29\xcf\xca\x40\x10\ \x03\x1e\xe9\x3c\x1f\xc3\x26\x2c\x1a\x8f\xc8\xa4\x52\x92\x00\x00\ \x21\xf9\x04\x09\x0a\x00\x00\x00\x2c\x00\x00\x00\x00\x10\x00\x10\ \x00\x00\x03\x33\x08\xba\x20\xc2\x90\x39\x17\xe3\x74\xe7\xbc\xda\ \x9e\x30\x19\xc7\x1c\xe0\x21\x2e\x42\xb6\x9d\xca\x57\xac\xa2\x31\ \x0c\x06\x0b\x14\x73\x61\xbb\xb0\x35\xf7\x95\x01\x81\x30\xb0\x09\ \x89\xbb\x9f\x6d\x29\x4a\x00\x00\x21\xf9\x04\x09\x0a\x00\x00\x00\ \x2c\x00\x00\x00\x00\x10\x00\x10\x00\x00\x03\x32\x08\xba\xdc\xfe\ \xf0\x09\x11\xd9\x9c\x55\x5d\x9a\x01\xee\xda\x71\x70\x95\x60\x88\ \xdd\x61\x9c\xdd\x34\x96\x85\x41\x46\xc5\x30\x14\x90\x60\x9b\xb6\ \x01\x0d\x04\xc2\x40\x10\x9b\x31\x80\xc2\xd6\xce\x91\x00\x00\x21\ \xf9\x04\x09\x0a\x00\x00\x00\x2c\x00\x00\x00\x00\x10\x00\x10\x00\ \x00\x03\x32\x08\xba\xdc\xfe\x30\xca\x49\xab\x65\x42\xd4\x9c\x29\ \xd7\x1e\x08\x08\xc3\x20\x8e\xc7\x71\x0e\x04\x31\x30\xa9\xca\xb0\ \xae\x50\x18\xc2\x61\x18\x07\x56\xda\xa5\x02\x20\x75\x62\x18\x82\ \x9e\x5b\x11\x90\x00\x00\x3b\x00\x00\x00\x00\x00\x00\x00\x00\x00\ \ \x00\x00\x00\x69\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x30\x49\x44\x41\x54\x78\x01\x63\xf8\xff\xff\x3f\x45\ \x98\x76\x06\xd4\x7a\x32\xfc\xc7\x87\x89\x32\x00\x07\x18\x42\x06\ \x50\x1e\x06\xdf\xa7\xc8\x62\xc5\x94\x1b\x30\x6a\x00\x6d\xa3\x91\ \xfe\xb9\x11\x00\xb4\xeb\xcf\xc5\x16\xef\x4d\x51\x00\x00\x00\x00\ \x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\x5e\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x04\x00\x00\x00\xb5\xfa\x37\xea\ \x00\x00\x00\x25\x49\x44\x41\x54\x78\x01\x63\xf8\x4f\x00\x92\xa6\ \xc0\xff\x3f\x2a\xc4\xa2\x00\x09\xd0\x48\x01\x61\x37\x2c\x40\xc0\ \x21\xac\x00\xd3\x9b\x94\xc7\x26\x00\x26\x7a\x34\x74\xa4\x87\xd9\ \xf9\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\xf7\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\xbe\x49\x44\x41\x54\x78\x01\x94\x8e\x07\x15\xc3\x30\ \x10\x43\x8d\xb2\x7c\x0c\xc3\x1c\x0a\xed\x24\x19\x81\xf7\xe8\x6e\ \x92\x7b\xef\x27\x5e\x1a\x2e\x84\x70\xf7\xde\xe7\x8b\xdc\xdc\x9a\ \x76\x70\x75\x9a\xe6\xc3\xc0\xcc\x36\x30\x64\x03\x32\x9e\x61\x83\ \xc7\x06\xcf\x02\x92\x1d\xa0\xfd\x1b\xf8\x63\x80\x67\x31\x33\x31\ \xe1\x44\x95\x7f\x0d\x30\x0d\x38\xc4\x5b\x24\x56\xa4\xc9\x5f\x03\ \x34\xe1\x48\x6f\x4c\x51\x9c\x48\x31\x2b\xea\x8f\x01\x4b\xdf\xd4\ \x0c\x5e\x69\x10\x00\x5f\x5f\x5c\x5f\x9e\x59\x5f\x73\x66\x6c\xdb\ \xb6\x6d\xdb\xb6\x6d\xdb\xb6\xd1\xc4\xaa\xf2\xc5\xed\x64\x37\xf8\ \x11\xad\x39\x58\x70\x7d\x93\x0d\xcc\xa6\x23\xec\x1d\x6c\x21\xaf\ \xc4\x06\x45\x55\x66\x28\xa9\xb1\x42\x64\x82\x1c\x62\xd3\xff\x0d\ \x79\x85\x7c\x2e\xe1\x2b\x38\xe4\x4b\x3e\x22\x00\x47\x04\x60\x79\ \x65\x1c\xb9\x65\x4a\x98\x5a\xf6\xc5\xc0\xa4\x33\x06\xa7\x5c\x30\ \x4d\xda\x89\x59\xa2\x60\x00\xa8\x7c\x06\xe0\x88\xdd\x4c\xd5\x2c\ \x2c\x0d\xa3\xb8\x46\x8d\xb0\xeb\x13\x15\xa6\x08\x8b\x93\x44\x69\ \xad\xd6\x59\x56\xb1\xdc\x05\xab\xe0\x46\x3a\xf5\x4e\x37\x52\x05\ \xb4\x4f\xf3\xf2\xea\x18\xb2\x4b\xe4\xd1\xd8\x16\xcd\x9c\x41\x60\ \xf4\xd7\xc6\xf4\x42\x69\xf0\x01\x28\x3b\xf5\x7f\xc4\xb2\xd3\xbc\ \xb3\xbb\x4e\x2c\x28\xa0\xb9\x23\x86\x01\x88\x4a\xf9\x53\x9b\x5e\ \x28\xc3\xb1\x40\x01\xb8\x57\x76\x74\xc3\xbe\x32\x83\xd2\xca\x60\ \x14\xd5\x2a\xa2\xa5\x33\x09\x34\x38\x3a\xeb\x13\x80\xbf\x0b\xd9\ \xa5\x0c\xc0\x83\x81\x6c\xf4\x47\x6a\xbe\x30\xc6\x17\x5d\xd0\x3a\ \xa8\x86\xd6\xae\x54\xd4\x35\xc5\xa2\x6d\x40\x1f\xa3\x0b\xf6\x48\ \xce\xfb\x76\x2c\x48\x4c\x4c\x6c\x78\xec\xd7\xf9\x84\xfc\x42\x69\ \x83\x18\x7a\x27\xb5\xe0\xec\xf5\x1d\xae\x6e\xd6\x88\x48\xfa\x8e\ \x81\x19\x1d\x14\xd6\x8a\x42\x49\xf3\xa5\xbd\xe0\xa9\x60\xef\xf9\ \x41\xcc\x2d\xf0\x53\x85\x5b\xe0\x87\x21\x19\xa5\x17\x22\x74\xcc\ \x23\xf8\x93\x91\x77\xd8\xc7\x21\x5b\xb7\x4f\x19\xff\x44\xdf\x3c\ \xbb\x04\xb8\x98\xf9\x7c\x7a\x8a\x28\x73\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\x76\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x3d\x49\x44\x41\x54\x78\x01\x63\xf8\xff\xff\x3f\x45\ \x98\x76\x06\x34\x55\x33\xfc\xc7\x87\x89\x32\xe0\xff\xdf\xbf\x20\ \x85\x10\xfa\xdf\x3f\x08\xfe\xfb\x87\x54\x03\x30\x01\x48\x8e\x4e\ \x5e\xf8\x84\x0d\x0f\x7a\x03\x46\x0d\xa0\x3c\x1d\xd0\x3f\x37\x02\ \x00\x62\x07\xdc\xb2\xf9\x76\xe9\x23\x00\x00\x00\x00\x49\x45\x4e\ \x44\xae\x42\x60\x82\ \x00\x00\x00\x68\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x04\x00\x00\x00\xb5\xfa\x37\xea\ \x00\x00\x00\x2f\x49\x44\x41\x54\x78\x01\x63\xf8\x4f\x00\x92\xa6\ \xa0\xe4\x3f\x2a\xc4\xa2\xe0\xef\xff\xff\x40\xfc\x0f\x08\xff\xe2\ \x52\x00\x07\x40\x05\xe4\x58\xf1\x18\x01\x87\xb3\x02\xcc\x70\xa0\ \x3c\x36\x01\x61\x48\x43\x40\x5d\x9d\x71\x1e\x00\x00\x00\x00\x49\ \x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x2e\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\xf5\x49\x44\x41\x54\x78\x01\xa5\xd3\x53\xba\x1d\x4b\ \x18\x80\xe1\xef\xaf\xae\xe5\x63\xdb\xbc\x3c\xb6\xb1\xbd\xc7\x90\ \x19\xc4\xb6\x35\x8c\x8c\x20\xb6\x9d\xeb\xd8\xb6\xd3\x56\xa5\xeb\ \xe9\xd8\x78\x9b\x65\x4b\x5b\x5b\x1b\x4f\x43\x77\x75\x75\xf5\x13\ \x91\x71\xc6\x98\x1a\x3c\x3a\x11\xf1\x8d\x31\x83\x75\xf1\x9a\xd4\ \xd6\xf1\x5f\xab\xd9\x68\x92\xa6\x86\x87\xd1\x5a\x70\x1c\xc5\xf9\ \xf3\xe7\xeb\x2b\x57\xac\x99\xa2\x6d\xcb\xcf\x3f\xff\x1c\xff\x77\ \xce\x22\xc2\xc5\x2a\xe2\xb8\x4b\x19\x45\x4d\x5a\x2c\x5d\xd8\x87\ \xe7\x9e\x7f\x1e\xa0\xa1\xa1\x14\xe5\x2e\xdf\xfc\xfa\x82\xed\x1a\ \x56\xf9\x91\xf2\x6b\x1f\x53\xfe\x6f\x5a\x7d\x96\xed\xdb\x76\xf2\ \xee\xbb\xef\x60\xe9\x28\x8a\x00\xc1\x88\xc6\x8b\xf2\xb2\x80\x01\ \x11\xb9\xad\x92\x38\x49\x39\x7b\xfe\x32\xa0\xf9\xf8\x93\x4f\x50\ \x92\x63\x69\xdf\xf7\xc9\xf3\x0c\xa5\x14\x61\x2a\x20\x58\x65\x4f\ \x28\x0b\x9e\x38\x73\x11\xcf\x8f\xb0\x1a\xea\x45\x76\xef\xda\xc5\ \x7b\xef\xbd\x43\x10\x04\x68\xfb\xca\xb2\x1c\x94\x26\x48\xcb\xd6\ \x0c\x82\x00\x5e\x10\x71\xf1\x4a\x80\x31\x9a\x4a\x5d\x63\x11\x38\ \xbc\xf7\xc1\xfb\x54\xb4\xc2\x36\xae\x3d\xcf\xc3\xe4\x06\xa5\x2a\ \x84\xb9\x46\xa0\xcc\x17\x25\xc4\x89\x42\xd7\x5a\xdc\x4a\x1c\x87\ \x7d\x7b\xf6\xf2\xe9\xa7\x9f\xe0\xba\x6e\xd9\x03\x83\xb1\x09\x44\ \xb9\x06\x03\xc6\x5e\x45\xb8\xe2\x70\x17\xe3\x24\xbc\xf5\xf6\x5b\ \x28\x45\x39\x04\xdb\x0d\x4b\x29\x8d\x53\x6d\xf1\x30\x89\x72\xc9\ \xb2\x0c\x83\x60\x7b\xaf\xf3\x3c\xc7\x7a\xb1\xa1\x89\x4f\x84\x3c\ \x4c\xab\xaa\x11\x65\x50\x22\x65\x0f\x0a\x57\x8e\x1c\x3e\xfa\xe2\ \xcc\x89\x5f\xf3\xa8\x92\x28\xe5\xc8\x91\xa3\x00\x17\xa5\x38\x0b\ \xbd\xb5\x5a\x6d\xac\x88\x69\x81\x28\xe0\x83\x4a\x45\xeb\x4a\xb5\ \x2a\x8e\x52\x58\x59\xd1\xcb\x38\x8e\x4d\x9a\xa4\x09\x70\x08\x3b\ \xed\x86\x4b\xbe\x1f\x8c\xd0\x69\x9a\xce\xb1\x0f\xe5\x16\x6e\xbd\ \xf8\xca\x0b\x5b\xfe\x6f\x6b\xfb\xf2\xbb\x6f\xbf\xe7\xf5\xd7\x5e\ \xb7\x71\x9c\x3a\x75\x92\xf5\x1b\xd6\x9b\x95\xab\x57\x6e\x0f\xae\ \x84\xbf\x14\x95\xf9\x94\xb8\xeb\x38\x2b\xa5\x9e\xab\x37\xeb\xd3\ \x9b\xad\xe6\x9f\xc5\xa1\xf9\x10\x30\x59\x9e\xed\x0f\xfd\x70\xc5\ \x95\x4b\xee\x30\x11\xf1\xb8\xc5\x55\x77\x8f\xf2\x46\x07\x76\xcc\ \xb0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\x74\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x3b\x49\x44\x41\x54\x78\x01\x63\xf8\xff\xff\x3f\x45\ \x98\x76\x06\x38\xa4\x49\xfc\xc7\x87\x89\x32\xe0\xff\xdf\xbf\x20\ \x85\x10\xfa\xdf\x3f\x08\xfe\xfb\x07\x6c\x00\x7d\x5c\xd0\x7b\xcc\ \x1e\x1b\x1e\xf6\x06\x8c\x1a\x40\x79\x42\xa2\x7f\x6e\x04\x00\x7f\ \x76\xba\x48\xa6\xb7\x92\x83\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ \x42\x60\x82\ \x00\x00\x00\x91\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x02\x03\x00\x00\x00\x62\x9d\x17\xf2\ \x00\x00\x00\x09\x50\x4c\x54\x45\x00\x58\x26\x00\xa6\x51\xff\xff\ \xff\x25\xa5\x29\xf6\x00\x00\x00\x03\x74\x52\x4e\x53\xff\xff\x00\ \xd7\xca\x0d\x41\x00\x00\x00\x34\x49\x44\x41\x54\x78\x01\x63\x58\ \x05\x04\x70\x82\x09\x48\xac\x08\x01\x12\x0b\x83\x80\x44\xab\x17\ \xc3\xaa\x45\xa2\x5a\x0c\xab\x5a\x42\x80\xb2\xa2\x41\x40\xa2\xd5\ \x0b\x48\x2c\xd4\x02\x12\x2b\xc0\x3a\x30\x08\x00\xa9\xce\x25\x05\ \x80\x1e\x13\xf6\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \ \x00\x00\x00\x70\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x02\x00\x00\x00\x90\x91\x68\x36\ \x00\x00\x00\x06\x74\x52\x4e\x53\x00\xff\x00\xff\x00\xff\x37\x58\ \x1b\x7d\x00\x00\x00\x25\x49\x44\x41\x54\x78\x01\x63\xf8\x4f\x22\ \xa0\x86\x06\x86\x38\x05\xac\x08\x9f\x06\xcb\xad\x5e\x68\x68\x98\ \x69\x18\xd5\x40\x38\x69\xd0\x3e\xb5\x02\x00\x01\xd8\x39\x17\x83\ \xda\x0c\xcb\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\x65\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x04\x00\x00\x00\xb5\xfa\x37\xea\ \x00\x00\x00\x2c\x49\x44\x41\x54\x78\x01\x63\xf8\x4f\x00\x92\xa6\ \x20\xe2\x3f\x2a\xc4\xa2\xe0\xef\xff\xff\x40\xfc\x0f\x08\xff\x02\ \x15\x90\x63\xc2\x06\x04\x1c\xd1\x0a\x30\x03\x8a\xf2\xd8\x04\x00\ \x9e\xd7\x34\x72\x5e\x59\xb0\x8a\x00\x00\x00\x00\x49\x45\x4e\x44\ \xae\x42\x60\x82\ \x00\x00\x02\x44\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\x0b\x49\x44\x41\x54\x38\x4f\xa5\xd3\x4b\x68\x13\x51\ \x14\x06\xe0\xff\x4e\xe6\x91\x34\x61\x62\x2c\x52\x4b\x6b\x7d\x34\ \x7d\x80\x8a\x10\x8b\x45\x03\x32\x1b\xeb\xaa\x1b\x05\x71\xa7\x51\ \x21\x28\x6e\x2c\x15\x21\x82\x14\xdc\xc4\x95\xf8\x80\x52\x45\x11\ \x4b\x2d\x98\x85\x0b\x41\x37\xd2\x64\xd5\x8d\x29\x8a\x41\x29\xd1\ \x95\xb5\xb6\x35\x8f\x89\xc9\x4c\x26\x65\x66\xee\x95\x84\x2a\xda\ \x9a\x18\xf0\xc2\xd9\xdd\xf3\xdd\xff\xc0\xb9\x04\xff\x79\xc8\xfa\ \xfe\x64\x72\x42\xd8\xdf\xe9\x12\x97\xa8\xc1\xda\xdb\xc3\x06\x21\ \x60\x8d\xde\xf8\x03\xc8\xe7\x27\xbc\xbe\x16\xdf\x56\xd8\x54\x06\ \x6c\xba\x6a\x3a\x0a\x92\x6a\x2c\x92\x9d\xa1\x4a\x3d\xe4\x17\xc0\ \xbe\x3d\xf5\xc0\x6d\xf6\xd9\xcc\xe8\x23\x56\x79\x1b\x1c\x92\xce\ \x71\x2d\x9f\x41\x85\x8f\xf0\x7c\xff\x44\x48\xd8\xfc\x1b\x52\x03\ \x18\x03\x41\x65\x7a\x3b\x2c\x63\x40\xd7\xd2\xa7\x05\xa7\x3f\xc0\ \xf1\xae\x0c\x58\xf6\x05\x68\x6b\x9c\x27\x62\x92\x78\x4f\xe4\x1b\ \x00\x63\x3c\x34\x7f\xaf\x69\x66\x15\xca\x70\x59\x90\x43\x3b\x18\ \x2d\x32\xaa\x4d\xbe\x16\xa4\xd6\x5b\x3a\xc4\x19\x8f\x27\xb4\xdc\ \x38\x41\x71\xb2\xdb\x76\x18\x83\xd4\x2c\x9e\x21\xe2\xa1\xa0\x65\ \x16\xc5\x95\x95\x54\x29\x97\xd3\xee\xf5\x74\xed\x8a\xca\x9d\xa7\ \x72\x75\x81\xda\x18\x85\x29\x9f\x25\x91\xdd\x6c\x55\xdb\x43\x59\ \x69\x60\xe1\x4b\x29\xb8\x90\xf5\xf6\x94\x0d\x51\x9f\x4f\xcd\x5d\ \x18\xbd\xf2\x70\xaa\x21\x50\x43\xb4\x27\x6d\x26\xe1\x3a\x04\xc6\ \x7c\x8b\x59\xd5\x39\x3b\x9b\x3e\xbf\x5c\xda\x72\xb4\x90\x2b\xeb\ \xa9\x37\x1f\x4e\x66\x32\xea\xab\x44\x22\x61\xfd\x0e\x6d\xd8\x03\ \xc6\x6e\x4b\x50\x25\x27\x2a\x30\xc3\x63\x73\x00\xa7\x46\xba\xba\ \xfb\x2f\x11\x46\x58\xea\xed\x7c\xc4\x6e\x63\xf7\x63\x37\x63\xc6\ \x4f\x64\x03\xb0\x3e\xa6\xa2\x28\x3c\x85\x75\x75\xf8\xf8\x50\x04\ \x94\x33\xdf\xbf\x4b\x5f\x7f\xf4\xe0\xf1\x8d\xa6\x81\xb5\x8b\x24\ \x70\x20\x70\x6d\xf8\xd8\x91\x8b\xd4\x22\xee\xcc\xd7\xdc\x1d\x22\ \xf3\xd1\xf1\xe8\xb8\xfa\xcf\x04\x00\x38\x00\x12\x00\xe7\xc1\xe0\ \xe0\x59\x65\x28\x38\xb2\x49\xf6\x6e\x7e\xf9\x3c\x3e\x9a\x98\x49\ \xdc\x6d\x06\x70\x01\xf0\x00\x90\x01\xb8\xfb\xf7\xee\x3b\x27\x39\ \xa5\xc3\xbd\xfe\x8e\x91\xd8\xf4\xb3\x78\x33\x40\x75\x0a\x1e\x80\ \xb0\x56\x36\x80\xea\x5a\x57\x8b\x35\x0b\xd4\xfd\x90\x3f\x00\xc5\ \x4a\xd1\x12\xe8\x9a\xc6\x83\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ \x42\x60\x82\ \x00\x00\x00\xf2\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x03\x00\x00\x00\x28\x2d\x0f\x53\ \x00\x00\x00\x39\x50\x4c\x54\x45\x00\x00\x00\x39\x55\x78\x39\x55\ \x78\x3f\x63\x8f\x42\x6b\x9c\x43\x6f\xa5\x56\x82\xb7\x49\x7c\xba\ \x46\x7a\xba\x4c\x7f\xbd\x45\x7a\xbe\x56\x86\xc3\x64\x90\xc7\x7c\ \xa1\xcf\x82\xa5\xd1\x86\xa7\xd2\x8f\xae\xd5\x9d\xb8\xda\xbc\xcd\ \xe3\x2f\x13\x10\x79\x00\x00\x00\x0a\x74\x52\x4e\x53\x00\x58\x59\ \x97\xae\xc0\xcd\xee\xf4\xf8\xf4\xc2\x56\xfa\x00\x00\x00\x5e\x49\ \x44\x41\x54\x78\x01\x8d\x4f\x83\x01\x45\x31\x0c\xac\x71\xa9\x2e\ \xfb\xef\xfa\x6d\xbe\xd8\x30\x5f\x00\xc0\xdd\xb1\x80\x85\x1e\x18\ \xd7\xac\x4e\x34\x36\x4c\xbd\x44\x30\x79\x86\x89\x6b\x0b\x7b\x91\ \x32\x08\x7b\x99\xd7\x58\x82\xf3\xf9\xd0\x85\xf3\x08\x52\x9c\x71\ \x42\x2a\xae\x15\x35\xb9\x58\xae\x15\x87\x19\xda\xab\x94\x75\x9d\ \xf1\xb6\xe5\xfd\x0e\xf3\x7a\xe9\xf5\x97\xad\xb0\x07\x45\x40\x05\ \xc6\x5c\x07\x02\x66\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ \x82\ \x00\x00\x01\x4c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\x13\x49\x44\x41\x54\x78\x01\x95\x93\x45\x72\xc3\x40\ \x10\x45\x7d\xbc\x6c\xc2\xcc\xcc\xcc\xc9\x3e\xbb\xdc\xc0\x8c\x07\ \x33\xdb\x62\x66\xa9\x33\x1d\x16\x96\xf4\xab\x9e\x58\x6f\xb0\x73\ \xe5\x72\xf9\xb1\x54\x2a\x49\xc5\x62\x51\xcf\x02\xf9\x47\xa9\x54\ \x2a\xef\x39\x72\x21\x5a\x96\x05\xb6\x6d\x67\x86\x88\xec\x1c\xda\ \x80\xa4\xd3\xe9\x40\xb7\xdb\x85\x5e\xaf\x87\x40\xbf\xdf\x87\xc1\ \x60\x10\x02\x9f\x23\x24\x28\x70\x7e\x05\x85\x42\x21\x12\xfc\x69\ \x62\x6a\xda\x47\xac\x20\x18\x7c\x36\x1a\x8d\x60\x6a\x71\xd9\x07\ \x4a\x63\x05\x1c\xc7\x20\xbf\x02\x8a\xa2\x60\x61\x73\xdb\x07\x4a\ \x53\x0b\x18\x86\x81\xb5\x83\x63\x1f\x28\x4d\x3b\x84\x48\xaa\xd5\ \x6a\xfa\x49\x6c\xb5\x5a\x3e\xa9\xeb\xba\x40\xd6\xff\x57\x80\xfb\ \x00\x05\xd8\x2d\x58\xdd\x3f\xf2\x81\xdd\x47\x81\xe7\xb9\x80\xc3\ \xe2\x79\x36\x5e\x40\xd3\x34\x6c\x9f\x5d\xf9\x60\x59\x36\xbd\x00\ \x5b\x3b\xb8\x7d\xf4\xc1\xf3\x7c\xfa\x21\x60\x6b\xc7\x8f\xaf\x3e\ \x44\x51\x4c\x9c\x44\x9f\x80\xe3\x38\x10\x04\x01\x24\x49\x02\x45\ \x51\x40\xd3\x34\x30\x0c\x03\x7e\xea\x04\xcf\xa6\x69\x82\xae\xeb\ \xa0\xaa\xaa\x4f\x20\x43\xf6\xfc\x09\x48\x77\xde\x48\xb7\x1c\x02\ \x64\xc4\xad\xd5\x6a\x85\x0f\xf2\xd5\x3e\xe7\x55\x4d\x3c\xa6\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\x03\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ \x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ \x79\x71\xc9\x65\x3c\x00\x00\x00\xa5\x49\x44\x41\x54\x78\xda\x62\ \xfc\xff\xff\x3f\x03\x25\x80\x89\x81\x42\x30\x6a\x00\x03\x03\xcb\ \x72\x46\x46\x06\x46\x06\x86\x99\x40\x6c\x4c\x8a\x46\x60\xdc\x9d\ \x05\xe2\x74\x96\x1f\x10\xbe\x71\x40\x5a\x9a\xf1\xd7\xaf\x5f\x19\ \x7e\xfd\xfa\x05\xc6\xbf\x7f\xff\x06\xe3\x7f\xff\xfe\x81\x31\x28\ \xba\x61\x98\x89\x89\x89\xe1\xda\x81\x03\x10\x17\xb4\x03\x89\x2c\ \x20\xfe\xf9\xf3\x27\xc3\xf7\xef\xdf\x51\x0c\xf8\xfb\xf7\x2f\x8a\ \x46\x18\x66\x04\xba\xfa\x23\x50\xcf\x34\x90\x01\xb7\x19\x18\x64\ \xce\x31\x30\x5c\x7b\xb9\x70\x21\x49\x7e\x7f\x0a\xd4\x03\xd2\x0b\ \xf4\x3a\x03\x1f\xc8\x20\x20\x66\x05\x62\x66\x22\xf5\xff\x05\xe2\ \xdf\x40\xfc\x87\x71\x34\x29\x53\x6e\x00\x40\x80\x01\x00\xeb\xe7\ \x5a\xad\x1b\x1c\xdf\xd7\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x01\x7a\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\x41\x49\x44\x41\x54\x78\x01\xbd\x90\x03\x72\x06\x41\ \x14\x84\xe7\x4e\xb1\x4b\xc9\x01\x62\xdb\xb6\x6d\xdb\xb6\x6d\xdb\ \xb6\x6d\x1c\xa2\x33\x2f\xb6\xf3\x4f\xd5\xb7\xec\xfd\x5e\xef\x30\ \x15\x35\x25\x3f\x1b\x3b\x2b\xbc\xc4\xcc\xc2\x84\x80\x81\xa1\x9e\ \x19\xfb\x68\x51\x78\x76\x76\xf6\x4d\x46\x47\x47\x60\x64\x62\x00\ \x3e\x44\xe3\x43\x41\x7f\x7f\x1f\x52\xd3\x52\xde\x83\x04\x2f\x89\ \x7b\x26\x98\x98\x98\x40\x69\x69\xe9\xa7\x94\x95\x95\xa1\xbc\xbc\ \x9c\x04\x78\x10\xd0\x7f\x52\x03\x03\xcf\x4c\x08\x9b\x14\x7f\x0b\ \x05\x9b\xec\x75\x12\x50\x03\x7a\x80\xd1\xf5\x2b\xf4\x2e\x9e\x7f\ \x09\xca\xd2\x37\x0f\x0d\xe8\xa6\x76\xec\x08\xd1\x0d\xeb\x5f\x81\ \xb2\xb7\x02\x3d\x03\x1d\x6a\xf0\x73\x01\x6d\x08\x35\xf8\x95\x40\ \xe0\x0d\x22\x6a\x57\x51\x3a\xb8\xff\xbc\x81\x84\x59\x21\x6a\xb8\ \x20\xaa\x7e\x8d\x42\x74\x46\x64\xdd\x1a\xc2\x79\x38\xa4\x6a\x05\ \xfe\xe5\x4b\xf0\x2c\x5e\x84\x53\xfe\x1c\x6c\x73\x66\x91\xde\xb1\ \xfd\xbc\x81\xba\x6b\x16\xb2\xba\x76\x60\x9f\x3b\x0b\xfb\x9c\xdb\ \x90\x6d\xf6\x2c\x6c\x38\xd6\x59\xb3\xb0\xca\x9a\x81\x65\xe6\x2c\ \x2c\x32\x66\x60\xce\x49\x6e\xdd\x7a\x14\x54\x54\x55\x20\x82\x4f\ \xa3\x90\x55\x26\x05\x67\x6e\x83\xe9\x33\x30\x4d\x9f\x86\x09\x3f\ \x9b\xa4\x4d\xc3\x38\x75\x1a\x46\x29\xd3\xc8\xec\xdc\x85\x92\x43\ \x2a\xe4\x75\x3c\xf2\x98\xac\x7e\x44\x3f\x99\xbe\x8b\x9c\x7e\xe8\ \x38\xfb\x8b\x75\x0d\x15\xce\x66\x94\x5a\x42\x0f\x18\x00\x00\x00\ \x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\xd3\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\x9a\x49\x44\x41\x54\x38\x4f\xa5\x93\x5d\x48\x53\x61\ \x1c\xc6\x9f\xb3\x9d\xa3\x9e\x39\x43\x50\x74\x8a\x62\x9a\x64\x7e\ \x65\x32\x73\xd3\xb4\xa0\xc4\x75\xd3\xc0\x2e\xac\x9b\x30\x2b\x25\ \xa2\x9b\xc8\x0c\x31\xbc\x90\x20\x84\xae\x82\xcc\x02\x4b\x41\xb3\ \x98\x61\xc4\xfa\x42\x6f\x76\xd1\xb0\xc8\xc4\x34\x85\x40\xcd\xa5\ \xf3\x1b\xe6\x57\x67\xc7\x73\xce\xde\x78\x9d\x9b\x2e\xc5\x9b\xce\ \xdd\x79\xdf\xf7\xff\x7b\x9f\xff\xf3\xbc\x7f\x06\xff\xf9\x31\xff\ \xd6\x0f\x00\xa1\x2e\x96\xbd\xce\xc9\xf2\x83\x7c\x40\xf0\xed\xf7\ \x03\xe1\xb3\x1c\x67\x8e\x94\xa4\x8e\x1c\x40\xf2\xad\x07\x00\xec\ \x00\xbf\xc4\x07\xb7\x1b\xef\x35\x98\x3f\xd7\xdc\x6e\x8a\x14\xc4\ \x1b\xf4\x30\x2d\x9e\xe6\x43\x06\x53\x2b\x2a\xa2\x7e\x3e\x6a\xaa\ \x34\x49\x52\xeb\x0e\x00\x2d\x96\x92\x12\xef\x27\x9f\x3b\x7f\x8d\ \xd7\x68\xb0\x3c\x3f\x87\x5f\x9d\x96\x7a\xc6\x39\xd3\xc2\x65\x66\ \x3c\x49\xbb\x52\x51\x44\x04\x01\xd3\xdf\xfa\xf0\xbb\xeb\xf5\xc5\ \x08\x49\x7a\x4e\xe1\x7e\x05\x36\xb5\xfa\x71\x72\x75\x75\x25\xaf\ \xd5\x02\x8a\x02\xb0\x2c\xfe\xb8\x5c\x18\xb3\xbc\xec\x4b\x29\x2b\ \xd7\x73\x2a\xd5\xc6\xa5\x0c\xc7\x61\xc9\xe9\x5c\x77\x34\x36\x5e\ \x3e\xae\x28\x6d\x7e\x00\x95\xb9\x9a\x67\x9c\xca\x28\x39\xab\x21\ \x6e\xb7\x57\x21\x01\x58\x5d\x34\xe4\x99\x59\x42\x7f\x88\x2c\x13\ \x26\x3c\x1c\xbd\x77\x6a\x9d\x31\x82\x3b\x33\x1b\x70\x05\x78\xd0\ \xcd\xb2\x05\x51\xa7\x4d\x6d\x09\x27\x4f\x25\x78\x56\x56\xbd\x84\ \x75\x99\x10\x51\x20\x9e\xd5\x35\x59\x0a\x09\xc2\x78\x4f\x8f\x4d\ \x33\xf4\xa3\x94\x16\x6f\x28\xda\x9e\x02\x01\x98\x6e\x8e\xd3\x87\ \x1e\x4a\xf9\x92\x5e\x7e\x89\x51\x16\xe6\xc1\x88\x12\x51\x26\x1d\ \x50\xed\x4f\x24\x83\x1d\xed\xbd\x41\xf3\x8b\xa5\xf9\x82\x30\xb5\ \x6b\x0a\x6f\x42\x90\x18\x16\x1d\x6f\xc9\xa9\xbc\xaa\x5f\xb7\xd9\ \x36\xf8\x84\xaa\xd8\xec\x47\x88\x8b\xc7\xe8\x3b\x6b\xbd\x76\x66\ \xee\xae\x2f\xca\x00\x0f\xdc\x69\xa9\x96\x24\x83\xa1\x88\x9d\x72\ \x6e\x79\x90\x9e\x06\x79\x78\xd8\x6b\x20\xcf\x63\x25\x4c\x8b\x91\ \x57\x9d\x0f\xf7\x09\xe2\x2d\xfa\x4e\xb6\x52\x60\x59\x7b\x56\x55\ \x55\x9e\xd2\xdf\x0f\x10\xef\xad\xc1\xc5\xc5\x18\x78\xd6\x8c\xf4\ \xfc\x02\x28\x13\x13\x5e\xa8\x5a\x0d\x55\x61\xa1\xf2\xbd\xae\xae\ \xf6\x84\x2c\x37\xf8\x01\x23\x40\xc4\xe2\x91\xc3\xdd\x29\x47\x0d\ \xd9\x2a\x87\x03\xca\x81\x24\x8c\x7c\x7c\x6f\x67\x26\x26\xeb\x22\ \x4c\xa6\xe6\x58\x9d\x2e\xc1\xb3\xb0\x00\x31\x46\x87\x81\xd6\x96\ \xae\x58\x41\xbc\x90\x05\xac\x05\x98\xf8\x36\x18\x07\xc3\x32\xf5\ \x2f\xb2\xce\x98\xb3\x87\x3b\x2d\x3d\xca\xe0\x50\xd9\x31\x60\xfa\ \x13\x50\x10\x64\xcc\xfd\x10\x97\x6b\xd0\x8c\x5a\xad\x8d\xea\xb1\ \xf1\x2a\xdf\x33\xdf\x31\x0b\x5f\x81\xc8\x15\x96\xb5\xda\x65\xb9\ \xa4\x16\x58\xf2\xb9\xfd\x14\xd0\x65\x00\x35\x1e\xe0\xa6\x11\x58\ \xde\x35\x85\xcd\x45\x8e\xb6\x0f\x80\xa7\x1d\x6f\x8b\x99\x0e\x10\ \x1d\x2e\x11\x80\xb2\x17\x60\xaf\x3d\x5f\xa6\x7e\xee\x5f\x00\x6c\ \x04\x20\xc3\xbe\x1e\x57\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x02\x22\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\xe9\x49\x44\x41\x54\x78\x01\x95\x92\x03\xac\x9d\x41\ \x14\x84\x6b\xdb\x41\xad\xa0\xb6\x6d\xdb\xb6\x8d\x67\xdb\xb6\x6d\ \xdb\xb6\xad\xab\xb0\xb1\x6a\x4d\xf7\xfc\xd5\x35\x92\x4c\xb4\x33\ \xdf\x1e\xf5\x03\x20\xa1\x0d\xaf\x06\xbd\xdb\xf0\x72\x10\xe4\x89\ \xde\xa4\xfd\x32\x00\x32\x56\xf2\x93\x50\xda\x17\x8b\xc2\x9e\x08\ \x24\xb7\x38\x23\xb4\xc6\x80\x13\xbd\xa9\x05\xa8\x12\x24\xc3\xbe\ \xe0\x2a\x2c\x72\x4e\xe3\x79\xc2\x5a\x1c\xf1\x19\xa2\x19\xa0\x52\ \x0c\xa0\x95\xb2\x5d\x73\x00\xb5\x60\x97\x7f\x19\xe6\xd9\xa7\xa0\ \x97\xb6\x57\x39\x60\xe3\xeb\x41\xe5\xe2\x83\xda\xf8\x66\xd0\xa7\ \x72\x5e\x3c\xac\xf3\xce\xc3\x24\xeb\x18\x9e\xc6\xaf\xfa\x07\xa0\ \x37\x71\x2f\x65\xe9\xc7\xdb\x67\xac\x17\x7e\x2c\xe9\x8d\x41\x05\ \x3f\x11\x14\x2e\xe9\x8b\x81\x55\xfe\x19\x98\xe4\x1c\xc6\xcd\x98\ \x99\x38\x19\x32\x0c\xe1\x0d\x86\x08\xab\x37\x40\x58\x9d\x01\x02\ \x2b\xb5\x70\xd0\x64\xda\x07\xca\x72\x65\x6c\x7c\x35\x28\x4e\x27\ \xfc\xe8\xe7\xac\x0e\x5f\xd6\xfb\x65\xd8\x14\x9d\x83\x65\xd1\x71\ \x18\xe4\xed\xc0\x85\xd8\x51\x38\x1f\x3b\x12\xe7\x63\x46\xe0\x5c\ \xf4\x28\x78\x56\xde\xc3\x6d\x9f\x55\x9f\x59\x26\x96\xb2\x7f\xfb\ \x1e\xc5\x76\x2c\xf2\x2e\x7c\xfd\x33\xba\xd9\x14\xd6\x25\x27\x60\ \x59\x7a\x18\xf7\x33\x66\xe0\x6a\xda\x68\x4e\x57\x52\x47\xc3\xba\ \xfc\x08\xcc\x32\x4f\xff\x64\xa5\x8b\x28\x43\x59\xf1\xe1\x2d\xde\ \xa6\x35\xfa\x63\x72\xab\x13\xbc\xea\x6e\x43\xbf\x64\x23\x6e\x64\ \x8f\xc5\xed\x3c\xa6\xfc\x71\xd0\x2d\x5f\x81\xc0\x86\xe7\xd8\xaa\ \x35\xe2\x23\x79\xe5\x6e\x81\x7a\x3a\x6b\xb7\xe0\x43\x66\xaf\x1b\ \xb4\x4b\x97\xe3\x6e\xd1\x78\x3c\x28\x9f\x80\x97\xd5\x33\x11\xd5\ \xab\x85\x13\xd6\x33\xb8\xbe\x95\xae\x91\x95\x17\xa7\x13\x73\xe8\ \x6b\x50\xc7\x63\xdc\x2f\x9b\x80\x27\xb5\x93\x10\x2e\x7c\x84\x17\ \x51\x5b\xbf\x6c\x7a\x33\x28\x56\x9d\x3b\x18\xc5\xd6\x25\xb2\x2f\ \xbc\xfc\xd3\xbe\xf9\x10\x9c\xda\x8f\xc0\xa5\xfc\xea\x4f\x16\xe6\ \xfa\x56\x0a\x10\x9f\xc7\x0e\x9d\x31\x1f\x43\x9a\x5f\x21\xb4\xf5\ \x35\x76\xe8\x8e\x11\xef\x5b\x39\x40\x7c\x1e\x67\x6c\xe7\x7d\x20\ \x89\xf7\xad\x36\x80\xc4\xca\xf6\x27\x29\xf3\xfc\x02\x28\x7a\x83\ \x03\x4b\x77\x68\x9c\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ \x82\ \x00\x00\x00\xd5\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x9c\x49\x44\x41\x54\x78\x01\x63\xc0\x07\xca\xa5\x3b\ \xf4\x5f\x16\xd9\xe8\x33\x90\x0a\x2a\xa5\xda\x34\xca\xa5\x5a\x77\ \x94\x4b\xb7\xed\x7e\x5d\x62\x0d\xc2\xfb\x5f\x96\xd8\xea\x11\xd4\ \x58\x21\xd7\x2e\x58\x21\xd5\x36\xb1\x5c\xba\xf5\x37\x10\xff\x47\ \x32\xe0\x3f\x10\xff\x7d\x5d\x62\xb5\xe8\x79\xa5\xad\x28\x86\xc6\ \x06\x86\x06\x26\xa0\x86\x38\x20\x7e\x05\xd2\x88\xc0\x08\x03\x90\ \xf0\xbb\x57\xc5\x36\xf9\xff\x1b\x1c\x58\xe0\x06\x94\x49\xb5\xce\ \x86\x69\x22\x60\x00\x02\x97\x5a\xcf\x46\x0a\xac\xb6\x55\xa4\x1a\ \xf0\xa6\xc4\x7a\x15\x15\x0c\x18\x35\x80\xe2\x68\xa4\x38\x21\x51\ \x92\x94\x09\x67\xa6\x32\xa9\x96\xed\xc4\x64\x26\x8a\xb3\x33\x00\ \x5f\x17\x7b\xf1\x44\x05\x23\x70\x00\x00\x00\x00\x49\x45\x4e\x44\ \xae\x42\x60\x82\ \x00\x00\x03\x05\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\xcc\x49\x44\x41\x54\x78\x01\x75\x52\x43\xd7\x24\x48\ \x10\x5c\x5e\x97\x7f\x63\x8d\xee\xbd\xad\x75\x1d\xdb\x36\x8e\x63\ \xdb\xf8\x6c\xdb\xb6\x8d\xb6\x6d\xdb\x88\xa9\x1c\x33\xdf\x2b\x57\ \x44\x65\x44\xe5\x7b\xaf\xc7\xa9\x95\xcb\xb9\x47\xb7\x6e\xca\x38\ \xb4\x63\xab\x84\xb5\x28\x35\x9a\xd3\x1e\x9d\xd1\x9d\x77\xc6\x8d\ \xff\xff\xcd\xc8\xdb\xb0\x8e\x67\xe4\xf1\x06\xc2\x3e\x9f\x2f\x95\ \x4c\xa6\xa9\xb1\x79\xc0\xb0\xb8\x30\x41\x67\x74\xe7\xad\xe0\xe2\ \x1f\x7f\xe8\x30\x95\x96\x54\xa4\xe2\xf1\xb4\x75\x64\x04\xc6\x8e\ \x0e\x18\x5a\x5b\x61\xee\xe9\x81\x73\x66\x06\x21\xa3\x11\x44\x66\ \x2c\x29\xe9\xa0\xbb\xaf\x80\x6f\xfe\xf7\x4f\x86\xb9\xac\xac\x32\ \x6c\x32\xa5\x34\xb9\xb9\x50\xe7\xe4\x40\x57\x5c\x0c\x63\x4d\x0d\ \xcc\x2d\x2d\x70\x8e\x8d\xc1\x2f\x91\x20\xa8\xd1\x20\xe1\xf7\x43\ \x5f\x52\x3c\x45\x98\xf7\x28\xf6\xee\xdb\xcd\xcd\x5f\xbf\x96\x47\ \x2f\x5b\xda\xdb\xa1\x2b\x2d\x85\xe2\xe6\x4d\x48\x2f\x5e\x80\xf4\ \xd2\x45\x28\x6e\xdd\x84\xa9\xa1\x1e\xee\xb9\x59\x04\xe4\x72\xb0\ \x47\xc0\xee\x22\x67\xdd\x1a\x11\x61\x89\x20\x43\xbf\xb8\xc0\xf7\ \x49\xa5\xb0\x76\x75\x41\x5f\x51\x0e\x6d\x51\x11\x1b\x2b\x68\x0d\ \x27\x93\xe3\x13\x0a\x61\x6e\x6e\x84\x67\x71\x01\x5e\x1e\x0f\x41\ \x9d\x0e\xe2\xbe\x5e\x13\x61\x89\x40\x12\xf1\xfb\xa3\x1e\x81\x00\ \x8e\xe1\x61\x68\x0b\x0b\x60\x6e\x6a\x82\xe8\xf8\x71\xd4\x72\x39\ \xd4\xd2\x24\x25\xa8\x52\x41\x5f\x5e\x06\xc7\xe0\x20\x9c\x73\x73\ \xb0\xab\x54\x31\xc2\x12\x41\x94\xcc\xf1\x2b\x14\xf0\xcc\xcf\x43\ \x5b\x54\x08\x37\x33\x8d\x80\x83\x5c\xee\xd7\x95\x9c\x9f\xbf\x6c\ \xf8\x85\x9b\x4a\x86\x42\x90\x5d\xb9\x04\x5b\x77\x37\x6c\xa3\xa3\ \x70\x19\x8d\x69\xc2\x3e\x23\x00\xb9\x4c\x46\x91\x84\x80\x52\x09\ \x06\x4a\x16\xff\xf4\xc3\xa7\xd4\xea\x38\x3f\x47\x88\x40\x7c\xfa\ \x24\x2c\xad\xad\xb0\xf4\xf7\xc3\xa1\xd5\x82\x61\x63\x8f\x25\x44\ \x03\x81\x44\xd4\xe5\x42\x88\x6d\xda\xfb\xfb\x40\x3a\x0d\xd5\xd5\ \xe9\x1a\x06\xac\xfe\xe9\x07\x27\xcb\x2a\xe5\x1a\x1f\x67\x19\x5c\ \x86\xb1\xae\x0e\xa6\xce\x4e\xe8\x79\xbc\x24\xc3\x4a\x89\x20\xc3\ \x24\x14\xda\x93\xb1\x18\x62\x4e\x27\x42\x7a\x1d\x1c\x43\x03\x88\ \xda\x6c\x48\x46\x22\xa0\xe6\x9a\x9e\xc6\xdc\x8e\xed\x50\x65\x66\ \x40\x5f\x56\x06\xca\x60\xb1\xbd\x2d\xf0\xcc\x44\x6e\xc1\xe6\x0d\ \xea\x74\x2a\x85\x54\x34\x4a\xff\x8c\x88\xd9\x0c\xd7\xe4\x24\x14\ \x77\xef\x40\x7c\xe6\x34\x7d\x27\x81\xa1\xce\xca\xa2\xfa\x00\xf9\ \x95\xbd\x71\xbd\x83\xb0\xcf\x0b\x49\x5b\x51\x6e\x4c\x27\x12\xcf\ \x49\xc2\x06\x03\xdc\xe4\xf6\xd0\x10\xcc\x6d\x6d\xd0\x57\x55\x41\ \xf9\xe0\x01\x19\x0c\xc1\xc5\x0b\xde\xeb\xff\xfd\x93\xf9\x4a\x35\ \x16\x72\x7e\xea\x50\x16\x15\x79\xa8\x48\x48\x4e\x88\x65\xe1\x65\ \xa6\x3a\x66\x67\x61\x61\xdf\x4b\xe5\xed\x63\x85\x34\x7f\xea\x64\ \x8c\xee\x3e\x85\x7d\xf8\x0a\xc9\xbd\x3f\x7e\xcf\xc8\x5c\xb1\xd4\ \xa9\x1c\x19\x49\xb8\xb4\x5a\x84\x3d\x1e\x04\x99\x2f\x36\x06\xe4\ \x35\x35\x26\xb3\x57\xad\x08\xd0\x9d\xf7\xde\x11\xef\xb3\xf6\xf1\ \xda\x75\x6b\x7e\xdb\xb1\x73\x5b\xfe\x9e\x3d\xbb\x34\x4c\x63\x72\ \xdf\xfe\x3d\xa9\x3d\x7b\x77\xe9\xd9\x5e\xc1\x73\xcd\x2f\xc5\x23\ \x04\x59\x83\x0f\x0f\x69\x80\xee\x00\x00\x00\x00\x49\x45\x4e\x44\ \xae\x42\x60\x82\ \x00\x00\x03\xca\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x03\x91\x49\x44\x41\x54\x38\x4f\x5d\x53\x7f\x68\x1b\x65\ \x18\x7e\xbf\xbb\x4b\x72\x49\xf3\xa3\x36\x5d\x93\xb4\x5d\x09\xdb\ \x6a\x3a\x3a\x2b\x6b\xa8\xb0\xad\x73\xda\xb5\x14\xad\x0a\xe2\x86\ \x56\x2a\x76\x28\x28\xe8\x1f\x61\x56\x90\xf9\x4f\xe6\x7f\x15\xaa\ \xd6\x1f\x13\x8a\xb0\x5a\x09\x9b\x43\xdc\xa4\x5b\x9d\x41\xc5\xc1\ \x92\xe9\xda\x34\x1d\xb6\x71\x4d\xdb\x2c\x4d\x32\x9b\x34\x49\x73\ \xb7\x5e\x2e\xb9\xcb\xdd\x7d\x92\xac\xc5\xb1\x17\x3e\xf8\xe0\xe5\ \x7d\xde\xe7\xf9\xbe\xe7\x41\xf0\x50\xb9\xdd\x6e\xc2\xed\x76\x2b\ \x00\xad\x6a\x67\xf7\x21\xed\xce\xc6\x7a\x14\xff\x3b\x20\x06\x02\ \x93\xfc\x56\x0f\x03\x40\xf9\x54\x0a\x3d\x30\xbf\x7d\xaf\xfa\xec\ \x8b\xf1\xae\x1d\x36\x6b\x57\xbd\xa5\x6e\x57\x41\x90\xa8\xf9\xc5\ \x44\x3a\x91\xcc\xdc\xb8\x30\xe9\xbf\x9a\x9c\x1b\x5f\x75\xbb\xdd\ \xe8\xfe\x92\xff\x01\xca\xc3\x78\x70\xf0\x7d\x6b\x6f\xcf\xa1\xcf\ \xf7\x1f\x3c\xf0\xbc\xbe\xba\x5a\x6d\x35\xaa\xd0\xed\x68\x0a\xcd\ \xaf\x89\xd8\x44\x93\x52\x26\xb2\x10\x99\xba\x3c\xf5\xe6\xb9\x89\ \xd1\xeb\xdb\x4c\x2b\x5b\x31\xc6\x08\xa1\xc3\xd5\x93\x97\x3f\x1a\ \xdf\xbd\xaf\xed\x39\x1d\x4d\x2a\x45\x99\x40\xc5\xa2\x48\x6c\xe4\ \x4b\x20\x96\x4a\xb8\xde\x40\xca\x35\x46\x0d\x79\x67\x39\x1a\xff\ \x6e\xe2\xfc\xc0\xd8\xd8\x88\x6f\x9b\x41\x19\x04\x8d\x7e\xe5\x19\ \x7e\x63\xf0\x45\x17\x41\x52\x90\x4c\x6f\x50\x02\x65\x00\x82\x52\ \x43\x8e\xe5\x41\x4f\x08\x60\x7d\x84\x86\x2a\x5a\xa5\xe4\x72\x1b\ \x70\xdd\x37\xbb\xf4\xcd\xc4\x4f\xcf\x1c\x74\x36\xae\x22\x8c\x31\ \xf9\xc2\xb1\x93\x2d\x9f\x0e\xbb\xfe\xb4\xdb\x1b\x74\xb1\x14\x83\ \x38\xac\x43\xb4\x46\x53\x66\x06\xcc\x66\x1e\x68\x25\x0f\xf1\x14\ \x0b\x26\x2d\x01\x75\x06\x52\x12\x8a\x3c\xf6\x7a\xfd\xae\xa1\xa1\ \xb7\xcf\x54\x24\x5c\xbc\xf8\x73\x7f\xe7\xd3\x47\xce\x86\x13\x0c\ \x29\x10\x34\x65\x31\x1b\x01\x01\x86\x82\x50\x02\x90\x4b\xd0\x5c\ \x47\x83\xac\x28\xb0\x96\x66\x80\xcb\xa5\x15\x96\xc9\x22\x9f\x7f\ \xde\x33\x79\xc9\xfb\x56\x85\xbe\xe7\xdc\x15\x57\x67\xd7\xe1\x8f\ \x6f\x2e\x65\x40\x00\x0d\x65\xd2\xd3\x60\xa4\x29\xe0\x0a\x22\xec\ \xb5\x69\xc1\x5e\xa7\xab\xfc\x1b\xc7\x71\x30\x1d\x0c\xe1\x95\xc8\ \x2a\xf8\xe6\x62\xde\x60\x20\xd2\x5f\x01\xf8\x70\xf8\xdb\x77\x1e\ \x6d\x3f\xf0\x49\x95\x4a\x46\xed\x0e\x1b\xb5\x9c\xe4\x60\x65\xbd\ \x08\x82\x28\x81\xbd\x56\x03\xed\x76\x23\x18\x74\x1a\xe0\x39\x16\ \xa2\xd1\x28\x3e\x3f\x35\x0d\x2b\x09\x6e\x8a\x67\x33\x03\x15\x09\ \xa7\x4e\x9f\xe9\x23\x1a\x1f\xff\x61\x57\x43\x8d\x6a\xe0\x68\x33\ \x21\x49\x32\x9a\x59\xce\xc2\xaf\xc1\x04\xb0\x79\x01\xee\x71\x79\ \xe8\x69\x35\x43\x5b\x93\x06\xe6\x43\x4b\xf2\xd9\xab\xb7\x11\xe2\ \xf3\x5f\xa3\x99\xe0\x7b\xe8\xf8\xf1\x0b\x64\xec\xda\x48\xed\x93\ \x27\x5d\x7e\x73\xd3\x6e\xfb\x89\x9e\x3d\x60\xd4\x6b\x89\x2c\x9b\ \x87\xd3\x9e\x19\x28\x08\x0a\x64\xef\xf1\xd0\x66\x21\xa1\x67\x5f\ \x15\xcc\x2d\x2c\x4b\xbf\xcc\x6e\xc8\x6a\x21\xf7\xea\x95\xef\x47\ \x7e\xac\x48\x28\x5b\xa1\xff\xf5\xa1\x13\x86\x96\xce\xd1\x8e\xe6\ \x1a\x5d\x77\xbb\x8d\xb8\x36\x77\x17\x79\x6e\x24\x81\x50\x14\xc8\ \x6c\x16\xa0\xb5\x06\xf0\xd1\xbd\xa4\xe2\xbf\x75\x17\x85\x23\xe9\ \xdf\xd7\x67\xfe\x7a\x79\x21\xd1\xcb\xdc\xb7\xaf\xd3\xa9\x82\x40\ \xc0\xda\xf5\xd2\xbb\xa7\x9e\xed\xed\x7e\xad\xa5\x41\xa3\xfb\xd2\ \x1b\xc7\xeb\x9b\x18\xcb\x25\x11\xa4\x22\x0f\x26\xb2\x48\x3c\x61\ \x57\xe1\x7f\xc2\xff\xde\x0a\x4e\xff\xf1\x81\xb8\xc9\xdd\x64\x98\ \x28\xbb\xed\x7f\x62\xa7\x63\xbf\x35\xbe\x18\xb4\xf7\xf5\x1d\x7b\ \xc5\xb6\xc7\x39\x90\x96\x4d\xfa\xa2\x28\x11\x72\xa9\x80\x48\x45\ \x52\x48\x99\x97\x53\xb1\x98\x2f\x34\xfb\xdb\x18\xad\x2a\x84\x72\ \x39\xea\x0e\x40\x9a\x7b\x30\x4c\xa4\xc5\x62\x31\xe7\x4b\xf9\x1d\ \x05\xd6\xec\xe8\x38\xd2\xf1\x18\x41\x69\x9b\xb0\x22\x53\x25\x41\ \xc8\xb2\xd9\x64\x78\x29\xe4\x9b\xd7\x98\x4c\x6b\x02\xab\x4e\x95\ \x87\x1f\x4e\xe3\x56\x30\x9f\xa2\xa0\x3e\xac\x06\x9e\x57\x03\xc3\ \xa8\xb6\xde\x48\xae\x75\x38\xc4\xcc\xe2\xa2\x08\x00\x25\x00\xa8\ \x24\xb1\x5c\xff\x01\xdc\xe3\xac\x93\x2c\x89\x5a\x3c\x00\x00\x00\ \x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\x9a\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\x61\x49\x44\x41\x54\x78\x01\xad\x92\xc3\x7a\x6c\x41\ \x18\x45\xcf\x43\xdd\xe9\x8d\xf3\x02\xb1\x6d\xdb\xb6\x26\x6d\x77\ \xc7\xb6\x6d\xdb\xb6\x31\x4d\x8f\x77\xaa\x2a\x36\xff\xef\x5b\x87\ \xbb\x56\x91\x33\xb7\x34\x4d\xf4\x0f\xf4\xc5\x4b\x3c\xbd\xdd\x19\ \xce\x2e\x8e\x9e\xdc\x47\x45\xc3\xb3\xb3\xb3\x6f\x32\x3a\x3a\x02\ \x57\x77\x67\x90\x4e\xac\xdf\x15\xf8\xf8\x79\xa1\xbf\xbf\x0f\x62\ \x89\x88\x21\x57\x4a\x51\x56\x51\xfc\x29\x62\x19\x3f\x95\x09\xe8\ \x30\x27\x26\x26\x50\x5a\x5a\xca\xa0\x3f\xbf\x52\x34\xc7\x04\xce\ \xae\x8e\xcf\x46\x70\x2f\xd0\x6a\xaf\xdf\xe5\x99\xc0\xde\xd1\xf6\ \xcd\x11\xd0\xe0\xf4\xf4\xe4\x4b\xde\x14\xfc\x6e\x04\xd6\xb6\x96\ \x5f\x1a\xc1\xfa\xfa\xda\x9b\x02\x7a\x0e\xd8\x08\x9c\x63\xe4\xf8\ \xe7\x5e\xfc\x2d\x0c\x7d\x55\xa0\x02\x36\x02\xfa\x61\x74\xfd\x0a\ \xbd\x8b\xe7\x5f\x82\x66\x69\x9b\x87\x11\xd0\x97\xda\xb1\x23\xe4\ \x35\xac\x7f\x4a\x76\xed\x2a\x4a\x07\xf7\x69\x9b\xe7\x23\xa8\x21\ \x82\xdc\xfa\x35\x1a\x62\xf7\x9c\xba\x35\x64\x91\x70\x7a\xf5\x0a\ \x92\x2b\x96\x11\x57\xba\x88\x88\xc2\x79\x84\x68\xe6\xa0\xec\xda\ \x79\x3e\x02\xab\x08\x05\x14\xe4\x63\x90\x7a\x16\x41\xaa\x39\x04\ \xa8\x66\x11\xa0\x9c\x85\x3f\xc1\x4f\x31\x0b\x5f\xc5\x0c\x7c\xe4\ \xb3\xf0\x96\xcd\xc0\x8b\x20\x6c\xdd\x7a\x14\x54\x54\x55\x20\x9b\ \xf4\x46\x43\xbe\x72\x12\x24\xb0\xa0\x74\x06\x1e\xd2\x69\xb8\x93\ \xbb\xbb\x64\x1a\x6e\xe2\x69\xb8\x8a\xa6\x21\xef\xdc\x85\x69\xb0\ \x18\xc6\xf6\xd1\x1a\x4e\xc7\x29\xbb\x9f\x9a\xbe\x8b\xae\x53\xc6\ \x38\xf7\x17\x75\x03\xca\xe9\x8a\x63\x73\x6a\xb8\xd3\x00\x00\x00\ \x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\xe6\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\xad\x49\x44\x41\x54\x78\x01\x75\x52\x03\x90\x20\x31\ \x10\x7c\xdb\xb6\x6d\xa3\xf4\xb6\x6d\xdb\x3a\xdb\xb6\x7d\xf7\xb6\ \xcd\xb3\x6d\xdb\xb6\x77\xb7\x3f\xc9\x59\xa9\x8a\xd3\x3d\xd3\x9d\ \xe9\xd4\xba\x69\xdd\xee\xbd\x92\x74\x73\xd2\x23\x49\xaf\xaa\xeb\ \x6c\x6d\xae\xb2\x67\xc5\x36\x91\x01\xa6\xe3\x3a\x75\xd4\xe8\x23\ \x1b\x85\x71\xc1\x35\x09\x52\x8e\x28\x0f\x2e\x06\x5f\x21\xd0\x4e\ \xd6\xa5\x55\x71\x12\x9e\x5a\x5b\x8f\x67\x28\x2c\xbe\xe4\xdf\x11\ \xf8\x7b\x9a\xd7\xc5\xe7\xe0\x4a\x84\xf2\x68\x11\xe4\x07\xdf\x44\ \x6e\xe0\x0d\x24\xfe\x10\x45\x9e\xa7\x0c\x2a\x93\x3f\x42\x6f\xc9\ \x53\xa8\x4c\x36\xae\xd0\xb8\x34\xec\xb7\x78\x7f\xb3\x71\x92\x03\ \x2c\x67\x34\x46\x26\xe0\x17\x28\xfc\xce\xc7\x3b\x9f\x44\x9c\xd3\ \x09\x24\xbb\x9f\x45\xa6\xef\x65\xf8\x5a\x48\x40\x61\xb4\x39\x94\ \xc7\x5b\x42\x66\x48\x5d\x57\x18\x63\xce\x49\x0f\x31\xe3\x24\x07\ \x9a\xef\x63\x9a\x69\xda\x34\x72\x41\xc8\x6d\xa4\x78\x9c\x47\xe8\ \xf7\xfd\x08\xf8\xb4\x0b\x81\x9f\x77\x23\xec\xc7\x41\x44\xbc\xbb\ \x03\xdd\x85\xc6\x90\x1e\x6c\xc9\xba\xf6\xfc\x27\x30\xb9\xbc\x3c\ \x9e\x62\x59\x74\xa2\x2f\x04\x99\xc6\x28\x0a\xbb\x8b\x54\xcf\xf3\ \x48\x72\x3b\x0b\x92\x11\xdb\x57\xc6\x8a\x01\xe9\xda\x78\x7a\x42\ \x9d\x80\xad\x18\xc1\xcb\x33\x76\xc8\xf2\xbb\x9a\x4e\xb1\x94\x20\ \x12\xe5\xe1\x55\x48\x55\x46\x45\x8c\x18\x12\x5d\x4f\x33\xed\x5e\ \x6f\xb6\xc2\x50\x74\x30\xed\x42\xd4\xa7\x9b\x30\x5a\x69\x8d\x2f\ \x22\xd2\x70\x51\x57\x86\xfe\x12\x13\x64\xbb\x1a\xd4\x50\x2c\x25\ \xa8\x62\x4e\x67\x5b\x43\x48\x91\x63\xd1\xb9\x24\x19\x06\xb4\x92\ \x1b\x3d\x5f\xef\xfe\x80\xa1\x7a\x17\xe7\xf0\x09\x8e\x21\x44\xd2\ \x1e\x14\x87\xdf\x45\xd4\x2b\x39\xa4\x3b\x3a\x08\x14\xdb\x40\x00\ \x14\x7c\x06\x91\xc1\x52\x47\x8e\x3d\x8c\xc5\x87\x70\xe4\x6e\x20\ \xed\x84\xac\x12\xd5\x19\xf0\x79\xb7\x9d\xfd\x4e\x49\xc4\x7d\x20\ \xcb\x1c\xe4\xae\xba\x4e\x42\x45\x74\x2d\xca\xfc\x81\xbc\xd7\x28\ \x8d\xbc\x0f\x2a\x27\xc3\xe7\xb2\x60\x20\x32\xb0\x92\x64\x90\x47\ \x64\xf1\xd5\xf1\x12\x08\xfa\xb2\x87\x6a\x07\x35\x9b\xec\x69\x80\ \x28\x66\x22\x49\x39\x07\xb5\x05\x60\x24\xf9\xef\x41\xeb\x00\x25\ \xee\x40\x4d\x0e\xeb\xb5\x89\x52\xf8\x69\xbb\x1a\xd1\x7f\x8f\x32\ \x93\x49\x06\xf4\xb7\x4a\x1b\x4c\x5c\x69\xaf\x3c\x21\x01\x42\x0d\ \x18\x49\x65\x02\x50\xf4\x1b\xa4\x1a\x11\xf1\xeb\x10\x7c\xdf\x6f\ \xa7\xdf\xc9\xc0\xb1\x8e\xc7\x59\x7d\x50\xbf\xac\xe5\xc7\xe4\x52\ \x6c\x63\x21\x11\xe6\x34\xe6\x45\x1d\x09\xf3\x84\x4f\x96\x41\x43\ \x55\xa6\x7b\x5f\x44\xd4\x9f\x23\xcc\x60\x42\x5a\xa4\x79\xab\xb7\ \x45\x9b\x52\x4e\x70\x39\x55\x48\x0a\x8a\x91\x90\xaa\x04\x32\xf4\ \x59\xfa\x65\x51\x22\x8c\x88\x1a\xe7\xfe\x72\x53\x35\x7d\x5b\x0f\ \xeb\xda\x9a\xc4\xdc\x4c\x6a\x44\x5e\x61\xe8\xed\x5a\x9a\x26\xf3\ \xa4\xd8\x19\x48\x53\xa7\xdf\xcb\x59\xc8\x8c\xac\xd3\xdd\x41\xeb\ \x4c\x7a\x77\x89\xd3\x9d\xd6\xaa\x5e\xeb\x66\xa7\x71\xb3\x67\x22\ \x79\xcc\x69\xdf\xe9\xc3\x93\x74\x53\xc8\x99\x3d\xd3\xdc\xaa\xfd\ \x07\xa8\x3e\x56\x6e\xe7\xcf\x2d\x29\x00\x00\x00\x00\x49\x45\x4e\ \x44\xae\x42\x60\x82\ \x00\x00\x02\x9c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\x63\x49\x44\x41\x54\x78\x01\x75\x53\x03\xcc\x1e\x51\ \x10\xac\x6d\x3b\x76\x1a\xa7\x88\x6a\xdb\xb6\x6d\xdb\xb6\x6d\x9b\ \xbf\x6d\xdb\xb6\x6d\x7c\x37\xdd\xd9\x5a\x97\xdc\xe3\xce\xec\x2c\ \x5e\x0d\x00\xbf\xfd\xc7\x57\x37\xec\x29\xff\x15\xf9\xc3\xe4\x2f\ \xe7\xcf\x35\xcf\x78\xf7\xa7\xfd\x9f\xe0\x2b\x37\xf7\x76\x09\xa8\ \x8c\xdd\x6e\x87\x92\x80\x02\x98\x4a\x0d\xfe\xb2\x2e\x2a\x8f\xde\ \xea\xc6\x3b\xda\xfc\x8b\x80\x60\xb3\x64\xf7\x85\x4f\x50\x5d\x68\ \x94\x44\x6c\x44\x4e\xc0\x4a\x64\xf9\xad\x40\x7e\xf0\x5a\x54\xc5\ \x6d\x07\x72\x3f\x80\x64\x89\xae\xf3\xcd\x68\xfb\x1b\x01\x59\x05\ \xfc\x14\x79\x66\xa6\x18\x87\x99\x88\xb6\x9f\x81\x04\x97\xb9\x48\ \xf3\x5a\x8c\x6c\xff\x95\x10\xef\x40\xda\x05\x20\xfb\x09\x50\x16\ \xcb\x3b\x8f\xef\x4a\x34\x66\x4a\xa3\xe7\xdc\xc0\xd5\x10\x0f\x08\ \x32\x1b\x0f\xdf\xf7\xa3\xe0\xf7\x61\x34\x82\xcd\x27\x22\xc3\x67\ \x29\xaa\xe3\x77\x02\x19\x37\x20\x4e\x20\xb6\xb8\xb1\xa7\x53\x08\ \xb1\xea\x5d\x3c\x04\xd2\x03\xe5\x26\xb9\xcd\x47\xbc\xf3\x5c\x88\ \x22\xdd\x97\x45\x6d\x06\x52\x4e\x68\x38\x46\xe2\x6e\x20\xe9\x00\ \x90\xfd\x02\xe9\xde\x4b\x53\x88\x25\x41\x18\x4a\x42\xca\x79\x51\ \x1a\xb9\x19\x71\x4e\xb3\xd5\xd8\xfd\xe5\x50\x9c\xdb\xd4\x92\xbf\ \x91\xea\xb9\x18\xc8\x7a\xa0\xe4\xcc\x8f\xaa\x49\x3d\x53\x41\x2c\ \x09\xca\x99\x1c\xca\xa3\x07\x7a\xa7\x01\x81\xd7\x77\x77\xec\x7e\ \x7a\x7d\xb3\xd6\x17\xb6\xb4\x32\xa1\x22\x15\xfe\x1f\xc7\xa0\x20\ \x64\x2d\x1d\x91\xd0\x20\xf6\x3b\x81\x66\x99\x61\x50\x3a\x32\xef\ \x40\x40\xd5\x72\xd7\x9c\xbf\x90\x95\x91\xc0\xf3\xf5\x70\xad\x4e\ \x61\xe8\x7a\x20\xfd\x0a\xe4\xae\x42\x43\x40\x69\x44\x15\x8a\x7d\ \x34\xb6\xa2\xb0\xf5\x1a\xa7\xc8\x36\xce\x6e\x6c\x5e\x26\x0a\xb2\ \x25\x2c\x53\x45\xcc\x56\x2a\x60\xec\x60\xb2\x65\x4f\x07\xe1\x9a\ \x44\x91\x9c\x89\xaa\x5c\x28\x49\xce\x1b\x8d\x13\x85\x2e\x40\x65\ \xa6\xfe\xec\x03\x8b\x5b\xbd\x11\x61\x33\x95\x79\xa0\x02\x56\xab\ \x88\x58\x2d\xe3\x9d\x03\xdd\x62\x61\x54\x82\x24\xac\x33\xf2\xad\ \x20\xdd\x88\x50\xcb\x49\xf0\x7a\x33\x5c\xcb\x49\x70\x94\xdd\x74\ \xf6\x00\xcb\xc9\x32\x66\x11\xfb\xa3\x91\x84\x39\x99\xb9\xf8\x4e\ \xc2\x9c\x98\x12\x76\xe2\x7b\x57\xa6\x78\x2c\x44\xb8\xf5\x14\x26\ \x98\xa4\xf9\xc7\x56\x35\xbc\xfa\x57\x2b\xc7\x3a\xce\xca\x63\x93\ \x28\x49\x9e\x19\x4b\xa5\xf2\x8b\xc3\x37\x2a\x11\x13\xe7\xf2\x6c\ \x50\xc5\xf7\x56\x96\xaf\xf6\x5f\x8f\xe9\xf2\xf6\x76\xd9\x79\x41\ \xab\xab\x28\x53\x73\x52\xe0\x00\x24\x1f\x61\x79\xab\xaf\xee\x6c\ \x5f\xf4\xdf\xc7\x24\x5f\x4d\xf9\xeb\x6e\x9d\x5d\xa3\xef\xa1\x65\ \x75\x6e\x1f\x5d\x59\x3f\x4e\x8c\xab\x4f\xac\x69\x64\x12\xb9\x89\ \x72\x76\xe7\x5f\xcf\xf9\x0b\x85\x5c\xd5\xd5\xa0\x61\xa0\x02\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\xd3\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\x9a\x49\x44\x41\x54\x38\x4f\xa5\x92\x6b\x48\x93\x51\ \x18\xc7\x9f\xf3\xee\xdd\xe6\x74\xed\xa2\xe8\x1c\x65\x5e\xdb\xa6\ \xce\x25\xee\x4d\x03\x9b\x97\xc9\x2a\x4d\xa8\x3e\x4c\x84\x75\x81\ \x3e\x48\xf5\x21\x88\x3e\x74\xf9\x14\x4a\x86\xf5\x25\xa8\x2f\xa1\ \x5d\x08\x8c\xda\x2b\x83\x32\x08\xed\x43\x23\xa3\x06\xe5\x85\x2e\ \x0c\xb1\x39\x24\x2f\x53\xd3\x41\x4a\xee\x72\xde\x73\xe2\x55\x2b\ \x92\x4a\xa1\x07\x9e\x73\x38\x70\xfe\x3f\x9e\xff\xf9\x1f\x04\xff\ \x59\x68\x45\x6f\x95\x16\x16\x33\xc5\xc0\x52\x19\xc1\x78\xc6\x3f\ \x34\x34\xb2\x51\x2e\xca\xca\xca\x4a\x50\xa5\xa4\xb6\x51\x00\x3d\ \x20\x14\x92\x30\xa8\x52\xa3\x51\x29\xb6\xea\xd3\xe6\x08\x25\x53\ \xdf\x16\xa3\xa7\x3c\x1e\xcf\xf8\xdf\x80\xc8\xc8\x71\x46\x19\x85\ \x26\x12\x59\x3a\x6f\x36\x9b\x73\xf3\x0c\x39\x0f\xc7\x43\xb3\xe6\ \x1a\x7b\x05\xd1\xa5\xa5\x0a\xbe\x57\xaf\xc3\x53\x53\xa1\xeb\x73\ \xb3\xe1\xfb\x3c\xcf\x07\xd7\x82\x50\x11\xc7\xed\x07\x82\xb4\xef\ \x07\xde\xdc\x3d\x7e\xb2\xe9\x72\x5d\x7d\xed\x19\x9f\xef\x2d\xab\ \xd6\xaa\x05\x47\x4d\x25\x06\x40\x74\x26\x34\x4b\xfb\x5e\xf6\xbd\ \x0b\x7c\x1a\x75\x0b\x71\xda\xce\xf3\xfc\xe2\x0f\x10\x2a\xe2\xca\ \xea\x28\x40\x7a\xee\xe6\xf4\x67\x25\x56\xcb\x08\x57\x56\x2a\xbf\ \x72\xf5\x1a\xb5\xed\xda\x49\x6a\xeb\x1c\x31\x04\x08\x4b\x58\x96\ \xa6\xa7\xea\xa5\xfd\xfd\x03\xb4\xb7\xa7\x67\x30\x3c\xff\xf5\xd8\ \xf4\xf4\xf4\xa8\xd7\xeb\xc5\x28\xdf\x6a\xd5\x4b\x81\x39\x97\xac\ \x55\xe5\x1c\x3c\x50\xbf\xd7\x66\x2b\x67\xbb\xbb\x9f\x10\xa3\xd1\ \x80\x33\xb3\x33\x31\x11\x04\x01\x0b\x18\x8b\xab\x46\xad\x95\x50\ \x8a\xd8\xc1\xfe\x21\xea\xf7\xfb\xef\x04\x03\x63\x6d\xcb\x29\x58\ \x2c\x96\x34\x89\x3c\x29\xbf\x6c\x47\x51\xeb\xa1\xc3\xae\xd2\x4d\ \x2a\x25\xc4\xe2\xd1\x65\x1d\x26\xa2\x3e\x2e\xe0\x65\x90\x80\x19\ \x86\xa1\xa6\xdc\x02\xf5\xe3\x47\xdd\x33\xde\xe7\x2f\x6a\x57\x63\ \x5c\x71\xd4\xe0\x6a\x28\x37\x6d\x33\xdc\x74\xec\xd9\x6d\x50\xab\ \x94\x24\x12\x8b\x60\xb1\x89\x80\xc5\x12\x04\x82\x71\x52\xa2\x92\ \xd1\x25\x6f\xd1\xb4\xb4\x34\xdf\xf8\xf8\xc1\x7f\xe1\x37\x00\x00\ \x20\x97\xcb\x69\x96\xca\x15\x8d\x1c\x57\x72\xb4\xc4\xca\xa5\xa8\ \xd4\x4a\x3c\x1f\xfe\x12\x8b\xc6\x22\x71\x84\x18\x92\x9d\x91\xa7\ \x6c\xbf\xd5\xb1\x10\x18\x0e\xda\x79\x9e\x1f\x5e\x0b\x58\x19\xe5\ \x22\x30\xae\x11\xa7\x49\x96\xa0\xb8\x6d\xab\xac\x28\xb4\x57\x55\ \x4b\x26\x42\x9f\x17\x65\x52\x39\x0a\x06\xc6\x12\xdc\x0f\xdc\xa7\ \xbb\xba\x3c\x1d\xe2\xd5\x3f\x03\x7e\x85\xcd\x34\x36\x3a\x8f\x98\ \x0a\xf2\x9b\xaa\xab\xab\xb6\x6b\x34\x5a\xe4\xe6\x79\xdf\xa5\xe6\ \x56\xfb\xcf\x18\x37\xf0\x65\x91\xd3\xe9\xcc\x48\x54\xca\xf7\xe9\ \x74\xfa\x13\x13\x93\x93\x67\x3b\xef\x75\x3e\x5d\x0f\x20\x4e\x96\ \x04\x00\x89\x00\xa0\x00\x00\x16\x00\x28\x00\x60\x00\x58\x02\x80\ \x05\x00\x88\xfc\xcb\x82\x08\x90\x01\x80\x7c\xb5\xa5\x00\x20\x00\ \x40\x14\x00\x62\xab\xbb\x78\x5e\xf7\x0d\xd6\x75\xf8\x1d\x97\x2f\ \x0f\xf3\xe0\xb2\x93\x22\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x02\x40\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\x07\x49\x44\x41\x54\x38\x4f\x95\xd3\xcd\x6b\x13\x41\ \x18\x06\xf0\xe7\x9d\xc9\x66\x2d\x49\x63\x55\x7a\xeb\x4d\xab\x87\ \x80\x60\x57\xa8\xa0\xf4\xe6\x45\xbc\xa6\x50\x3f\x4a\x43\x62\xa2\ \x42\x51\x0f\x7a\xf0\xa0\x69\xd1\xaa\x15\x2f\x15\x84\xd8\xa8\xa5\ \x4d\x3c\xd4\x7f\x40\x10\x84\x2a\x04\x24\xda\xc6\x43\x2e\x5a\xd4\ \x62\x2f\xd6\x8f\x26\xa9\xc9\x76\xbb\xb3\x33\x12\x51\xa9\x92\x2d\ \xeb\x5c\xdf\x79\x7f\xef\x30\xf3\x0c\xc1\xc3\xda\x31\x38\xa6\xcf\ \x6f\xfb\x66\x23\x95\x92\xff\x6e\x27\x0f\xfd\x30\x12\x0f\x0f\x48\ \x32\x57\xe6\xd2\xb1\xd7\xff\x05\x18\x89\xc9\xee\x8a\x5e\x2e\x06\ \xcd\x2d\x3d\x9c\x10\xff\x54\xd6\x07\x16\x1f\xf5\x9a\xeb\x11\xf7\ \x13\x24\xd2\x9a\xe1\x6c\x8a\x10\xb1\xb0\xaa\xd4\x47\x11\x0a\xdc\ \x54\xc4\x9e\xcd\x66\xfa\x72\x00\xa9\xdf\x48\x53\x20\x1c\x49\xf9\ \xf5\xd6\xed\x91\xba\x90\x8f\x83\x1a\x8f\x29\xd0\x7b\x61\x8b\xbc\ \xe6\x67\x23\x65\x93\x06\xe7\x73\xc7\xaa\xae\x40\xf7\x40\xae\xf3\ \x7b\x4d\x5b\xd0\x37\xaf\xf5\x72\x25\x2b\x50\xa2\x04\xee\x8f\x4a\ \xaa\x0d\x13\x5a\x46\x05\x39\x77\x8a\xe9\xe8\x5b\x57\xa0\x2b\x9e\ \xdd\x47\x0c\xbb\x98\xa0\xe7\x0e\x93\x27\xed\x3a\xbb\xe6\x0f\xc8\ \x8b\xa0\xb6\xcb\xe4\x54\x93\xc4\xe4\x8b\xc2\xf8\xf1\xbc\x2b\xb0\ \x3b\x9e\xed\xd0\x89\x92\x42\x58\x63\x8c\xeb\x23\xb6\xe5\x9c\xf5\ \xeb\xec\xca\x8a\xb4\x2e\x05\xb9\x7e\x54\x29\xf6\x66\x36\x73\xe4\ \xa9\x2b\xb0\x37\x3e\x75\x50\x31\x65\x3a\x8a\x59\x5c\xa1\x87\x09\ \x3b\x07\x9f\x76\xe6\xb3\xb0\x87\xda\x35\xed\x9c\x70\xe4\x93\xb9\ \xfb\xfd\x2f\x5d\x81\xf0\xe9\xe9\x60\x49\x2c\x5b\x86\x0a\x9c\x70\ \x40\x79\xa6\x64\x2b\x81\xf6\x83\xd5\x6e\x11\x02\xb7\xc9\xe6\x57\ \x0b\x0f\xfa\x3e\x6e\xf8\x0a\x88\x4c\xf3\x3d\x21\xbb\x43\x7d\x5d\ \xab\xf8\xda\x7d\x59\x87\xd1\x85\x55\x21\x59\x80\xe8\xfc\x17\x21\ \x92\x1f\x26\xa2\xab\x1b\x03\xbf\xaa\x46\x7c\xea\x14\x49\x2c\x55\ \x4d\x3e\x13\x0a\xca\x49\x82\xbc\x51\x18\xef\x9f\xf1\x16\x24\x00\ \xe1\x58\x66\x6b\xe9\x5e\x6c\xd9\x88\x65\x0f\x31\x42\x97\x64\xf5\ \xeb\xaf\xee\x26\x6d\x2f\x80\x0f\x80\x06\xe0\x67\xd0\x76\x1e\x1e\ \xee\x5c\x5a\x7c\xb7\x50\x2e\x4e\x54\x00\xfc\x49\x61\xa3\xd6\x2c\ \x89\x8d\x46\x1d\x40\x0b\x00\xbe\x6e\x5a\x63\x72\xe3\x1f\x58\x00\ \x1c\x2f\x77\xd0\x0c\xff\x6b\x7a\x03\xf9\x01\x8c\xc6\xcb\x11\xea\ \xaa\xe6\xbb\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x1f\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\xe6\x49\x44\x41\x54\x78\x01\x63\xc0\x07\x14\xa2\xb6\ \x70\x88\xc6\x1e\xb1\x64\x0b\x39\x90\xc5\x1e\x7e\x78\xa2\x78\xca\ \xf1\x93\x72\xb9\x67\xbe\xf1\x07\x6e\x4f\xc6\xa9\x89\x29\xf8\x90\ \x9a\x40\xfc\xd1\x85\xd2\x69\x27\xef\x48\x64\x9d\xf9\x2d\x53\x70\ \xfe\x8f\x56\xfd\xd5\xff\x26\xdd\xb7\xff\xdb\x4c\xbd\xff\x5f\xb7\ \xe9\xda\x7f\x86\x90\xc3\x0d\x38\x0d\x90\x72\x9f\x9d\xab\x59\x7b\ \xf5\xbf\x11\x50\x83\x35\x50\x83\xfb\x82\xc7\xff\x03\x57\x3f\xff\ \xef\xb5\xec\xd9\x7f\xe5\xa6\x1b\xff\xd9\x32\xce\xe1\x37\x40\xd1\ \x67\x76\x49\xe8\xa6\xd7\xff\xfd\xd6\xbc\xf8\x6f\xb7\xe4\xd9\x7f\ \xb5\x59\x2f\xfe\xf3\xf7\xbe\xfe\xcf\x50\xfd\xf0\x3f\x43\xf1\xcd\ \xff\xdc\x25\x17\xff\xe0\x33\x80\x51\x25\x68\x51\x89\xdf\xfa\xd7\ \xff\xd9\x72\xae\xfe\x61\xc8\xbc\xf1\x8b\x29\x72\xff\x45\x26\xff\ \x2d\x93\x18\x3d\x57\xa4\xb2\xdb\x74\x68\x6a\x97\xec\xc7\xef\x02\ \xb5\xd0\x45\x25\x06\x5d\x37\xff\x33\x39\xcf\x75\x64\x50\x4f\xe1\ \x40\x97\xd7\xaf\x3a\x44\xc0\x80\x90\x85\x60\x03\x70\xc9\xeb\xd7\ \x1c\xa6\xd0\x80\xda\x23\x04\x0c\x08\xa6\xd0\x00\x55\x8a\x0d\x08\ \x9a\x8f\x61\x80\x79\xd9\x09\x76\x8e\xa8\xc3\x0d\x5c\xb1\x47\x37\ \x6a\xd4\x9f\xf9\x8b\xdd\x80\xa0\x43\x9c\x2c\xe1\x87\xd3\x05\x42\ \xb7\x6c\x42\x37\x40\x34\xed\xf8\xb6\xf4\xed\x6f\xff\xc7\xef\xff\ \xf0\x5f\x73\xca\x43\x2c\x2e\x88\x3a\xc4\xa2\x5c\x78\xf6\x6c\xda\ \xa6\x17\x7f\x8d\x27\xde\xfe\x8b\x6c\x40\xf4\xc4\x6d\x2c\xb2\x05\ \xd7\xee\x4a\xcd\xfd\xfe\x9f\x75\xf6\xf7\xff\x4c\x8d\xf7\xff\x4b\ \x26\x1d\x34\x44\xd1\xcf\xe2\xb7\x43\x5b\xa7\xeb\xc9\x7f\x95\xc5\ \x3f\xfe\x73\x4c\xfa\xf8\x9f\x25\xe7\xfc\x53\x64\x79\x3e\x8f\x59\ \x05\x0c\x75\xd7\x7f\x32\xb4\x5e\xfe\xc9\xee\xd0\xd7\x1c\xba\x6a\ \x15\x23\x4a\xea\x13\x54\x75\x15\x63\x49\xdc\x77\x9d\x61\xe9\xd7\ \xff\x8c\x53\x1f\xfc\xe2\xf5\xeb\xad\x00\x8a\xf3\x02\x31\x3b\x10\ \xb3\x02\x31\x0f\x03\xb3\x80\x11\x03\x13\x97\x11\x54\x9c\x0d\x94\ \xef\xe0\x0e\x00\x62\x53\x06\x46\xe6\x22\x06\x3e\x8d\x7e\x06\x16\ \xfe\x1a\x20\xdf\x17\x88\x41\xce\x94\x02\x62\x11\x20\x96\x04\xe5\ \x6e\x28\x96\x06\x62\x21\xa8\xc1\x8c\x00\x6e\x47\xe8\x46\x78\x5a\ \x35\x43\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\xd6\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\x9d\x49\x44\x41\x54\x78\x01\x6d\x52\x43\x80\x24\x5b\ \x10\xfc\xb6\xcd\xcb\x1a\xc7\xb5\x6d\xdb\xd6\x70\x6d\xdb\xb6\xdd\ \x1a\xdb\xb6\x6d\xdb\x9e\xa9\x76\x57\x6e\xbc\x6a\x0c\x0f\xd1\x48\ \x46\xc4\xcb\x8f\x88\xc8\x84\xa1\xdb\xde\xff\x3f\xda\x5c\xf2\x6a\ \xb4\x85\xa4\x60\xf8\x0e\x91\x7c\x94\xb9\xa4\x1c\xbf\xfd\x10\x5f\ \x02\x7c\xde\xa5\xf6\x08\xf0\x93\x29\x30\x6c\xdb\xfb\x85\x23\xcd\ \xc4\x72\xb3\xdb\x41\xea\x5b\x4e\xa9\x24\x0d\x29\x24\x69\x70\x01\ \xdd\x73\x4e\xa3\x0d\x97\xbc\xdb\x46\xec\x10\x35\xa1\x61\xa5\x50\ \xbb\xfd\xfd\x76\xfc\x26\xe0\x47\xe3\xb4\x81\xa3\xcc\xc5\xdc\x75\ \xfb\x54\x7a\xe1\x9f\x47\x6f\x02\xf2\xc9\x2d\xb6\x9c\x02\x53\xab\ \x29\x2e\xbf\x81\xd2\x4b\x9a\x28\x30\xa5\x92\xe6\x1c\x72\xe4\xc0\ \x2c\x68\xca\x1e\x5b\x05\x86\x68\xd1\xf7\x83\x30\x00\x54\x9f\x99\ \xdf\x0e\xd2\xed\x7d\x1c\x4e\x63\x2d\xa5\x84\x24\x81\xba\x76\xdd\ \x05\xcf\xd6\x37\xbe\xd9\x7c\x56\x59\x33\xe5\x56\xb4\x50\x7e\x55\ \x2b\x5d\x96\x24\xe8\xa2\xb2\x6a\x58\x5e\x85\x01\xdf\x0b\x03\x40\ \xbd\x75\xd2\x6e\x5b\x1a\x6b\x25\xe3\x91\xe0\x90\x38\x00\xfc\x0d\ \x4c\x1b\x69\x2e\x8e\xdf\x74\xd9\x9b\x8b\xcf\xad\xa5\x92\x5a\x8e\ \x2a\x1a\xe4\x02\x50\xa7\x46\xfe\x5b\xa3\x7e\xd6\xa8\x31\xbb\x13\ \xf9\x02\x5a\x2b\x90\xe8\xdd\xc5\xb0\x8d\x8c\x55\x64\x66\x35\x45\ \x64\xd5\x52\x69\x1d\x87\x01\x62\x36\xe0\x1b\x63\x81\x76\xee\x11\ \xd7\x93\x07\x5f\x24\x0e\x5e\x71\xce\xf7\x57\xfc\xff\xa4\x43\xf3\ \x67\x60\x18\x76\xcb\x2e\x49\x5d\x89\xcd\x90\x62\x60\x20\xd6\x20\ \xf7\xb5\xb1\xe8\xfb\x8e\x1b\xbb\x6c\x7f\x0d\xd0\x8c\xfd\xf6\xca\ \xb9\x87\x1c\x15\x02\x0e\x3b\x2a\x0c\xaf\xf0\x95\xb1\x88\x19\xc2\ \x1b\xa0\x03\xb4\x00\xdb\xe0\x0d\xfc\x0f\xfc\xd5\x03\xfe\x63\xbd\ \x26\x09\xe5\xf5\x1c\x95\xd7\xcb\xa9\x0c\xfa\x9e\x7b\x66\xea\x46\ \x98\x89\x8b\xd8\xa1\x74\x65\x74\xf2\x7d\xfa\x3f\x7b\x9f\xc4\x8f\ \x32\x2c\xf9\x4e\x08\xb2\x37\x65\xba\xc2\x32\x6b\x29\x3a\xbb\x86\ \x0c\x4c\x2c\xba\x48\xf9\x76\xd8\x76\x51\xb5\xd5\x83\xe8\x1b\x4b\ \x4e\x79\x86\x18\x24\x7c\x2e\x24\xe1\xbc\x8e\x0d\x60\xee\x96\x81\ \x49\x70\x4a\x05\xcd\x3b\xe2\xc4\xe1\x09\x63\x50\x34\xdc\x30\xc0\ \x62\xac\xa5\xac\x7e\xc1\x71\x0f\xf6\x84\xfc\x08\x33\x91\xda\x24\ \x01\x2e\xeb\xd8\xb1\x3c\x70\x49\xe3\x8b\xaa\xdb\x84\x83\x89\xcb\ \xab\xa7\x5b\xf6\x29\xfc\xdc\xc3\x4e\x1c\x36\x6b\x70\xa9\x04\xf0\ \xe3\xac\x65\x84\x18\x1b\x22\x65\xbd\x46\x06\xfc\x9a\xf3\x9e\x0a\ \x9c\x69\xda\xfc\xa3\xce\x6d\x6e\x31\xa5\x14\x9d\x53\x47\x7e\xc9\ \x95\xe4\x14\x5d\x4a\xaf\x03\x0a\xe8\xb2\x6d\x0a\x9d\x15\x25\xd0\ \x4d\x87\x54\xc2\x42\x76\x6c\x83\x3a\x9a\x48\x68\xbe\x67\xf8\xbd\ \x74\xc4\x0e\x71\xc3\xb2\xd3\xee\xad\xe7\xc5\x09\xf4\xc8\x23\x8b\ \x9e\x78\xe7\xd2\x55\xbb\x54\xb2\xbe\x1f\xa2\x45\xb3\x1c\x9e\xad\ \x61\xb5\x1d\x07\xbc\x05\x3e\xee\x60\xd8\xe7\xc0\x12\xd0\x0c\x80\ \x0f\xd5\x68\x50\xe3\x77\xd9\x18\x4b\x89\x0d\xe2\x7d\x3a\x9a\xfb\ \x01\x41\x5a\x86\x0e\xb8\x53\x4c\x49\x00\x00\x00\x00\x49\x45\x4e\ \x44\xae\x42\x60\x82\ \x00\x00\x01\xa3\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\x6a\x49\x44\x41\x54\x78\xda\x95\xd2\x3f\x4b\xc3\x40\ \x18\x06\xf0\xe7\x92\xd6\xfe\x11\x3a\x48\x77\x47\x41\xb0\x08\x0e\ \x7e\x03\xa5\x28\x55\x11\x05\x8b\xa8\x83\x93\xe0\xa8\xb8\xba\xb9\ \x0b\x2a\x38\xb8\x14\x04\x45\xc1\x45\x67\xfd\x08\xe2\x50\xa1\x0e\ \x8a\x5a\xaa\x31\xb1\x96\x4b\x93\x34\xc9\xf9\x36\x81\x0a\x9a\x82\ \x77\xf0\x72\x21\xdc\xfd\xee\xcd\x73\x61\x88\x18\xb9\x9d\x8c\xf8\ \xfd\xee\x76\xeb\x8b\x45\xad\x65\xdd\x80\xcd\x95\x6d\x58\xbe\x89\ \x26\xd5\x61\x69\x57\x1e\x58\x5f\xda\x80\xf1\xe1\x02\x99\x4f\x94\ \x8e\x8f\xe4\x81\xd5\xc5\x35\x68\xaf\x1e\x94\xac\x8e\xf3\x93\x53\ \x79\xa0\xb8\xb0\x4c\x80\x0b\x35\x6b\xe0\xea\xec\xb2\x3b\x10\x15\ \x58\x7b\x4c\xcf\xcf\x42\xab\x12\xd0\x67\xe0\xfa\xe2\x26\x6a\x49\ \x80\xb2\x89\x83\x61\x31\x37\x55\xa4\xb0\x78\x18\x9a\xc7\x83\x6a\ \x70\x1b\xf5\xba\x03\x3f\x59\xc7\x93\x73\x07\xdd\xd4\x80\x16\x35\ \x61\x53\x59\x0a\xc4\x63\x22\x04\xf2\xfb\x43\xa2\x50\x98\x81\xae\ \xb5\x82\x8d\x61\xf2\x1c\xdc\xb2\x60\x09\x8e\x07\x5e\x46\x43\xd4\ \x82\x8f\x55\xda\x2d\xd3\xac\x56\xe9\xe9\x2d\x1e\x02\x63\x7b\x83\ \x62\x7c\x32\x1f\x6c\x0a\x00\x8f\x00\xd7\xa4\x0e\x2c\x94\xdf\x2b\ \x30\x59\x0d\x31\xe1\xff\x9c\xdc\xa4\xcd\xbc\x5d\x6a\x08\x44\x65\ \x90\x8c\xa7\x91\x1e\xf5\xf1\xa2\x37\x90\x8e\xb5\x20\x2a\x09\xc0\ \x27\x80\x9c\x60\xf6\x68\x16\x2c\x04\xba\xdd\x02\x1b\xa1\x8e\x9c\ \x1e\xa4\x1c\x17\xe2\x3e\x25\x7f\x8d\x2c\x67\xc2\x56\x62\x48\x18\ \xa2\x13\x98\x1c\x30\xd0\x84\xd3\xab\x22\xfe\xcc\x3a\x81\xc9\x01\ \xfd\xf6\x9f\xc0\xa4\x80\xa8\x9f\xe6\xdf\x80\xcc\xf8\x06\x7b\xec\ \xd8\x11\x17\x66\x22\x86\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x02\x24\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\xeb\x49\x44\x41\x54\x78\x01\xa5\x93\x03\xb6\x1c\x61\ \x14\x84\xe7\x68\x6c\x33\xb6\x6d\xdb\xb6\x77\xf5\x36\x90\x0d\xc4\ \x9b\x88\x07\x6d\x3e\xbf\x15\xcc\x61\xa5\xee\x74\xc7\x4e\xee\x39\ \xed\xfe\x6e\x55\xfd\x88\x7c\x5b\x0f\x23\x91\xed\x3c\xc6\x78\x28\ \x3c\x86\xe1\xa1\x84\xef\xb6\x47\x7e\x55\xf2\xd3\xb3\x4c\x06\xf6\ \xae\x5d\x98\xba\x78\x11\xb3\x77\xee\x60\xe6\xf6\x6d\x4c\x9c\x39\ \x03\x63\xcb\x16\x3c\x4b\xa5\x20\xff\xfc\x0c\x7e\x31\x58\xbb\x16\ \x73\x0f\x1e\x60\x62\xfd\x7a\x78\xad\x16\xbc\x7a\x1d\x6e\xad\x06\ \xbb\x5e\x83\xbb\x62\x25\x26\x2e\x5d\x42\x77\xd9\x32\xc8\xbf\xdf\ \x29\x0b\x3c\xcb\x1f\xfc\x76\x1b\x7e\xa3\x11\xc2\x55\x38\x95\x0a\ \xec\x72\x19\x56\xa9\x0c\xb3\x52\x86\x77\xe8\x10\xde\x2d\x5e\x0c\ \x61\x3e\x65\x16\xdb\x73\xf7\xef\x8f\x60\xaf\x41\x90\x8a\x8e\x28\ \x57\xab\x23\xd8\x24\x6c\x94\x8a\xd0\x0b\x05\x68\xc5\x22\xbc\x93\ \x27\xf1\x34\x99\x84\xb0\x23\x75\x7b\xe7\xce\xc0\xb6\x28\xd7\xea\ \x70\xaa\x84\xa9\x6c\x89\x72\xb9\x04\xa3\xc8\x83\xb0\x9e\xcf\x43\ \xcb\xe5\xa0\xcd\x9f\x8f\xc1\xca\x55\x81\x0b\x19\xe1\xa9\x0b\xe7\ \xe1\x35\x9b\x70\x09\xff\xae\xd4\x6c\x06\x83\x6c\x0e\xc6\xce\x5d\ \x10\x56\x1a\x0c\x67\x6e\xdd\x0e\x6c\xd3\xf2\xef\x6a\x90\x4e\xa3\ \xcf\xd9\x30\xf7\xef\x87\xb0\x41\x83\xeb\x37\x98\xb9\x2a\xb6\x7f\ \xdf\x80\x70\x8f\xf9\x8d\x7d\x7b\x3f\x35\x50\x26\x38\x28\x36\x07\ \x4d\xf2\x9a\x25\xc9\x5c\xfc\x94\x57\xcd\x65\xa1\x64\x32\xa2\xfc\ \x09\xee\xf2\xaa\x6f\xde\xfc\x29\xc2\x98\xb1\x69\x13\x9c\x65\xcb\ \x09\x17\x09\x17\xbe\x86\xb3\x01\xdc\x4f\x86\x70\x22\x8e\x3e\xc5\ \xba\x1f\xa7\x52\xa6\x42\xa6\x64\xe2\xec\x39\x18\x74\xf0\x09\xce\ \x8a\x72\x3a\xcc\x9c\xfc\x04\xbf\x4f\x26\xa0\x6d\xdf\x8e\xc7\xb1\ \x18\x84\xfd\xb4\x90\xde\x2f\x59\x02\x6f\xdf\x3e\x68\x85\x42\x08\ \x67\xc4\x72\x60\x3b\x91\x40\x37\xce\x83\xb0\xba\x6e\x1d\x5e\x71\ \xc6\x84\xf9\x6e\x29\xbf\x5b\xb4\x08\xee\xd1\xa3\xd0\x3a\x1d\x0c\ \x32\x59\x51\x0e\x60\x1e\x3d\x0e\xb2\x4e\xe5\x10\x7e\xf1\xd3\xcd\ \xf4\x84\x3f\x0f\x96\x2f\x87\xb1\x6d\x1b\xcc\xbd\x7b\x61\xec\xde\ \x0d\x75\xc3\x06\xf4\xd8\x9c\xb6\xbf\x57\xfe\xdf\xed\xfc\x01\xe4\ \x4f\xcc\x26\x2a\x72\x89\xa6\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ \x42\x60\x82\ \x00\x00\x01\xd2\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\x99\x49\x44\x41\x54\x78\x01\x95\x93\x33\x94\xdc\x51\ \x18\xc5\x47\x41\x99\xbe\x8b\xd3\x97\xb1\xfb\x3a\xc6\xd8\xe8\x62\ \xab\x0e\xda\x38\x6d\xd6\xb6\x59\xad\x6d\xff\xbd\xd6\xdd\xef\x8d\ \xce\x18\xdf\x39\xf7\xf9\xfe\x9e\x55\xb1\x71\xeb\xd6\x2d\xf5\xdd\ \xbb\x77\xca\x28\x47\xa4\x82\x6d\x6a\x55\xa2\xb0\x5f\x55\xeb\x1d\ \x57\xd5\x3c\xe5\xb0\x5d\xd5\x92\xe1\x26\xbe\x7c\xf9\x88\x6f\xdf\ \x3e\x31\xb1\x32\x6b\xf3\xf7\xd1\x18\x26\xce\x76\x45\x75\xdb\x6f\ \xb6\x5e\x56\xe9\xc8\xac\x54\xbf\x39\x86\xdf\xcf\xce\xc3\xed\xd0\ \xe3\xce\x9d\x3b\x78\xfe\xfc\x09\x5e\xbc\x78\xea\xd7\xb3\x67\x8f\ \xfd\x6d\x4e\xbb\x1e\x3f\x9e\x5c\x44\xc9\xf3\xa3\xb0\x5f\x51\x8b\ \xe4\xd5\x30\xc0\x21\xd7\x75\xcd\x7a\xe3\x87\x93\xf0\xb8\xf4\x90\ \x24\x09\x63\x63\x63\x28\x2b\x2b\x8b\x12\x6b\x93\x65\x19\x4e\x9a\ \xa0\xe1\xfd\x49\x90\x6f\x9b\x74\x30\x0a\x60\x34\xeb\xc1\x42\x14\ \x85\x84\x62\x61\xb2\xe8\x51\xfb\xee\x54\x3c\xa0\xe1\xfd\x89\x28\ \x40\x5e\x41\x41\x94\x04\x81\xcf\x0e\x10\x21\x32\x73\xe0\xb8\xc5\ \xe4\x00\x67\x8a\x15\xe4\xe6\xe7\x23\x27\x37\x17\x0b\x0b\x73\xd9\ \x01\xd8\x92\x39\x6e\x09\x8b\x8b\x0b\x7e\xf3\xe0\x60\x7f\xe6\x00\ \x9e\xe7\xb0\xb4\xb4\x48\xe6\x79\xcc\xcf\xcf\x62\x60\xa0\x0f\xed\ \x1d\xad\x19\x03\xc8\x1c\x98\x75\x6e\x6e\x06\x1d\x9d\x6d\x28\x29\ \x2d\x44\x71\x49\x41\x0a\xc0\xb5\x68\x00\x9b\x75\x76\x76\x1a\xfd\ \xfd\x3d\x28\x29\x29\x44\x28\x32\x05\x90\x79\x0a\x7d\x7d\xdd\xa8\ \xa9\xad\x44\x51\x51\x7e\x94\x99\x1e\x51\x52\x00\x3d\xe3\x87\xe0\ \x68\xff\xbb\xbb\xbb\x21\xb1\xc3\xa4\x7c\x27\x64\x0e\x29\x0a\xa0\ \xa3\x77\xad\x94\xd2\xfb\xfe\xf7\xf4\x0c\x7c\xf4\x9c\xad\x76\x73\ \x9c\x7e\x3d\x39\x17\x56\xde\xe3\x23\xb0\x85\xfe\x02\x0b\xaa\xe8\ \x09\xe2\xff\x8d\x19\x88\x99\x39\x32\xfb\x7f\xe3\x1e\xdb\x65\x5b\ \xaa\x7a\x0d\xe2\xf4\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ \x82\ \x00\x00\x02\xf7\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\xbe\x49\x44\x41\x54\x38\x4f\x63\x64\xc0\x01\xce\x97\ \x96\xaa\x72\x70\x73\xa7\x6b\x36\x34\x94\xe0\x52\x03\x12\x67\xc4\ \x25\xb9\xc3\xc0\x20\x9f\x85\x93\xb3\xdf\xb9\xad\x8d\x8d\xd1\xd1\ \xf1\x0f\x2e\x75\x58\x0d\xf8\xff\xff\x3f\xe3\x1e\x43\xc3\xaa\xff\ \xcc\xcc\x2d\x86\x93\x26\x45\x8a\x5a\x5b\xaf\x20\xc9\x80\x23\x49\ \x49\xbc\x3f\x6f\xdd\xba\xfd\xed\xf9\x73\x71\xb9\x80\x80\x9d\xfa\ \xbd\xbd\x1e\x44\x19\xf0\xe1\xf4\x69\x25\x01\x53\xd3\x7b\xbb\x05\ \x05\xf9\x3f\xff\xff\xff\xfe\xdf\xbf\x7f\x8c\x02\xda\xda\xe7\x5c\ \x8e\x1f\x37\x3e\x9b\x97\x37\x57\xc4\xda\x7a\xb9\x7c\x78\xf8\x1e\ \x64\xc3\xe0\x5e\x78\xb2\x64\x89\xcc\xa5\xd6\xd6\x47\x2c\xbc\xbc\ \xc1\x02\xda\xda\x67\x1e\x2e\x5b\xf6\xf0\xdf\x9f\x3f\x0c\x5c\x12\ \x12\x17\x8d\x37\x6e\xb4\xbe\x9a\x99\xf9\x8c\x5b\x55\xf5\x80\xe5\ \xb2\x65\x81\x8c\x0c\x0c\xff\x61\x86\x30\x5e\xed\xeb\x13\xfa\xfb\ \xe3\x87\xd8\x87\x03\x07\x76\xbe\x3e\x7d\x5a\x4e\xd4\xd9\x79\x0d\ \x87\x80\x80\xe4\xf3\x9d\x3b\xcd\xfe\xfe\xf8\xc1\xfc\xf7\xfb\xf7\ \x4b\x1a\x05\x05\xb3\x6e\x4e\x98\x30\x91\x47\x41\xe1\x81\x90\xab\ \x6b\xa0\x59\x7f\xff\x55\xb8\x01\x67\xf3\xf3\x7d\xde\x1e\x3f\x3e\ \xe9\xf3\xb5\x6b\xb2\x7f\xbe\x7f\x67\xe1\x56\x50\xf8\xfb\xef\xfb\ \x77\x26\x76\x71\xf1\x13\x7f\x3e\x7d\x52\xf8\xfe\xfa\xf5\x27\x01\ \x5d\xdd\x33\x6f\x4f\x9c\x88\x62\xe6\xe4\x64\x60\x13\x11\x09\xb4\ \x9d\x3f\xff\xe2\xab\x4b\x97\x6c\xd5\xf3\xf3\x17\x33\xae\x97\x94\ \x34\xfe\xf5\xf6\xed\x99\x7f\xbf\x7f\x33\x70\x08\x0b\xff\xff\xfd\ \xe5\x0b\xe3\xff\x7f\xff\x18\x98\xd9\xd8\x26\x72\x2b\x2a\xda\xfd\ \x78\xf1\x42\x8b\x47\x51\xf1\xfb\xfb\x0b\x17\x04\x98\x98\x98\xfe\ \xf3\x1b\x1a\xf6\xfc\xfe\xf8\xb1\x98\x5d\x50\x70\xa3\xb4\x9f\x5f\ \x3c\xe3\x6e\x17\x17\xdd\xcf\x37\x6f\x9e\xfe\xf1\xec\x19\xbb\x9c\ \xaf\xef\x9e\x97\xfb\xf7\x3b\xfd\xf9\xfc\x99\x91\x4d\x54\x74\xa2\ \x4c\x60\xa0\xce\xbd\xd9\xb3\x9d\x99\x58\x58\x18\x38\x24\x25\x19\ \x38\xc4\xc5\x3f\x7e\xb9\x73\x87\xff\xe7\x87\x0f\x8c\x9c\x12\x12\ \x7f\xb9\x65\x64\x8c\x18\x57\x85\x86\x32\x33\x1f\x3c\x38\x99\x91\ \x99\x39\xc3\x78\xca\x14\x95\x93\x79\x79\xab\xff\xbe\x7a\x65\x28\ \x66\x6b\xdb\xa7\x5f\x55\x75\x6f\xa7\x9b\xdb\x54\xe6\xff\xff\x19\ \x94\xb2\xb3\x9f\x7c\x3c\x7b\xb6\xf3\xcd\xc9\x93\x93\x41\x21\xcf\ \xcc\xc5\x05\x72\xb1\x31\x38\x16\x40\x09\x87\x91\x91\x11\x1c\xb2\ \xeb\x14\x15\x57\x7f\x7d\xf9\xd2\xcb\x6c\xe6\x4c\x0f\x49\x47\xc7\ \x6b\x1b\xe5\xe5\xdf\xb0\x32\x32\x32\x18\xcd\x9a\xe5\x7a\xb1\xb2\ \x92\xfb\xfb\xc7\x8f\xeb\x25\x75\x75\xaf\xb8\x9c\x3a\x65\xc4\xc0\ \xc0\xf0\x17\x23\x25\x1e\x0f\x0f\x37\x7a\xba\x6b\x97\x44\xc8\xfb\ \xf7\xdb\x40\x06\x2e\x61\x63\xfb\xcf\xcc\xc4\xc4\xc0\xc1\xc1\x21\ \xc8\x67\x63\xc3\xcd\xc6\xc3\x33\x41\xda\xd9\xb9\x59\x29\x35\xf5\ \x12\xde\xbc\x00\x8b\xa6\x95\xc2\xc2\x7f\x19\x18\x19\x19\x85\xf8\ \xf8\x04\x5d\xef\xdd\xfb\x88\x9e\x22\x71\x66\x26\x98\xc2\x03\x9e\ \x9e\x3b\xbf\xbf\x79\x23\x2c\x6a\x68\x68\x67\x32\x6b\xd6\x37\x92\ \x0d\x38\x99\x9d\x2d\xfc\xe5\xdc\x39\x0e\xe7\xe3\xc7\x9f\x62\xcb\ \x0f\x00\x7c\xfe\x14\x90\x23\x8b\x56\x14\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ \x00\x00\x03\x02\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\xc9\x49\x44\x41\x54\x78\x01\x75\x53\x03\x90\x6c\x57\ \x14\x0c\x8b\x71\x0a\xb1\x93\x52\xcc\x72\xa5\x14\xb3\xfc\x6d\xdb\ \x58\xc7\x2e\x7d\xae\x6d\xdb\xb3\x8f\x83\xf5\x8e\x6d\xac\xed\x37\ \xaf\x73\xef\x8d\xf1\x4f\x55\x3f\x77\x9f\xd3\x3d\x67\x6e\xfa\x77\ \x1d\x3a\x7c\xe0\x2d\x82\xab\x04\x56\x82\x95\xdf\x41\xaf\xaf\x1e\ \xfb\xe4\xfc\xfb\xa7\xef\xba\xfc\xc8\x4d\x37\x2a\xfa\x51\x72\x72\ \xe2\xb0\xd3\xe9\xd4\x2c\x2c\x2c\xcc\xc6\xe3\x71\x95\x82\x5c\xcf\ \x3b\x1c\x76\x6d\xc2\xbb\x3f\x44\xce\xbc\xfc\x6d\xff\x8d\xc8\x2d\ \x92\x2c\x96\x28\x8a\xa2\x5a\x2c\x16\x0c\x0c\x0c\xa0\xaf\xaf\x0f\ \x62\x93\x0e\x43\x92\x09\x11\x5f\x0c\x3f\xbf\x5a\x8c\xb4\x27\x33\ \x97\x0e\xef\x3a\xdc\x71\xee\xce\x2b\x8f\x5c\xb8\xeb\xfa\x73\x7f\ \x76\x96\x64\xa9\x74\x6a\x6a\x2a\xde\xdd\xdd\x8d\xae\xae\x2e\xf0\ \x3c\x0f\x9d\x4e\x87\xa6\x2b\x5d\x48\x79\x30\x03\x69\x8f\x66\x23\ \xe1\xbe\xeb\x0c\x29\x0f\x65\x28\x17\xef\xbb\xa6\x5c\xb8\xfb\xea\ \xe7\xcc\x33\x1d\x9b\x76\x1e\x1c\x1c\x84\x20\x08\x68\x6a\x6a\x44\ \x4d\x6d\x35\x6a\xeb\xaa\xd1\xd4\xdc\x80\xce\xca\x1e\x7c\xff\x52\ \x3e\x12\xee\xbd\xce\xf0\xe3\x0b\x45\x48\xdc\x93\xea\xa6\x5c\xd6\ \xdd\xee\xb0\x8f\x84\xc3\x61\x0c\x0f\x0f\x43\x92\x44\x70\x1c\x47\ \xce\x12\xbb\xb7\xd9\x6c\x08\x06\x83\xb8\xbe\xb1\x92\x90\xd3\x99\ \x40\xfe\xb6\x26\x18\x0c\xfa\x30\xe5\x52\x01\xeb\xe2\xe2\xe2\x8a\ \xdf\xef\x87\xd5\x6a\x45\x0f\xa7\x61\xde\xcb\xca\xca\x70\xf2\xd4\ \x71\x0a\xb5\xbb\x96\xc3\x2f\x6f\x95\xa0\xe0\x54\x2d\x6a\xbf\xee\ \xc2\x0f\xaf\x14\xc0\xc8\xdb\xd6\x28\x97\x0a\xac\xd0\xa4\xa3\xd1\ \x28\xbc\x5e\x2f\x38\x5e\x03\xb7\xdb\x4d\x88\x27\xd4\xc4\xc4\x84\ \x17\x8e\x1d\x3f\x72\xff\x99\x5d\xe7\xe3\x76\x8d\x9f\x59\x1a\x19\ \x19\x01\x57\xaa\x83\xb1\xdb\xae\x52\xee\x1f\x02\x98\x9c\x9c\x04\ \xb5\x21\xc9\x02\x62\xb1\x18\x4e\x9f\x39\xa9\x90\x77\x77\x53\x90\ \x29\x96\x57\x57\x57\x51\x51\x59\xc6\x7e\x1d\xa3\xd1\x88\x48\x24\ \x02\xf2\x6e\x95\x59\x58\x5a\x5a\x5a\x9f\x9f\x9f\xc7\xf8\xf8\x38\ \x4c\x66\x23\xa8\x1d\xad\x56\xab\x9e\x38\x79\x6c\x99\x4c\x30\xc1\ \x71\x3d\x71\x87\xc3\x81\xba\xfa\x5a\xe8\xf5\x7a\xd0\xb0\xc9\x3d\ \x6d\x60\x63\x21\x92\x91\xc7\xd6\xd7\xd7\x41\x45\x26\x26\xc6\x61\ \xb1\x9a\x30\x3b\x3b\x8b\xb5\xb5\x35\x06\x97\xcb\x85\xf4\x8c\x6b\ \xe8\xe8\x68\x87\x28\x8a\x6c\x02\x41\x14\xe6\xff\x08\xf1\xad\xd4\ \xb4\x64\x8f\xaa\xaa\xa0\x22\xcb\xcb\xcb\x98\x9e\x9e\x82\xd3\xe5\ \x40\x4b\x6b\x13\x2a\xab\xca\x89\xf7\x1a\x46\xee\xec\xec\x64\xfb\ \x41\xf3\x4a\x4a\x4a\x18\xa7\xdc\x3f\x17\x49\x94\x84\x10\xcd\xe2\ \x0f\x11\x9a\x89\xc7\xe3\xc1\x1f\x5b\x29\xcb\x32\xda\xda\xda\x58\ \xc0\x55\x55\x95\x33\x07\x0f\xed\xbf\xf6\x9f\x55\xd6\x68\x34\xd3\ \x64\xa1\x98\x08\xd9\x4a\x84\x42\x21\x36\xbe\xd9\x6c\x66\x42\x34\ \xb8\x92\x92\xe2\x55\xfa\xed\xef\xb4\x5b\xff\xf3\x67\x3a\x77\xfe\ \xcc\xc4\xd0\xd0\xd0\x3a\x1d\x93\x66\x32\x33\x33\x83\x40\x20\x00\ \x8e\xe7\x94\x0b\x17\xcf\xfd\xe6\xfb\x06\x75\x33\xc1\xed\x9b\x36\ \x6f\x7c\x7b\xef\xbe\xdd\xd9\x07\x0f\xee\xf7\x92\x8f\x95\xc3\x47\ \x0e\xc6\xc9\xb8\x01\xf2\x2c\x87\x79\xfe\x57\xfd\x0a\xaf\x8b\x80\ \x03\x77\xc7\x6b\x19\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ \x82\ \x00\x00\x02\x5d\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\x24\x49\x44\x41\x54\x78\x01\x8d\x51\x03\x8c\x1e\x41\ \x14\x3e\xdb\xb6\x6d\x44\x8d\x1a\xd4\xb1\x75\x8d\x6d\xd6\x8d\xca\ \xb3\x6d\xdb\xb6\x6d\xdb\xb6\x6f\x4f\xb3\xaf\x6f\x9a\xec\xf9\xe7\ \xfa\xe3\xbc\x11\xb9\xfb\xf3\xf6\xfd\xfb\x06\xcf\x9d\xe0\xd0\x80\ \x51\xfa\xfe\x00\x0b\xf7\x0f\xf4\xd9\x13\xe1\xf7\xf3\x0b\xf0\x3e\ \x63\x18\x86\x20\xb9\x3c\x34\x3c\xe8\xfb\x5d\x2c\x20\xd0\x77\x6d\ \x68\x78\x80\x45\x93\x7a\x5e\x7a\x9a\xc2\x0e\x8d\x0c\xc0\xfe\xc1\ \x3e\x09\x0a\xf1\xcf\xe1\xbe\x7b\xf9\xfc\x79\x9e\x95\x93\x0e\x6d\ \x1d\xcd\xe0\xeb\xef\xc5\xf0\x34\x40\xf0\xb8\xb8\xb4\x00\x18\xe6\ \x8c\x45\xb3\x71\xae\x05\x36\xbb\xec\xe9\xeb\x84\xbc\xfc\x6c\xda\ \x6e\x84\x97\x9e\x26\x3d\x43\x02\x4c\xcf\x4c\xd2\x16\x6c\x60\xb0\ \x5f\xad\x8f\x9f\x67\x64\x59\x45\x31\xf4\xf6\x75\x03\x62\xd4\x80\ \x5b\x1a\xcf\x16\x8b\xa5\x65\x45\xc0\x30\x0c\x15\x1c\xc5\x27\xc4\ \xb0\xa5\x65\xc5\x50\x51\x55\x4a\xc5\x45\xbc\x95\xb7\x73\x70\xa7\ \xb3\x58\x5c\x9c\x87\xd3\xb3\x53\x12\x15\x13\x0e\x19\x99\xa9\xd4\ \x8c\xa1\x18\x6f\xe5\xfd\x16\x89\x55\xd5\xe5\x64\x7d\x63\x0d\xe8\ \xf0\x70\x06\x2c\x2e\x2f\x9c\xc3\x85\x69\xe1\xe1\xe9\xf9\x13\x5a\ \xdb\x9a\xa1\xb8\x28\x1f\x62\x9c\x1d\x4e\x85\x4e\xcf\xd3\xd1\xf1\ \xcd\xd6\xd2\x3a\x4e\x93\x92\x82\xe4\x77\x2f\xd9\x34\x31\x31\x68\ \xd7\xd3\x85\x5c\x6d\xed\x03\xc4\x3c\x79\x0b\x75\x75\xbf\x67\x4a\ \x4b\x93\x21\x13\x13\xd2\x67\x64\xc4\x96\x2b\x2b\x91\x14\x49\xb1\ \x94\x0c\x09\xf1\xeb\x4e\x3d\x3d\x18\xc4\xef\xdd\xc6\xc6\x24\x53\ \x4e\x8e\x20\xf7\xfd\x3d\x71\xbe\x9e\x9e\x47\x86\x94\x14\x3b\x60\ \x6c\x7c\x95\x21\x2e\x5e\xd1\xa0\xa9\x71\x9e\x26\x2a\x0a\x14\x4b\ \x90\x13\x51\xea\x35\x34\x20\x0d\x9a\xea\x17\x88\x95\xf7\x1a\x1a\ \x5e\xa6\x23\x17\x35\xb7\xdb\x99\xad\xa1\xb1\x36\x6a\x6e\x4e\x90\ \xf0\x85\xbe\xf7\x19\x1a\x9e\x62\xd2\x35\x87\xe3\x77\xd2\xa5\xaf\ \x77\x44\x9f\xd3\xc5\xc4\x3e\xd2\x36\x54\x73\x6b\xa0\xa8\x78\xd1\ \x8f\xce\xf4\xb9\x54\x5e\x3e\x60\xd8\xd4\x94\x64\x29\x28\x8c\x72\ \x78\x96\xac\x6c\xf7\x18\x06\x54\x28\x2a\x86\xd2\xf7\x41\x6c\x9a\ \xad\xaa\x7a\x79\x6b\xa0\xae\x3e\x3f\x6d\x6d\x4d\x70\x09\x17\x98\ \x4e\x32\xa4\xa5\xd7\xb0\xa2\x3b\x87\xd3\xe7\x2c\x19\x99\xcd\x11\ \x33\x33\x82\x41\xff\x39\xa8\xd9\xbc\x37\x83\x2c\x25\xa5\xf5\x6c\ \x15\x95\x13\xdc\x01\xe6\x8e\xf8\x3e\x47\x51\xb1\x39\x4b\x5e\x7e\ \x2f\x53\x5e\x7e\x89\xe3\xfc\x03\xb6\xb3\x37\xbd\x80\xbc\x56\xea\ \x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x44\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\x0b\x49\x44\x41\x54\x78\x01\x9d\x90\x83\x92\x5c\x51\ \x14\x45\xfb\x3f\xf2\x21\xb1\x53\x88\x6d\x1b\x63\xdb\xb6\xcd\xc2\ \xd8\xb6\x3d\x6f\xac\x70\xec\x69\xdb\xbd\xf3\xee\x4d\x55\x23\x4e\ \x4e\xd5\xba\xdc\x67\x3d\x70\xac\x43\xdc\xbc\x2f\x7a\x24\xf8\x1e\ \x78\x1d\x65\xc7\x12\xcd\x32\xc7\x82\xa3\x6f\x23\x55\xe7\x1c\xfc\ \x37\xef\xb8\x7b\x86\x70\x7e\x57\xa7\xec\x13\x1f\x3d\x4b\x6d\xf3\ \x0d\xa8\x98\x7e\x58\x33\xb1\x35\x3e\xb5\x2c\xc4\xc0\x67\x1e\xb2\ \x7a\x97\xf1\x26\x2a\x5f\x7f\xda\x36\x5c\xff\xce\xef\xde\xbe\x5f\ \x0a\xac\xfc\xc2\x53\x5f\x39\x38\xe1\x99\xbd\x0b\xce\xd9\x84\xe0\ \xa4\x73\x26\xce\xb8\xe7\x21\xb7\xeb\x23\x7a\x3e\x72\x71\x35\xb4\ \x0a\x97\xde\xf9\xc0\xd6\xde\xea\x67\xf8\x73\xac\x1c\x6c\xb1\xb6\ \xb5\x08\x52\x8b\xab\x0b\x18\x59\x58\x84\x6d\x46\x23\xc8\x67\x94\ \x31\x6b\x48\x6c\x9a\xc5\x0d\x9f\x2c\xcc\x2f\x6f\xc2\xbc\x64\x32\ \x19\x95\x70\x1c\x5d\xac\xa0\xd7\xeb\x21\x10\x08\xc0\xe7\xf3\xb1\ \xbc\xb6\x04\xb7\xf4\x6a\x2a\xf0\x28\x1a\x45\x5c\xdd\x04\x8e\x5a\ \xc5\xa2\xbc\xbd\x8f\xbd\x67\x33\x02\x02\x1f\x52\x99\xf4\x9b\x80\ \x0c\x3a\xad\x0e\x5c\x1e\x8f\x36\x99\x43\x04\x77\x12\x3b\x7e\x38\ \xe7\xb1\x59\xa9\xd4\x4c\xa0\xd1\x68\xb0\xbb\xbb\x87\xd1\xc9\x7e\ \x1a\x48\xef\xfa\x02\x87\xbc\x2e\x23\x4e\x79\xdd\x48\x6d\xfb\x40\ \xef\x52\x4a\xaa\xb0\xc7\x66\x25\x12\x89\x49\xa0\x56\xab\xa9\x64\ \x63\x63\x03\x03\x4c\x97\x51\x42\x1a\x09\x65\xc3\xcb\xf4\x2c\xb1\ \xb0\x9c\x64\xe8\xf7\x8b\xc5\x62\x0b\x01\xf9\x07\x3f\x48\xca\x99\ \x75\x2a\x3a\x6c\x15\x67\x6c\x26\x19\x92\xb5\x10\x28\x95\x4a\xac\ \xaf\xaf\x1b\x25\x84\x89\xb9\x61\x1c\xb7\x8e\xa5\xa2\xcc\xca\x7a\ \x7a\xa6\xd3\xe9\x48\x86\x66\x45\x22\x91\x49\x60\x30\x18\xf0\xbb\ \x22\xaf\x4c\xe0\x72\xb9\x84\x1f\x05\x13\x13\x13\x18\x19\x19\xb1\ \x60\x60\x60\xc0\x7c\xfd\x03\x0b\x0b\x0b\x96\x02\x72\x98\x97\x97\ \x87\xaa\xaa\x2a\xb4\xb7\xb7\xa3\xbf\xbf\x1f\xc3\xc3\xc3\x98\x9a\ \x9a\x42\x49\x49\x09\x66\x66\x66\x8c\x73\x4b\x4b\x0b\x46\x47\x47\ \x4d\x82\xf1\xf1\x71\xf4\xf5\xf5\xa1\xb4\xb4\x04\xf5\xf5\xf5\xe8\ \xed\xed\x35\x0a\x18\x86\x41\x71\x51\x31\xa6\xa7\xa7\xd9\xb9\x88\ \xce\x4d\x8d\x8d\x96\x82\xc5\xc5\x45\x7c\xf9\xf2\xc5\xc8\xfc\xfc\ \x3c\x65\x72\x72\xf2\x97\x98\x0b\xfc\x59\xf0\x9f\xf8\x7f\x05\x5f\ \x49\xda\x7a\xe4\x88\x03\xed\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ \x42\x60\x82\ \x00\x00\x02\xb6\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\x7d\x49\x44\x41\x54\x78\x01\x7d\x53\xc3\x9a\x75\x47\ \x14\x0d\xa7\xe1\x73\x64\xdc\xd3\xbc\x46\x30\x6b\xdb\xf6\x4b\xc4\ \x18\xb5\x6d\xf7\xb5\x6d\xdb\xb6\xef\x3d\x2b\xb5\xe3\x9f\xe7\xfb\ \xca\xb5\xf7\x42\xed\xf3\xce\xcb\xdf\xf0\xc8\x60\x17\x6b\xdf\xb3\ \x66\x63\xad\x4e\x8d\xe6\xb4\x47\x67\xef\xbc\xed\xa3\x4b\x6b\x6b\ \x2b\x06\x97\xcb\xc5\x2b\x97\xcb\x85\x4e\xa7\xc3\x51\x63\xf3\x92\ \xd3\xe9\x90\xd1\x19\xdd\x79\x53\xf0\x8d\x44\x2a\xde\x69\xb7\xdb\ \x9c\xd5\x6a\x85\x56\xab\x85\x5a\xad\x86\xc1\x60\x80\xdb\xed\x46\ \x26\x93\x01\x25\x13\x8b\x45\x37\x74\xf7\x15\x64\x89\x54\xb2\x9b\ \xcd\x66\x3b\xcf\xcf\xcf\x78\x7a\x7a\x82\x50\x28\x84\x5c\x2e\x87\ \x46\xa3\x81\xc3\xe1\x40\x24\x12\x41\x32\x99\x44\xad\x56\x83\x48\ \x24\x54\x50\xcc\xbf\x9a\x89\x1a\x21\xeb\x74\x3a\x76\x28\xc2\xd5\ \xd5\x25\x4e\x4e\x8f\x71\x7a\x76\x8c\xab\xeb\x0b\x28\x55\x0a\x78\ \xbd\x1e\xc4\x62\x31\x30\x10\xb0\xbb\x58\x59\x5d\xb2\x50\xec\x9f\ \xe8\x0e\xa7\xc3\x48\x08\x44\x57\x22\x11\x43\x20\x10\xb0\x51\x02\ \x5a\xdb\xed\x76\x84\x42\x21\xa8\x35\x4a\xf8\xfd\x3e\x04\x02\x01\ \xa4\x52\x29\x28\x95\x8a\x08\xc5\x52\x02\x5b\xa5\x52\xa9\xd3\x81\ \xcd\x66\x03\x5f\xc0\x03\x69\xdf\xdb\xdb\xc3\xd4\xf4\x04\x35\x4e\ \x2e\x97\x21\x91\x48\x40\x2c\x11\x81\xfc\xf1\x78\x3c\x08\x87\xc3\ \x0d\x8a\xa5\x04\x75\x32\x87\xe8\xf9\x7c\x3e\x08\x84\x3c\xba\xc0\ \x02\x27\xb9\x95\x95\xe5\x2f\xc6\x27\x46\x3f\x9f\x99\x9d\xea\x34\ \x1a\x0d\x9c\x9d\x9f\xc0\x68\x34\x12\x10\x25\xe4\x28\xf6\x9f\x04\ \x20\x97\x49\x86\x44\x2a\x42\x3c\x1e\x07\x0b\x6a\xb3\xb3\x8f\xa9\ \x31\x16\x35\x4a\x70\x70\xb8\x07\x7a\x1d\x93\xc9\x84\x68\x34\x0a\ \x76\xd6\xf8\x53\x42\xb5\x5a\x6d\x95\x4a\x25\x90\x36\xb3\xc5\x04\ \x92\x23\x93\xc9\xb8\xc9\xa9\xf1\x1a\x63\x90\x16\x08\xf8\x1d\xa7\ \xd3\xc9\x18\x9c\x42\xa1\x50\x80\xcc\x66\x6b\x02\xb0\xff\x69\x22\ \xa3\x9c\x6c\xb5\x5a\xa0\x24\xe9\x74\x0a\x56\x9b\x19\x85\x42\x01\ \xcd\x66\x13\xd4\xa8\x0e\x7e\xfa\xf9\x07\x3c\x3c\xdc\x43\x2c\x16\ \x83\x18\x88\xc4\xa2\xd2\x3f\x26\x76\x6d\x6c\xae\x79\x39\x8e\x03\ \x25\xa1\x77\xce\xe5\xb2\x70\xb9\x9d\xb8\xb9\xbd\xc2\xe1\xd1\x3e\ \x7b\xce\x13\x50\xf0\xe3\xe3\x23\xa8\x3e\xc8\xaf\xd5\xd5\xe5\x14\ \xc5\xfe\x5b\x48\xcc\xe1\x30\x79\xf1\x4f\x12\xf2\xc4\xeb\xf5\xe2\ \x9f\xaa\x94\x4a\xa5\xb8\xbb\xbb\x03\x19\x7c\x74\x74\x98\x1f\x1a\ \x1e\xf8\xe1\x95\x52\xe6\xf1\x78\x39\x56\x24\x94\x84\x0a\x86\x9e\ \x0a\x44\xdf\x62\xb1\x80\x12\x91\x71\x3b\x3b\xdb\x8d\xff\x95\xf2\ \xfb\xaf\x94\xf4\xfc\xc2\x6c\x5a\xaf\xd7\xb7\x88\x26\x79\x92\xcf\ \xe7\x11\x0c\x06\x21\x10\x0a\xda\x8b\x4b\xf3\x7f\xe9\x7e\xc3\xf7\ \x2e\x6b\x1f\x7e\xf3\xed\xd7\x5f\xf6\xf5\xf7\xfc\x36\x34\x34\xe0\ \x63\x97\xdb\x23\xa3\x43\x1d\x46\x37\xc8\xf6\x7e\x7f\xdd\xef\xfc\ \x07\x35\x05\x94\xfb\x69\xf1\xe2\x31\x00\x00\x00\x00\x49\x45\x4e\ \x44\xae\x42\x60\x82\ \x00\x00\x02\xee\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x02\xb5\x49\x44\x41\x54\x38\x4f\x85\x53\x5b\x48\x14\x61\ \x14\xfe\xfe\x59\xb7\x82\xd2\xca\x74\x85\x7d\x53\x14\x61\x5c\x21\ \x5b\xc8\x87\x7c\x88\xa4\xec\xa9\x37\x85\x24\x2c\x65\xf5\x21\xc4\ \x5d\x6f\x7b\xdf\xd9\xad\x05\x2f\x91\x19\x14\x66\x4b\x6b\x28\xd8\ \x9b\xa1\x69\x68\x5a\xe6\x8b\x68\x0f\x06\xf6\x90\x12\xa5\xb0\x86\ \x4a\x24\x82\xa1\xcb\xcc\x2c\x3b\xf1\xff\x33\xb3\xe6\x53\x03\xc3\ \xf9\x6f\xe7\x3b\xe7\x7c\xdf\x39\xc4\x6c\x36\x5f\xad\xb8\x7e\xed\ \xc9\xd9\xcc\x33\x85\x48\x7d\xe4\x70\x09\xe0\xe8\x4e\xbd\xfa\xbd\ \xb3\xf3\x7d\x7c\x6c\xa2\x83\xd4\xd6\xdd\x59\xed\xec\xe8\x2a\x54\ \xb4\x87\x84\x10\xd0\x1f\x9a\xa5\xce\x84\xe3\x18\x08\x3d\xe7\x38\ \x8e\xbd\x94\x65\x11\x76\x87\xfd\x27\x69\x69\x73\x28\xce\x56\x37\ \x1e\xf7\x3e\xc2\x95\xf2\x72\x7c\x5e\x5a\xc2\xad\x9a\x1a\xf6\x58\ \x75\xa0\x96\x63\xeb\x81\x68\x14\x56\xab\x15\x1f\x67\x67\xe1\x74\ \xbb\x71\x3f\x1c\x82\x0a\xd0\xe6\xc6\x97\xe5\x65\x64\x56\x54\x60\ \x2f\x10\x40\x6e\x6d\x2d\x3e\x0c\x0e\x22\x2f\x99\xc4\x4a\x4e\x0e\ \x2a\xab\xaa\x58\xe4\xa1\xb1\x79\xbc\x5a\xfc\x83\xce\x4a\x13\x4a\ \x4b\x4b\x11\x0a\x07\x29\x40\xb3\xe2\x6a\x77\xe3\x65\x34\x8a\x8b\ \xf1\x38\x56\x38\x0e\x37\x6c\x36\xfc\x08\x87\x71\x2a\x12\xc1\xfe\ \xdc\x1c\x8a\x2c\x45\x2c\x8b\xe7\x2f\x86\x70\x22\x2b\x0f\xf2\xaf\ \xaf\xb8\xdb\xd8\x88\xe0\x3d\x41\x05\x70\x3b\x3d\x10\x25\x11\x09\ \x59\x46\x5a\x9a\x11\xfb\x07\x07\x78\x37\x39\x09\x9e\xe7\x59\x49\ \xb6\x86\x06\x96\x81\x5a\x16\x07\x03\x2d\x8b\x23\x10\x82\x01\x0d\ \xc0\xe5\x81\x24\xc9\x98\x8a\x44\x90\xbf\xb5\x85\x65\x9e\xc7\xcd\ \xea\xea\x14\x69\xba\x73\xca\x52\x4e\x38\x82\x80\xe0\x53\x01\x3c\ \x2e\x1f\x63\x75\xf5\x59\x3f\x32\xc2\x61\xec\x4e\x4f\xe3\x7c\xc9\ \x05\x46\xe0\x61\x64\x02\x03\x55\x40\x53\x82\x66\xe3\x17\xbc\x20\ \xad\x6d\xcd\x8a\xd7\xed\x83\x24\xcb\x78\x3d\x32\x02\x8b\xc5\x82\ \xc5\x85\x05\xd8\x1a\xea\x59\xba\x0c\x80\x02\x11\x0e\xa2\x28\x41\ \x12\x45\x18\x8d\x69\x48\x2a\x0a\x1e\x3c\xec\xd6\x00\x3c\x01\x24\ \x12\x52\x4a\x2e\xaa\x3b\x8b\xae\x03\x68\x72\x6e\xc4\x36\x30\xbd\ \xde\x07\x25\x7d\x0f\x25\xc9\x3a\xbc\x99\x18\x53\x01\xfc\x3e\x01\ \x92\x2c\x31\x07\x16\xd5\xc0\x81\xd3\xfa\xa0\xb7\xa7\x07\x97\xca\ \xca\xf0\x7e\x66\x06\x8d\x4d\x4d\x18\xde\xac\xc7\xee\xf1\x55\xdc\ \x36\xbe\xc5\xc0\x60\x14\xa4\xb5\xbd\x59\x09\xf8\x82\x90\x65\x59\ \x05\x60\x75\xab\x6c\xd3\xf4\xa7\x3e\x0d\x63\xd1\x24\xe0\xf2\x66\ \x1f\x0a\x73\x8b\x11\x8b\xc5\x90\x9e\x91\x01\x59\x92\x30\x3a\x3e\ \xaa\x02\x08\xfe\x10\x12\x89\x84\xe6\xa4\x77\xa0\x2a\xdb\xd3\xfe\ \x1e\x98\xad\x06\xc4\xe6\x13\x70\x3a\xbd\xa9\x0e\xa5\x77\x2e\x8f\ \x13\xc4\xd1\x62\x17\x05\x7f\xf0\x18\x63\xf7\x1f\x86\xf5\xbe\x67\ \x96\x96\x93\xea\x03\x35\x40\x3c\x1e\x87\xc7\xeb\x4a\x12\x9e\xe7\ \x43\x0e\x87\xdd\x61\xca\x31\x9d\x56\x14\x75\xa4\xe8\x2c\xe9\x56\ \x9f\x09\x42\xc7\x49\x1f\x4b\x05\x58\x5b\x5f\x17\xbb\xbb\xba\x67\ \xe9\xd1\xb9\x02\xbe\x20\x3f\x3b\x2b\xfb\xe4\x91\x19\xfe\xcf\x66\ \x7b\x7b\x5b\x5a\xfb\xb6\x16\xfb\x0b\xc0\xf5\x00\xde\xe0\x14\xa2\ \xf7\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x0e\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x01\xd5\x49\x44\x41\x54\x78\x01\xdd\x91\x33\x7c\x9e\x41\ \x1c\xc7\xef\x71\x6c\x2f\x35\xb7\xd8\xb6\x6d\xdb\xb6\x6d\xec\x4b\ \x9c\xec\x9f\x6e\xd5\x5c\xdb\xed\x58\xdb\xd6\xaf\xd7\x5b\x92\xbc\ \x5a\x8b\xef\x19\x7f\x93\xff\x18\x5f\x5f\x1f\x5f\x3f\x7f\xdf\xc1\ \x03\x07\xf7\xab\x0d\x37\x77\xd7\xc1\xb0\xb0\x50\x67\xa2\x0d\x3b\ \x7b\xbb\xcc\xfc\xfc\xbc\xcf\x93\x93\x93\xe8\xe8\xe8\x40\x4b\x4b\ \x0b\x1a\x1b\x1b\xd1\xdb\xdb\x8b\xea\xea\x6a\x54\x54\x54\x60\xef\ \xde\x3d\xa3\x44\x13\x56\x56\x56\x8d\xa9\xa9\x29\xdf\x57\x57\x57\ \xf1\xfe\xfd\x7b\x4c\x4f\x4f\x23\x27\x27\x07\x43\x43\x43\x78\xfb\ \xf6\x2d\x96\x96\x96\x90\x9c\x9c\x0c\x4b\x4b\x8b\x31\xb2\x19\x8e\ \xe3\x78\x33\x33\xb3\xe9\xa8\xa8\xa8\x9f\x59\x59\x59\xb8\x72\xe5\ \x0a\xbe\x7e\xfd\x8a\x77\xef\xde\x61\x7d\x7d\x9d\x09\x7f\xff\xfe\ \x1d\x37\x6f\xde\x44\x6c\x6c\x2c\x0c\x0d\x0d\x27\xc8\x26\x64\x43\ \x43\x83\xb5\x80\x80\x00\x50\x05\x08\x0f\x0f\x67\x56\xae\x5e\xbd\ \xca\x94\x7c\xfb\xf6\x8d\x09\xdf\xb9\x73\x07\x61\x61\x61\xf0\xf5\ \xf5\x85\x28\x0a\x93\x9b\x15\xd4\x3b\x39\x39\xb1\x07\x4f\x4f\x4f\ \xb8\xb9\xb9\xc1\xc5\xc5\x05\xdd\xdd\xdd\x4c\x90\x0e\xa6\x64\x70\ \x70\x10\x7b\xf6\xec\x81\x83\x83\x03\xa8\xc7\x53\x9b\x15\x18\x8b\ \xa2\x78\xc8\xc4\xc4\x04\xc6\xc6\xc6\x30\x35\x35\x45\x4c\x4c\x0c\ \xee\xdf\xbf\x8f\x97\x2f\x5f\xe2\xc9\x93\x27\x78\xf1\xe2\x05\xee\ \xdd\xbb\x87\xc8\xc8\x48\xc8\xb2\x0c\x9e\xe7\xa7\x89\x0a\x7a\x82\ \x20\x2c\x18\x18\x18\xfc\xb4\xb4\xb2\xc4\xa9\x53\xa7\x98\xe0\xb9\ \x73\x67\x51\x50\x98\x8f\x93\x27\x4f\xb2\xf3\xe5\xcb\x97\xb1\x77\ \xdf\x5e\xd0\xbf\x33\x44\x03\x22\x7d\x98\xdc\xb1\x63\xfb\x8f\xe4\ \x94\x24\x1c\x3d\x7a\x04\xf9\x05\xb9\x48\x4a\x49\x44\x51\x71\x01\ \x8e\x1d\x3f\x86\xd2\xb2\x12\x1a\xa2\x0b\x24\x49\x9a\x25\x5a\xe0\ \x69\x7c\x2d\xce\x2e\xce\xdf\x32\xb3\xd2\x51\x5e\x59\x8a\xd6\xb6\ \x26\x36\x2a\xab\xcb\x91\x9e\x91\x8a\xc8\xa8\x88\xdf\x55\x98\x23\ \x3a\xe0\x68\x49\xd3\xe9\xa7\x05\x45\x51\x16\x55\x87\xbe\xbe\xfe\ \x82\x85\x85\x45\x18\xf9\x2b\xf8\x05\xbe\x08\xf9\x21\x6d\xb5\xa8\ \x13\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\xba\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x02\x81\x49\x44\x41\x54\x78\x01\xdd\x92\x03\xb0\x1b\x5d\ \x14\x80\x7f\xdb\xb6\x6d\xa3\xc6\xe8\xd9\xb6\x6d\xdb\xb6\xed\xb7\ \x51\x6d\xdb\xb6\x6d\x06\xeb\x4d\x72\x37\xa7\xf7\x0d\x3b\x6e\x50\ \x9e\x99\x2f\x59\x7e\x7b\xf4\xd8\xa3\x13\x37\x6e\xf4\xbd\x0c\x94\ \xe4\x2d\x9a\xee\x7f\x1b\x60\xf4\x15\x80\x82\xc7\xcd\x12\x5e\xba\ \xd4\xf5\x02\xf0\xa3\x9f\x02\x23\xfb\x15\x38\xd9\x7f\xc0\x11\xff\ \xeb\xd8\x91\x3f\x04\x8d\xf4\x4b\x50\x8e\xbe\x68\x92\x14\x60\xf1\ \xb3\x20\x28\xbe\x02\x4e\x3a\x11\xb1\x52\x27\xc4\x48\xfd\x10\x43\ \xf8\x23\x5a\xea\x0a\xbc\x7c\x2a\x50\xb2\xef\xe1\x46\xdf\x33\xc6\ \x8b\x59\xe9\x07\x58\x3a\x0e\x51\x23\x3e\x48\x53\x55\xa3\x57\x66\ \x2c\x40\xea\x7c\x39\xa2\xda\x8a\x10\x4d\xf8\xeb\x69\xe9\x14\xe0\ \xa5\x9f\x18\x99\xad\xe2\x49\xa0\x14\xdf\x02\x23\xb7\x46\x64\x4d\ \xa9\xea\x5c\x9c\x46\xab\x91\x1a\xf4\xdc\x26\x84\xd4\x55\x47\x11\ \xd5\x50\x89\xc8\x11\x17\x2d\x23\xf9\x05\x60\xc1\xe3\x46\x88\x07\ \x9e\x03\x66\xf4\x67\x44\x11\xce\x48\x95\x33\xc8\xdf\xec\x10\x45\ \x44\x81\x28\xea\x40\xcf\x6d\x15\x91\x32\x79\x85\x81\xee\x08\xd5\ \xb1\xd2\xbf\x40\xa5\x78\xf2\xce\xc5\x27\x9a\x9f\x01\x5a\xf2\x23\ \x62\x65\x0e\x48\x5d\xdc\x21\xa8\x5a\x59\x51\x77\x15\x44\xc4\x80\ \x96\x9c\x03\x67\x0e\x65\x5e\xd0\xaa\x3b\x82\xf0\x20\x7f\x07\x98\ \x67\xdc\x86\x00\xaf\xf8\x1c\xf7\x71\x3a\xa2\x07\xa2\x90\xba\x64\ \xbe\xa0\xec\xd0\x70\x37\x87\xc5\xeb\x67\xb3\xe0\xe8\xa1\x6e\x71\ \xd1\xec\x44\xe6\xdc\x89\xee\xaf\x8c\x1f\x9e\x86\x78\x55\xc7\x10\ \xbf\xe1\x8d\xb0\x41\xf4\x50\x28\xee\x75\xf9\xd5\x93\x19\x0b\x37\ \xad\x2b\xd6\x2c\x5a\xda\x08\x2b\xd7\xc9\x0d\xf5\x35\xa1\x57\xdc\ \x3c\x5c\x5e\x36\x5e\xce\x48\xde\xc6\xfc\x32\xb6\x01\x06\x1a\x7f\ \x80\x19\x72\xd8\xb9\xa5\x2e\x58\x4e\x24\x30\x6d\xfd\x55\x30\x73\ \x01\x01\x76\x0e\x36\x67\xbd\x7d\x3d\xdf\x35\x5e\xae\x52\x3c\xcb\ \xa9\x86\xde\xc1\x7b\xfb\xa5\x40\x8d\x7c\xc2\xab\xa5\xaf\xfb\xf9\ \x7b\xbc\x1f\x1d\xed\x7c\xa5\xb9\xa7\x12\xe4\xf3\x86\xc0\xd9\xc5\ \xf1\x52\x50\x48\x00\x6e\x8b\x05\x02\xb7\xe0\x95\x89\x53\xc6\x1d\ \x6b\xec\xaa\x30\x48\x66\xf7\x81\x9b\xa7\xcb\xb5\xc8\xe8\xb0\x7f\ \x2d\x25\x7f\xf6\xff\xf1\xff\xec\xad\xef\x28\x43\x23\x33\xba\xc1\ \xdb\xd7\x43\x19\x1b\x1f\x65\x6b\x29\xf9\x93\x7f\xfd\xfb\xe7\xa6\ \xea\xd6\x62\x61\x40\xd6\x0e\x7e\x81\x3e\x9a\x84\xa4\xb8\xe0\xc7\ \x2c\x14\x4f\xfc\xf6\xc7\x2f\xcb\xcb\xea\xf3\xe9\x5e\x49\x0b\x84\ \x84\x07\x70\xe9\x99\x29\x41\x26\xcb\x30\x2f\x60\xde\xc0\xbc\x8f\ \xf9\xf8\xfb\x1f\xbf\x5d\x58\x54\x95\xa5\xe9\x97\xb6\xc1\x7f\xe3\ \xfe\xbd\x82\xaf\x3d\x63\x8a\xf8\x39\xcc\xeb\x98\x0f\x31\x5f\x63\ \x7e\xc1\xfc\xf9\xcb\x5f\xff\x6c\xc7\x5c\xf4\x0d\xf0\x4c\xc7\xe7\ \x2f\x9a\x9a\xf5\x53\x98\x17\x6f\xcb\x7a\x8c\x37\x31\x2f\x61\x9e\ \xc6\x3c\xa4\x71\x0b\x78\x92\x78\x10\xaa\xd3\x69\x89\x00\x00\x00\ \x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\xb6\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x01\x7d\x49\x44\x41\x54\x78\x01\xd5\x95\x35\x72\x03\x31\ \x14\x86\xdf\x82\x99\xb1\xf3\x09\x82\x07\xc8\x1d\x4c\x75\x98\xda\ \x54\x61\x68\x43\x75\xc8\xd8\x06\x4b\x9f\xc7\x7d\xb0\x33\xef\x46\ \x6f\xc6\x6f\x47\x33\x5a\x85\xf1\x9f\xf9\x47\xf0\x4b\x9f\xc8\xa0\ \x98\xa6\x09\xdf\x21\x95\xf9\x7f\x81\xf5\x6a\xb5\x7a\xd2\xed\x76\ \xe7\xbf\x70\x11\x43\xd7\xf5\x02\x14\x0a\x85\x9e\xf9\xc5\x42\xa6\ \x4e\x3b\xad\xd7\xeb\xa0\x28\x0a\x90\xb0\x6e\x53\x0a\x75\x7a\x7c\ \x2a\x53\xa9\x14\x16\xaa\xce\x0f\xbe\xbb\xbb\x83\x66\xb3\x29\x85\ \x90\x7c\x3e\x1f\x24\x12\x09\x5b\xb0\xf0\x78\xaa\xaa\x82\xa6\x69\ \x58\xbe\x6a\x1a\x87\x8b\xf2\xfd\xfc\x26\x74\x1e\x9c\x4c\x26\x69\ \x97\x64\xc9\x75\x88\xd7\x80\x19\x96\x02\x18\x83\x5a\xad\x06\x32\ \x65\x32\x19\x6b\xdc\xc4\xfc\xa2\x90\x57\x4f\x8f\xe5\x60\xd4\xec\ \xec\x8c\x30\xa9\x54\x2a\xe3\x89\xac\x71\xbe\x40\x90\x8f\xa9\x5f\ \x76\x15\x34\x51\x85\xa7\xa7\x07\x20\x85\xc3\x51\x2b\x27\x79\x45\ \x30\xe6\xd2\x1d\x0b\x30\x5e\x04\xbe\xb8\xb8\x80\xb1\xd1\x41\x21\ \xbf\xb9\xb9\x81\x7c\x3e\x2f\x07\x17\x8b\x45\xb0\x13\xbf\xe3\xb9\ \xb9\x39\x3e\xa2\x79\xc4\x10\xc1\xd9\x6c\x16\x96\x36\x77\x84\x49\ \x27\x87\x7b\xc2\xa9\xec\xae\x4b\x0a\x46\xbb\xdc\x6e\xe9\xc3\xd0\ \x44\xd3\x34\x20\x14\x0a\x53\x8c\x6d\xe1\x54\x02\x58\x77\x38\x6d\ \xaf\x81\x72\xfa\x94\x88\x92\xec\x98\x82\xbd\x8d\x15\x0b\x44\x25\ \xef\xa9\xa9\x29\xfa\x52\x58\x36\x0c\xc3\x2a\x79\x30\xb6\xd4\x68\ \x34\x0a\x5f\x28\x43\x67\x2a\xb0\x17\xfd\xca\xdf\x63\x93\x9d\xb4\ \x6c\xfd\xe7\x29\x4c\x78\x82\xbe\x1d\xcc\x4e\xce\xae\x7e\x1f\x8e\ \xe9\x32\x77\x98\xdb\x9c\x3b\x64\xc6\xeb\x21\xef\xdb\xfe\x4c\x9f\ \x01\xc2\xbc\xd8\xc0\x78\x71\xcc\xaf\x00\x00\x00\x00\x49\x45\x4e\ \x44\xae\x42\x60\x82\ \x00\x00\x02\x9c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x02\x63\x49\x44\x41\x54\x78\x01\xd5\xd3\x03\x90\x1c\x41\ \x00\x85\xe1\xd8\xb6\x4b\xb1\x6d\xdb\xe7\xb5\xcf\xb6\x6d\xdb\x56\ \x6c\xdb\xb6\x6d\xdb\xf6\x4b\xf7\x9c\x6d\x75\xd5\xb7\x83\xae\xfd\ \xc7\xb5\x6a\xde\x10\x0a\x85\x23\xf8\x42\xde\x0b\x81\x48\x40\xbd\ \x14\x88\x84\x39\x88\xc5\x62\x56\xa9\xc2\x72\x72\x8b\xce\xee\xdb\ \xbf\x0f\x05\x51\xd3\xd2\xfb\xa1\xc2\x56\x1a\x53\xe2\xf0\x22\xb9\ \x85\x27\x37\x6d\xde\x04\x27\x27\xc7\x7c\xb9\xba\x79\x80\xc5\x51\ \x79\x45\xe2\x23\x4a\x14\x5e\xb0\x70\xfe\xae\x9d\xbb\x0f\x22\x34\ \x34\xb4\x40\x81\x41\xa1\x20\xe1\x82\x04\x17\x70\xc6\x0b\x8e\x6e\ \xde\xbc\xf9\x57\x6c\x6c\x2c\xec\xec\x6c\x61\x6f\x6f\x07\x7b\x07\ \x7b\x38\x3a\xd2\x33\x2e\x1a\x8d\xe7\x1b\x26\x13\x91\x31\xb1\xd1\ \xbf\x96\x2c\x59\xc2\x9c\x5d\x50\x50\x50\xb1\x05\x06\x06\x16\x1a\ \xb6\x70\x73\x77\xf9\x9a\x98\x98\x08\x6b\x5b\x6b\x18\xd9\x78\x60\ \x86\x6e\x3c\x46\xaa\x2f\x29\xd4\x44\xad\x64\x2a\xdf\x39\x9e\xb1\ \xff\x71\x1a\x66\x99\x98\x1a\x7f\xd8\xbd\x7b\x37\x82\x83\x83\xa1\ \x62\x18\x8c\x7d\x97\x5e\x94\xc9\x22\xfd\xb0\x0f\x34\x3c\x53\x57\ \x4f\xf7\x75\x5c\x7c\x1c\x6c\x6c\x6d\x30\x57\x37\x12\x5b\x4e\x3d\ \x86\xf7\xba\x6b\xa5\x92\xba\xff\x1e\xa6\x69\x84\xbe\xa1\xe1\xce\ \x6c\x8e\xca\xb3\x9d\x3b\x77\x32\x67\x3c\x57\xa7\x6c\xe1\xf0\xb5\ \xa7\xff\x8d\x93\x45\x5c\x60\xee\xb3\xb2\x8a\xd2\xa3\xf8\xf8\x78\ \xa8\x19\xda\xc0\x33\x69\x1f\x36\x97\x32\x1c\xb5\xe3\x36\x7c\x96\ \x1c\xf9\x43\xef\x02\x13\x96\x97\x97\xbb\x4b\x6f\x05\x4f\xdf\x05\ \x4b\xb6\x9d\x46\x0a\xb9\x1c\xaf\xb5\xd7\xe0\xb9\xf6\x2a\x3c\xd7\ \x5c\x23\xe8\x32\x6b\x3d\xe7\x5c\x86\xab\xcc\xff\xf4\x7d\x37\x7c\ \xa7\x77\x81\x09\xf3\x05\xdc\x55\x9e\x5e\x1e\x70\x0e\x5d\x85\x75\ \xc7\x1f\x21\x64\xeb\x4d\x38\xad\xbc\x92\x66\x05\x75\x19\x8e\x2b\ \xb2\xd6\x9d\x57\x64\xcc\x65\x09\xd9\x72\x13\x09\x7b\xee\x62\x8a\ \xd4\xef\x56\xe6\x2b\x27\x10\xf0\xac\x6d\xed\xac\x11\xb6\xfa\x08\ \x62\x77\xdd\x81\x75\xea\xa5\x2c\x4b\x2e\x32\x4b\xab\xac\x75\xb2\ \xcc\x9a\xcb\xd8\xe7\xb7\xf1\x06\xf4\x7c\xd6\xff\x22\x67\x2b\xcc\ \xe8\x36\x9c\x3a\x75\xea\x42\x6d\x1d\xcd\xbf\x9b\x8e\xdc\x80\xe7\ \xba\xeb\x30\x4e\xbc\x90\xcd\x79\x86\x11\x5d\xa6\x33\xc9\x35\xe7\ \x49\xee\x2f\x35\x53\xe2\xfc\x34\xfb\x37\xd2\xb5\x5d\xbb\x76\x6c\ \x0e\x97\x03\xdd\xd8\x73\x79\xc5\x9c\x85\x4e\xcc\x39\xe2\x6c\xfa\ \x7a\xde\x39\xd7\x55\x57\xe1\x1a\xbf\xf7\xcf\x4c\x96\x4e\x30\xe9\ \xd5\xc9\x08\xb7\x20\x86\xf5\xeb\xd7\xd7\x99\x7e\x35\xa5\x35\x54\ \xd1\xe1\x3a\xe9\x8c\x4e\xef\x65\x8e\x46\x44\x1b\xa2\x07\xd1\x9f\ \x18\x58\x42\x03\x88\x5e\xf4\xea\x89\xc6\x44\xbe\x83\x5e\x4a\xdd\ \x52\xaa\x4d\x54\xec\xf8\x0f\xad\x7d\x2e\x56\xc2\x0d\x87\xbb\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\xe7\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x01\xae\x49\x44\x41\x54\x78\x01\xbd\x95\x35\xb4\x15\x41\ \x0c\x40\x77\x9f\xbb\xbf\x06\xd7\x9e\x12\x69\xb1\x0e\xf7\x16\x1a\ \xdc\xdd\x3b\xdc\xdd\xa1\xc4\x1d\x5a\x68\xb1\x96\x8e\x06\xe7\xb9\ \xdb\x84\xe4\xfb\xcf\xfa\xb7\x3d\xe7\xae\x65\x72\xcf\xf8\x48\x00\ \x30\x28\x0c\xbd\xf8\xa3\x2c\x8f\x7e\xe7\x74\x7e\xa3\xa7\x56\xfc\ \xad\xc7\xf3\x43\x2b\xae\x29\x7d\xe5\xf3\x40\xe9\xce\x1d\xa0\x27\ \x7d\x5b\x89\x13\xba\xd2\xf4\xe1\xc3\x3c\xd9\x30\x4e\x10\xba\x52\ \x82\x25\x4f\x35\x88\x8f\x56\x15\x53\x9f\xf2\x24\x9e\xac\x16\x4f\ \x1d\x38\x00\xf4\x9f\xf2\xb5\x6a\x1c\x7f\x18\xf4\x83\x96\xbc\x48\ \xff\x0f\x1d\x42\x0e\x03\x3d\x53\xfb\xf7\x89\x7f\x7b\xf6\x88\xc2\ \xcd\x9b\x2d\xb5\x1a\x73\xf9\x48\xd5\x9a\x1d\xa2\x9a\xa1\x74\xdf\ \x01\x64\xbf\xf8\xb7\x79\x4b\xf3\xcf\xba\xb5\x8d\xc2\xb5\xab\xf5\ \x57\x7e\x8f\x20\xa9\x95\x59\x41\xc2\x0e\xf1\x41\x48\xef\xd9\x0b\ \xa9\xb5\x6b\xc4\xbf\x55\x2b\xeb\xbf\x56\xac\x28\xe7\x8f\x1e\x2d\ \xbf\xf2\x7b\xc5\x27\x97\x6b\x8c\x95\x79\x4c\x72\x41\xcd\x4f\x1d\ \xd8\x0f\xe9\x1d\x3b\x20\xbd\x6b\x37\xa4\x16\x2f\x86\x3f\xf3\xe7\ \x42\xfa\xe0\x41\xa0\xf8\x97\x58\x6c\x9c\xe9\x79\xfc\x24\xe0\x9b\ \x42\x35\x2e\x5f\xbd\x0a\xa9\x99\x33\xdb\x99\x35\xbb\xeb\xfd\x5f\ \x1b\x33\xa0\x74\xe2\x84\xf5\x79\x4c\x49\x1d\xa2\x5e\xc2\x14\x43\ \x4b\xae\x2a\x2d\x5f\xbc\xc8\x04\xc8\x8c\x99\x50\xb9\x77\x8f\xde\ \x39\x54\xde\x78\x1e\xab\x49\x09\x92\xe2\x40\x41\xf9\xec\x59\x4d\ \xb9\xde\x3c\x1e\x8d\xfd\x4b\x85\xb8\x94\x6a\x24\x3e\xda\xed\xd3\ \xf0\x49\xdf\x16\x6a\xcc\xe4\x9d\xc9\xf4\xe4\x7b\xc5\x7b\xa7\x43\ \x33\x6e\x38\x8f\x69\x05\xf2\x24\x3e\x16\x3c\x6e\x69\x3f\x7e\x20\ \xcb\x13\x25\x49\xf2\x72\x9e\xca\xf2\x04\x2c\xf3\x15\x99\x64\x7a\ \x1e\xe3\xe5\x40\x02\x48\x02\x19\x8e\x4c\x50\x61\x24\x92\xec\x28\ \x67\xd7\x15\x73\x28\x01\xf1\xa8\xd4\xd8\x87\xb8\x10\x79\xc8\xcf\ \xbc\xff\xc9\x10\xae\xf8\xcf\xc7\x12\x97\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ \x00\x00\x05\x22\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x04\xe9\x49\x44\x41\x54\x78\x01\xb5\x93\x03\x80\xec\xc8\ \x16\x86\xbf\xaa\xa0\x31\xbe\x63\x3d\x0d\xaf\x9f\x6d\xdb\xb6\x6d\ \xdb\x6b\xdb\xb6\x6d\xdb\x1c\x5f\x73\xcc\x66\xba\x93\x54\x6d\x6d\ \x06\x6b\xef\xfe\xcd\xe8\x3b\xff\x11\x2f\x95\x04\x4f\xa3\xfd\x3f\ \xf0\xbe\x6f\xa5\xcb\xcb\x3e\xe1\xdb\xce\x9b\xb4\x94\x0d\x80\x10\ \x5a\x8d\x39\xbe\x7f\x67\x59\x2a\x7d\xfe\xaf\xaf\xba\xe6\x84\xe7\ \x04\x3e\x7b\x79\xf7\xb7\xd2\x65\x65\xff\x59\xf3\xee\xf7\x54\x77\ \xfc\xe8\x47\xc9\x78\x5d\x9d\x94\xb1\x18\x20\x50\x85\x3c\xf9\x91\ \x11\xbd\xed\xdc\x73\xbd\xbb\xcf\x3b\x77\xda\x04\xf8\xdb\xe7\x07\ \x06\x4f\x78\x46\xf0\xf9\x1d\xed\x07\xb6\x7d\xec\xe3\x5f\x6b\xff\ \xcb\x5f\xaa\xd0\x5a\xe4\xb6\x6d\xc3\x1b\x1d\x47\xab\x10\x3b\x11\ \xc7\x4e\x26\x89\x37\x36\x10\xab\xad\x25\x2c\xfa\xf4\xff\xf5\x2f\ \xd9\x6d\x37\xdc\x70\xc2\xa7\xd7\xad\xff\x19\x8f\x92\xf5\x78\xe8\ \x8a\x1f\xfd\xf8\x3b\x9d\x7f\xfd\x5b\x45\xba\xbf\x5f\xcc\xdc\x75\ \x37\xde\xd8\x04\x2a\x08\x90\xb6\x83\x15\x8b\x45\x01\x50\x0a\x95\ \xcf\x23\x2c\x49\xf3\x17\xbe\xe8\x86\xc3\xc3\xaf\xf9\xf0\xf4\xd4\ \xab\x4e\x9f\x9c\xba\xe0\xf1\xe0\x28\xfd\xe6\x15\x2b\xfe\xb8\x6a\ \xef\xbd\x2b\x26\xae\xbd\x8e\x54\xff\x80\x81\x68\x54\xa8\x40\x08\ \xac\x92\x12\xac\x78\x1c\x0c\xd8\x32\x40\x54\x00\x5a\xa1\x0a\x1e\ \xf5\x1f\xf9\xa8\xcc\xdc\x73\xcf\xaa\x77\x3a\xce\xf6\xf3\x46\x46\ \xef\x07\x90\x2c\x68\xba\x7a\xd9\x2e\xab\x0f\x39\xb4\x32\xbd\x6e\ \x1d\x7e\x3a\x15\xa5\x2a\x5d\x17\xa4\x88\xe0\xa1\x71\x1d\x1a\x97\ \xba\x58\x44\xe5\x32\xe8\x42\x0e\x7c\x0f\x1d\xf8\x84\x99\x34\x5d\ \x7f\xff\xbb\x3d\xdd\xdc\x74\xc0\x63\x1c\xef\xfa\xe9\x4f\x7e\xeb\ \x5d\xef\xff\xc0\x97\x1b\xde\xf2\x56\x3b\x33\xb4\x2e\x72\x25\x6d\ \x0b\x61\x59\x68\x03\x4c\xb4\xb6\xe2\x94\x96\x62\x97\x95\x22\x75\ \x80\x28\x64\x91\x92\xe8\x3a\x4a\xe3\xa7\xd2\xc6\x48\x1d\x99\x6d\ \x5b\x9d\xd6\xf6\xf6\xd9\x9b\xef\xbf\xff\x0e\x1b\x20\x57\x5a\xf2\ \xa5\x57\x7c\xe6\xb3\x6e\x61\x62\x02\x84\x46\xa0\x23\x27\x96\x84\ \xd8\xcb\x5a\x98\xeb\xed\x61\xdd\x19\x67\xa0\xd0\xb4\xbd\xfb\x9d\ \xb4\x7c\xe0\xed\x84\xc6\x75\xe0\x07\x04\xc1\x14\x32\x59\x8a\x70\ \x63\xb4\xbd\xe7\x7d\xf2\xd6\x4b\x2e\xfa\x2d\x70\xa0\x04\xc0\x75\ \xdf\x5e\xda\xdc\x8c\xca\xe7\xb0\x6c\x3b\x72\xa5\x73\x29\xcc\x5f\ \xb2\x43\x03\x8c\x5d\x7a\x89\x6e\xed\xea\xda\xd4\xb9\x62\xe5\xa5\ \xd9\x2d\xdb\xc3\xd1\x9b\xef\xc6\xa9\xa9\xa7\x30\x39\x81\x10\x02\ \x61\x3b\x28\x53\xa2\xea\xf6\x76\x3c\xdf\x6f\x5e\xaa\xb1\x1f\xaa\ \x12\x53\x4f\xa2\x8f\x63\x61\x09\x1f\xe9\xcd\x61\x05\x59\xee\x3d\ \xe9\x44\x6a\x5a\x5a\x2f\x5b\x7b\xfc\xf1\x6d\xdd\xc7\x1c\xf3\x31\ \x27\x9b\xfd\xc0\xfa\xcb\xaf\xc4\x6d\x6a\xc5\x2a\xaf\xc2\xae\xae\ \xc3\x2a\x2d\x03\x21\x71\x92\x49\x7c\xbf\x68\x2f\x81\x85\x40\x44\ \xbf\x0f\xbb\xb5\x25\x52\x15\x11\x7e\x1e\x82\x02\xd5\x26\x58\x6a\ \xdd\xba\x3d\x58\xd0\x03\xa3\xc3\x37\x69\xd0\xb8\x09\xb4\x1d\x43\ \x4b\x89\xd6\x1a\xad\x14\x52\x4a\x16\x15\xd1\x1d\xcb\xf6\xb4\xef\ \x27\xcc\x38\xa1\x4d\xbd\x54\x79\x0d\xd2\x0b\x50\x96\x43\xfd\xdb\ \xde\x4a\xe6\xee\xfb\x8f\x38\xfe\x8d\xaf\x5f\x0d\xd0\x96\xc9\xee\ \xdf\xf0\xbd\x6f\x8b\x30\x97\xc6\x4f\xa7\xd1\xc2\xc2\x10\xa3\x46\ \xca\x82\x87\xeb\xb8\xe1\x12\x38\x61\xdb\x3b\x72\xa3\xa3\x1d\xf1\ \x8a\x72\x94\x01\x5b\xe5\xcb\x50\x0a\x42\xaf\x48\xfd\xa7\x3e\x4b\ \xbc\xbd\xbb\xbb\xfd\xac\x33\x0b\x68\xad\x5a\x7e\xf6\x53\xab\xfa\ \xf5\x6b\xc9\xad\xeb\x45\x3a\x36\x41\x2e\x8b\x10\x92\x78\x43\x03\ \xa9\xe1\x61\x92\x89\x44\x7e\x09\xcc\xd4\xf4\x21\x1b\xce\x3a\x6b\ \xbf\x35\xbf\xf8\xb9\x90\x5e\x01\x6b\x99\x99\xe1\xd2\x72\xfc\x6c\ \x8e\x62\x26\x47\xb2\x7b\x15\xed\xff\x7d\x8d\x34\x00\x29\x74\x40\ \x7a\xf3\x56\x64\xd1\xc3\x2a\xa9\x44\xa5\x72\xa8\xb0\x88\x53\x5d\ \xcd\xfa\xb3\xce\x22\xe9\xba\x77\x2e\x81\xff\x7a\xf6\x39\x07\x34\ \x4f\x4f\xed\xb5\xe2\x5b\xdf\x72\x6c\x33\xaf\xc2\x71\x08\xb2\xc6\ \x89\x96\xc8\x50\x90\xdd\x39\x8c\x3f\x9b\x5a\x98\xdb\x00\x27\x66\ \xe1\xb8\x20\xb3\x29\x10\x56\xe4\x56\x79\x1e\xc3\x7d\x3d\x7a\xc5\ \x5c\xfa\x8b\x8f\x59\xe9\x1f\x24\x93\x99\x4c\x4f\xcf\x07\x9b\x3e\ \xfd\x69\x01\xa0\x11\xa0\x41\x0b\x81\x2a\x06\xc8\x44\x02\xcb\x74\ \xdd\x72\x5d\x08\x43\x08\x82\x68\x03\x63\xf5\x4d\x24\x5f\xf9\x2a\ \x06\xff\xff\x5f\x8a\xd9\xdc\xf9\x9f\xbc\xf8\xe2\xe3\x1f\x03\x3e\ \x75\x62\xe2\x9e\xef\x15\x0a\x22\x97\xcf\xbd\xab\xfa\x6d\x6f\x07\ \x88\x6a\x87\xd6\x84\xa6\x24\x80\x81\x3a\x08\x34\x84\x41\xb4\x91\ \xf1\xa6\x16\x03\x7d\x25\x9b\x0f\x3d\x94\xe9\x9e\x9e\x99\x8f\x5e\ \x7d\xcd\x5b\x00\x0d\x68\x0b\x10\x40\x1c\x48\x1c\x3d\x35\x75\xff\ \xe7\x53\xa9\x55\xe3\xf7\xde\xd3\x5e\xf3\xe6\x37\xe3\x54\x56\x1a\ \xb7\x3e\x4a\x85\x08\xdb\xc2\x8a\xb9\x98\x32\xe3\x98\x26\x97\xb6\ \xb7\x19\xbe\xcf\xd0\xbe\xfb\x32\xdc\xdb\xe3\xfd\xaf\x7f\xe0\xed\ \xdb\x3d\xaf\x00\x04\x8f\x06\xcb\x85\x5f\xe7\xf8\xe1\xe1\xeb\x3a\ \xaa\x2a\xe5\x03\xb7\xdf\xfa\x06\xe5\xe5\x85\x6d\x86\xdf\xa9\xaa\ \x8a\x9a\x63\x55\x54\x20\xca\xcb\xc8\xf9\x3e\x0f\x5c\x73\x35\x57\ \x9d\x73\x36\xa9\x7c\xfe\xec\x6f\x5f\x75\xf5\x77\xb7\xe4\xf3\x29\ \x20\xbb\x00\x56\x82\x25\x21\x17\x9a\x59\x06\xd8\x25\xf1\x78\xc9\ \x5f\x7f\xff\xbb\x4b\x95\x52\xaf\xca\xe4\xb3\x76\xc1\x94\x09\x20\ \x16\x8b\x91\x4c\x24\x95\x63\x3b\xc3\xa7\x9c\x78\xd2\xa7\xfb\xb6\ \x6c\x99\x01\x66\x01\x0f\x28\x00\xfa\xf1\x60\x1e\x17\x64\xb1\x07\ \xf6\xaa\x55\xab\x2a\xcd\xa7\x29\x6e\x82\x8d\x1a\xdd\x72\xcb\x2d\ \xe3\xe9\x74\xda\x5b\x74\xb7\x58\x57\x1e\xa5\x87\x00\x07\x89\x40\ \xf1\x00\xb8\x17\x98\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ \x82\ \x00\x00\x05\x71\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x05\x38\x49\x44\x41\x54\x78\x01\x7c\x94\x03\x74\x2b\x69\ \x1c\xc5\xbb\xe6\xc1\x7a\x0f\x77\x0f\xd6\xb6\x6d\x7b\x9f\x6d\xdb\ \x2a\x9e\xad\xda\xb6\xcd\xa8\x4a\x52\xb7\xb1\x55\xb7\x31\x66\x82\ \x1b\xd5\xba\xc9\x78\xe6\xf7\xfd\x1d\x00\x60\xce\x2d\x30\x90\x72\ \xb7\xf7\xb8\xd0\x96\x05\xdc\x15\x10\xf0\xdf\x5d\x73\x3d\x9b\x75\ \xc3\xa3\x3b\x01\xdc\x35\xe5\xfa\xd1\x27\x9f\xf9\xf7\xe3\xaf\x7e\ \xdb\xf3\xff\xb2\xf5\x81\xcb\x97\x2f\xdf\xff\xcb\x0b\x01\xaf\xbf\ \xe0\xb9\x7f\xc7\xa4\x11\x81\xb3\x8c\x98\x0d\x9d\x3c\x7f\xf6\xda\ \x8d\x84\xb3\x39\xc5\xd4\x9e\x06\x66\xa7\xbd\xb7\x6f\x18\x02\x89\ \x1a\x49\x45\xcd\xb8\x18\x53\x36\xbc\xeb\x64\x54\xc9\xd3\x6f\xad\ \xff\x77\x3e\xf8\x9c\xd0\xad\x9b\x8e\xfc\x5d\x54\x54\xa5\x10\x2a\ \x86\x20\x1a\x24\x60\x71\xc0\x27\x89\x6a\x00\x05\x4c\x25\x52\x18\ \x6a\xd4\x74\xf4\xa3\xb4\x96\x85\xdd\x07\x2f\xa7\x78\xbe\x7d\x6c\ \x26\xdc\xbf\xf3\xbb\xe5\x03\x6f\xdd\x7a\x62\x65\x59\x75\x23\xf8\ \xbd\x26\x08\xd4\xa3\x64\xb7\x42\x4b\x8a\x34\x3a\x67\x0b\x4f\xe5\ \xaa\x69\x57\xba\x8a\x99\x72\x57\x61\xb3\xc2\xde\xc6\x53\x10\x52\ \x55\x1f\x14\xea\x01\xdc\xb8\x19\x47\x9d\x06\x1f\x07\x53\x28\x14\ \xff\x45\xc0\x17\x9f\xb5\xb0\x3b\xad\x04\x00\xc5\xb0\xcd\xaa\xd5\ \x1b\x20\xea\xd5\x43\x38\xe4\x40\xb7\xc6\x0a\xa6\xd4\x84\xba\x4e\ \x0d\x3a\x84\x6a\x48\x3d\xd6\x8f\x68\x0d\x76\x8b\xd9\x48\xca\xe4\ \x4a\x04\x87\xdc\x48\x9d\xf4\xfe\x3f\x6f\x52\xfd\x59\xf5\xe8\x9e\ \x8c\xec\xb2\x1a\xf8\x65\x23\x6d\x16\xf0\xa5\x2a\x74\x29\xb4\x90\ \x68\x01\xf1\x88\x13\x2c\xa9\x11\x4c\xe1\x08\xfa\x06\x47\x60\xb6\ \x58\xe1\x95\x5e\xa7\x75\xc8\xe5\x32\x54\x56\x51\xf0\xc3\x6f\x9b\ \xff\x1f\x83\xdf\xe5\xdd\xf9\xac\xfd\xfe\xa7\xed\x3f\x49\x24\x0a\ \x3f\xd5\x6a\x71\x70\xa4\xfd\xe8\x54\x99\x21\x1c\x76\x41\x34\xec\ \x84\x60\xd0\x81\x26\x89\x11\x5c\xc5\x28\x5a\x38\x32\x14\xd0\x38\ \xa8\x6e\xe2\x82\x2f\x56\x42\x2c\x16\x11\x5c\x2e\x17\x57\xae\x44\ \x94\x7b\x73\x35\x2d\x79\x09\x09\xb9\x97\x09\xd2\x09\xa3\xc9\x62\ \x67\x73\x14\xa8\x17\x9b\x20\x1a\x05\x84\x5e\xe8\x90\x13\xdd\xbd\ \x04\xe8\x02\x1d\x84\x1a\x1d\x2c\x36\x02\xca\x01\x3d\xea\x5a\x65\ \xa0\x32\x79\xe0\x70\x38\x8e\x66\x26\x13\x51\xd1\x89\xda\x47\x1f\ \xfd\xe9\xe5\xa9\xc9\xbb\x93\x46\x6d\x2c\x93\xf6\xe9\x71\xbb\xb8\ \x9b\x48\x6b\x50\xa3\x8a\x67\x44\xab\x8a\x40\x4f\x9f\x1d\x5d\x5e\ \xa8\xd0\x80\x36\x99\x1e\x76\x87\x13\x53\x25\x57\xaa\x51\x51\x5b\ \xef\x2a\x2c\xa9\x40\xd0\x85\x48\x4f\x38\xb6\xfd\x31\x15\xfc\x60\ \x4a\x66\x65\x93\x66\xc4\x82\x4a\xb6\x8c\x8c\xae\x95\x23\xb9\x5e\ \x83\x1c\xf6\x10\x6a\x38\x3a\xd4\x71\xb5\xc8\x67\xf7\x43\xa0\x31\ \x60\x5c\x4e\x97\x0b\x24\x49\x40\x22\x95\x23\xbb\xb0\xda\x15\x11\ \x97\xee\xda\x78\xe8\x16\xde\xfe\x76\xe7\x9a\xa9\xe0\xfb\x4f\x5c\ \x49\xa3\x67\x33\xe4\x48\xab\xee\x22\x99\x5c\x0d\xe4\x03\x46\x14\ \x32\x95\xb8\x51\x26\x46\x3c\x45\x8e\x9b\xa5\x42\x14\x34\xc9\x61\ \x32\x5b\x41\x12\x36\x10\x24\x89\xd1\xd1\x51\x08\x85\x42\x50\xa9\ \x54\xd7\xfe\xc0\x9b\xf8\x69\xdd\x45\xfc\xbe\xe8\xe0\xb2\x69\x31\ \x3e\x75\x31\x31\xf5\x44\x4a\x3b\x76\x46\x34\x92\xfd\x23\x7e\xcb\ \xb4\x06\x33\xb2\xe9\x12\x04\x66\x74\x22\x30\xad\x1d\x47\x12\x58\ \xb8\x96\xd9\x8c\x33\xf1\x14\xb0\xba\x24\x50\xab\x94\x68\x6d\x6d\ \x45\x51\x61\xa1\x73\xe5\xee\x8b\xf8\x66\x59\xa0\xeb\xaf\xbf\x76\ \x7c\x31\x0e\xf6\xf5\xfc\xbe\x43\x97\x77\x1d\x8f\xa2\x22\x38\x99\ \xe5\x34\x98\xac\xae\x09\x97\x9d\x2e\x30\x3a\x14\xd8\x19\x4a\xc3\ \x96\x5b\x54\x2c\x3a\x53\x8e\xb7\x76\x67\x23\x34\x9d\x8a\x9e\xee\ \x4e\x30\xe8\x54\x9c\xbb\x12\x4e\xbe\xb7\x28\x18\xbf\xaf\x3c\x21\ \x7c\xfd\xe1\x8f\x9f\xf4\x81\x37\x6e\x8c\xbc\xc7\x7b\xf2\xc6\x7d\ \x6f\x3c\x7f\xe0\x7c\x7a\xff\x8e\x50\x06\x68\x2c\x9e\xc7\x5b\x0b\ \x2c\xbe\x5a\x75\x82\xc1\xe6\xe2\xf7\x93\x05\x58\x71\xae\x1c\x8b\ \x83\x0a\xf1\xe5\xde\x0c\x1c\xbd\x9e\x0f\x2a\x85\xe2\xb1\x36\x0f\ \x47\x02\x2f\x91\xdf\xaf\x3a\x83\x5f\x17\xed\xbf\x32\x1e\x81\x69\ \x33\x62\xc3\xf6\x90\xb3\xbb\x6f\x54\x61\x69\x70\xa1\x93\xd1\xd4\ \xee\xec\xef\xd3\x40\x2a\x95\xe0\x7c\x74\x29\x3e\xde\x93\x89\x3f\ \x8e\x66\xe3\xdb\x3d\xa9\x78\x65\x5d\x1c\x76\x9d\x4e\xf1\x40\xf3\ \x11\x16\x16\x46\xac\xd8\x16\x8c\x1f\x16\x1d\xee\x7f\xeb\x15\x7f\ \xa9\x7d\xf9\x65\xe0\xdd\xe3\x93\xc9\x07\xf7\xe8\xf9\xd5\x3b\x2f\ \x32\x57\x9f\xaf\x40\x7c\x4e\xad\xa3\xb3\xad\xd9\x11\x91\x98\x87\ \x0f\x36\xc7\xe1\xab\xdd\xa9\xf8\x6c\x5b\x02\x3e\xda\x18\x03\xf7\ \x34\xa2\x26\xfd\x77\x4b\xee\xf9\x37\x75\xea\xd4\x5f\xf5\x2d\x5d\ \xff\xdd\xc2\x2b\xfe\x1b\x58\x84\x55\x81\x52\x97\x03\x52\x59\x01\ \xc6\xea\xd6\xd6\xbc\x40\x09\x19\x20\x0e\xf4\x4d\x6c\xbc\xbc\x64\ \xf5\xce\xff\xeb\x37\x6c\xfa\xef\x97\x33\xe5\xaf\x44\xc4\xd4\xdf\ \x06\xb1\x93\xff\x68\x44\xf4\xfd\xd1\x0c\x69\xff\xa3\xee\x5f\xf7\ \xc7\x3c\xa4\xe6\x7f\x46\x71\xdb\xff\xf0\x84\xb2\xff\x5a\x46\x81\ \x33\x80\xfa\x8c\x25\x15\x15\xe5\x19\x18\x54\xd8\xd1\x8b\x4d\x16\ \x25\x6d\x63\x39\x20\x6d\x00\xc4\x11\x09\x71\x59\x5b\x33\x8b\x3b\ \xbf\x05\xe4\xcd\xf8\xef\x9f\x37\xf3\xbf\x6b\xda\xa4\xff\xce\x49\ \x3d\xff\x5d\x13\x3b\xfe\x7b\x25\x02\xc3\x33\xb6\xf6\xbf\x9d\x57\ \xd6\x7d\x11\x19\xdb\x6e\xa0\x7a\x17\x51\x51\x51\x43\x6e\x06\x31\ \x71\x48\x49\x89\x59\xd0\xb3\x49\x29\x69\xcb\x89\x49\x0b\xe8\x03\ \xd9\xce\x0c\x0c\xca\xa5\x2e\xde\x49\x2b\x3d\xc3\x0b\xcf\x7a\x47\ \x95\x3d\xf0\x8a\x28\x7d\xec\x1e\x5c\x70\xd3\xc1\x33\x79\xbf\xbe\ \x89\xc7\x14\xa0\x9a\x78\x20\xb6\x11\x93\x96\x06\xaa\x17\x95\x00\ \xb2\x99\xf1\x55\x4d\x8c\x42\x42\x2a\x7c\xae\xae\xae\x52\x40\xb6\ \x12\x10\x83\x2c\x71\x04\x62\x3f\x20\x0e\x02\x62\x2f\x20\xb6\x06\ \x62\x6d\x90\xbc\xbc\xa6\xb1\x24\x28\x6c\xf1\x54\x4d\x98\xd8\x38\ \x2d\x8d\xd5\x33\x37\x97\x9d\x41\x81\x81\x03\xa8\x99\x13\x86\x1d\ \x1c\x1c\x38\x40\xe2\xa1\xd0\x22\x17\x1b\x06\x00\xe9\xe8\xdb\x12\ \x93\xe1\x5a\x62\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \ \x00\x00\x03\x8c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x03\x53\x49\x44\x41\x54\x78\x01\xb5\xd3\x03\x90\x63\x59\ \x14\x06\xe0\xac\x6d\xb3\xb4\xb6\xed\xdd\xc2\xd8\x4e\x7a\x6c\xdb\ \x46\x7b\xec\xd8\x4e\x7b\xac\xb6\xcd\x24\x83\xb6\x11\x2e\x5a\xff\ \xde\xfb\x3a\x79\xc9\xd8\xa7\xea\xab\x73\x2a\xe7\xd5\x9f\x47\xce\ \xd5\x4a\xa6\x10\x0f\x97\x29\xc5\x10\x4b\x05\x10\x49\xf9\xd7\xc2\ \x1e\x23\x94\xf0\x37\x72\xae\x57\x7a\x93\x1a\x37\x53\x0e\x87\x03\ \xf4\x44\xd8\x00\x1e\x8f\xf7\xe5\x18\xde\xe8\x7a\x6e\x00\x97\x6a\ \xe0\x06\xf0\x1a\xa6\x4c\x9b\xd2\xa4\xd6\x2a\x40\xab\xb3\xb3\x83\ \xe8\xbc\xaa\xf6\xf6\x76\x46\x6b\x6b\x0b\x34\x7a\xa5\x2f\x78\xc0\ \x80\x7e\xd9\xa7\x4e\x9f\x82\x3f\xb5\x56\x89\xf8\xc4\x33\xa0\x95\ \x99\x99\x81\xa8\xe8\x28\xc4\xc4\x44\x23\x26\x96\x88\xf1\x8a\xa1\ \x9d\xd9\x25\x24\xc6\x33\xc1\x2a\xad\xdc\x17\xdc\x6f\x40\xdf\xf4\ \x68\x72\xc0\xba\x75\x6b\x19\x21\xa1\xc1\xcc\x01\x1d\x1d\x1d\x68\ \x6b\x6b\x21\x5a\xe1\x74\xda\xe1\x76\xbb\x08\x27\x99\x1d\x70\x38\ \xec\x84\x0d\x36\x5b\x1b\xd1\x0a\x97\xcb\x01\x8b\xd5\x72\xf1\xad\ \xe8\xd3\xb7\xf7\xb1\xa3\xc7\xe3\xb1\x6b\xd7\x2e\x06\x0d\x2d\x2f\ \xbf\x00\x9b\xcd\x86\xbc\xbc\x1c\x08\x84\x7c\x28\x54\x0a\x86\xd2\ \xd3\x25\x32\x09\x43\xad\x51\x21\x25\x25\x09\x76\xbb\x0d\x49\x29\ \x09\x30\x45\xe9\xfd\xcf\xb8\x4f\x32\xb9\xac\x76\x3e\x9f\x8f\xcd\ \x5b\x36\x42\xaa\x10\xa1\xa1\xa1\x1e\xb9\xb9\x59\xc8\xc8\x4c\x43\ \x7a\x66\x2a\x32\x32\xd2\x18\x99\x59\xe9\x9e\x9e\x86\xac\xec\x9e\ \xb9\xbc\xa2\x1c\xe9\x19\xe9\x10\x88\x0f\x22\x22\xda\x10\xc6\x06\ \x0f\x1b\x31\x64\xdf\x41\xfe\x81\x76\x85\x42\x81\xed\x3b\xb7\xa1\ \xaa\xaa\x0a\xc5\xc5\x85\x30\x9a\x0c\xe4\xac\xc4\x90\x48\xe9\x2b\ \x27\x82\x58\x2c\x80\x88\x10\x53\x52\x21\xe4\x4a\x09\x0c\x46\x2d\ \xca\xcb\xcf\xd3\x50\x7a\xb6\xe1\x9e\x48\x36\x78\xc9\xa6\xcd\x1b\ \xdc\x34\x40\xa3\x57\xe0\xc8\x89\x93\x08\x58\xbc\x0d\x5f\x4d\x56\ \x5c\xd3\x4f\xd3\xa4\xd4\x15\x77\xa3\xe7\x87\xa7\xd2\xe0\xe1\x0b\ \x16\xce\xb7\x29\x35\x32\x98\xcd\x65\xd8\x12\x12\x8e\xe3\x39\x55\ \x38\x91\x5b\xdd\x23\xaf\x86\xa8\xc5\xc9\xfc\x3a\x9c\x2a\xa8\xa7\ \x9d\x9d\xaf\xa6\xdf\xec\xdd\x36\x1a\xfc\xd7\xcc\x59\x33\x9b\xe8\ \x13\xad\xad\xad\x41\xff\xd9\x7b\x10\x93\x56\x81\x60\x53\xc9\x2d\ \x91\x9f\x3e\x8f\xdf\xa7\xec\x6a\xa6\xc1\xaf\x8c\x18\x39\xac\x56\ \x2c\xe5\x23\x2b\x27\x13\x43\xe7\xed\x42\x74\x5a\xf9\x2d\x07\xef\ \x31\x66\x76\x7f\x3f\x61\x6f\x1e\x87\xd6\xd8\x71\x01\x35\x06\x93\ \x0e\x67\xe2\x4f\x22\xf0\xa0\x09\x51\xa9\xb7\x16\xbc\xff\x88\x15\ \x21\x8a\xa4\x4e\x7a\x17\x98\xe0\x41\x83\x06\x9c\xd7\x19\x55\x98\ \xb6\x78\x03\x24\x91\xf1\x90\x9e\x3a\x87\x20\x63\x09\x02\x8d\xc5\ \x08\x34\x94\x10\xb4\xb3\xb3\xff\xce\x4f\x31\x64\xe4\x36\xcc\x0e\ \x8d\xfc\x87\xde\x05\x26\x98\xcb\x1b\xad\x0f\x0c\xda\x8c\xf5\x3b\ \x94\x30\x24\x5f\xc0\xce\x38\x33\xd6\x69\x8b\x7a\x68\xa8\x42\xac\ \xd5\xf8\xe6\xf5\x1a\x76\xc7\xda\x19\x6b\x86\xe8\xc4\x39\xfc\x3a\ \x3e\xcc\xc2\xf1\x16\x97\x3b\x7a\xc5\xca\x55\xcb\xb1\x4b\x97\x80\ \x83\x47\xad\x58\x2e\x2f\xf0\x51\xe4\x33\x7d\x19\x3b\xd3\xce\xee\ \xd8\xdf\xc2\xa2\xca\x30\x2b\x24\xa2\x9d\x9c\x2d\xcf\x9b\xfb\xd0\ \x6f\xbf\xfd\xd6\x77\xfa\x8c\xa9\x5d\xd1\x49\x65\x08\x34\x95\x62\ \xbe\x38\xcf\x4f\x2e\x63\x9e\xa7\x53\x0b\x2e\xd9\x05\x9a\x4a\x18\ \x7f\x8d\x5b\x5f\xc3\xf1\xab\xd7\x9e\x7f\xfe\xf9\x11\x23\x47\x8d\ \xc4\x4c\x7e\xce\xe5\x0e\x66\x63\xc6\xc1\x1c\x22\x9b\x9d\x2f\xdd\ \x6d\xd4\x15\x63\xa3\xf0\x64\xe7\x5f\xc3\x67\xec\x20\x79\xf7\x7a\ \x83\x9f\x24\x3e\x7f\xff\xfd\xf7\xd6\xd3\xaf\xe6\x56\x7d\x36\x78\ \x4d\x29\xc9\xf9\xc6\x93\xc7\xd6\xc3\xc4\xb3\xc4\x9b\xc4\x07\xc4\ \x47\x37\xe9\x43\xe2\x6d\xe2\x35\xe2\x11\xce\x55\xea\x5e\xe2\xbe\ \x5b\x74\x0f\x71\x77\xeb\x7f\x02\x2c\x41\xb1\xca\xec\x9a\x0b\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x04\x79\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x04\x40\x49\x44\x41\x54\x78\x01\xa5\x93\x03\x74\x24\x5b\ \x18\x84\x7b\x6d\xdb\xb6\x6d\xdb\xb6\x6d\xdb\x8e\x6d\x27\x6b\x7b\ \xe3\xb5\x6d\x33\xb6\xcd\xae\x57\xf9\xcf\x64\x9e\xdd\xe7\xd4\xf0\ \xf6\x77\xeb\xd6\x5f\xad\xfc\xdf\x6b\x5b\x49\x87\x0a\xeb\x4a\x5a\ \x55\xf9\x57\x37\xed\x5b\x9b\x6f\x16\x75\x86\x0a\xa4\x32\xa9\x2c\ \x2a\x98\x3a\x4f\xcd\x52\x78\x6d\x2a\x63\x3f\x7d\x5b\x69\x87\xe5\ \xff\x06\xe8\xe7\x73\xb2\x6b\x22\x62\x75\xb3\x91\xfe\x16\xc8\x8a\ \xa6\x62\x80\xf4\x37\x50\xa3\x77\xaa\xf7\xdd\x07\xa4\x70\x4d\xd0\ \xb6\xea\xba\x6f\xb7\x95\xb1\x7f\xf4\x4f\xa0\x16\x04\x46\x23\xe9\ \xa2\x8a\xa4\xf3\x48\x0a\x5c\x8f\xa0\x37\xf3\x11\xf8\x7a\x2e\xc2\ \x3f\x2e\xc6\xbb\x0b\x9b\xf1\xd5\xdd\x10\xdf\x6f\xdc\xc6\x17\xaf\ \x8b\x20\x14\xdb\xca\x3a\x60\x7b\x49\xbb\xc1\x5b\x4b\xd9\xf7\xdc\ \x52\xca\xb6\x2f\xdf\xbb\xff\x0e\xfa\xd4\x77\x78\x22\xb2\xc2\x91\ \xe0\xbf\x16\x9f\x1f\x4d\xc5\x87\xfb\x13\xe5\xdd\xff\xe5\x6c\x44\ \x7c\x5a\x8a\xd7\x67\xd7\x43\xb7\xa1\x35\xb6\x13\x46\x81\x50\xc2\ \xed\xb4\xdf\xb9\x51\x24\xe1\x7d\x7e\x75\xfc\x63\xd6\x0d\x62\xc0\ \x23\x87\xbe\x5f\x88\x97\x37\x46\xe1\xcd\xed\xb1\x22\xc2\x11\xf0\ \x6a\x2e\x22\x3f\x2f\x13\x78\xd4\x87\x75\x38\xb3\xd0\xe0\xb7\x40\ \xec\xac\xbd\xfb\xe1\xd6\xd2\x76\x25\x7f\xeb\x36\x08\x89\xa7\xd4\ \xcc\x88\xad\xf8\xf1\x7c\x26\xbe\x3f\x9b\x21\xc0\xb7\x77\xc6\xe3\ \xdd\xdd\xf1\xf0\x7b\x31\x1b\xc1\x6f\xe7\x23\xe4\xdd\x42\x44\x7f\ \x5d\x2e\x70\xfd\xa6\x36\x5a\xb0\x4e\x3d\x1b\x1c\x58\x59\x26\xe1\ \x77\xc3\xe2\x30\xd2\x91\xf6\x52\x1c\x31\x4f\x11\x37\xc0\xfb\x7b\ \x13\x24\xdb\xa8\x2f\xcb\x11\xef\xb7\x5a\xde\xf9\x1d\x1f\x3d\x36\ \x62\x5b\x39\xba\xac\xe0\x80\xdd\x55\x6d\xc5\xf1\x91\xed\xc3\x54\ \x9d\x0d\x05\x57\x6a\xc1\xfb\xd7\xe5\xf3\xe0\xa4\xc1\x06\xc8\x71\ \x39\x2c\xc9\x94\x12\xd0\xf3\x6b\x23\x60\xba\xb3\x24\x4c\x76\x94\ \xc4\xcd\xf3\xbd\x24\xff\xb3\xcb\xf6\xc1\xac\x83\x05\xbe\xdf\x5f\ \x86\xd0\xd7\x2b\x61\xdb\xc7\x02\x87\xa6\x1c\x80\xd9\xae\x52\x7e\ \x5a\xb0\xee\xc6\x42\x09\xc8\xf8\x01\xc6\x80\x98\x6f\x2b\x05\xfc\ \xf5\xc9\x34\x39\xf6\xeb\x5b\x63\xe0\x6c\x54\x43\xbd\xe8\xd6\xe6\ \x9b\xc7\xd1\x8e\xee\xa7\xec\x9b\x66\xdd\x77\x1f\x84\x1b\x07\x74\ \xf0\xf9\xc1\x4c\x71\x1f\xf7\x63\x35\x12\xfd\x37\xe2\x8e\xa1\x11\ \xf4\x37\x94\xc8\xfc\xa5\xe3\x6c\x64\x27\x81\xae\x91\x18\xb0\x56\ \x86\xc7\x26\x08\xd8\x70\x6b\x51\xb8\x1f\xe9\xe0\x9e\xbb\xf6\xb8\ \x4d\xa3\x3e\x74\x0f\x24\x1e\x93\x75\xc9\x41\xeb\x91\x16\xba\x19\ \xa9\x21\x1b\x81\xc4\xd3\x20\x0b\xbf\x04\xab\x04\x33\x0a\x43\x59\ \x18\xf6\x81\x19\x3e\x98\x24\xce\xed\x74\x2b\xc1\xd1\xa0\x6a\xcf\ \x5f\xcc\x23\x3f\xc1\x2a\x92\x3d\x65\x1d\x7b\x8e\x94\xe0\x8d\x72\ \x1f\x92\x3d\x7e\x0d\xd6\xdf\x5c\x24\x05\x99\x41\x40\xe2\x71\x64\ \x45\x6e\x47\xec\xf7\x95\x92\x2f\xc1\xb8\x75\xa1\x0f\xdc\x4c\x6b\ \x7d\xc8\x01\xe6\x88\x19\x5a\x31\x73\x20\xde\x56\x86\x4b\xb8\xc4\ \xc1\xd9\x00\xf1\x36\x30\xd8\x52\x24\x4b\x0b\xa6\x83\xcf\x74\x0b\ \xa4\xde\x07\xe2\x4c\xc5\x01\x87\x26\x47\x65\x34\x52\x3b\x57\x93\ \x9a\xd9\x2e\xc6\x35\x33\x19\x91\x8a\x98\x5d\x52\x39\x36\x47\x0c\ \xb0\xe3\x32\x50\x1a\x82\xd5\xbe\x72\x89\x5a\x30\x2b\xb2\xea\xde\ \x95\x01\xaa\xb8\x4e\x3a\x2d\x91\x64\x47\x6d\x97\xe3\x11\x20\xc3\ \xe1\x8d\xb2\x89\x66\x06\xd2\x69\x56\x53\xfa\xcd\xce\xcb\x7c\xbc\ \x4f\x74\x81\x93\x61\xf5\x6b\xbf\xea\x32\x8f\x90\xc1\x8c\x80\xf4\ \x77\xcc\xca\x9b\xce\xad\xa5\x25\x04\xca\x91\xf9\xa0\x48\xa7\xf9\ \xb0\x48\x63\xe8\x52\xe0\x74\x2c\x4d\x4a\x0f\xdb\x0c\x5b\x9d\x4a\ \x2a\x67\x52\xee\x57\x60\xe6\xbc\xe6\xac\x53\x73\x71\x8d\xb4\x17\ \x74\x7e\x5e\x7a\xcd\x1b\x24\xc7\xc0\x37\x73\xb5\xfd\x26\x58\xc4\ \x58\x24\x32\xc4\x99\xe3\xb4\x43\x33\xd8\xeb\x55\x39\xa7\xfc\xc1\ \x55\xc0\x7c\x77\x99\xbd\x0f\x3d\x07\x03\x19\x7e\xcc\xfb\x11\xf8\ \x88\x03\x31\x7b\x25\x0e\xc2\xe5\xe8\xac\xa0\x9c\xe0\xcb\xe3\xa9\ \x32\x30\xc4\xea\xc0\xf7\x54\x37\x30\xdb\x18\x32\x0a\x51\x79\x73\ \x81\x79\xa8\xc2\x54\x29\xaa\x32\x17\x78\x9e\x73\x6e\x21\x51\x48\ \x2c\xf1\xf6\x32\xcc\x78\x3f\xc9\x19\x51\x9a\xcc\x39\x03\x79\x3f\ \xe3\xd8\x1c\x6c\x4a\x6a\xab\xc6\x4a\x4b\xde\x5f\x8e\x2a\x98\x0b\ \x27\x58\xbe\x14\xa3\x2a\x52\x0d\x76\xae\x50\xac\x8c\xb6\x15\x57\ \x1f\x79\x0d\x96\xfc\x08\xe1\x46\x96\x52\x27\xc4\xec\x11\xe0\xb5\ \x33\x3d\xc0\xb9\x80\x8d\x3a\x9d\x3f\xbf\xd2\x8e\xf7\xd5\x15\x73\ \x5a\xb0\xf6\x92\x2f\x05\x35\xbb\x56\x2a\x5e\x54\xa9\xcb\xcc\x3e\ \x72\x20\x19\x39\x9b\x1c\x58\x9f\x1f\x14\xf8\x19\x36\x07\x2a\x66\ \xf3\xa1\x09\xa4\xcb\x5c\x60\x59\xaa\x28\x95\x4f\x38\x7f\x71\xe5\ \xd5\xa8\x00\x55\xa4\x63\x2b\xa5\xca\xf2\x99\x4a\xbb\x8d\x8b\x94\ \x9e\x53\x46\x28\x0d\x4b\x97\x50\x4a\x6b\xe2\xcb\xaf\x59\x97\xe7\ \xb7\x80\x9f\x00\x24\x4f\xbc\x83\x09\x00\x80\x48\x00\x00\x00\x00\ \x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x03\x9b\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x03\x62\x49\x44\x41\x54\x78\x01\xd5\x95\x03\x74\x34\x57\ \x14\x80\x6b\xdb\xb6\xdb\xe8\xb7\x6d\xc7\x5a\xd7\xb6\x6d\xdb\xb6\ \xdd\xae\x6d\xdb\x0c\xd6\xb6\x66\x83\xd7\x3b\xa9\x6d\xe5\x9c\x6f\ \xc7\xdf\x7b\xe7\x2a\x5b\x21\x84\xfe\x16\xfe\x7e\x71\x53\xd3\x82\ \xbd\x9b\xa7\xcf\x3a\xa3\x79\xfa\xcc\x8b\x9a\x66\xce\xa6\xb6\xcc\ \x98\x31\xeb\x4f\x8b\x5b\x66\xce\x6c\x6b\x9e\x31\x2b\x0d\xbc\xdf\ \x3c\x73\xf6\xa3\xcd\x33\x67\xbd\xd2\x36\x7b\x4e\x65\xf1\x8a\x15\ \x29\x12\x65\xd0\x4e\x24\x0f\x3a\x08\xa4\x7e\x7a\x77\x6f\xe7\xf1\ \xbf\x4b\xdc\x34\x63\xd6\x67\xb0\xd3\xbb\xbe\xbe\x09\x82\x8e\xb3\ \xcf\x3b\xab\xbc\x72\xfd\x06\xf4\xd8\xd3\x4f\x4c\xca\x94\xe2\xf1\ \xb7\xdf\x7f\x13\x23\x90\x06\x2a\xfd\x83\xbd\x8f\xc0\xf3\xdd\x7f\ \x55\x0c\xbb\xdc\x09\xc8\x03\xfb\x7e\x25\xdd\x71\x80\xd0\x17\x17\ \x4a\x78\xe8\xf1\x67\x9e\x9c\x3c\xf7\xa2\x0b\x27\x6d\x2e\x53\xc3\ \xed\x73\x60\x1e\x9f\x13\x7b\xf2\x99\xc7\xcb\x20\xcf\xc2\x7b\x34\ \x60\xeb\x5f\x12\xaf\x00\x54\x5f\xdf\xe8\x1b\xe8\xb9\xe1\x96\xdb\ \x6e\xaa\xd8\x5d\x16\x74\xc5\x55\x57\xa0\x0d\x9d\x1d\x93\x5a\x93\ \x12\xd3\x1a\x15\x55\xbd\x45\x5d\x09\x8c\x78\x6b\x56\xbb\xa9\x7e\ \xd3\xad\xd7\x17\x06\x08\xbd\x0e\x90\xcf\xfd\x39\x71\x13\xe0\xfe\ \x6a\xb7\x07\xc1\x6e\x2a\x7a\x93\x06\xbd\xf7\xd1\x3b\x68\xd1\xf2\ \xe5\xa8\xbd\xb3\x13\x31\xd8\x9f\x60\x1a\xa3\xbc\xaa\xd4\x49\x4a\ \x72\x8d\xb0\x60\xb2\xe9\x8a\x91\x58\xb0\xa6\xd2\x28\x6a\xe7\x5d\ \x70\x4e\x09\xbe\x79\x1f\xbe\x3d\xf4\x87\xe2\xed\x00\x1b\x70\x1b\ \x3c\xbc\xef\xae\x7b\xef\xa8\xfb\x87\xdd\x48\x22\x17\xa0\xe5\xab\ \x96\x21\xda\x19\x94\x49\x99\x5a\x58\x53\x1b\x64\x15\x5c\x0c\xe7\ \x79\xb1\x92\x97\x15\x29\xb8\x69\x87\xc7\x52\x48\xa5\x13\x75\x16\ \x87\x5e\x26\x51\x08\xa5\xde\xfe\xee\x1b\xf1\x50\x7e\x93\x3c\x90\ \x1e\x08\xc8\x66\xcc\x9d\x97\xe9\x1b\xec\x9d\x30\xdb\x0c\xc8\xe9\ \xb5\x21\x93\x4d\x3b\xae\x37\xab\x30\x08\x05\x2e\x2e\x2b\xb4\xe2\ \xa2\x54\x2d\xc8\x89\x95\xdc\x8c\x40\xc6\x4a\xf2\x24\xf4\xb8\x48\ \xce\x89\x8f\x84\x02\xa5\x4c\x2e\x8d\xbd\xf4\xca\xf3\x05\x90\x47\ \x40\xbe\xcf\xf7\xe2\x02\xf2\x43\x36\x77\x6c\x7e\xf1\xfa\x9b\xae\ \xad\x0c\x07\xfd\x93\x36\xa7\x69\x0c\xe2\x3a\x15\x5f\x95\x5e\x5a\ \x96\x6b\x45\x05\xa9\x8a\x9f\x13\x29\x38\x69\x5c\xcc\x15\xd3\x63\ \x2c\xe1\xa7\x11\x06\xef\xa3\x60\x38\x3a\x5a\x92\xca\x45\x78\xd5\ \x70\x7f\xb2\xf3\x60\xb5\xed\xa1\x2a\x7c\x8f\x3d\xf1\x48\xd5\x13\ \x70\x8f\x7b\x03\xae\x86\xce\xac\xaa\x2a\xf5\x5f\xc6\x57\xa2\xe2\ \x67\x61\x97\x69\xbe\x94\x99\xe0\x88\x3f\x8f\xb1\x04\x9f\x84\xa5\ \x6a\x7e\xac\x50\xcc\x37\xc8\x54\x62\x05\xbe\x3f\xee\x5b\xf1\x8f\ \xe5\x7b\x40\xc6\x1f\x25\x53\x08\xe5\xcf\x18\x1f\x63\xc1\xf0\x70\ \xc3\xe1\xb1\x56\xe5\x1a\x51\x5e\x02\xf1\x15\xca\xd9\x29\x9e\x84\ \x91\xe0\x88\x3e\x8b\x32\x05\x1f\x87\x93\xa9\x78\xf5\x8d\xb7\x5f\ \x2b\x41\x45\x3d\xf4\x9b\x66\x05\xde\x69\x83\xc4\x7e\xc1\xc5\x97\ \x5e\x50\xd4\x1a\xd4\x8d\x70\x2c\x58\x37\xda\x34\x05\x08\x43\x0a\ \x8f\x2f\x5b\xf8\x59\xd4\x60\xd5\xa4\x23\xb1\x30\x06\xd2\x2c\xde\ \x38\xbf\x22\xfe\xd1\x02\xab\x21\x3c\xc1\xfb\x1e\xbc\xa7\x38\x3c\ \x1a\xc0\x42\x91\x11\x7c\xf7\x49\x8e\x98\x1e\x2d\x14\x73\xd8\xed\ \x77\xdd\x5a\xc0\x1b\xe6\x8f\x4c\x37\x5c\xbe\x1d\x64\xfc\x32\x58\ \xa0\xf8\xfe\x87\xef\x96\xf1\x2a\x48\xa6\xe3\xd0\x2c\xe6\x1a\x24\ \xcc\xf1\x55\x17\xfe\xf1\xb1\x09\x82\xfd\x41\xf4\xca\x19\x67\xd1\ \x2a\x2a\xed\x54\x83\x94\xe1\xde\xbc\xbf\x6c\x1e\x83\xac\xb5\x9f\ \xd0\xab\x83\x45\x3e\xfc\xdd\x83\x1e\xfe\xb6\x06\x76\x06\xf6\x06\ \x0e\x02\x8e\x00\x8e\x01\x8e\xfd\xea\x78\xc4\xc1\x87\x1c\x7c\xf4\ \x57\xcf\x77\x06\xb6\xfe\xad\xe2\x6d\x81\x5d\x80\x7d\x80\x43\x81\ \xe3\x81\x66\xa0\x15\x38\x05\x38\xfa\xab\x05\xf7\x02\x76\xfc\xb1\ \xf8\xff\xf4\x3f\xef\x0b\x48\x11\xe7\x18\x29\x78\xf3\x7e\x00\x00\ \x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x04\x2c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x03\xf3\x49\x44\x41\x54\x78\x01\xb5\x55\x03\x90\xb4\x4b\ \x10\xfb\x7e\xdb\xb6\x6d\xdb\xb6\x6d\xdb\xb6\x6d\xdb\xb6\x6d\xdb\ \x3a\xdb\xb6\xf7\xcb\xeb\xcc\xd9\xa5\x77\x55\x59\xce\xa4\xd3\x49\ \xf7\x9e\x06\xe0\x7f\x41\x92\x5f\xae\x9a\x99\x6a\x98\xe0\xb2\xc0\ \x56\x10\x2a\x08\x13\xd8\x0b\xae\xf1\xbb\xe4\x89\x13\x26\xb4\x7a\ \x78\xa1\xa1\x2f\x3c\xd7\x19\x10\xfc\x1b\x08\x73\x17\x78\x00\xc1\ \xbf\xa0\xbb\x2f\xd5\xdf\xde\x69\x13\x20\x67\xec\x12\x2b\x90\x10\ \xe9\x4e\x21\x74\x87\xdf\x0d\x1d\x7e\xd7\xe0\x67\x3b\x1b\x76\xbf\ \x46\xc3\xf6\xe7\x48\x38\x1b\x8d\x87\x97\xe5\x74\x48\x31\x20\xe8\ \x07\x10\xf0\x08\x37\x4f\xd4\xf4\x93\x3b\x7b\x92\x24\x26\xe9\xe7\ \x47\x9d\x7d\x11\xe6\x0c\x1f\xeb\x99\x30\xf9\x30\x10\xff\xde\xf6\ \x55\xcf\xd6\xdf\x87\xc3\xc5\x78\x22\x5c\x4d\x26\xc1\xdf\x6e\x36\ \x42\x5d\x16\x02\x3e\x47\xa5\x0b\x57\x3c\xb9\xdc\x24\x74\xcd\xec\ \xd4\xc7\x12\x24\x66\x4b\x67\xf7\x94\xf1\x60\xcb\x8e\x7f\xc7\xe2\ \xfb\xb3\x6e\xf8\xf5\xb2\x27\xa1\xc8\x6d\x7e\x8c\x24\x29\xc9\x55\ \xd1\x00\xfb\xb9\xc2\xb9\x18\xf0\xde\x07\x84\x18\xe3\xd2\xc1\x4a\ \xcc\x60\x78\x42\xc4\x76\xf0\xbd\xa8\x53\x89\xe5\xd7\xa1\xb0\xf8\ \x32\x44\x11\xfe\x7e\xd5\x1b\x7f\x5e\xf7\x86\xd5\xb7\xe1\xb0\xff\ \x3d\x1a\x0e\x7f\xc6\xc2\xdd\x6c\xb2\x22\x0f\x71\x16\xd5\x9e\x1b\ \x41\xcb\x58\x64\xdd\xdc\x74\xde\xb1\x88\xa9\x56\xc2\x08\x46\xd0\ \x77\x2a\xa2\x9f\x84\x2a\xf0\xf7\x4d\x1f\xe5\xad\x9b\xe9\x64\x78\ \x5b\x4d\x57\xcf\x7c\xef\x69\x31\x95\xaa\x15\x79\xa0\xc3\x5c\x21\ \xbf\x44\xbf\x0d\x6b\xe7\xa4\x9d\x1a\x45\xbc\x7a\x56\xaa\xbb\x92\ \xb4\x0a\x85\xed\x32\x2c\x7a\x4a\x90\xe8\xeb\x93\x2e\xd8\xb6\x34\ \x2b\xb6\x2e\xc9\x8a\xe7\xd7\x9a\x51\x2d\x05\xa8\xef\x9c\xfe\x8d\ \x57\x1d\xb0\x00\xef\x6d\x5f\x96\xcd\x2a\x8a\x58\x5a\xf0\x41\x88\ \xa5\x0a\xc4\xc3\x7c\xaa\x3a\x60\xf6\x69\x90\x6a\xfb\xe7\x8b\x1e\ \x38\xb2\xb9\x88\x7e\xe3\x78\x0d\xf3\xbb\x67\xea\xde\xb9\x78\xa0\ \x62\xd8\xbb\xbb\xed\xa8\x96\x76\x45\x4d\x0a\xdf\xc3\x63\x15\x36\ \x2e\xc8\x10\x1a\x53\xb1\x01\x06\x3f\x50\xb5\xaf\xcd\x4c\x15\x1e\ \x27\x81\xc4\x9b\x16\x66\xc4\x9d\xd3\x75\xee\xf0\x1c\x71\x6e\x6f\ \xb9\x16\x54\x0f\xdf\xb3\xea\x1c\x27\x24\xc8\x71\x3e\xed\x90\xcf\ \x2e\x41\xb8\x10\x93\x58\x17\x62\xb1\x62\x13\x0f\xaa\xf6\x8c\xde\ \xf5\x53\xca\xf7\xaf\xcb\x87\x43\x1b\x0b\x36\x8d\x11\x72\x6a\x21\ \xd6\xe1\x7f\x8f\xe7\xd4\x9c\x53\x2d\xef\xc1\xff\x6e\x6c\xe2\x0d\ \xf3\x33\x04\x20\xd4\x4e\x2a\x9e\x63\xba\x0c\x46\xf9\x4b\xe2\x17\ \xd7\x5b\xe0\xf8\xb6\x62\xff\x48\x48\x88\x87\xbb\xe9\x39\xc7\x8c\ \xe1\x92\x9c\x76\x30\x1b\x78\xef\xa5\x15\x61\x51\xc4\xa2\xc0\x84\ \x6a\x11\xf8\x16\xf0\xda\x46\x05\x0c\x86\xad\x2a\x6b\x38\x76\xc7\ \xb6\x16\x35\x1c\xdd\x52\x34\x54\x2c\xd2\xe1\xb1\x8c\x81\xa9\xc9\ \xa1\x00\xce\x38\x03\xa5\xa0\xdd\xab\x72\xf9\x46\x11\xcb\x88\x4c\ \x7b\x73\xbb\x8d\x4e\xd5\x1c\x1b\x16\x31\xb8\x2d\x66\x7b\x24\x60\ \x38\xbc\xc8\x22\x51\x19\x70\xa6\x39\x19\x9c\x6f\x86\xc8\x7c\x1e\ \x9c\x6f\x80\xc3\x9b\x0a\x3f\x89\xb5\x20\xd2\x42\x08\x3d\x42\xf0\ \x1f\xf1\xea\x81\x28\xdf\xa3\xa6\x84\x84\x6c\x99\x8b\xc2\x99\xe6\ \xb2\x70\x62\xa8\x92\xe4\x54\xcc\x49\x0a\x76\x9a\x8f\x7d\x6b\xf3\ \xe9\x92\x49\xae\x98\xc4\xf4\x79\xc6\x95\xc3\x95\xc3\x55\x07\x7d\ \xe3\x36\xa9\xb9\xe6\x05\xfa\x68\xfb\x6b\x64\xe4\x7c\x93\x98\xe0\ \xe4\xd0\x32\x11\xb1\x83\x2b\x8d\x03\xeb\x0b\x5c\x8d\xb7\xd2\xf2\ \x97\x66\xc7\xf2\x1c\x2b\xdf\xdf\x6b\x2f\xbb\x6f\x25\x7e\x7f\x90\ \x30\x2f\xca\x6c\xae\x54\x76\x90\x9c\xad\x73\x04\xd9\x81\xe9\xc7\ \x81\x0c\x4c\x8a\xaf\xc5\xa3\x8b\x8d\xe8\xad\x87\x70\xa4\x13\xa4\ \x24\x1f\xff\x52\x08\xd2\x0b\xb2\x09\xf2\xcb\x81\x7b\x57\x8f\x54\ \x51\x56\x28\x5b\xbc\x0f\x30\x4c\xae\xb3\xb2\xc5\x2d\xc2\x73\x66\ \xc0\xe7\xcb\x87\x2a\x73\xdb\x02\xab\x95\xd7\xaa\xca\xfd\x5c\x82\ \xb4\x24\x8f\x24\xe6\x9b\x4c\x82\xbc\x82\x32\x4b\xa7\x68\xbb\x37\ \x2f\xca\xac\x7f\xb8\xdf\x9e\xfe\x91\x44\x0a\xed\xe2\x38\x49\x07\ \x2b\x48\xc8\x9f\x4a\x8e\x16\x57\xfd\x52\xea\xd4\x5a\x2d\xb9\x57\ \x92\xe2\x22\x89\x63\x5a\xc1\x2a\x69\x23\xaa\xe6\xcb\x9c\x51\x2b\ \x29\x9e\x19\x49\x20\x21\x2c\x22\xbf\xb7\x20\xe4\x35\xf6\xae\xc9\ \x6b\x90\xa5\xb1\x15\x95\x91\x84\x39\x05\x19\x05\xa9\xc8\x93\xf0\ \xbf\xa6\xe8\x22\x44\x1a\x41\x86\xba\xd5\xb4\x02\x93\x87\x6a\xb5\ \xe6\x8e\xd3\x9a\x0e\xe8\xa2\x95\xcd\x9e\x45\xcb\x1e\x61\x5f\x6a\ \x9e\x63\xd7\x71\x39\xfe\x03\xaa\x6e\x0b\x3a\x5c\x6d\x3b\xb8\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x22\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x01\xe9\x49\x44\x41\x54\x78\x01\xb5\x94\x03\xaf\x1d\x41\ \x18\x86\x6f\x6d\x9b\x41\xed\xf6\x0c\xea\x46\xb5\x6d\x77\x66\xee\ \xd6\xb6\x6d\xdb\x98\x6f\xb6\x27\xae\x8d\xb8\xb6\xe2\xdc\x7f\x72\ \xfa\xee\x3d\xb5\x17\xdd\xe4\x4d\x9e\x83\x7d\x3e\x2c\x72\x32\x99\ \xcc\x7f\x49\xa4\x93\x44\x2e\xf5\x62\xe6\x58\xc9\xc4\xc5\xdc\xd0\ \x16\x6e\x4e\x77\x8b\x27\xfe\xa1\x4b\xae\x6c\x81\x94\xb1\xcb\x84\ \xa6\xcb\x29\xe5\x97\x8b\x2d\x66\x9a\x8e\x0a\x6d\x37\x40\x5c\xb8\ \xb5\x77\xae\x38\x57\xf4\x3c\xa5\xdc\xc4\x16\xa3\xd2\x05\x22\x89\ \x8b\x2f\xdf\x54\xf7\x93\xac\x22\x64\x1f\x20\xeb\x07\x2e\xc0\xb5\ \xeb\x89\x95\xbc\x6f\x31\x32\x5d\x26\x92\x58\x4e\xf1\xd7\x43\x3c\ \x3f\x60\xc8\xda\x42\x96\x87\x2e\x2b\x81\x8b\x63\x82\x77\x6d\x26\ \x53\x97\x50\x62\xa1\x1d\x6b\xa7\xfc\x02\xcc\xd8\x2a\x5c\xd3\xfd\ \xf6\xb9\xae\x55\xf6\x7b\x7b\x87\x99\x73\x23\x3f\xf1\x29\x91\x7b\ \x6e\x61\x28\xb1\xd4\x6e\xae\xd4\xd4\x3d\x5f\xa0\xc8\x63\x8a\xd6\ \x7e\x92\x2d\x15\x86\xf6\x67\x99\xa6\x80\xd3\xe1\xc4\xea\x5c\x73\ \xae\xed\x9e\x80\x99\xb2\xad\x99\xa6\xab\x01\x73\xe5\x77\xc4\x04\ \xaf\x3e\x71\x7b\xf0\x9b\x70\x62\x63\x1b\x73\xe3\x76\x04\x2c\x8c\ \x4b\x61\xa7\x57\xb2\x32\xea\x84\x4e\x5f\x7e\x62\x09\x7e\x1f\x4a\ \xcc\x0d\xcd\x63\x1e\xf5\x0f\x98\x19\xd2\xcc\xb8\x35\x9f\xc6\x5f\ \x84\x22\x87\x3e\xf1\x68\xf0\xc5\xb0\x17\xaf\x59\xce\xda\xd5\x05\ \xbb\xac\xbc\x53\x19\x27\xdf\xc0\xe7\x8e\x9f\x0a\xde\x42\x46\x65\ \x27\xa1\x83\xf8\x6d\x45\xa4\xdb\x8d\xe1\x56\xc3\x7e\x97\x04\xdc\ \xca\xc3\x4a\x94\xcd\x6b\x6b\x5c\x8d\x96\x9e\x2b\x82\x02\x4f\x20\ \xef\x19\x49\xcc\x95\x6b\xc9\x8c\x5f\x40\x4c\xf1\x2b\x61\xf4\xd7\ \x72\x9a\x3f\xfc\xc7\x22\x91\xc4\x5f\x0b\xd0\x09\x88\xf7\x7d\x2a\ \x52\x10\xdd\xde\x91\x53\xdd\x52\x88\x0b\xc5\x13\x6b\xdb\x13\xf7\ \x70\xd1\x7c\x9e\xe2\x66\xa1\xc8\x43\x39\xfd\x7c\x8d\x64\x5f\x9b\ \xca\xee\x83\x78\x74\xe4\xd7\x26\x8e\x42\x48\x31\xa4\xc4\xb7\x69\ \x3d\xee\x50\xff\x5a\x6c\x70\x69\x70\x81\x50\x62\x1c\x85\x91\xd2\ \x48\x65\xa4\x16\x52\xff\x17\xa9\x83\x54\xf9\xf4\xbf\x42\x51\x3a\ \x2e\xfe\x63\xc7\x48\x49\xa4\x68\xe8\x8e\x93\xc8\x47\x7a\xb9\xb1\ \x01\x4c\x4e\xb1\x4f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ \x82\ \x00\x00\x03\x59\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x03\x20\x49\x44\x41\x54\x78\x01\xb5\xd4\x53\x9c\xec\x56\ \x00\x80\xf1\xef\x9c\x93\x64\xac\xb5\x6a\xdb\xb6\xdf\x6a\xdb\x7d\ \xad\x8d\xe7\xe2\xa9\xb6\x6d\xdb\xb6\xcd\xc5\xd5\x6a\x3c\xc9\x6c\ \x74\xaa\x6c\xb9\xb8\xfc\x06\xf1\xff\x17\x0b\x16\xb2\x95\x0f\x7d\ \xca\x74\xcc\x5c\xa1\x6c\xfb\x85\x00\x99\xb7\x0c\x56\x11\x5a\x6f\ \xaf\x62\xc6\xb6\xa1\x90\xab\x7b\xd5\xc6\xb9\xce\xc3\xbb\x5e\x46\ \x94\xc1\x0c\xad\x77\xfa\xfb\xeb\x54\xeb\xe1\x0e\xe3\x55\x77\xed\ \x7e\x97\xe5\xb3\x4a\x74\xb6\xb5\x25\x7b\xad\xa4\xd9\x86\x94\x96\ \x50\x52\x27\x33\xa6\x54\x61\xc0\x57\x9f\xd8\xdd\x00\xb3\xc2\xc6\ \x5e\x2f\x5c\xf5\xe3\x3c\xf7\xa4\x7c\xc6\x12\x3d\x3d\x39\x19\xcf\ \x98\x78\x42\x80\x12\xc4\x2c\x45\x2a\x65\x92\xcf\x99\xc2\x6b\x06\ \x7c\xf0\xfa\x5c\x5c\x69\x25\x17\x0a\xee\x9c\xe8\xdf\x21\xbf\xe1\ \x36\xea\xb7\xbd\xc3\x8a\x2b\xe2\x31\x49\xfc\xb7\x61\x22\xa1\x50\ \x86\xa4\xd6\xf0\xf9\x79\xc8\x66\x60\xbe\x8d\x4e\x5a\xd0\xf0\x59\ \x28\xd8\x32\x88\xaf\xb0\x46\x01\x25\x35\xb1\x98\x42\x28\x68\xb8\ \x9a\xfe\xb2\xcf\x2f\xc3\x4d\x2a\x9e\x01\xe9\x3c\x74\xa6\x49\x97\ \xc6\x60\xd4\x5e\x38\x18\x29\x69\xed\x4a\xfc\xb6\x57\x0e\x83\xc3\ \x82\xaa\x13\x52\xaf\x36\x75\xd0\x0c\x34\xbe\x1f\x88\xda\xf0\x02\ \x51\x29\x7d\x9b\x16\x6e\xa6\x75\xa3\xd5\xb7\xac\xc3\xac\xb0\x00\ \xb4\x61\x59\x8c\x8f\x34\xf9\xe0\x95\xc1\xd0\x73\x5c\x5f\xfb\x5e\ \x53\x39\xa3\xef\xa9\xea\xc0\xdd\x19\x67\xfe\x0b\x95\xcc\xaa\x25\ \x03\xdf\x6f\xcd\x65\xcf\xed\x6c\x8f\x6d\xf9\xcb\x42\xc0\x1a\x40\ \x08\x4d\x69\xac\x81\x37\xdc\x7f\x9e\xb0\x47\x9e\xd0\x03\x1f\x7f\ \x1f\xf4\xdf\xe2\x01\x94\xf9\x33\x17\x10\x7b\xdf\x8a\x8c\x19\xb0\ \x10\x70\xc4\xeb\x3f\x07\xa9\x9e\x07\xf4\x33\x07\xfd\xc4\x4c\x19\ \x92\xff\x26\x59\x1a\x09\xb1\x8c\x60\x60\xe1\x61\xbd\xac\xe0\x25\ \xcc\x60\x9a\xc2\xd9\x76\xf9\xe0\x57\x4d\x7c\x52\xa1\x3f\x60\x2d\ \x12\x2c\xf4\xf4\xf0\x0a\x27\xbd\xb9\xfc\x70\x4d\x9f\x6d\xa5\xd4\ \xfa\xe3\x13\x3d\xed\x3d\x42\xcc\x02\xef\xf3\x86\x10\xc9\x30\x2b\ \x02\xa9\x26\xdc\x7e\x93\x29\xda\xf8\x8c\x77\x56\xfa\x6e\x58\x7f\ \x7e\xd8\x3e\xcb\xa7\x57\x59\x23\xc7\x7b\x3f\x54\x18\xf5\xe4\xf4\ \xb0\xdc\xe7\xb5\x98\x95\x10\x87\xad\xd4\x93\x39\xa5\xd2\xf0\x97\ \xaf\x3b\x2b\xfc\x6f\xed\xa3\x6f\x79\x45\xbc\xfa\xae\x77\xcc\x6a\ \xeb\xf4\xa5\x8b\xb9\x0e\x3e\xfc\x2a\x60\x68\x9e\x85\xe9\xd6\x00\ \xfa\xa7\x84\xb3\x2d\xe6\x2a\x6b\xae\x94\xbb\xe2\xe0\x83\x56\x4f\ \xfe\x38\xd6\xe4\xd3\x81\x3a\xc1\x98\xcd\x3f\x6b\x0c\x8e\x11\x7a\ \xdd\xc1\x17\x3f\x9b\x7c\x91\xf0\x08\x72\x0a\x56\x48\xc3\x43\x3f\ \x72\xd0\x46\xce\xfd\xf7\x3d\x38\xc5\x5d\xe1\xd9\xfe\x8e\x7e\xb6\ \x90\xbc\xfd\x93\x90\x87\xbe\x92\xbc\x33\xc7\xe2\x83\xef\x6a\x25\ \x25\x43\x97\xa8\x07\x2e\x3a\x40\xf7\x1a\xdf\xde\x26\xe6\xbf\xff\ \x7e\x60\x8f\x78\x34\x86\x03\xf1\xe2\x5b\x73\x5b\x86\x5f\xdc\x3f\ \x5c\xad\x3c\xf5\x1e\x27\xb4\xf3\xc3\x67\x73\x65\xdd\x6f\x37\xd2\ \xba\xcf\x80\xd6\x38\xe9\xc1\x6f\xde\xca\x8e\xbd\x58\x9e\x07\x82\ \xa8\x77\xae\x3f\x7e\xc0\x28\xac\x71\x0c\x9f\xad\xb4\x13\x3a\x48\ \xe0\x8c\x7e\x58\xaa\xf4\xbf\xf1\xc0\xcb\x65\x26\x5f\x60\x93\x23\ \x12\xc8\x0b\x2b\xb3\x8e\xda\xfc\x84\xf3\xfd\x4d\xf7\xd8\x0c\xfc\ \xd0\xfc\xf2\xe3\xb9\xe2\x93\x3b\x2e\x71\xc7\xbf\xff\x08\xa8\x02\ \x36\x10\x44\xeb\x9b\x93\x3b\x15\x41\x3e\xe0\x01\x13\xd1\x30\x9c\ \x84\xdb\x81\xf5\x40\x2e\x4f\xac\x35\x03\x3a\x60\xa2\x54\x81\x60\ \x14\x28\x46\xbf\x5a\x04\x4c\xc2\x2a\xda\xb1\x20\x9a\xef\x02\x4e\ \x34\x0c\x7f\x05\xc8\x0a\x58\x87\x6a\xb4\x2e\xfc\x00\x00\x00\x00\ \x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x03\x67\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x03\x00\x00\x00\xf3\x6a\x9c\x09\ \x00\x00\x01\xbc\x50\x4c\x54\x45\x00\x00\x00\xff\xff\xff\xff\xff\ \xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe3\xe3\ \xff\xe6\xe6\xff\xea\xea\xff\xdb\xdb\xff\xd2\xd2\xff\xd3\xd3\xff\ \xd3\xd3\xf7\xde\xde\xff\xde\xde\xf0\xe1\xe1\xf9\xe8\xe8\xef\xd4\ \xd4\xe4\xbf\xbf\xeb\xd1\xd1\xed\xcd\xcd\xea\xc9\xc9\xef\xcb\xcb\ \xe9\xbc\xbc\xed\xca\xca\xec\xcf\xcf\xf0\xc4\xc4\xeb\xbf\xbf\xee\ \xc6\xc6\xed\xc8\xc8\xef\xc2\xc2\xe4\xa3\xa3\xe9\xbc\xbc\xe3\xa9\ \xa9\xe1\xaf\xaf\xdb\xa4\xa4\xda\x9b\x9b\xdb\x92\x92\xd7\x90\x90\ \xd7\x92\x92\xd6\x8b\x8b\xd8\x8d\x8d\xd3\x7f\x7f\xd5\x87\x87\xd8\ \x87\x87\xce\x79\x79\xd0\x6e\x6e\xd0\x6d\x6d\xcc\x6d\x6d\xc4\x58\ \x58\xcb\x6b\x6b\xcb\x6a\x6a\xc8\x57\x57\xce\x70\x70\xc4\x56\x56\ \xc3\x4f\x4f\xc0\x46\x46\xc0\x47\x47\xc1\x47\x47\xc3\x4d\x4d\xc3\ \x4e\x4e\xc2\x4d\x4d\xba\x3b\x3b\xbd\x3e\x3e\xbe\x40\x40\xb9\x32\ \x32\xb9\x33\x33\xbb\x38\x38\xbc\x3d\x3d\xac\x15\x15\xaf\x1b\x1b\ \xb1\x16\x16\xb1\x18\x18\xb2\x19\x19\xb3\x1e\x1e\xb3\x1f\x1f\xb4\ \x22\x22\xb5\x22\x22\xb5\x24\x24\xb6\x24\x24\xb6\x27\x27\xb8\x30\ \x30\xb9\x30\x30\xb9\x32\x32\x9b\x00\x00\x9c\x00\x00\x9c\x01\x01\ \x9d\x00\x00\x9e\x01\x01\x9e\x02\x02\x9f\x00\x00\xa0\x02\x02\xa2\ \x03\x03\xa3\x06\x06\xa4\x01\x01\xa5\x01\x01\xa5\x03\x03\xa5\x0a\ \x0a\xa6\x09\x09\xa6\x0a\x0a\xa7\x01\x01\xa7\x05\x05\xa8\x01\x01\ \xa8\x02\x02\xa8\x03\x03\xa8\x07\x07\xa9\x02\x02\xa9\x03\x03\xa9\ \x04\x04\xa9\x05\x05\xaa\x04\x04\xaa\x06\x06\xaa\x07\x07\xaa\x08\ \x08\xaa\x0a\x0a\xab\x08\x08\xab\x09\x09\xab\x0a\x0a\xab\x11\x11\ \xab\x12\x12\xac\x0b\x0b\xac\x0c\x0c\xac\x0e\x0e\xad\x0e\x0e\xad\ \x0f\x0f\xad\x10\x10\xad\x13\x13\xae\x11\x11\xae\x12\x12\xaf\x13\ \x13\xaf\x14\x14\xaf\x16\x16\xaf\x17\x17\xb0\x14\x14\xb0\x17\x17\ \xb2\x1e\x1e\xb3\x1d\x1d\xb3\x1f\x1f\xb5\x27\x27\xb5\x2a\x2a\xb8\ \x2c\x2c\xb8\x30\x30\xb9\x30\x30\xba\x34\x34\xbc\x38\x38\xbe\x41\ \x41\xc8\x5c\x5c\x14\x8b\x06\x10\x00\x00\x00\x55\x74\x52\x4e\x53\ \x00\x01\x02\x03\x04\x05\x06\x09\x0a\x0c\x0e\x11\x17\x1d\x1f\x1f\ \x22\x2d\x2f\x30\x32\x38\x3d\x40\x44\x48\x50\x56\x58\x66\x73\x7d\ \x82\x8c\x91\x92\x9d\xa0\xbd\xc7\xcd\xd1\xd3\xd7\xd8\xd9\xe0\xe5\ \xe7\xe8\xed\xf0\xf3\xf4\xf4\xf6\xf8\xf9\xf9\xf9\xf9\xfa\xfb\xfc\ \xfc\xfc\xfd\xfd\xfd\xfd\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\ \xfe\xfe\xfe\xfe\xfe\xba\xc1\xe1\xa5\x00\x00\x01\x05\x49\x44\x41\ \x54\x78\xda\x6c\xc1\x03\x43\x34\x01\x18\x80\xc1\xe7\xf0\xd9\xb6\ \xcd\x6c\x1b\x97\x6d\xd7\xbb\x67\xdb\xd9\xfc\xc1\xd9\xed\x0c\xa7\ \x9e\x7c\x44\x4d\xe5\x26\x6a\x56\xb7\x6f\x70\xd5\xdd\xe5\xad\xf7\ \x5c\xf5\x23\xb8\xfe\x8f\xab\xca\x95\xc5\x22\x2e\x7a\xa5\x87\x69\ \x09\xb6\x43\xf6\x67\x4e\xdd\x59\xe9\xe0\xba\xd7\x6a\x5c\xe2\xe6\ \xd6\x0e\xa7\x52\x93\x2b\x4f\xbf\x58\x9a\xbc\x61\x3e\x85\xd6\x9e\ \x01\x80\xee\x17\x75\xca\x6c\x46\xa9\x39\x2d\xea\xd6\xe4\x8a\x2b\ \x07\x00\x1e\xf9\x8a\x1d\x66\x4b\xcb\xbc\xed\x5d\xad\xe3\xb9\x41\ \x64\x81\x7b\x3f\x81\x6b\x41\xa3\x24\x5c\x6e\xa7\x57\xdf\x6a\xfe\ \x1d\xb1\xd8\x3d\x8f\x2b\x36\x1e\x00\xf1\x11\x29\x9b\x11\x89\x51\ \x20\x0d\xb6\x44\xb3\x54\x39\x3c\x99\x40\x4a\xf7\xd4\x9b\x7c\x11\ \x03\x7f\x15\x91\xb6\x3f\x8a\x59\x64\x0e\xd0\x05\x6a\x78\x3b\x30\ \x92\xc7\x87\x51\x51\xfe\xdf\x37\x4d\x2a\xbe\x98\x1e\xd0\x01\x13\ \xbd\x5f\x79\x38\x38\x69\x7b\xcd\x58\x4f\xbd\xf6\x16\xc7\xd2\xfd\ \x7a\x34\x43\xc3\x3e\x2d\x25\x7d\xdf\xb8\x68\xbc\x3f\x09\xb7\xbf\ \x73\x49\x75\x67\x23\x2a\x0a\xbb\xb2\x50\xf1\x32\xfe\x82\xbd\xe1\ \x07\x00\x45\x9d\x3f\x95\x73\xaf\x02\x38\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ \x00\x00\x05\x28\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x04\xef\x49\x44\x41\x54\x78\x01\xa5\x93\x03\x74\x2b\x69\ \x18\x86\xd7\xb6\x6d\x1b\xc7\xeb\x3d\xea\x31\xd7\xb6\x6a\xdb\xbd\ \x66\x71\xae\x6d\xd5\x6d\xae\x63\x73\x66\x62\x54\x69\xec\xe4\x36\ \x29\xf3\xee\x3f\xdd\xa6\x6b\x7f\xe7\x3c\xe3\x79\x3e\xcc\x3f\xe7\ \xfd\xdf\xa8\xba\x66\xf3\xcd\x45\xd7\xb4\xdf\xfe\xaf\x5e\xfa\x21\ \xfb\xbb\x4f\x08\x47\x09\x4e\xc2\x0c\x61\x96\xe0\x22\x74\xb1\xf7\ \xd8\x67\xca\xae\xdf\xf4\x61\xd5\x75\x9b\xb3\xff\x8d\x70\x64\xd7\ \x9e\xdd\x09\xaf\xd7\x3b\x97\x4c\x4d\x62\x66\x76\x96\x30\x87\x64\ \x2a\x09\xb7\xdb\x95\xee\xec\x3c\x96\x24\xcf\x8c\x57\xdc\xd9\xa6\ \xaf\xba\x7e\x93\xfc\x9f\x48\x5b\x89\x30\x14\x8d\xc6\xd2\x91\x68\ \x0c\x56\xbb\x03\x0a\x95\x16\x72\xa5\x1a\x3a\x8a\x86\xe0\x88\x1c\ \xaa\x2e\x1a\xe6\x33\x43\xa0\x07\xac\x20\x52\x54\xdd\xb0\x19\xd5\ \xd7\x6c\xcc\xaa\xbc\x76\xd3\xab\x15\xd7\x6e\x78\x93\xec\x5f\xfe\ \x9d\xb4\xaf\xbf\x2f\x31\x3d\x33\x0b\xb3\xd5\x06\x9e\x40\x84\xd3\ \x5c\x21\xb8\x42\x29\x24\x0a\x0d\x74\xb4\x1e\xdc\x03\x12\xb4\x3c\ \xbc\x03\xd5\x44\x46\x60\xa5\xac\x3c\x73\xce\x1e\x07\x88\xfc\x8d\ \x5f\xb5\xbf\x66\xcd\xaa\x30\xdb\xb2\x46\x4b\x61\xf0\xf8\x29\x1c\ \x3f\xc5\x05\xe7\x34\x1f\x27\x79\x12\x48\x94\x34\x74\x7a\x13\x28\ \x9a\x82\x99\xa2\xb0\xe7\xf3\x9e\xdf\x0a\x51\x79\xef\x7a\x59\xe5\ \x75\x1b\xaf\xf9\x6d\xb5\xe3\xe1\x48\x24\x3d\xe6\x74\x42\x2c\x95\ \x41\x24\x91\xcf\x57\xcb\x39\x23\x00\xe7\xac\x18\x7c\xb9\x0e\x0a\ \x0d\x0d\xad\x46\x0d\x8b\x5e\x09\x9b\x56\x81\xa5\x8f\xef\xcc\x88\ \xd1\xfc\xe0\x0e\xe4\x7d\x97\x1f\xff\xdd\xc7\x3a\xd6\x79\x74\x2a\ \x99\x4c\x82\xa6\x19\x28\x94\x4a\x82\x0a\x62\x99\x1c\xa7\x88\x5c\ \x49\x9b\xa1\x31\xda\x61\xb0\x0e\xc1\xa0\xd7\xc1\xac\xe3\x43\xd5\ \x7d\x1a\x55\x37\x6e\x46\xed\xcd\x5b\x51\x77\xfb\x96\xf9\x8a\x5b\ \xcb\x36\xa6\x73\x72\xb3\x73\x17\xc5\xd9\x39\xdf\x0f\xba\xdc\x2e\ \x90\x15\x00\x86\xa1\xa1\x52\xc9\xa1\x90\x8b\xe7\xa1\xf5\x7a\xf4\ \x0f\x72\x50\x56\x56\x8a\x92\xb2\x62\x1c\x3a\xb8\x1b\x4e\x2b\x0f\ \x07\xbe\x3d\x88\xd5\x2f\xee\x86\xea\x2c\x0f\x8c\x52\x8d\xd6\xd7\ \x0e\x62\xd3\xdb\x47\x51\x5e\x5e\x3a\xb2\x28\xce\xcb\xcf\x89\x4f\ \x4d\x4d\xc1\xe9\x1c\x85\xd9\xa8\x87\x4e\x25\x82\x5c\x78\x1c\x8c\ \x86\x8f\xd3\x27\x07\xd0\xd2\xd2\x9c\xde\xb4\x69\x93\x63\xfb\xf6\ \xed\x03\xad\xad\xeb\x67\x7b\x3a\xf7\x61\xb0\x61\x10\x4a\x6e\x3f\ \x8c\x3a\x09\xac\x26\x06\x0e\xab\x0d\x9c\xa5\x22\x14\xe4\x16\xcc\ \xfc\x62\x14\xdf\xcf\x91\x80\xc7\x3d\x8e\x21\x2b\x03\xa3\xf6\x34\ \x54\x82\x83\xb0\x68\xfa\x50\x5c\x9c\x8b\x6d\xdb\xb6\x0d\x64\x9e\ \x5d\xbb\x76\xed\x1b\x65\xe5\x25\x38\x17\x72\x42\xaf\x11\x61\xc4\ \x6e\xc4\xd8\xb0\x1d\xc3\xc3\x43\x88\x44\x22\x20\xdd\xe3\x97\xa3\ \x48\xb3\x62\xbf\xcf\x83\x31\x07\x03\xab\xae\x0f\x1a\xde\x36\x58\ \x54\x47\x51\x5f\x5f\x85\xa6\xa6\xa6\x57\x7f\xf1\x3d\x2e\x2a\x2b\ \x2f\x4e\x9f\x8b\x05\xc1\xe8\x94\x18\xb2\x5b\x30\x3c\x64\xc7\x90\ \xc3\x81\x58\x2c\xf6\x6b\x71\x7e\x41\x5e\x72\x7a\x7a\x1a\xd1\x70\ \x10\x1e\x27\x79\xc8\x28\x06\x23\xeb\x83\x41\xc9\xc1\x91\x43\xbb\ \xb0\x6c\xd9\x52\x13\x2b\xfc\x49\x5a\xda\xce\xe1\x74\x22\x14\x1c\ \x83\x4c\x2a\x02\xa5\xd3\x82\xa2\x28\x30\x0c\x83\x40\xc0\x8f\xc2\ \xc2\xfc\xd9\x45\x71\x59\x59\x89\xd5\xe7\xf3\x61\xe2\x5c\x1c\x41\ \x9f\x1b\x4e\x87\x01\x16\x46\x06\x46\x23\x86\x8d\x54\x74\x86\xcb\ \xc5\x92\xa5\x2d\x73\x2d\x4b\x9b\x67\x78\xc2\x33\x69\xaf\xcf\x0c\ \xb3\x49\x00\x95\x52\x0c\xb9\x5c\x06\x85\x42\x01\xab\xd5\x0a\x93\ \xc9\x88\xaa\xea\xca\xc4\xa2\x38\x37\x37\x3b\xef\xe8\xb1\xa3\xe9\ \xe9\xe9\x29\x44\x23\x61\xf8\x89\xdc\xe3\x1a\x21\xed\xd9\x40\x19\ \xcd\xa0\x2c\xa3\xa0\xec\x6e\xd0\x0e\x2f\x0c\x8e\x31\xa8\x29\x09\ \x74\xda\xb3\xa4\x4a\x15\x64\x32\x19\x24\x12\x09\xdc\x6e\x37\x76\ \xed\xda\x89\xa6\xe6\xc6\x33\xe7\xfd\x32\x0a\x8b\xf2\xa7\xd9\x19\ \xa5\x52\x29\xc4\xe2\x71\xf8\x03\x01\x8c\x8c\xbb\x61\xb0\x8d\x82\ \x2b\xa7\xd1\x77\x56\x81\x01\x9e\x0a\x83\x5c\x19\x78\x62\x31\x94\ \x4a\x1e\xb4\x1a\x1e\xa9\x58\x02\xa3\xd1\x88\xd1\xd1\x11\xd4\xd6\ \xd5\xa4\xeb\xeb\xeb\x6f\xcc\x38\x33\x73\x2e\x68\xef\x68\x4b\x4f\ \x4d\xcf\x60\x22\x99\x42\x38\x1a\x87\xcb\x17\x82\x7d\xcc\x0b\x39\ \x65\x83\x50\x6d\x81\x50\x63\x81\x40\xc1\x10\xb1\x02\x22\x31\x1f\ \x22\x21\x07\x7a\x3d\x05\xbf\xdf\x8f\xb6\xf6\xf5\xa8\x6f\xa8\xeb\ \x3c\xef\x0f\xe2\xe2\x8a\x8a\xb2\xc6\xae\xee\x4e\x4c\x4d\x4d\x23\ \x31\x91\x42\x28\x12\x87\xd3\x1b\x82\xd6\xe8\x80\x9c\xb6\x41\xc9\ \xd8\x21\xd3\x1a\x21\x92\xa9\x20\x10\x8a\xc0\x30\x14\xbc\x5e\x0f\ \xf6\xec\xd9\x8d\xea\xea\xca\x30\x71\x5c\x4a\xb8\x20\x23\x3c\x9f\ \x70\x19\xe1\x5a\xc2\x6d\x64\xf8\x9c\x8e\x0d\x1d\xf3\xa3\x48\xa6\ \xa6\xe0\x0b\x46\x60\x1d\x76\x42\x6f\x1d\x99\x1f\x0b\x65\xb0\xc2\ \x68\xb6\x62\xdc\xe5\x86\xc9\x6c\x22\x95\xb6\xb2\x7f\x5b\xea\x91\ \x47\x1e\x79\x86\xbc\x7f\x23\xe1\x92\x8c\xfc\xfc\x85\x93\x2b\x09\ \xb7\x10\x1e\xfe\xfa\xeb\xaf\xda\x8b\x4b\x0a\xd3\xdd\x3d\xdd\x30\ \x18\x4d\x70\xba\x3c\xf0\x06\xc2\xf0\x05\x22\x70\x7b\x7c\x44\x68\ \xc1\xde\xfd\x7b\xd9\xa5\x85\xd2\xb2\x92\x23\x17\x5d\x74\xd1\x8b\ \xe4\xbd\x07\xe6\x8b\xcb\x88\x33\xb1\x70\x72\xc9\x42\xd6\x5b\xaf\ \xb8\xe2\x8a\x07\x1a\x1a\xea\xcd\x75\x75\x35\xd3\x6c\x92\x9c\xdc\ \x1f\xc0\x42\x8e\x51\x5b\x5b\x3d\xd7\xd8\xd4\xe8\x24\x55\x66\x84\ \x37\x10\xae\x20\x5c\xf8\x6b\xe9\xef\xe3\x82\x05\x2e\x26\x5c\xfe\ \xd4\x53\x4f\xdd\xfe\xee\xbb\xef\xbe\xf8\xe9\xa7\x9f\xbe\x9a\x95\ \x95\xf5\xc8\xd5\x57\x5f\x7d\xdd\xc2\xf8\x2e\x5a\x78\xee\xfc\xdf\ \x0a\x7e\x04\x89\x01\x0b\xe6\x62\x1a\x41\x11\x00\x00\x00\x00\x49\ \x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x03\x2b\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x02\xf2\x49\x44\x41\x54\x78\x01\xad\xd1\x03\x70\x63\x51\ \x14\x06\xe0\x8e\xb1\x63\xef\xd8\x46\x87\x6b\xdb\xb6\x59\xbd\xa6\ \xb6\x6d\xdb\xb6\x92\xd4\x56\x9c\x97\xd4\x58\x6f\xed\x99\xa9\xdd\ \xb3\x39\x6f\xe6\x65\xdf\xab\x95\x99\x7f\x6e\x7b\x72\xee\x77\xcf\ \xcd\xd5\x60\x7e\x08\x37\x8b\xb8\x3b\x56\x41\x13\xc7\xb5\x7c\x80\ \x99\x0b\x84\xf7\xcc\x33\x4b\x3b\x19\xc7\xe5\xf1\x29\x8d\xbd\x7e\ \xc2\x92\x8c\x34\x2f\x1a\x06\x2f\x7d\x0c\xaf\x9c\x76\xca\x6b\x99\ \x35\xcd\x6c\x5b\x0d\x28\xf9\x01\x66\x59\x1d\xf0\x29\x51\x09\x8f\ \xac\xbc\x7f\xdd\xe0\x38\xf7\xeb\x3a\xbc\x7c\xb5\x27\xf4\x81\x6a\ \xa3\x56\xb2\x72\xf9\xb2\x65\xec\xea\x39\xe3\xf0\xb5\x53\x44\x10\ \x5c\xb3\x4b\xa1\x50\xd3\xcc\x76\xd0\x4b\x6b\x83\x9b\x8e\x99\x70\ \x4e\xd7\x6b\x8e\x70\x7a\x76\x63\x57\xf0\x5b\x5b\xab\x72\x7d\x3f\ \xff\xea\x57\xf6\x9e\xed\xd6\x7e\x9f\x5f\x60\xcd\xd2\xe7\xf5\xf5\ \x8b\x1c\xcf\xc9\x6b\x76\xc9\x6b\x6f\xa3\xc5\x80\x79\x15\x5e\xaf\ \xc2\xd3\x57\xcf\x11\xee\x93\xa6\x2e\x77\x8e\xec\x08\xdf\x31\x76\ \xfe\x65\xe8\xed\x9c\x1f\x95\x42\x1c\x65\xd6\x2d\x7c\x08\x13\x9c\ \xfc\x91\x6f\x31\x3c\x0f\xae\xa6\xe0\xf7\x71\x72\x78\x6c\xe1\xd8\ \xf6\xde\x8e\x93\xb4\x23\xfc\xca\xda\x98\x6f\xeb\xa4\xef\xca\x31\ \xd4\x03\x66\xcc\xac\x08\x7c\x3c\x44\x59\xd1\xf6\x0a\x81\xc7\xc6\ \x36\xcc\x5e\x3a\xe6\x2c\xd8\xd2\xf3\x8b\x8e\xb1\x99\x1e\xcc\xcd\ \xcd\x6d\xc8\x66\xf0\xfb\x58\x19\xd6\x37\xf4\x22\xce\x82\x55\x85\ \xcb\x2e\x6e\x8e\x30\x3e\x3e\x4e\x35\x8c\x8d\x8d\xd1\x51\xc3\xf7\ \xbd\xf8\x6a\xf8\x53\x52\x33\xd6\xd5\x3d\xdd\xdf\xbb\xa9\x55\x65\ \x20\xae\xc9\x84\xb5\x62\xe2\xa2\x28\x78\x74\x6c\x94\x9e\x14\xb3\ \x29\xfc\x31\x41\x49\xd5\x4f\xeb\x07\x51\x6b\x4f\x6f\x0f\x05\x87\ \x86\x07\x21\xfc\x94\x09\x17\x93\xa4\x1c\xfa\xfa\xfa\x58\x93\xe2\ \x95\x31\x5a\x29\xad\x14\xfc\xc0\xe7\xff\x23\xbe\x8e\x68\x84\xb7\ \x51\x22\xd6\xe4\x4d\x4d\x0a\x84\x5d\x98\x30\x0c\x0e\x0e\xb2\xe0\ \xd6\x4e\x12\x27\xa2\xe0\xbb\x1e\x5c\xb8\xef\xc9\xc3\x15\x0f\xc0\ \x95\x82\xf1\x7b\xb2\x45\xa8\xde\x83\x37\x46\x8b\x46\x35\x2d\xac\ \xcc\x36\xfd\x7d\x65\xcd\x02\x9c\x88\xc6\xe9\x03\x10\xc5\x3a\x7e\ \xaf\xee\xc5\xbd\x68\x38\x38\xd9\x03\x9a\x08\x3f\xf5\x0f\xf0\xa3\ \x8a\x03\x03\x03\xea\x17\x9e\x98\x98\xd8\x80\x6f\x86\xd2\xfd\xb8\ \x17\x0d\xb4\xd0\x44\xd8\xa5\xac\xac\x0c\x86\x86\x86\xd6\x46\x46\ \x86\x71\x65\x1d\x80\x69\xeb\x26\x69\x1c\xaf\x8f\xff\xb3\x40\x4c\ \xaf\xea\x01\xff\xfe\xfd\xbb\xc6\xe3\x71\x11\x0e\xd0\xd0\xd2\xf9\ \xcc\x6b\x6e\x6e\x86\xe9\xe9\x69\x6c\xd8\x32\x22\xb2\x8e\xc2\xa5\ \xca\x46\xba\xc6\x42\x7b\xfb\x7a\x60\x72\x72\x12\x94\x4a\x25\x7c\ \xd5\xfe\x1c\x8b\x13\xe3\xc9\x87\x1a\x34\x29\xb8\xa7\xa7\x07\x4f\ \xda\x32\x0a\x85\x62\xcb\x90\x24\xc9\x0c\x5a\x6c\x58\x22\x91\xc0\ \x8f\x1f\x3f\x20\x28\x28\x08\x22\x23\x23\x21\x25\x35\x19\xb8\x5c\ \x2e\xf0\xf9\x7c\xa8\xac\xac\x84\xaa\xaa\x2a\x10\x0a\x85\xd0\xd2\ \xd2\x02\x75\x75\x75\xd0\xd9\xd9\xb9\xd9\xba\x39\xdc\xd5\xd5\x05\ \xa1\xa1\xa1\x10\x11\x11\x01\x69\x69\x69\x90\x95\x95\x05\xb9\xb9\ \xb9\x50\x51\x51\x01\xf5\xf5\xf5\x20\x10\x08\xf6\x0e\x4b\xa5\x52\ \xbc\xb6\x6a\x2a\x01\x8d\x60\xa3\x3a\x62\xb1\x18\xb3\x37\x58\x2e\ \x97\xaf\x61\xe1\x10\x82\x37\x5f\xa3\xe0\x27\xcf\x1e\x3d\xc5\x3f\ \x0e\x33\x2a\xf3\xc4\x3f\xf2\xf0\xd6\xbb\x01\xe0\xe6\xef\x00\x00\ \x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x04\x4f\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x04\x16\x49\x44\x41\x54\x78\x01\xb5\x93\x63\x74\x1c\x6d\ \x14\xc7\x5f\xdb\xb6\x3e\xbf\xb6\x6d\xbb\x56\xb8\xde\xd8\x4e\x6a\ \xdb\x9d\x6e\xb9\x39\x3d\xb5\xdd\x6e\x3c\xcb\xd9\x0d\x46\x6b\xc5\ \x58\xc4\x99\xa7\xf7\xc9\xa9\xcd\x7b\xce\x7f\x7c\x7f\x97\x73\xd3\ \xa5\xec\xc7\x1f\x7f\xbc\xf9\xa6\xeb\x65\x29\x69\xc9\xc5\xca\x38\ \x59\x38\x2e\x5e\x2e\x28\xe2\xa4\x48\x19\x2f\x1b\x90\x2b\x25\xa1\ \x8c\xac\xb4\x85\x57\x05\x4c\x4d\x4b\xfe\x5c\xae\x94\x75\x6b\x75\ \xa4\xc0\x72\x3c\xaa\xae\x65\x04\xaa\x86\x19\xa8\xa5\x59\x54\x07\ \x22\xc9\x2a\x41\xa6\x90\x76\x41\x80\x8f\x2f\x1b\x9a\x9b\x9f\xf5\ \x1e\x64\x06\x40\x0e\x2d\x28\x3e\xda\x23\x5f\x58\xd6\x91\xac\x32\ \xf6\x26\x10\x46\x21\x89\x30\xf4\x8a\xe6\x6a\x3a\x96\x6e\xd4\xf4\ \xd8\x6c\x36\x04\x55\x0c\x14\x8d\xcf\x7f\xeb\xb2\xc0\x32\x85\x24\ \xcc\xb2\x2c\x9a\xb2\xae\x6c\x10\x56\x41\x37\xb5\xb8\x9a\xc2\x4d\ \x8e\xc6\x90\xbf\xd6\xdd\xe1\xde\x54\xe9\xb6\x25\x10\x86\x81\x19\ \xea\xb2\x1e\x0e\x82\x4b\xe5\xe2\xe0\x25\xa1\x59\x39\x19\x29\xeb\ \xd5\xeb\x7a\x57\x6c\x2e\xed\x01\x67\xa1\xd6\xd5\xe1\xf7\xb7\x76\ \x35\x35\x07\xba\xdb\x3d\xcd\x9d\x0d\x9c\x2f\xe8\xa2\x1c\xed\xfc\ \xc2\x3d\x9c\x26\x7e\x85\x41\xd8\xb8\x8f\xec\x59\xbd\x46\xd5\x95\ \x9b\x9f\xad\xbc\x28\x58\x2c\x8d\x69\x62\x59\x06\xc5\xcc\x3a\x1c\ \x2e\x2e\x75\x71\xac\x37\xe0\x72\x36\x86\xbd\x9e\xe6\xb0\x07\xb2\ \xb6\x71\xbe\x40\x9d\xd9\xd1\x46\x8d\xdf\x50\xbb\x0e\xaa\x39\x58\ \xb0\x86\xec\x65\x18\x1a\x81\x9f\xff\xa2\x60\x98\x78\x1f\x6f\x85\ \xde\x2d\x21\xfb\x4b\x6b\x1b\x6b\x2d\x8e\x76\x16\xe0\x75\xb6\xfa\ \x90\xc5\x5a\x1f\x34\x30\x9e\x40\xf9\x5e\x83\x7f\x43\xc2\x72\xe3\ \xd4\x84\x15\xc6\xa5\xf8\x3b\x97\xcb\x85\xa0\x7d\x7d\x17\x05\xc3\ \x30\x04\x9a\xe5\x10\x94\x89\x76\xe8\x7c\x5a\x2d\xd7\xac\xb7\x38\ \xdb\xcb\xeb\x3c\x1d\x47\x18\x6f\x60\x4f\x25\xd3\xac\x4a\x53\x51\ \x8a\x44\xc2\x98\x81\xe1\xf8\x3b\x0c\xc6\x7e\x97\x18\x9c\xb8\xd7\ \x6e\xb7\xa3\x94\x95\x7a\x61\xe6\x56\x66\xfb\x2e\xbd\x6f\x67\x05\ \xd3\xbc\xed\x88\xa5\x61\xf1\x36\xd2\x9b\x91\x44\x18\xff\x8c\x27\ \x8c\xc3\x93\x56\x98\x64\x00\x2f\x50\x2c\xa9\xea\xc7\x03\x04\xbf\ \x9e\x8b\x82\x0b\xc7\xe7\x55\x94\x96\x96\xf6\x8d\x5f\x47\xf6\x8b\ \xe7\x95\x95\xe1\xac\x00\x94\x9c\xb0\x92\x1a\x9d\x4c\x50\x3f\x42\ \xf9\x1f\x27\xad\x30\xfe\x01\x01\xa2\xc7\x4d\xd9\x7b\x78\x72\xb1\ \x76\xa0\xaa\xaa\xb2\xbb\x68\x42\xc1\x91\x8b\x82\x27\x4e\x2e\x8a\ \x28\x28\xca\x77\x51\x8c\x13\xc1\x56\x20\xe5\x32\xfd\xa2\xf8\x15\ \xa6\xd4\x04\xc2\x14\x81\x81\xf1\x84\xfe\x47\x18\xda\x7f\xb2\x85\ \x15\x99\xf8\xbd\x99\x71\xa0\x58\x71\x74\x63\x52\x4a\x7c\xce\x25\ \x57\x4e\x22\x13\xf9\xd5\xc5\xea\xe6\xbd\x24\x3f\x08\x1f\x39\x7e\ \xbb\x29\x6e\x29\x99\x92\x40\xe8\x23\x00\x28\x1e\x96\xb7\x51\x9f\ \x08\xcf\x0f\x19\x6c\x48\xa7\xd3\x0d\x6c\xdc\xb8\xd1\x2b\x12\xc7\ \x04\xfe\x1b\xf2\xcf\xd3\x17\x05\x4f\x9a\x32\xfe\x75\xe8\x59\xd7\ \xec\x39\xb3\xdd\x9c\xab\x01\x2d\xd8\x6e\x46\x69\x2a\xbd\x00\x41\ \xe0\x6c\x10\x16\xee\xb0\xa0\x1a\xde\x83\x24\x52\xd1\x80\xd3\xe9\ \x44\x6a\xb5\x1a\x2d\x58\xb0\x20\x18\x23\x8a\x6a\xb8\x2c\x38\x64\ \xee\xc9\xcd\xcb\xe6\x67\xce\x9a\xe9\xd2\x68\x34\x01\x3c\xd4\x43\ \x87\x0e\xb5\x4f\x9e\x32\x99\x8e\x8a\x89\x70\x8e\x1a\x35\x2a\x52\ \xae\x90\x22\x9a\xa6\x31\x18\x2d\x5e\xbc\xb8\x1b\xda\xd2\x7a\x49\ \xf8\xf1\x00\xe3\x40\xbb\xa0\x82\x30\xec\xb8\x80\xcf\xf8\x1e\x3f\ \xc7\xef\xa5\x52\xe9\x04\x99\x5c\x82\xcc\x66\x33\x5a\xba\x74\x29\ \x86\xf7\x03\x3c\xf0\xef\xff\x7f\x3f\x73\xd3\x65\xd8\x2d\xa0\xdb\ \x40\xb7\x83\xee\x38\xae\x3b\x4f\x5c\x47\x46\x46\x46\x01\x5c\xc0\ \x70\x95\x4a\x85\x5b\x23\x88\x24\x31\x9d\x7f\xff\xfb\xd7\x0b\xe7\ \x03\x61\xa7\xbb\x41\xf7\x81\x1e\x7c\xeb\xad\xb7\x1e\x3f\x70\xe0\ \xc0\xe7\x16\x8b\x65\x08\x68\xc4\xe1\xc3\x87\x7f\x8c\x88\x88\x78\ \x05\xde\x3d\x0e\x7a\xf4\xcf\x3f\xff\xfc\x55\x2a\x13\x0b\x14\x45\ \x21\x58\x57\x54\x53\x53\x83\x86\x0e\x1f\xe2\x3d\x1d\x8a\x33\xbb\ \x0b\x74\x2f\x06\x62\xa7\x7b\xee\xb9\xe7\xa9\xf5\xeb\xd7\x7f\x05\ \x1f\x27\x35\x37\x37\x6f\xf4\xfb\xfd\x6b\x48\x92\x4c\x9d\x36\x6d\ \xda\xf7\xf0\x1e\xc3\x5f\x04\x3d\xfb\x21\xd8\xe8\xb1\xa3\x10\xcf\ \xf3\x83\x7f\xa3\x48\x12\xcb\x0f\x26\x79\xdc\x6e\x3e\x9e\xed\x3d\ \xa0\x07\x40\x0f\x1f\xcf\xea\x29\xec\x0c\xc2\xe5\xbd\x04\x7a\xf9\ \xf8\x19\xdf\x3f\x7b\xfc\xfd\xe3\x1f\x7c\xf4\xfe\x1b\x63\xc6\x8e\ \x36\x89\x25\xa2\xfa\x21\x43\x86\xbc\x76\xbe\xbe\xde\x7c\x3c\xda\ \xed\xa7\x55\x70\x3f\xe8\x21\x1c\x0c\xeb\xf8\xf5\x03\xc7\xdb\x75\ \xcf\xf1\x84\x6e\x3d\xee\x7b\xe3\xed\x18\x52\x0a\x94\x45\x6d\x64\ \xa6\x64\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x04\xfe\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x04\xc5\x49\x44\x41\x54\x78\x01\xc5\x94\x35\x78\x23\x57\ \x14\x85\xc3\xcc\x9c\x32\xd8\x84\x93\x3a\xd4\x6d\x5f\x6d\x15\x66\ \x30\x33\xb3\x1d\x4e\x0c\xcb\xcc\xcc\xbb\x5a\xb4\x98\x99\xa5\xb1\ \x46\x34\x92\x46\x4c\xb6\xc5\xd2\xc9\x93\x23\x3b\xcc\xf4\xbe\xef\ \x17\xde\xf7\xcf\xbd\x67\xe0\xb2\xff\x65\x7d\xf8\xd1\xfb\xaf\x12\ \x0e\x11\x18\x42\x91\x50\x22\xf8\x09\x47\xab\xff\xfd\x55\xa1\x7b\ \xfb\xce\x1d\xf3\xc1\x60\xb0\x9c\xc9\xe6\x50\x2c\x95\x08\x65\x64\ \xb2\x19\x04\x02\xfe\xca\x91\x23\x87\x33\xa4\xc6\x57\xad\xfd\xa3\ \xd2\x49\x22\x8c\x25\x93\xa9\x4a\x22\x99\x02\xe5\xa0\xa1\x50\x69\ \x21\x57\xaa\xa1\xd3\x1b\x60\xb3\xd9\x10\x0a\xb2\xc8\x66\x16\x91\ \x4e\xa7\xb1\x61\xe3\xba\x05\xb2\x67\xe6\x77\xa5\x27\x4f\x9d\x9c\ \x2f\x14\x4b\xb0\x51\x73\xe0\x09\x44\xb8\xc8\x15\x82\x2b\x94\x42\ \xa2\xd0\x40\x67\x30\xc1\x68\x34\xc2\x49\x53\xf0\x33\x6e\xc4\xa3\ \x61\x94\x8a\x05\xec\xda\xbd\xb3\xf8\x71\xdd\x87\x5b\x7f\x75\xfc\ \x2f\xbf\xfc\x3c\x5e\x1d\x59\xa3\xd5\xe3\xcc\xd9\x0b\x38\x7b\x81\ \x0b\xce\x45\x3e\xce\xf3\x24\x90\x28\x0d\xd0\x99\xac\xd0\x1b\xf4\ \xa0\x29\x13\x3c\xb4\x05\xac\xcf\x89\x58\x84\x45\x2e\x97\xc1\xe4\ \xd4\x37\x45\xe2\x78\xed\x97\xc4\xbe\x78\x22\x51\xf1\x32\x0c\xc4\ \x52\x19\x44\x12\xf9\x52\xb7\x9c\x4b\x02\x70\x66\xc5\xe0\xcb\x75\ \x50\x68\x0c\xd0\x6a\xd4\xb0\x9b\x94\x70\xdb\xd5\x60\xbd\x56\x44\ \x83\x5e\xa4\x92\x31\xf8\x7c\x0c\xea\x1b\x3e\x4e\xfd\xac\xdb\xc3\ \x47\x0e\xe5\x33\x99\x0c\x0c\x06\x23\x14\x4a\x25\x41\x05\xb1\x4c\ \x8e\x0b\x44\xae\x34\xd8\xa0\xb1\x38\x60\xa6\x9c\x30\x9b\x74\xb0\ \xe9\xf8\x70\x5b\xc4\x08\xd0\x5a\xb0\x1e\x1b\x3c\x4e\x0a\xc9\x44\ \x1c\x1b\x36\xac\x2b\x7f\x5c\xf7\x51\xdd\x8a\xf8\xa3\x8f\x3f\x38\ \xe3\x0f\xf8\x41\xae\x00\x92\xa1\x01\x2a\x95\x1c\x0a\xb9\x78\x09\ \x83\xc9\x84\x53\x67\x38\x68\x6f\x6f\x43\x6b\x7b\x0b\xf6\xef\xdb\ \x01\x86\xe2\x81\xd6\x5f\x00\x6d\x10\xc0\xac\x15\xc3\x66\xd6\x81\ \xf1\xba\xc9\x3e\x15\x3a\x3a\xda\xdc\x2b\x62\x32\x42\x3a\x9f\xcf\ \x83\x61\x3c\xb0\x59\x4c\xd0\xa9\x44\x90\x0b\xcf\xc2\xa8\xe1\xe3\ \xe2\xf9\xd3\x18\x1d\x1d\xa9\xac\x5f\xbf\x9e\xde\xb2\x65\xcb\xe9\ \xc9\xc9\x6f\x4a\x27\x8e\xee\x01\x4b\xab\xa1\x11\x9f\x81\x45\x27\ \x01\x65\x35\xc2\xe5\xa4\x11\x64\x59\x34\x36\x35\x14\x7f\x10\xc5\ \x07\x65\xb2\xc0\x06\x7c\x70\x52\x46\x58\xb4\x17\xa1\x12\xec\x83\ \x5d\x73\x12\x2d\x2d\x75\xd8\xbc\x79\xf3\xe9\xe5\xda\xaf\xbe\xfa\ \xea\xe5\xf6\x8e\x56\x2c\xc4\x18\x98\x34\x22\xb8\x1d\x16\x78\x5d\ \x0e\xb8\x5c\x4e\x24\x12\x09\x90\xe9\xf1\xc3\x28\x2a\x55\x71\x38\ \xc4\xc2\x4b\x1b\x41\xe9\x4e\x42\xc3\xdb\x0c\xbb\xea\x10\x06\x06\ \xba\x31\x3c\x3c\xfc\xc2\x0f\xce\xc7\x55\xed\x1d\x2d\x95\x85\x54\ \x14\x46\x9d\x12\x4e\x87\x9d\x74\xeb\x80\x93\xa6\x91\x4a\xa5\x7e\ \x2c\x6e\x68\xac\xcf\x14\x0a\x05\x24\xe3\x51\xb0\x0c\x29\xb2\x88\ \x61\x94\x9d\x84\x59\xc9\xc1\xc1\xfd\xdb\x31\x31\x31\x6e\xad\x0a\ \xbf\x93\xb6\x4d\x73\x38\x47\x10\x8b\x7a\x21\x93\x8a\xa0\xd7\x69\ \xa1\xd7\xeb\x97\xae\xef\x48\x24\x8c\xa6\xa6\x86\xd2\x8a\xb8\xbd\ \xbd\x95\x0a\x85\x42\x58\x5c\x48\x23\x1a\x0a\x80\xa1\xcd\xb0\x1b\ \x65\x24\x63\x31\xe6\x48\x47\x97\xb8\x5c\x8c\x8d\x8f\x96\x47\xc7\ \x47\x8a\x3c\xe1\xa5\x4a\x30\x64\x83\xcd\x2a\x80\x4a\x29\x86\x5c\ \x2e\x83\x42\xa1\x00\x45\x51\xb0\x5a\x2d\xe8\xee\xe9\x9a\x5f\x11\ \xd7\xd5\x7d\x54\x7f\xe8\xf0\xa1\x4a\xa1\x90\x5f\xba\x6c\xc2\x44\ \xce\xfa\xdd\x64\xbc\x39\xe8\x2d\x36\xe8\xed\x1e\xe8\x1d\x01\x18\ \xe8\x20\xcc\xb4\x17\x6a\xbd\x04\x3a\xed\x2c\xe9\x52\x05\x99\x4c\ \x06\x89\x44\x82\x40\x20\x80\xed\xdb\xb7\x61\x78\x64\xe8\xd2\x8f\ \xae\xe5\xa6\xe6\x86\x42\x35\xa3\x6c\x36\x8b\x54\x3a\x8d\x70\x24\ \x02\xb7\x2f\x00\xf3\x9c\x07\x5c\xb9\x01\x27\x67\x15\x38\xcd\x53\ \xe1\x0c\x57\x06\x9e\x58\x0c\xa5\x92\x07\xad\x86\x47\x3a\x96\xc0\ \x62\xb1\xc0\xe3\x71\xa3\xaf\xbf\xb7\x32\x30\x30\x70\xe7\x8f\xc4\ \x24\xe7\xc6\xe9\x99\xa9\x4a\xbe\x50\xc4\x62\x26\x8b\x78\x32\x0d\ \x7f\x28\x06\x87\x37\x08\xb9\x7e\x0e\x42\xb5\x1d\x42\x8d\x1d\x02\ \x85\x91\x88\x15\x10\x89\xf9\x10\x09\x39\x30\x99\xf4\x08\x87\xc3\ \x98\x9a\xfe\x06\x03\x83\xfd\x47\x2e\xfb\x85\x75\x75\x67\x67\xfb\ \xd0\xd1\x63\x47\x90\xcf\x17\x30\xbf\x98\x45\x2c\x91\x06\x13\x8c\ \x41\x6b\xa1\x21\x37\xcc\x41\x69\x74\x40\xa6\xb5\x40\x24\x53\x41\ \x20\x14\x91\x28\xf4\x08\x06\x59\xec\xdc\xb9\x03\x3d\x3d\x5d\x71\ \xe2\xb8\x96\x70\xc5\xb2\xf0\x72\xc2\x75\x84\x5b\x09\xf7\x91\xf0\ \x39\x33\x6b\x67\x96\xa2\xc8\x64\xf3\x08\x45\x13\xa0\x5c\x0c\x4c\ \x94\x7b\x29\x16\xbd\x99\x82\xc5\x46\xc1\xe7\x0f\xc0\x6a\xb3\x92\ \x4e\x27\xab\x77\x5b\xf6\xd1\x47\x1f\x7d\x92\xec\xaf\xc6\x70\xcd\ \xb2\xfc\xf2\xda\x97\x1b\x09\xf7\x10\x1e\x79\xe7\x9d\xb7\xa7\x5b\ \x5a\x9b\x2a\xc7\x8e\x1f\x83\xd9\x62\x05\xe3\x67\x11\x8c\xc4\x11\ \x8a\x24\x10\x60\x43\x44\x68\xc7\xae\x3d\xbb\xaa\x97\x16\xda\xda\ \x5b\x0f\x5e\x75\xd5\x55\xcf\x91\x7d\x0f\xd6\x9a\xab\x89\x6b\xab\ \xf6\xe5\x9a\xda\x51\xef\xbd\xe1\x86\x1b\x1e\x1c\x1c\x1c\xb0\xf5\ \xf7\xf7\x16\xaa\x07\x21\xcf\x5b\x54\x21\x9f\xd1\xd7\xd7\x53\x1e\ \x1a\x1e\x62\x48\x97\xcb\xc2\x3b\x08\x37\x10\xae\xac\x79\x7e\x75\ \x5d\x51\xe3\x6a\xc2\xf5\x8f\x3f\xfe\xf8\xfd\xab\x57\xaf\x7e\xee\ \xb5\xd7\x5e\x7b\x61\xd5\xaa\x55\x8f\xde\x7c\xf3\xcd\xb7\xd5\xe2\ \xbb\xaa\x56\x77\xf9\x4f\x05\xdf\x02\x12\x7d\x18\x74\x50\xf5\x15\ \xf7\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x77\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x02\x3e\x49\x44\x41\x54\x78\x01\xad\x55\x35\x90\x1c\x41\ \x0c\xec\x95\x99\x99\xd3\xff\xd0\xcc\xcc\xcc\xcc\x76\xf4\xd1\x47\ \xcf\xcc\xcc\x18\x99\x9d\x99\x99\x9d\xe7\x75\x91\x19\xe3\x83\xcc\ \x8c\x5d\x53\xab\xaa\x99\x1b\xb3\x75\x37\x2b\xd5\xae\xd4\xd3\x82\ \x9d\xed\x09\x60\x0c\x80\x92\x9d\xbb\xb7\xa7\xe3\x3f\xc8\xf9\xb3\ \x17\x8f\x02\x38\xd9\x93\xa0\xaf\x5f\xbf\x4e\x1f\x3a\x74\xa8\xe5\ \x10\x04\xc1\xef\xda\xaa\x3f\x7f\xfe\x8c\xda\xea\xfa\xb4\xd4\xd4\ \xd4\xb1\x3d\xc9\x74\xf0\xe0\xc1\x88\xc7\xe3\xf4\x40\x60\x1c\xa9\ \x69\x80\x36\xb5\xb8\xb6\xb5\x84\x1a\x22\xc0\xf0\xe1\xc3\xf1\x1d\ \x73\x23\x19\x43\x44\x40\xa8\x86\xba\x3a\x6c\xda\xbc\x19\x94\x6b\ \x57\xaf\xa2\xb0\xb8\x98\x01\xce\x82\xda\x65\x25\x25\x96\x7f\x5d\ \x7d\x3d\x7a\xf6\xec\x09\x0a\xaf\x46\x02\xd0\x09\x23\xd7\xae\x05\ \x65\xd3\xed\xdb\xdc\x0c\x35\x55\x55\x56\x70\x79\x65\x85\x66\xc2\ \xfb\xc5\x97\x62\xa0\x54\x6f\xdb\x1c\x66\xa4\xc0\xca\xc4\x95\xc0\ \xb7\x19\xd3\xe6\xcf\x13\x10\x48\xc0\xab\xc3\x18\x42\x46\x0c\x56\ \x76\x93\xa6\x4c\xf1\x6c\xa6\xe5\xa0\x0f\x99\xaa\xff\x82\x85\x0b\ \x69\x5a\xc0\x74\x66\x4d\x35\x68\xd2\xe4\x29\xd4\x49\x9b\xcd\x9c\ \x35\x4b\x6b\x5c\x5b\x5f\xaf\xcd\x5b\xb0\x60\x01\x63\x6d\xc6\xa6\ \x36\x34\x92\x6b\x5a\x56\x51\x09\x11\xf0\xa1\x05\x4a\x30\x09\xd9\ \x0b\x9f\x8a\xc1\x71\x19\x8b\x79\xe0\xa9\x29\xac\x91\xe2\x12\x6a\ \x09\xe7\x57\x47\x14\xe0\x3f\x99\xb1\xbf\x81\x42\x04\x67\xe4\xa0\ \x80\x26\x06\xbc\x08\x7d\x3d\xcd\x33\x0d\x15\x4f\x4d\x67\x32\xdc\ \x06\x17\x03\x0a\xea\xc0\xd8\x4a\xce\x2d\x05\x9d\x29\x65\x15\x15\ \x20\x4b\x06\xcd\xf8\x0e\x4a\x67\x83\xa1\x80\xfc\x29\xbb\xec\xcc\ \x4c\xab\x27\xed\x9d\x9d\x4e\x29\x20\xda\x24\xe2\x52\xf4\x55\x0d\ \x6b\x0b\xe1\x63\x65\x1d\xf6\xe4\xfe\x84\xc3\xa6\x27\x9b\x4f\x43\ \x44\x3c\x2f\x08\x35\x68\x68\xd7\xad\xda\xe6\xe5\xe6\x58\xec\x5a\ \xda\xdb\xbd\x73\xee\xcc\x71\x98\xa2\x18\xc6\xce\xb9\xe0\x61\x47\ \x7f\x6e\x40\x5b\x37\x5b\xb5\x7a\xb5\xc5\x58\x97\xcd\xda\x5a\xde\ \x57\xbe\xa3\xab\x53\x7d\x57\xac\x5a\xe5\x67\x2c\xa2\xa3\xe5\x5d\ \x2e\xbb\x65\xcb\x97\x03\x9e\x89\x50\xe0\x0f\x1f\x3e\xf8\xcf\x5a\ \x67\xb5\xb4\xb5\x69\x33\x09\x6a\xee\xc3\x3a\x94\x62\xb1\x98\x01\ \xe6\xa7\x24\x3b\x33\x37\x2d\x25\x25\x05\xfd\xfb\xf5\xe3\x57\xc0\ \x4d\xdf\xbf\x91\xa7\x3c\x89\x44\x02\x91\x48\x84\x9f\xa7\x67\x64\ \xdc\x3d\x7b\xf6\xec\xb1\x3c\xf5\xf1\x1f\x84\xa0\x00\x5a\x08\xfc\ \x10\xc0\x81\xef\x37\x7a\x03\xe8\x81\x7f\x93\x2f\x00\x3e\x02\x78\ \xfb\x0d\x7f\x36\xa3\xf0\x67\xf0\xcd\x0e\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ \x00\x00\x04\x5e\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4\x6c\x3b\ \x00\x00\x04\x25\x49\x44\x41\x54\x78\x01\xa5\x92\x03\x94\x24\x59\ \x10\x45\x7f\x16\x46\x6d\xdb\x1a\xdb\xb6\x6d\xdb\xb6\xdb\x63\xdb\ \x8d\xf5\xd8\x56\xdb\xb6\x6d\xdb\x78\x1b\x35\x9b\xbd\x1e\xd7\x39\ \xb7\x94\x11\x37\xde\x07\x3b\x3d\x47\x20\x47\x6c\x3b\x3b\x57\x98\ \x76\x66\x9e\xa8\xec\xec\x5c\xc1\x41\x00\xec\x7b\x61\x67\xe6\x0a\ \x0e\xdf\xda\xdd\xbe\x3a\xf8\xe6\x6a\x84\xfe\xba\x04\xd7\x57\x2b\ \x95\xd3\xa0\x59\xdf\x2d\x3e\xbf\xa0\x79\x7e\xd8\xdd\xf5\xf0\x3c\ \x62\x82\x80\x4b\x03\xe1\xe7\x30\x07\x94\x3a\x93\xe4\xe2\xef\x12\ \x4b\x24\x21\x94\xd6\xfb\x78\x3b\xb8\x98\xcb\x23\xf2\xf6\x32\xdc\ \xdc\x61\x52\x41\xe2\xf5\x37\x2c\xd8\x04\x47\xdb\x66\x9e\x57\x57\ \x32\xf1\xb7\x88\x0f\xfd\xbc\xd5\xb0\x32\xf2\xde\x3a\xf8\x5f\xe8\ \x07\xef\x13\x1d\x10\xfc\xeb\x52\x5c\x5a\x27\xa8\xfa\xf1\x90\x4c\ \xa5\x83\x4d\xb3\x9a\x6b\x56\xcc\xf4\xab\xc5\x94\x8c\x93\xc8\xaf\ \xae\x50\xac\x0a\xfe\x65\x09\x82\xec\x27\xe0\xf1\x19\x13\xdc\x3c\ \xa5\x82\xd2\x74\x73\xbc\x74\x68\x5b\x7a\x7d\x3f\x9b\xfe\xd5\x62\ \xfe\x8b\x64\xc0\xf4\xf3\x0b\x9b\x55\xfe\x7a\x50\x11\x4f\xaf\xe9\ \xa0\x20\x61\x25\x2a\xd2\xb6\xc2\x63\xba\x29\x9e\x88\xb9\xfa\xa7\ \x02\xc1\xf9\x6f\x12\xdb\x5b\x31\xc3\x1b\x96\x2c\xeb\xf5\x0f\x1a\ \x8d\x19\x61\xa3\x91\xee\xdc\x0d\x4e\xed\x14\xe0\xa1\x22\x8f\x04\ \x1d\x1d\x3c\x12\x71\x75\xd7\xcc\xd9\x42\x42\xfc\xc5\xe2\x1b\xe6\ \xac\x9f\x9d\x15\x2b\xf6\x7c\x60\x5c\x9f\x16\x32\x04\x11\xf6\x86\ \x78\x29\x2f\x46\x88\xba\x2a\x52\xf4\xf5\xf1\xac\x85\x10\x2e\x6b\ \xdb\x48\x56\x52\x6e\x67\xcd\x65\x92\xfc\xb3\x5b\xd3\x94\xf6\x99\ \x9d\xb5\xb8\xee\xde\x79\xe5\x92\x17\xab\x65\xeb\x9e\xb5\x14\x22\ \x41\x57\x17\x11\x1a\xea\x78\xd2\x4a\x84\xd0\x73\x6d\x90\x13\x35\ \x82\xf6\x7c\x0f\xd2\x42\x97\xe0\xfe\x05\x8d\x0a\x07\x1b\x81\x2f\ \x0d\x30\xfe\xec\x56\x5c\xb7\x62\xda\xd7\x2d\xd8\x88\x27\x2d\x04\ \x45\x31\x9a\x9a\xf0\x54\x92\xc3\x13\xb5\x16\x08\xbf\xd9\x1c\x89\ \x9e\xad\x90\x11\xa0\x8c\xdc\x30\x3d\x94\x26\x4d\x41\x65\x8e\x0d\ \xe2\x7c\xe6\x34\xd2\xad\x29\xbf\x61\xc9\xed\xa3\x01\xa2\x8f\x8a\ \x9b\xa0\x43\x32\x7f\x24\xe6\x1a\xde\x0f\x37\x6a\x8c\x7e\x2d\x42\ \xac\xab\xf0\x4f\x71\x5e\xb8\x2e\x8a\xa2\x4d\x50\x1e\xdf\x05\x35\ \x59\xcb\x51\x9e\x79\x08\x6e\x0f\x86\x57\x3a\xda\x36\x97\x6c\xcf\ \x2a\xa2\xf9\x47\xc5\x12\x28\x79\x4d\x66\xf4\x3a\xc4\xb9\xcb\xf2\ \xe2\x96\xbc\x58\x0f\xc5\x31\x5d\x50\x1c\xdd\x16\x35\x29\xdd\x51\ \x9f\x39\x1c\xf5\x05\x3b\x50\x98\x6c\x05\xa7\x5b\xbd\x2a\xec\xac\ \x04\xc5\x74\x01\xcc\x69\x40\x8b\xff\x15\xdb\x59\xb3\x84\x78\xff\ \xd9\x48\xf4\x35\xfe\x20\x4e\xf0\x68\x81\xf4\x00\xa5\x0f\xe2\xd2\ \xb8\x36\x28\x4f\x1a\x89\xe2\xc8\x0e\xa8\x4b\x1b\x88\x86\xcc\x21\ \x68\xc8\x9e\x82\xba\x02\x1b\xe4\xc4\x6e\xc1\x75\x2b\x41\xf5\x47\ \xc5\x34\xf5\xa4\xf7\x93\xbe\x0d\xe9\xa1\x63\x48\x2c\x40\x8c\xb3\ \x08\x69\xbe\x0a\x24\x36\x45\x51\x94\x29\x6a\x52\xfb\xa1\x22\x79\ \x3a\xf2\x42\x3a\xa3\x2e\x63\x14\x89\x27\xa3\xa1\xf8\x14\x3c\x1e\ \xf4\xad\xa6\x43\xb5\xfb\xe8\x56\xd0\xc4\xbe\x3f\x1d\x6d\x55\x96\ \x13\xb7\x09\x71\x6e\xad\x10\xed\x24\x40\x82\xbb\x0c\x1d\x9e\x09\ \xf2\x23\xba\xa1\x38\xaa\x1d\x1a\x72\x66\xa2\x2c\x69\x2e\x32\xfc\ \x7b\xa3\xb6\xe0\x24\x22\x5c\x66\xd4\x39\xd8\x08\x63\xa9\xb7\xe5\ \xff\x8a\xe9\x25\x20\xa4\xae\x59\x30\xff\x50\xa7\x31\x8d\x29\x41\ \x7d\xfe\x48\xed\xd4\x0a\x69\x3e\x2a\x28\x49\x18\x89\xec\xc0\xb6\ \x28\x8b\xe9\x86\x86\xc2\xfd\x28\x88\xdb\x01\x8f\xbb\x9d\x6b\xed\ \xac\x45\xe1\x27\xb7\x32\xb5\x26\xcf\xbf\x85\xb2\x84\x06\x61\x3c\ \x6f\x2c\x9b\xf3\xf3\x31\xc5\xaa\xec\xd8\x0d\x48\xf0\x52\x47\x92\ \x8f\x3e\x62\xde\xc9\x20\x2f\xcc\x18\x15\x69\x8b\x91\xec\xd9\x13\ \xc5\xc9\xfb\xf0\xf4\x46\x9b\xea\x0b\x7b\xc5\xae\xda\x6a\xac\x1d\ \xdf\x2b\x71\x70\x4d\x52\x8e\x90\xe1\x1f\x98\x12\x5d\x89\x41\x27\ \xb6\x31\x3f\xa7\x9b\x66\x75\xd9\xb1\x2b\x49\xae\x85\x54\xbf\xce\ \x88\x77\x35\x46\x75\x8e\x35\x32\x43\xe6\xe2\xe6\x71\xe9\x1a\xab\ \x35\xc2\x07\x02\x8e\x0d\xe6\x7b\x4c\x79\x87\xf4\xdf\x13\x4b\xf3\ \x7f\x9a\x10\x5d\x88\x81\x94\x62\xc6\xc5\xbd\x2c\x27\xe0\xd5\x90\ \xc6\xcc\xc8\x59\xc8\x4f\xdc\x8c\x04\xcf\x11\x70\xf9\x4d\xbf\xfe\ \xaa\xb9\xa0\x74\xf2\x10\x76\x80\xea\xc6\x4a\x6a\xf9\x1e\x13\xde\ \x21\xfe\xf7\xfe\xb6\x20\xd4\x08\x03\xa2\x1d\xd1\xbd\x7b\x5b\x36\ \xf5\xaa\x39\x57\x12\xee\x36\xa5\x31\xf0\x75\xef\x46\x7b\x5b\x61\ \xf5\x91\x4d\xdc\x5b\x0d\x65\x36\x91\x9e\x0f\x90\xd4\xf0\xb5\x92\ \x1e\x79\x42\xd8\xb4\xc7\x1f\x3b\xc0\x16\x7c\xa1\xea\xf2\x29\x6c\ \x04\x9d\x76\x19\xa5\x77\x5e\x33\x83\xcd\xa0\xff\xf4\x08\x4d\x42\ \x99\x5f\xa9\x98\xe0\xfe\xee\xf8\x1d\x54\x70\x6d\xa1\xab\x09\xa9\ \xf6\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x05\x61\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ \x00\x00\x05\x28\x49\x44\x41\x54\x78\x01\xed\x9a\x03\x94\x23\x59\ \x18\x85\xd7\xb6\x77\xd3\x59\xdb\x46\x34\xb6\x6d\xdb\x8a\x47\x8d\ \x60\x6c\xdb\x56\xdb\xb6\x6d\x8d\x11\x37\x82\x56\xfe\xad\xbc\xec\ \x79\x67\x73\x5a\xd5\x49\x35\x06\x75\xce\x6d\x84\xf7\xab\xba\xef\ \x7f\xaa\xc7\x1e\x1d\x0d\x1c\xc2\x0f\xe5\xaf\x0b\x3f\x90\x6d\x15\ \x7e\xe0\xd6\x5d\xcd\x63\xf4\x54\x72\x19\xc7\xee\x88\xd8\x6f\x77\ \x78\xe3\xce\x8f\x39\x3f\x21\xa0\x49\x27\x12\x52\x10\x02\xa1\x93\ \xbc\xb7\x82\xc7\xea\xad\xe4\x32\x81\x90\x46\xb1\x8c\xb5\x08\x46\ \x8c\x78\xb2\x43\x9a\x17\xd0\x25\x1c\x01\x4d\x92\x66\x31\x6e\x51\ \x3d\x00\x2c\x56\x8e\xe5\xaa\x74\x18\xe3\x7c\xfa\xda\x0f\x84\x34\ \xc9\x31\xc2\xb0\x99\x10\x34\x07\x80\xb5\x8c\xe1\xa9\x5d\xc6\xf8\ \xa8\xfd\xe2\xf2\x9d\xf3\x33\x02\x27\xe9\x22\xc2\x68\x39\x36\x4d\ \x1a\x00\x4b\x4f\x5c\x11\xe7\x52\xe7\xce\xcf\xb5\x6d\x5c\x3e\x90\ \x0c\x20\x0c\x16\x63\xb3\x76\x03\x60\x5d\x57\xf1\x58\x13\x5b\xdd\ \xb8\x88\x26\xfb\x92\x4f\x93\x78\x63\x93\x94\x01\x60\x05\x6b\xb8\ \x8c\xef\xa9\x8f\xcb\xdb\xce\x2f\x09\x9d\xa4\xce\x84\xa9\x2a\x6c\ \x90\x72\x00\xac\x1a\x15\x97\xb5\x55\x23\xf8\xed\x55\x0a\xac\xc3\ \xe3\xff\x95\xc5\xbb\xd8\x58\xeb\x02\x60\x29\xb8\x4c\x95\x43\x65\ \x97\x4f\x97\xfc\xce\xa7\x49\x63\xb0\x21\x8a\x01\xc8\x8b\x91\x7c\ \x8f\xc7\x66\x92\x36\x2e\xfe\x48\xfa\x3e\x61\x7c\x2f\x61\xa0\x8e\ \x10\xb4\x2f\x00\x96\x59\xc5\x65\x9e\x53\x2f\xe5\xd0\x9b\x34\x4f\ \x18\xef\x82\xcb\x22\x09\x39\x7f\xb9\xd1\xe6\xff\xad\x3d\x77\x82\ \xb7\xe4\x02\x64\xfb\x86\x41\x69\x5c\x0c\x04\x6e\xf2\x58\x42\x0d\ \x00\x56\xb9\x92\xcf\xec\xd2\x28\x00\xd1\x50\x67\x92\x31\x2e\xfb\ \x71\x07\x1c\x1f\x76\x05\x7c\xe6\x45\xc0\x76\xce\x11\xd8\x3f\xea\ \x00\xdc\x4c\x4b\x00\x30\x17\xda\x28\xfa\x88\xef\x0a\xaa\x00\xb0\ \x96\xb1\x66\xda\x03\x80\x8d\x9f\x1a\xe9\x61\x31\x8e\x55\xe4\x97\ \x0a\x50\x87\x4d\xc3\xf5\x94\x78\x08\xdf\xe9\x05\xe7\x16\x9f\x84\ \x43\x63\x0e\x8d\x21\x0b\xa0\x59\x3f\x02\xf4\x3e\x6b\x90\x34\x6b\ \x87\x51\x0b\xb0\xea\xb3\xf5\x70\x6e\x9c\x2f\x32\x1c\xc8\x8d\x82\ \x7c\x8f\x14\xb8\x15\x9f\x01\x77\x53\x33\xb1\xf1\x3c\xff\x28\xd8\ \xd4\x75\x87\x5d\x6d\xa0\xfc\xf8\x52\x80\x9a\x3c\xfc\x59\x50\x93\ \x0b\xe5\xc7\x16\x53\x07\xb0\xe1\xcf\x7d\xd8\x7c\xe5\xdd\xdc\x7a\ \x51\x29\xf4\x4a\x05\xaf\x39\x61\x20\xff\xa9\xe5\x00\x2a\x71\x17\ \x30\xeb\xd3\x00\xea\x0a\xc0\x18\x7f\x08\x4c\x49\x47\xd1\x67\xd6\ \x95\x25\x81\x52\xc0\xa1\x06\x60\x77\xf7\x13\x08\x20\xef\x4a\x32\ \xfa\xf0\xbb\xb9\x29\x70\x76\xe1\x09\xb8\x91\x6c\xcd\x7d\x69\x70\ \x1a\x7a\xde\x73\x66\x08\x88\x9c\x64\x2d\x02\xd0\x6e\x19\x8f\x3e\ \xa3\xe6\x9a\x3f\x7e\xac\xf6\x4e\x38\x7a\x4c\xb3\x7e\x24\x35\x00\ \xc7\x87\x5b\x1b\xec\xed\xc4\x0c\xf4\xc1\x27\x67\x1f\x43\x8f\xef\ \x1b\x79\x00\xfd\xaf\xce\xcf\x46\xcf\x5f\x9c\x1c\xd0\xe2\x2b\xa0\ \x96\x0e\x42\x67\xdf\xac\x4f\x07\xed\xb6\x09\xa0\xdb\x31\x19\xcc\ \xa6\x6c\x80\xda\x7c\x50\xbb\xf4\xa5\x06\xe0\xf2\xd4\x20\x6b\x83\ \xf5\x4d\x45\x86\x8b\x22\xa2\x61\xcf\xd0\x7d\x44\xb9\xb4\x9e\xa9\ \x1b\xd1\xe9\xe8\xf9\x63\x43\x2e\xdb\xd5\x06\xaa\x8b\xbc\xea\xc5\ \xd2\x18\x77\x90\x9a\x36\xb0\xe2\x93\x75\xb8\xe2\x04\x8b\xa3\xc0\ \xa8\xc9\xb7\xf9\xa2\x1a\x43\x01\x84\xbb\xc6\xa0\xe7\x77\x77\x3b\ \xd1\x62\x00\xdd\xce\x29\xa8\xd1\xa2\xdc\x6b\xe3\x51\x7c\xf4\xfe\ \x1b\x40\xc9\x67\x53\x03\xb0\xe6\x97\x5d\x36\x65\x33\x74\x65\x34\ \x3a\xe3\xda\x92\x1c\xb8\x19\x97\x01\x11\x6e\x31\xf8\xb9\x75\xbf\ \xef\x69\x11\x80\x6e\xd7\x54\x30\x1b\xac\xb1\xac\xca\x3c\x07\x4a\ \x1e\x9b\xfa\x7e\x60\x47\xe7\x23\xc8\x1c\x19\xad\xfe\x62\x23\x39\ \x00\x01\x07\x2a\x7d\xe4\xb8\x74\xd6\x94\xfa\x81\x6a\x79\xb7\xd6\ \xe9\xc8\x0e\x0f\xbc\x40\xca\xbc\x07\x51\x81\x84\xcd\x8c\x85\x54\ \x2b\x7b\x42\xc5\xc5\x15\x50\xab\x88\x02\x1c\xc1\x52\x5f\x50\x89\ \xba\xb6\x5e\x4f\x7c\x7e\x82\x2f\x29\x80\x53\xa3\x3c\x1a\x1c\xcc\ \x95\x9f\x13\x4d\x33\xc6\x1e\x80\xea\x7c\x77\x30\x57\xa3\xac\xd7\ \x53\x75\xb1\x37\xaa\x3e\x94\x03\x88\xe8\x32\xf0\x9a\x1b\x4e\x0a\ \x60\x7f\xef\x33\x0d\x02\xe8\xa3\xf7\xac\xf8\xbf\xd9\x3a\x5d\x22\ \x18\x42\xb7\x83\x21\x72\x17\x54\x97\xf8\xa0\x12\x8a\x9e\xab\xcd\ \x07\xbd\xdf\x1a\x6a\x01\x24\xdf\x6d\x25\x9d\xff\xcd\x8c\x03\x0d\ \x02\x18\x42\xb7\x72\x6b\x6e\x85\x80\x29\xf9\x84\x65\x78\x50\xaf\ \x77\xd5\xac\x1b\x0e\xa6\xd4\x93\x18\xd0\x94\x7c\x1c\x8c\x31\xfb\ \xa1\xf2\xb2\x33\xd1\x2e\xba\x3a\x04\x40\x98\x3a\x48\x1a\xc0\xed\ \xbb\x2d\x0e\xcd\x07\xf4\x01\x9b\xea\x45\xab\xf6\x76\x28\xa8\x56\ \x74\xb7\x1f\x60\x5f\x9f\xd3\xa4\xcc\x7b\xcf\x09\x47\x71\x73\x04\ \xc0\x10\xb2\xd5\xda\xa8\xaf\x07\x42\xc5\xa5\x55\x78\x38\x51\xe9\ \x29\xb1\x1f\xe0\xd4\x68\x4f\x52\x00\xe7\x26\xf8\x3a\x3c\x23\xab\ \x2e\x70\x47\x86\x75\x7b\x67\xa0\xff\x2d\x71\x43\x91\x4a\x3d\x61\ \x1f\x80\x90\x10\x51\x1a\x49\x01\x1c\x1e\x78\xde\x51\x00\x34\x12\ \x45\x86\x53\x8e\x83\x76\xf3\x58\xd4\xb1\x59\xfe\x37\x84\x6c\xb3\ \x0f\x80\xe8\x94\x90\x39\xaf\xd9\xa1\x70\x72\x84\x3b\xec\xec\x72\ \x0c\xa4\x3f\x6c\x47\x93\x9a\x4b\x53\x02\x6c\x00\xb6\x77\x3a\xe2\ \x28\x00\x1a\x79\x9a\xab\xb2\xc0\xa6\x62\x55\xa4\x80\x5a\x32\xd0\ \x2e\x00\x34\x89\x91\xff\xb4\x1d\x84\x74\x79\x3d\x63\xe2\x8f\xd7\ \xc2\xa1\x01\xb8\x83\x43\xc3\x0d\x2a\x26\xf5\xe8\xcc\x67\x9c\x25\ \xf2\x1f\x86\xae\x04\x9e\x99\xd9\x01\x40\x4a\xeb\xff\xd8\x0b\xee\ \x33\x42\xd0\x80\x8f\x0a\x00\x0a\x3b\x32\xf2\x5a\xf9\x29\x36\xdf\ \xfe\x00\xe4\x96\x55\xc8\x0b\x03\x50\xbf\xac\xf2\x00\x2e\x6c\x3d\ \x00\x4b\x8b\x0f\xc0\xe2\x2e\x89\xe5\x75\x53\x3b\x2c\xaf\x3f\xda\ \xe0\x68\x87\x2d\xa6\x07\x7f\x93\xef\xd1\x36\x6b\x7d\x10\x27\xc9\ \x9f\x84\xe9\x78\x12\x00\xa9\x8a\x65\x0c\x4e\x07\xbf\xd5\x40\x76\ \x8f\xc4\xad\x06\x0f\xc4\xcd\x1e\x8f\x8e\x7f\x01\xd7\x2b\x79\xd4\ \xea\x76\x04\x5f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \ \x00\x00\x03\xb3\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\ \x00\x00\x03\x7a\x49\x44\x41\x54\x78\x01\xc5\x97\x03\x94\x6b\x47\ \x00\x86\xf3\x6a\xdb\x37\x35\x0e\x6a\x37\xaa\x6d\xdb\xb6\xa2\x35\ \xa3\x67\xdb\xb6\x6d\x5b\x6b\xdb\xb8\xb1\xf9\x77\x3a\xc5\xf2\x26\ \xb9\xab\xcc\x39\xff\x6e\xfc\x7d\xe3\x19\x41\xcf\x0b\x06\x25\x0a\ \x35\x0c\xfb\x87\x88\x81\x40\x30\x48\x30\x90\x45\xc9\x68\xef\x89\ \x13\x6a\xf7\xc4\x31\x5a\x0d\xab\x90\x6a\x5a\xe5\xd2\xc3\xe4\xff\ \xc3\xfd\x0e\xfe\x43\x98\x76\x49\xdc\x35\xba\xd1\x6a\x46\x1b\x20\ \xc1\xff\x02\x0a\x09\x48\x82\xad\x0a\xf1\x9c\x26\x85\xf8\x8a\x3e\ \x07\xa7\x09\xd2\x4e\x21\xc0\x4f\x49\x5a\x48\x40\xd3\x59\xa0\x2d\ \xa6\x16\xb9\xf4\x37\xa4\x3d\x7e\x5a\xdf\x34\xb7\x50\xf7\x18\x01\ \x65\xb7\x81\x23\x08\xb4\xa5\xb0\x59\x2e\x7a\xae\xc7\xe0\xbf\x07\ \x58\x1c\xa3\x99\x43\x40\x21\x12\xf0\x14\x68\x8b\x5c\xbc\xb6\x31\ \xee\x91\x1b\x78\xf4\xf3\x88\xb3\xe3\x84\x5a\x35\x01\xd8\xb9\xc0\ \xdc\x02\x9c\x71\xb5\x28\xc4\x86\x96\x1f\x1f\x3f\x2f\x2c\x5c\x7d\ \x8d\xe6\x15\xb5\x50\x5b\xc1\x01\xe4\x23\xc0\x11\x69\x2d\xab\x94\ \x7e\xda\x3d\x9c\xd1\x64\x72\x81\x92\x6f\x1e\x86\x61\x0f\x4c\xa2\ \x8f\x27\xbe\x31\x05\x87\xe6\x6c\x46\x5d\xd6\x11\x1c\x5d\xb4\x75\ \x2b\xb7\x00\x77\x58\xa5\x38\xb3\x8b\x80\x8a\xd1\x4e\xee\x0c\x4e\ \xbd\x75\x18\xa6\xbc\xb0\x10\xeb\xbe\xdf\x85\xad\xca\xbd\xa8\x3e\ \x74\x14\x08\x95\xc2\xd6\x9c\x8b\xec\xd5\x3b\xb0\x36\x6d\xd9\x22\ \x4e\x01\x95\x0c\x96\x29\xdf\xc2\x32\xee\x73\xb4\x2a\x65\x1d\xdf\ \x53\x8a\x27\x47\x14\x18\x29\x9a\x8a\xf5\x3f\xec\xc6\xc9\xe9\x47\ \xd1\x92\x9b\x0b\x67\x4b\x11\x02\x9e\x12\x6c\xce\x58\x8b\xf8\xeb\ \x75\x11\xbb\xc0\x93\xb5\x80\xca\x92\xc0\x7d\x78\x06\x7f\x81\x59\ \xaf\x2d\xc3\xc1\x11\x87\x80\x60\x29\x4a\xb6\xef\x87\xcb\x54\x80\ \x86\x23\xb9\x58\xf7\xe3\x6e\x68\xee\x18\x13\x56\xc0\xa8\x79\x85\ \x82\x6d\xb3\x7f\x83\x6d\x9e\x1c\x08\x96\xc0\x98\xfa\x1c\x3f\x81\ \x65\x9f\x6c\x42\xfe\x92\xe3\xf0\x3a\x8a\x90\x70\x83\x1e\x47\xe6\ \x6c\x85\xad\xb6\x10\x1b\x7e\xda\x13\x51\x80\x4d\x7c\x0a\x21\x6f\ \x1e\x3c\x47\x67\xc1\x73\x72\x1e\x42\xae\x1c\xb0\xf1\x4f\x46\x2f\ \x10\x7f\x9d\x9e\x36\xff\xae\xb4\x03\xf0\xbb\x4b\x60\x6d\xc8\x43\ \xd0\x5f\x82\xa2\xd5\x27\xb0\xfe\xc7\x3d\xe4\x7d\x43\x58\x01\x93\ \xfe\x0d\x04\xed\x27\xa8\x44\xa0\x79\x2f\xac\x33\x7e\xe2\xd7\x05\ \x5a\x52\x43\x52\x53\x9a\x3d\x9a\x03\x28\x59\x77\x02\xc7\x27\x1f\ \xa1\xcf\x97\x7d\xba\x29\xec\x34\x34\x0d\x7d\x07\x01\xe3\x41\x04\ \xcd\x47\x60\xcc\x78\xa9\x0d\xca\x47\x60\x94\x78\x3a\x85\x75\x17\ \x32\x36\xba\x13\x20\xb0\x17\xe1\xda\x36\x12\xf0\x17\x22\x68\x39\ \x0a\x93\xe1\xcd\x36\x20\x5f\x81\x69\x2f\x2c\xe6\x14\x18\xf7\xf8\ \x9c\x0e\x02\xae\x1d\x63\x56\xfb\x6b\xb6\x00\x81\xe2\xff\x46\x3d\ \x1d\x74\xde\xc2\x15\xa4\x35\xde\xed\x99\xc0\xa2\xf7\xd7\x71\x0a\ \x18\xee\x9d\xd0\x41\xc0\x73\x6c\xf6\x76\x7f\xcd\x56\x38\x37\x19\ \x60\x4c\x7f\x11\x6c\xca\xb3\xb0\x2d\x54\x22\xd0\xba\x1f\x21\x77\ \x2e\xec\x2b\x92\x61\x9d\xf6\x03\x5d\x17\xa2\x12\x88\x23\x59\xfb\ \xed\x0e\x4e\x81\xe4\x9b\x86\x44\xb3\x14\x13\x91\xe7\x10\x72\x65\ \xd3\xd6\x20\xad\x43\x67\x43\x54\x02\x69\xb7\x0d\xe7\x84\xaf\xfa\ \x7a\x5b\xb4\x7b\x01\x1d\x03\xa4\x3b\x68\xed\x6d\x73\x7e\xa7\x8f\ \x8d\xa9\xcf\x47\x16\x20\x6b\xfe\xff\xc0\xe5\x9f\x6f\xc1\xca\x2f\ \xb7\xfe\xff\x7c\xfe\x3b\x6b\xa2\x16\x60\x93\x9e\x46\xc8\x93\x4f\ \x57\x44\x6f\xc1\x32\x84\x1c\x27\xc1\xc6\x3d\x1e\x59\xc0\x70\xcf\ \x78\x8c\x78\x64\x1a\x52\x6e\x19\x46\x21\x09\x37\x0c\xc6\xcc\x57\ \x97\x51\x81\xc9\xcf\x2e\xe0\xb5\x1b\x5a\x26\x7d\x03\x7f\xd5\x66\ \xf8\x6b\xb7\xc1\x32\xf1\xeb\xe8\xba\x80\x2b\xc3\x1e\x9e\x82\x21\ \xf7\x4f\xe4\x23\xc0\x6f\x1a\x72\x6f\xc7\xfc\xcf\x03\xfc\xb7\xe3\ \xd8\x1f\x48\x62\x7f\x24\x8b\xfd\xa1\x34\xf6\xc7\xf2\x98\x5d\x4c\ \x78\x94\xb8\xeb\xf4\x17\xc7\xe0\x6a\xd6\xfd\xe5\x54\x7d\x8d\x76\ \x77\x87\xcb\xa9\x4a\xf4\x50\x3f\xa0\xfa\xff\x7a\xfe\x17\x2b\xf8\ \x83\x81\x17\x01\x04\xca\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x33\x3f\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x01\x00\x00\x00\x01\x00\x08\x06\x00\x00\x00\x5c\x72\xa8\x66\ \x00\x00\x33\x06\x49\x44\x41\x54\x78\x01\xec\xdd\x03\xac\x24\x59\ \x18\x05\xe0\x7f\x6c\xbb\x6e\xad\x6d\xab\xba\xde\xac\x6d\x85\xab\ \xe8\x45\xe3\x2e\x8d\x2b\xe9\xaa\x5e\x3b\x7a\xc1\x3a\x58\xdb\xb6\ \x6d\x1b\xed\x1e\xe3\xe1\xee\xed\xdd\xbe\xeb\x7d\x6c\x14\xce\x97\ \x9c\x38\x19\x9e\x93\xf9\xef\x24\x29\x82\x08\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\xb0\xd9\x45\x7b\x59\xcc\x5f\x4e\x3d\xc8\x19\ \x89\xe5\x99\x45\xfa\x5e\x04\x00\xe1\xb7\x50\x75\x27\x3b\x4a\xfa\ \x6a\x9b\xf9\x1d\x36\x4b\x3f\xde\x8b\x01\x78\x5c\xa4\x33\x67\x68\ \x37\x65\xe6\x1f\x34\x83\xc2\x07\x00\x5a\xf7\x6b\x1b\x66\xab\xfe\ \x7c\x51\xfc\x92\x08\xaf\xa4\x0f\x03\xc0\xab\x59\x93\x33\x74\xf7\ \xf3\xb9\xc7\x8f\xa0\x70\x00\x00\x47\x49\x1d\x25\xca\xff\xa1\x2c\ \xfe\x00\x06\x40\xe6\xf3\xac\xa9\x9f\x4d\xc1\x05\x00\x96\x92\xda\ \xc9\x62\xde\x83\xb2\xf0\x35\x1c\x00\x99\x27\x33\x46\xcb\x9e\x04\ \x10\x48\xb8\xf3\xdb\x45\x78\x1d\x07\x80\xcb\xf7\x81\x5f\x0c\x6d\ \x3a\x01\x40\xf3\xb8\xe4\x0e\x75\x54\xbf\x55\x94\x3b\x2b\x4b\xde\ \x80\x01\x90\x29\xe6\x0d\xdd\xe6\xee\x6e\xc3\x09\xa0\xf1\x70\xe7\ \x5b\xcc\x7f\x5f\x96\xbb\x09\x03\x20\xf3\x29\xde\x07\x00\x1a\x64\ \x31\x4b\xef\xe8\x30\xef\x7e\x59\xea\x00\x0c\x80\xcc\xe3\x45\x43\ \xdb\x9d\x00\xa0\xf6\x9c\x2d\x2f\x9c\x64\x29\xfe\x45\xa2\xc8\x9b\ \x44\x78\xc0\x06\x40\xa6\x3d\x67\x6a\x6d\x3f\x2f\x6e\x99\x46\x00\ \x35\x81\x3b\x7f\xb0\x28\xef\x79\xa2\xbc\x19\x59\xe4\x00\x0f\x80\ \x4c\x21\x9b\xd4\xe7\x73\xf7\xb0\xa1\xd4\x3f\x00\xe0\xb0\xd4\x11\ \x0e\xf3\xdf\x95\x05\x0e\xd1\x00\xc8\x7c\x92\x35\x13\x27\x50\xef\ \x01\x80\xa9\xa6\x76\x10\x65\xbd\x4d\x16\x37\xc4\x03\x20\xf3\x78\ \xc1\x4e\xec\x46\xff\x0f\x00\xdc\x69\xee\x58\x47\xf5\x5d\x51\xd6\ \x8d\x22\x3c\x22\x03\x20\xb3\x39\x6f\xe8\x57\x97\x16\x1c\x36\x91\ \xfe\x0b\x00\xee\x7c\xff\x17\x59\xd6\x08\x0c\x40\xf7\xef\x03\x67\ \x9f\x3d\x84\x00\xe2\xce\x51\x53\x87\xd9\xcc\x7b\x47\x96\x34\xfa\ \x03\x20\xa3\x7f\x94\x35\xf5\xe3\x08\x20\x8e\x96\x2a\xde\x16\x0e\ \xf3\x6e\x92\xe5\x8c\xdd\x00\xc8\x24\xb5\xfb\x33\xa6\xb6\x1d\x01\ \xc4\x81\x31\xe3\xd2\x31\xd5\x3b\x7f\x83\x08\x8f\xd7\x00\x74\xff\ \x3e\x50\xb4\xf7\x9b\x40\xd1\x04\xc0\x07\xfd\x76\xe7\x2b\xde\xcf\ \xb2\x90\x18\x80\xbf\x27\x6b\x24\xf2\x78\x1f\x88\x1c\x58\xac\xa6\ \x0f\x12\x05\x7c\x59\x16\x11\x03\xd0\x63\xde\xca\x26\xb5\xd9\x14\ \x66\x00\x16\xbb\x58\xad\xde\xf9\x5d\x22\x1c\x03\xd0\xf7\xf7\x81\ \x5f\x8c\xd9\xdb\x50\x98\x00\xb8\x8a\x3b\xba\x7a\xe7\xaf\x97\xe5\ \xc3\x00\xf4\x3b\x9b\x2a\xef\x03\x85\xb9\x07\x8d\x27\x80\xa0\xdf\ \xf9\x0e\xf3\xce\x16\x65\xfb\x56\x96\x0e\x03\x50\xb3\xfc\x98\x4b\ \xea\xad\xdc\xa5\xc1\x04\x10\x34\xce\x16\x17\x1e\x20\x4a\xf6\xa2\ \x2c\x1b\x06\xa0\x6e\x79\x23\x9b\xd4\x75\x02\x08\x82\xa5\xaa\xc7\ \xba\xb9\xf3\x31\x00\x75\x7c\x1f\xf8\xd9\x39\x78\x6b\x02\x68\xe2\ \x9d\x6f\x8b\x62\xad\x91\x05\xc3\x00\x34\x3c\xeb\xb3\x86\x76\x51\ \xce\x4a\x8c\x23\x80\x06\xdf\xf9\x5f\xcb\x62\x61\x00\x9a\x9e\x1f\ \xea\xfe\x3e\x00\xe0\xcc\xba\x70\x3f\x51\xa4\xe7\x03\x57\x6c\x0c\ \x80\xcc\x6b\x99\x45\x87\x68\x54\x4b\x00\xd6\x16\x17\x2b\x16\xf3\ \xdb\x44\x89\x3a\x45\x38\x06\x20\xd0\xe9\xca\x1b\x89\xdb\x4a\x49\ \x6d\x2b\x02\x18\x08\x77\x37\x77\x78\xf5\xf3\x5a\xab\x65\x89\x30\ \x00\xa1\xc9\xba\x9c\xa1\xbb\xdf\x2f\x3c\x64\x14\x01\xf4\x95\xad\ \x78\x27\x8b\xf2\x7f\x25\xcb\x83\x01\x08\x6b\xf4\xef\xf3\xa6\x7e\ \x1e\x27\x1a\x44\x00\x3d\x31\x15\x7f\x5f\x5b\xf1\x9f\x95\xa5\xc1\ \x00\x44\x24\x49\xfd\xd5\x82\xa1\x1d\x42\x00\xff\x25\xa9\xb8\x53\ \xe5\x67\xb4\x45\x38\x06\x20\x92\xe9\xaa\x7c\xd6\x2c\x6b\x1e\x36\ \x93\x00\xfe\xfa\x19\x6d\x91\x55\xdd\x94\x07\x03\x10\xad\xac\xad\ \xbc\x0f\x7c\xed\x1e\x36\x92\x62\x0e\x77\x3e\xf3\xbf\xe8\xa6\x34\ \x18\x80\x68\xe7\xbb\xca\xfb\x00\xc5\x0b\x58\xb3\x52\xbb\x58\x8a\ \xf7\x70\x37\x65\xc1\x00\xc4\x2b\x4f\x67\x4d\x6d\x6f\x82\xc8\x7f\ \x57\x6f\x4a\x0d\xee\x7c\x0c\x40\x04\x23\x3f\x7b\x9e\x99\x7f\xd0\ \x0c\x82\x88\xde\xf9\xcc\x2f\xd7\xa0\x48\x18\x80\x68\x67\x4d\xe5\ \x7d\xe0\xf3\xb9\xc7\x8f\x20\x88\xc6\x67\xb4\x45\xf9\x3f\xec\x63\ \x59\x30\x00\xc8\xe7\xf8\xec\x79\x88\x2d\x56\xd2\x3b\x5b\xcc\x7b\ \x10\xa5\xc7\x00\x0c\x30\x4f\x66\x8c\x96\x3d\x09\xc2\x61\xa1\xea\ \x4e\xae\xde\xf9\xed\x28\x3c\x06\xa0\x96\xef\x03\xbf\x18\xda\x74\ \x82\xe0\xde\xf9\x8e\xea\xb7\xda\xcc\xcf\xa1\xe8\x18\x80\x3a\xa5\ \x94\x37\x74\x1b\xef\x03\x01\xbc\xf3\x2d\xe6\xbf\x8f\x82\x63\x00\ \x1a\x94\x4f\x83\xf1\x3e\x80\xff\xd6\xdb\xd1\x61\xde\xfd\x28\x76\ \x4c\x06\xc0\x6a\xe1\xf9\xab\xcf\xe5\xa5\xdb\x96\xf0\x55\x0f\xa4\ \x79\xf9\x3e\x8f\x17\x6f\x31\x78\xfe\xe2\x33\x9b\xf5\x73\x7a\xbc\ \x68\x68\xbb\x13\x34\x96\xb3\xe5\x85\x93\x2c\xc5\xbf\xc8\x66\xfe\ \x26\x94\x3a\xfa\x03\x90\x5f\x79\x2c\x5f\xfd\xf8\x95\xbc\x63\xf5\ \x5b\x9c\x77\x7d\xfe\x9f\xd9\xfc\xd3\x33\xbc\x7c\xfb\x52\x9e\xb3\ \x67\x37\xfa\xe7\xd7\x9e\x33\xb5\xb6\x9f\x17\xb7\x4c\xa3\xfa\x02\ \x97\xdc\xa1\xbf\xdf\xf9\xe9\x4c\x3c\x0a\x8b\x01\x28\x5e\x3f\x97\ \x77\xac\x91\xc5\xef\x39\x9b\x7e\x78\x8a\xe7\x2f\x3c\xa3\x19\x3f\ \xd7\x62\xe5\x7d\x80\xbb\xbb\x0d\x27\xa8\x3d\x8b\xf9\x47\x3a\xcc\ \x7f\x37\x3e\x65\xc5\x00\x94\xef\x5a\xc1\x79\xe7\x67\xb2\xdc\xbd\ \x4e\xe7\xba\x77\x79\xe1\xda\x0b\x9a\xf5\xf3\xfe\x24\x67\x68\x27\ \x12\xd4\x86\xa9\xa6\x76\xb0\x59\xfa\xb6\x78\x15\x15\x03\x50\xbc\ \x7e\x9e\x2c\x7f\xbf\xd2\xb1\xe6\x6d\xf9\x2f\x81\x66\xe5\xf1\x82\ \x9d\xd8\x8d\xfa\x07\xdc\x69\xee\xd8\xea\xe7\xb5\x36\xc6\xac\xa8\ \x18\x80\x15\xc7\x54\x0a\x2c\xcb\xdc\xef\xfc\xca\xde\x57\x80\x45\ \x95\x77\xe1\xef\xbf\xbb\x57\x51\x74\xfb\xfb\x36\x9e\x5d\x45\x25\ \xc5\x5c\xb7\xbb\x57\xa5\x63\xe8\x6e\x10\x51\x4a\xda\xc6\xee\x6e\ \x51\x44\x0c\xec\xae\x75\x8d\x21\x86\x10\xa4\x63\x86\x6e\xd0\xf3\ \xbf\x9c\xcf\x67\x10\x99\xdf\x9d\xcb\x04\x33\xdc\xf9\xf1\x3c\xef\ \x86\x30\x33\x77\x64\xde\xf7\x9e\xdf\x89\xf7\x74\x14\x64\x68\x5a\ \xc8\x70\xed\x79\x83\xef\xec\xff\x4d\x19\xcd\xfd\x9c\xff\x6f\xfb\ \xd6\x68\x33\xa8\xa6\x84\xd5\x4d\x01\x68\x3e\xb7\x12\x09\xac\x02\ \x60\x24\xa1\x05\x11\x8d\x84\xdb\xda\x73\x7a\xce\xff\x34\x64\x5c\ \xec\x03\x4a\x54\x1d\x16\x80\x90\x59\xec\x77\xff\x67\x22\x68\xb8\ \xb2\x05\xca\xd7\xf9\x40\xe5\xd6\x60\x68\xcd\x3e\xce\x2a\x00\xed\ \xc2\x43\xda\xe4\x4f\x98\xc3\xf4\x0f\x7c\x4d\x99\x3e\xc8\x98\x23\ \xe1\xcd\xbe\xf5\x5a\x94\xa0\x54\x00\x24\x6b\x1c\x88\x64\x7e\xde\ \x2b\x82\x92\x04\x3b\x78\xfc\xe7\x27\xfd\x98\x3b\x01\xea\xd2\x53\ \xc8\x8f\xe9\xce\x85\xba\xf0\x39\x5a\xb7\xd6\xac\x26\xc8\xfc\x3d\ \x9d\x27\x7e\xa0\x5e\xf2\x7f\x7b\x71\xce\xef\x60\x00\x14\x54\x00\ \x1a\xd3\xa2\x88\x64\x6e\xbc\xb6\x15\x49\xff\x2a\x84\xf3\x26\x42\ \x57\xf5\x75\xe2\xe3\xc4\x4b\xe7\x69\x9e\xf4\x84\xfc\x40\x7d\x88\ \xe1\xff\xd2\xdd\x73\xbe\x7e\x6c\x15\x25\xe5\xcb\xa0\x02\xd0\x7c\ \x76\x19\x91\xc8\x65\xab\x3d\x90\xf0\xb2\x20\x39\xbd\x9a\x9c\x07\ \xd8\xe2\xae\xb5\x6d\xc5\xb5\x81\xd3\xc4\x3a\x95\x1f\x08\x1b\x1f\ \x67\xca\x7c\x18\x6f\x52\x32\x0e\x15\x54\x00\xca\xd7\x7a\x91\x05\ \xe0\xec\x1a\xe2\xe3\x24\x1b\x5c\x46\xc2\x7c\xc1\xfd\xda\x00\xf3\ \x99\x3c\x4e\xf0\x25\x8e\x97\xbb\x46\x9b\x82\x1e\x01\x8e\x2c\x22\ \x12\xb9\xf9\xde\x3e\x24\x7b\xb6\xa5\x01\xe4\xda\x4f\x86\x5c\xbb\ \xc9\x20\x9c\x3f\x01\x84\x16\x93\xa0\xa7\xfe\x0e\x59\x00\x96\x59\ \x68\x29\xe9\x09\xf9\x01\xbf\xe9\xef\xf2\x6c\x8d\x36\x9e\xf3\xdb\ \x29\x01\xa9\x00\xc8\x43\x75\xf2\x7c\xd6\xac\x7e\xcd\x81\x45\x50\ \xe0\x69\x2a\x45\xa1\xef\x34\x68\xba\xbd\x8b\xa5\x2b\xf0\x11\xd4\ \x05\xe3\x7c\xc0\x48\x42\x57\x5f\x7e\x40\xe2\x65\xfa\x3f\xf9\xb0\ \x46\xfb\x29\x25\x1e\x57\x50\x01\x28\x71\x33\x85\x1e\xc9\x6d\xf6\ \x06\x9f\xa2\x4c\xa8\xcf\x5a\x01\x8d\x97\xd6\x42\x6f\xe3\x5d\xd6\ \x9f\x6d\xba\xb5\x0b\xaa\x7c\xa6\x8e\xd4\xb1\xe3\x4a\x5c\x7b\x3e\ \xd2\xf2\x03\xa1\x6f\xc4\x1b\x33\x1f\xb8\xeb\x94\x70\x54\x00\x86\ \x8a\x27\x4e\x86\x50\x73\x00\x2b\x01\x2a\x41\x51\xc4\x1f\x50\xee\ \x61\x3a\xd2\xfd\x07\xee\xd5\x06\x4d\x9b\xa1\xfd\x7d\xfb\x6f\x27\ \x8d\x09\x1e\x1f\xb7\x97\x9e\xf3\xa9\x00\x28\x8a\x62\x81\x21\x64\ \x5b\x1b\x42\xb7\xf8\xb6\xb2\xe4\x97\xe6\x0c\xca\x3d\x4d\xf9\xb1\ \xd6\x2c\xc8\x7c\xaf\x56\xaf\x35\x63\xa6\xf5\xec\x28\xc9\xa8\x00\ \x28\x83\xa7\x2e\x46\x50\xba\xc2\x15\x9e\xf7\xe4\x29\x43\x7e\x3c\ \x46\xe4\x3a\xcf\x40\x01\xa8\xf4\x36\xe3\x8d\x1b\x11\xb3\xc0\xc4\ \x8e\xe7\x02\x40\xb1\xe0\xed\x78\x58\xf3\xe3\x7a\x38\xbe\xf0\x20\ \x5c\xdf\x92\x09\x79\xe7\xaf\x40\xc5\xa3\xbb\x20\x29\x79\x00\xad\ \x75\x42\x68\x6f\xc8\xc6\xff\xae\x2b\xba\x0f\x25\x77\x6e\x42\xf6\ \xa9\x4b\x70\x75\xc3\x49\x38\x12\xb4\x0f\xd6\xfd\xb2\x11\xa2\x3e\ \x4e\x1e\xb1\x02\x20\xde\x19\x88\x1d\x7f\xca\x92\x3f\xdf\xff\x5b\ \x69\xa7\x60\xad\xbf\x96\x13\x9b\x0a\x00\x45\xf4\x84\x14\x38\x1a\ \xba\x1f\x0a\xaf\x5e\x83\xee\x76\xa5\xee\x7e\x88\x96\x9a\xc7\x90\ \x73\xe6\x32\x9c\x4e\x38\x0a\x1b\x7e\xdf\x04\x11\xef\x27\x6a\xb3\ \x00\xa0\xdd\x57\xd3\xe9\x14\xec\xf5\x57\xea\x7d\x3f\x38\x0c\x79\ \xae\x33\xa5\xfd\x01\x22\x9b\x49\xda\x4b\x68\x2a\x00\x14\xa9\xdf\ \xaf\x03\x61\xe6\x45\x78\xd6\x8d\xb3\xef\xea\x02\x3e\x7f\xfe\xa5\ \x6b\xd5\xf2\x7e\x87\xcd\x57\xd6\x17\xb7\xde\xda\x8a\x0d\x39\x8d\ \x69\x91\x50\xbf\xd5\x0b\xe7\xea\x19\x82\xaa\xef\x83\x9d\xf0\x2b\ \x74\x96\x9c\x91\xf7\x1e\x88\x91\xc1\xf3\x1e\x11\x43\xfc\x43\x80\ \x73\x02\x73\x27\x0c\x68\x10\xd2\xca\x04\x20\x15\x00\x8a\x35\x3f\ \xac\x87\xc2\x6b\xd8\xbf\x3e\x6c\xa8\x2d\xb8\xdf\x24\xef\x77\xd8\ \x59\x7c\xba\x5e\x26\xc9\xba\xb2\xa1\xab\x34\x0b\x5a\x6f\x6c\x82\ \xfa\x5d\xfe\x50\x17\xf9\xad\xf2\x1f\xe8\xa8\xef\xa0\xf9\xd2\x5a\ \xe6\xb9\x73\xe4\x5d\x3b\x46\x06\x65\xab\xdc\x20\xc7\xd1\x1c\x8a\ \x63\x2c\x98\xff\x76\x87\xd2\x15\x6e\xf0\x64\xf1\x9f\x90\x6d\x6b\ \x22\xb3\x3b\x50\x64\x6d\xa0\x5d\x04\xa6\x02\x40\x11\xf9\x51\x32\ \x5c\xdb\x9c\x09\xcf\x7a\xf0\x8e\xaf\xfd\x02\x40\x24\x64\x3e\x74\ \x55\x5c\x84\xe6\x8b\xa9\x20\x5e\x61\xc5\xfd\x43\x1c\x34\x1d\xed\ \xba\x5a\xaf\x6f\x42\x51\xe1\xfa\x5a\x15\x5b\x82\x91\xd4\x5c\x21\ \x64\x22\x81\x6a\x1f\x2d\x4a\xfe\x51\x01\xa0\xd8\x34\x77\x33\x34\ \xd7\x3c\x56\x9e\xcc\x9a\x14\x00\x02\xba\xab\x2e\x43\xd3\x89\x58\ \xa8\x8b\xfe\xfe\x95\xf9\xfe\x99\x20\x66\x42\xfc\xfa\xdd\x01\xd0\ \x72\x6d\x23\xf4\x88\x6f\x12\x9e\x83\x1c\xf6\x97\xaf\xf5\x1e\x32\ \xf9\x2b\xb4\xa5\xf4\x47\x05\x80\x82\x69\x85\x86\xcc\x25\x47\x94\ \xbe\xeb\x3f\xeb\x11\x41\x6b\xfd\x63\x44\xaf\x02\x39\x83\x32\xe1\ \xad\x2e\x75\x09\x80\x14\x0c\x61\xbb\xca\xce\x33\x82\x70\x05\x7a\ \x1a\xee\x28\x55\xce\xeb\x6d\xfe\x1b\x4a\xe2\x6c\x86\x44\xfe\x5c\ \x4b\x03\xf5\x77\xfe\x45\x7c\x01\x0d\xfb\x42\xa0\xf5\xf6\x36\x60\ \xfe\xbe\xa0\xbb\xfa\x2a\xe6\x30\xda\xfe\xda\x05\x8d\x47\x17\xe3\ \xf1\x88\x0a\x80\x8e\x61\xc1\x5b\x09\x90\x64\xb4\x5e\x66\x49\xef\ \x51\xfa\x85\x21\x7f\xf8\x2b\xf3\x6f\xc3\xb9\x6d\x87\x60\x9d\xe7\ \x0a\x08\xff\x7c\x21\x38\xbf\xef\x09\xf3\x5f\x77\x1c\x00\xfb\xb7\ \x5c\xc1\xd3\xc0\x0f\x42\x67\x87\xc1\x4a\xc7\x14\x38\x9a\xbc\x03\ \x6e\xa7\x67\x40\x65\xc1\x6d\x78\x2e\xc3\x50\xf3\xe9\xc3\xeb\xbd\ \xf2\x7e\x87\xed\x79\x27\xda\xf0\xe7\x35\x8c\x36\xd1\x49\x10\x79\ \x7d\xc6\x99\xf8\x39\x0c\xf1\x9f\xba\x18\xf7\x0d\xd3\xa8\x8f\xf8\ \x61\x73\xa0\xf9\xec\x52\x78\xd6\x21\x94\x17\xb5\xe0\x11\x47\x1c\ \xf9\x0d\x15\x00\xfe\xd7\xec\x13\x21\x75\xf6\x4e\x38\xee\x74\x1e\ \x32\x3d\xae\xc0\x9a\x39\x3b\xa5\xdf\x0b\x7f\x27\x1e\x72\xce\x5e\ \xe1\xfa\xa1\xc7\x3b\x7b\xfa\x8a\x5d\x10\x32\x33\x0c\x09\xae\x0c\ \x04\xff\xf0\x80\xc4\x79\x71\x28\x0a\x8f\x2f\x65\x41\x47\x73\x36\ \x27\x01\x68\xcb\x4e\xeb\xd6\x20\xf1\xd1\xd2\xbb\x6a\xf7\x42\x34\ \xf9\x78\x95\xe4\xc5\xce\x46\xd8\xd4\x53\xea\x6a\x0c\x25\xce\x86\ \xf8\xff\xa5\x6e\xc6\x50\xa5\xfe\x46\x1f\x3c\xc6\x74\x57\x5f\x19\ \x5a\x04\xd3\x70\x17\xf3\x1d\x54\x00\x78\x88\x85\xef\x26\xc1\xda\ \xcf\x76\x41\xba\xf3\x05\x24\xfe\xcb\x58\x3d\x6b\x07\x84\xbf\x9b\ \x80\x0d\x3c\x5c\x89\xbf\x2b\x62\x03\xde\xd1\x91\xc0\x6a\x80\xa5\ \x9e\x13\x04\x98\x84\xb5\xc8\xfb\x1d\x16\x7a\xcc\x6a\x28\x5c\xf0\ \x0b\x94\x24\xda\x43\xc5\xe6\x40\x10\x67\xae\x84\x56\xe1\x31\xf4\ \xe9\x53\x2b\xf1\x3b\xb3\xd1\xda\x2b\xc7\x71\xaa\xcc\x3b\xfc\x13\ \x81\xa1\xa6\xce\xdd\x48\xfe\xde\xa6\xbf\x14\x79\x5f\x68\x49\x26\ \x49\xb5\xa7\x02\xc0\x1b\xe2\xbf\x97\x04\x1b\xbe\xdc\x03\x19\xae\ \x17\x91\xec\x32\xe1\x79\x05\x0a\x2f\x71\x4b\x76\x5d\xd9\x77\x14\ \x5c\x3e\xf4\x42\x92\xaa\x1b\xd6\xa3\xdc\xe4\x26\x01\xf3\xac\x27\ \x4a\x48\x61\xb6\xc8\xe7\x4b\x46\x14\x82\xa0\xe9\xce\x1e\x1c\xb3\ \x55\x05\xf1\x7b\x24\x77\xa0\xee\x58\x12\xe4\xb9\xcd\x22\x86\xf7\ \xc5\x4e\x48\x7e\xcd\x20\xec\x53\x5c\x3b\x86\xd7\xab\xcc\x8e\x82\ \xd8\x9f\xa8\x00\x8c\x64\x2c\xfe\x67\x0a\x6c\xfc\x66\x1f\x64\xb8\ \x5d\x42\x92\xb3\xa1\x20\x53\xfe\xdd\xb2\xa3\x25\x07\x56\x09\x52\ \x88\x64\xb5\x18\xe5\x04\x0e\xa3\xbd\xc0\x75\x4c\x20\x78\x8f\x5d\ \x00\x7e\xfa\x8b\x20\x68\x5c\x0c\x04\xe9\xc7\x40\xf0\xb8\x58\x08\ \x1c\x17\x0d\xfe\xfa\x91\xe0\x3b\x36\x82\xf9\x7e\x38\xb8\x8d\x09\ \x06\xc1\x68\x3f\xb0\x1b\xed\x01\x16\xaf\x3b\xa9\x4e\x00\x08\x10\ \xce\x37\xc0\x04\x1d\x33\x6e\x8b\x89\x3e\x45\xda\x75\x4b\xe2\x6d\ \x07\x87\xfa\x03\x0d\x3f\x31\xdc\xd7\x64\xd6\xbd\xe9\x54\xa2\x6a\ \x72\x1a\x7f\xed\xa6\x02\x30\x12\x11\xf9\xfe\x32\xd8\xf2\xdd\x01\ \x38\xe9\x76\x19\xc9\x2d\x0f\x7f\x6f\xb9\x0b\xf0\x8c\xfd\xc3\x50\ \x5d\x74\x07\x02\xa6\x06\xc9\x20\xbd\x00\x04\x7a\x3e\x48\x68\xc6\ \x2f\x51\xa9\xeb\xf6\x1f\x1b\x09\x1e\x63\x42\x19\x51\xf0\x05\x9b\ \x51\x7d\x47\x0b\x81\xf2\x02\x40\x40\xae\xd3\x74\x3c\xb7\x77\x55\ \x5d\x1b\x9a\x67\xff\xf9\xf5\xe8\xe6\x23\x33\xa9\x67\x31\x11\x2a\ \xbd\x35\x5c\xca\x5b\xf8\x05\xe6\x24\x54\x20\x00\xd8\xcb\x20\x4e\ \x99\x47\x05\x60\xa4\x20\xe6\xa3\x15\xb0\xed\xc7\xc3\x70\xd2\x1d\ \x89\xcd\x09\x57\x62\x6e\x40\x6f\x27\x7b\x79\xae\x3c\xef\x16\xb8\ \x7f\xe2\x33\x90\xf8\x0c\x39\x9d\xf5\xfc\x21\x50\x3f\x4a\x5d\xef\ \x07\x23\x07\xef\xb1\x0b\xef\xa8\x42\x00\xc8\x51\xc1\x44\x28\x4b\ \xf5\x82\xae\x8a\xcb\xdc\xef\x8c\x39\xc7\xb1\xbb\x4f\xfa\x1c\x0c\ \x8a\x1c\xa7\x40\xad\xbf\xb9\xe6\xd7\x93\xed\x0a\x60\x37\x1b\xb9\ \xbd\x1b\x0a\x02\xbf\xc7\x68\x48\xe4\xfd\x05\x8b\x2f\x21\x02\x2b\ \x08\x54\x00\xb4\x1c\x4b\x26\xac\x86\x5d\xbf\x1d\x83\x93\x48\x6a\ \xee\x38\xe5\x75\x05\x1a\x8b\xb3\xd9\xeb\xf0\x39\x37\x07\x9d\xf7\ \xed\x47\x7b\x42\x80\x7e\xa4\xd6\x8c\x03\x33\xb3\xf3\xb7\xca\xdc\ \x4d\xb0\xa4\xd6\x37\x9b\x9f\x6f\x3b\x19\xef\xc6\x43\x13\x02\x03\ \xa8\xd8\xe8\xcf\x39\x71\xd6\x59\x76\x09\x72\x5d\x66\x42\xbe\xdd\ \x64\xad\x72\xef\x61\x66\x22\x58\xbd\x06\x06\xce\x1d\x20\x58\x77\ \x14\x74\x3e\x3d\x4b\x05\x40\x5b\x11\x37\x31\x15\x89\x8f\x84\x56\ \x00\xa2\x13\xec\x2b\xac\xbb\x9a\x45\x10\x31\x7b\x71\xff\x5d\x9f\ \x81\x9b\x5e\x10\xf3\xda\xb1\x23\xc2\x0f\xa0\xc6\xdf\x1c\x8d\x35\ \x98\x6c\x3c\x36\xdb\x70\x3c\x1a\x60\x8e\x80\x83\x08\x60\x89\xad\ \x2e\xfa\x07\xad\xea\xba\xeb\x2c\x3c\x49\x76\x1a\x5a\xf8\x9b\xec\ \xa3\x8b\xbd\x19\x56\x34\x64\x56\x3a\x5a\x1f\x50\x01\xd0\x36\x24\ \x4c\x5e\x0b\xfb\xe7\x67\x0c\xed\x6e\xef\x7d\x05\xce\x2f\xb8\x06\ \x17\x17\x5d\x87\x33\x7e\x57\xe1\xea\x92\x9b\xf0\xac\x87\xa5\xc4\ \xd5\x95\x0f\x37\x52\x6e\xc1\x61\xfb\xd3\x20\x78\xcb\x07\xac\x5e\ \x77\x06\x5f\xfd\x88\x11\x6d\x08\x52\xe3\x37\x15\x9e\xba\x1a\x41\ \x9e\x95\x7c\x31\x28\x5b\xe3\x2d\xa7\x79\x06\x81\xdd\x83\xe2\x45\ \x5f\x69\x8d\x00\x74\x57\x91\x8f\x32\xd9\x36\x46\xc4\xf7\xdb\x55\ \x73\x83\xf4\x38\x9c\xaa\xa4\x02\xa0\x69\x8c\x8f\x83\x24\xc3\xf5\ \x70\xc0\xf2\x24\x67\xd2\xdf\x5a\x71\x0b\x4a\x2e\x3d\x80\xd6\xea\ \xdc\xc1\xb5\x5e\x39\x49\xbf\xc7\xfb\xee\x49\x9f\xe7\x80\xcd\x49\ \x08\x7d\x37\x8e\x4f\x8e\x40\xd8\x9c\x53\xe4\x60\x88\x3d\xf8\x04\ \x52\xa0\x41\x47\x27\x87\xdc\x40\x67\x51\x26\x96\xde\xb4\x40\x00\ \x58\x47\x94\xf3\x7d\xbf\x94\xf9\x3e\xb3\xad\xa6\x10\x13\x87\xcf\ \x3a\x1e\xd3\x08\x40\xd3\xc4\x4f\x31\xd9\x08\x87\x6d\x4e\x73\x26\ \xfe\xed\x55\xb7\x09\x67\x7b\x6e\xa8\x7d\xfc\x18\x32\x3d\x07\x3e\ \xe7\x41\xeb\x53\x10\xfe\x76\x22\xdf\x2c\xc1\xf0\x98\x50\xec\x64\ \x04\xd9\xf3\x64\x0b\x41\x8e\x83\x19\xb4\xe6\xa4\xcb\x4f\x0c\x3e\ \xd8\x87\x53\x83\x9a\x16\x80\x96\x3b\x3b\x48\xd7\x88\x09\x3f\xa1\ \xa5\x01\x88\x9c\x0c\x21\xdf\xcd\x18\xf2\x5c\x8c\x21\xc7\xca\x00\ \x2a\xb7\x86\x90\x8f\x81\x15\x97\xa8\x00\x68\x02\x8c\x4b\x31\x2c\ \x33\xdb\x0c\x47\x6c\xcf\x70\x26\xfe\x69\xef\x2b\x50\x7c\xfe\x81\ \xdc\x92\x9e\xbc\x73\xff\xf9\xb0\xeb\xb2\x9e\x1f\x8f\x1d\xcc\xfc\ \x00\x0f\x04\x80\x20\x04\x82\xbe\x88\x40\x46\x82\xd0\x72\x32\x36\ \x12\xc9\xfb\xbb\x6b\x4c\x8f\xd5\xfc\xe4\xdd\xee\x05\xec\x65\xcc\ \xb3\xcb\xa1\xc8\x7f\x26\xe0\x7e\x02\xe6\xdf\x75\x47\x22\x71\x18\ \x8a\xf8\xf3\x67\x56\x51\x01\x18\xde\x01\x9d\x78\x58\x3d\x73\x3b\ \xa4\x39\x9e\x23\x10\x9d\x4c\xfe\x9a\x87\xca\x8f\xed\x32\x25\x41\ \x3c\x3a\x90\x5e\x67\xcf\x1f\xe9\x28\x4e\x3c\x35\x05\xc5\x8c\xbe\ \x8c\x1c\x01\xd6\xfe\x99\x12\x9a\xbc\x81\x1a\xdc\x14\x8c\x91\x40\ \xd4\x77\x38\x61\x37\xdc\x47\x83\x42\x8f\x99\xf2\xcc\x4a\xf0\xfb\ \x3d\x92\x9b\x9c\x4c\x4d\x8a\xa3\x68\x1f\xc0\xb0\x20\x9c\xb9\xb3\ \xae\x9d\xb3\x4b\x3a\xa0\x33\x04\x60\xb8\x5e\xfd\x40\x75\x33\xfb\ \x3d\xed\xf9\x70\x39\xfa\x06\xf1\xf5\x76\xfe\x72\x14\xc7\x85\xf9\ \xea\x0a\x5c\x1b\x30\x0d\x5b\x79\x85\x32\x44\xa0\xf5\xd1\x11\x76\ \x72\x75\x0a\xb1\x97\xfe\x55\xdf\x81\x96\xcb\x6b\x41\x9c\xfc\x87\ \xda\x05\x20\xd7\x72\xa2\xb4\xb6\xaf\x2c\x5a\xb3\x8f\xe3\xfb\xae\ \xa5\x02\xa0\xee\xce\xbd\xa5\x90\xee\x82\x7d\xfa\x0a\x21\xfb\x20\ \xd6\xae\x55\x8a\x96\xca\x5c\x38\xed\x73\x95\xf8\x9a\x5b\x7f\x38\ \x04\xa1\xfc\xb6\x05\xc7\x12\x62\xf6\xfc\x81\xb9\x81\x1c\x3b\x13\ \xe8\x78\x7a\x5e\xc1\xce\x3a\x11\xb4\xdd\xdb\x09\x75\x8b\xbe\x54\ \x9b\x00\x30\xd1\x0b\xe6\x2d\x7a\x1a\xee\x29\xf3\xfb\x47\x11\x2b\ \x0c\xfd\x09\x84\xf3\x3e\xa1\x11\x80\xba\x91\x62\xb2\x41\x51\xf2\ \xe3\x79\xbd\xb7\x2b\x5f\xfe\xc2\x09\xf1\x23\x78\x78\xfe\x2c\xdc\ \xcb\xcc\x84\xa7\x42\x6e\xbe\x7e\xa2\xe3\xf7\x59\x5f\x7b\xd3\xd7\ \xfb\x79\xbf\x17\xa0\xda\x77\x2a\xde\x55\x5f\x12\x01\x9c\xf9\x57\ \x66\xd2\xb0\xa7\xee\x06\x88\x53\xe6\xaa\xe5\x7a\x0b\x6c\x27\xe3\ \x35\x32\x35\x7f\x2c\x63\x2a\xda\x02\x5c\xbe\xc1\x5f\xda\xde\x4c\ \x05\x40\xcd\x58\xff\xc5\x1e\x85\x05\xa0\xe8\xec\x03\xd6\x5f\x66\ \x5d\xe9\x5f\xb0\xcc\x2e\x11\x2c\x46\x0b\x06\x74\xf3\xf9\x1a\x05\ \xc0\x8d\xa3\xec\xd9\xed\xee\x56\x11\xf6\x0c\xb0\xbc\x3e\x8e\x17\ \xf3\x7d\x31\x48\x8d\x9f\x39\xe4\x59\x0d\xec\xff\x7f\xba\xcc\x59\ \xb9\x5c\x4b\xc3\x5d\xb5\x34\x11\x95\x38\xf7\xd7\xfa\xab\xf7\x0f\ \x79\x5d\x19\x0e\x49\x55\x6c\x44\xf2\x23\x0a\xed\xa7\x50\x01\x50\ \x37\x76\xff\xae\x48\x37\x1f\xb6\xf2\x22\x49\x49\xbf\xcc\x92\x47\ \xd7\xa5\xae\x3c\x24\xec\x8f\xdd\xc2\xfa\x81\x78\xb4\xfb\x9e\xdc\ \xeb\x48\x9d\xbd\x9d\xf7\x9b\x81\x98\x3d\xf8\x20\x7a\x29\x39\x58\ \x9f\xa5\xfc\x39\xbb\xf3\xc9\x29\x4c\x18\xaa\x38\x62\x91\xe6\x2e\ \x1a\xaf\x6e\x1d\xd2\xf5\xb4\x17\x64\x62\xd8\x4f\xb6\x28\xa7\x02\ \xa0\x16\xa4\x39\x66\x29\x24\x00\x37\x97\xde\x22\x7f\xb8\xda\x72\ \xc0\xc7\x30\x80\xcb\xcc\x3d\x5a\x72\x11\x9e\x07\x93\x8b\xf2\xae\ \xe3\x24\x83\x15\xd3\x36\xf3\x7e\x35\x58\x8d\x3f\x26\xd9\x20\xdf\ \xef\x6b\xe9\x32\x10\x25\x81\xc3\x3b\x2a\x3f\x06\xd8\xe1\x31\x00\ \xc7\x97\xe5\x55\x03\x3a\x4b\x2f\xa0\x98\x3d\x89\xb6\x18\xdc\x16\ \x6d\x65\x40\xc7\x81\x87\xc1\xa5\x47\xe1\xf0\x3f\xef\x38\xb9\x97\ \xff\xec\xe6\x03\x03\xa7\xf7\x18\xd8\x8c\x76\x05\xdb\x51\xee\x60\ \x39\xca\x79\xc0\xf7\xfc\xcd\x82\xc8\x42\xd2\x94\xc7\xe9\x5a\x98\ \x09\x44\x6c\x58\xe2\xfb\x6e\xc0\x6a\x5f\x73\x68\xbc\xbc\x49\x2e\ \xb1\x5a\x1e\x1d\x81\xe6\x7b\xfb\xd1\x24\x94\xb5\xef\xa2\xf4\x9c\ \xea\xaf\xd1\x6f\x2a\xe4\x7b\x7f\xc6\xe6\x6b\x20\x1d\x0a\x62\x43\ \xb9\x97\x29\x15\x00\xf5\xf7\xf5\xaf\x51\x58\x00\x4a\x2e\x92\xcf\ \xff\x89\x73\xe3\xa4\x26\x1d\xee\x63\x82\xd1\x98\xe3\xe5\xd7\x65\ \xfa\xfa\xd1\x88\xe3\x85\x08\xa0\xa1\x27\x51\x4c\x02\xaf\x71\xba\ \x9e\x0c\xf7\xcb\x90\x38\x65\x2d\xbf\x97\x83\x06\xcd\x60\x4d\x00\ \xb6\x3e\x3e\x2a\x5d\xf0\xc9\x00\xb7\x06\x37\x5c\x61\x0f\xc5\xc5\ \x71\xbf\xa8\xfe\xc8\xb2\xd6\x85\xdc\xb0\x74\x7d\xbb\x3c\xf2\x63\ \x77\x24\xb5\x04\x53\x3f\xf0\xfc\xac\xa8\x00\x54\xde\x23\x5b\x5c\ \x05\x9a\x07\x83\xd5\xeb\x2e\x10\x38\x8e\x7d\x5e\xdf\x49\xcf\x0f\ \x05\x80\xa9\x10\x10\x9f\xeb\x42\x38\x0a\x00\x17\xa0\xed\x58\xbc\ \xc1\x6a\xbe\x0a\x00\x66\xef\x89\x89\xbd\xd6\x87\x58\x26\x1c\xec\ \x12\x34\x11\x3a\x9e\x90\x7b\xf4\xeb\x77\xab\xfe\x18\xd0\x98\x1e\ \x43\x7c\xbd\xaa\xdd\x11\xac\xe4\x7f\xe2\x64\x48\x4d\x41\x87\x0b\ \xdb\x7e\x3a\xac\xb0\x00\x54\xdc\x26\x0b\xc0\x82\xcf\x17\xa2\x25\ \x17\x87\x6b\xc0\xf9\xfe\xdc\x1b\x17\x49\xcf\x85\x53\x84\x43\xb9\ \xae\x13\xce\x17\x20\xe6\xe3\x95\xbc\x14\x00\xc9\x06\x67\x32\x91\ \x2f\x6c\x20\x92\x8a\xad\xdf\xbe\xe9\x64\x82\xea\x7d\x01\x6e\x6c\ \x26\xbe\xde\xd3\x14\x01\xc1\xd1\x68\x02\x94\x7b\x98\x50\x5b\xf0\ \xe1\xc4\x21\xeb\x53\x8a\x97\x00\x4f\x93\x43\xd1\xdd\x21\xdc\x93\ \x72\x41\x6f\x47\xa1\xdf\x9f\xec\xb2\x50\x01\x9c\xf2\xba\x3a\xe4\ \x6b\x3b\x2e\x38\x0f\x51\x1f\x2e\xe3\x9d\x00\xd4\x6f\xf3\x22\x87\ \xf2\x19\xcb\x48\x02\x80\x7b\x01\x89\x02\x70\x2a\x49\xe5\xd7\xd9\ \x2e\x3c\x44\x7c\xbd\x82\xc0\xef\xfa\xbb\x1b\xe7\x4d\x80\x7c\x9b\ \x49\x50\xea\x66\x42\xde\x4f\x40\x05\x40\x3d\x08\x7b\x23\x8e\x60\ \xd8\xc9\x0d\x8f\x76\xdf\x25\x3b\xf9\x3c\xba\x0d\xa1\x6f\x70\x1b\ \xe1\x3d\x1c\xb8\x97\xfc\xe1\x2c\xcd\x55\xf8\xfa\xf6\xcf\xcb\xe0\ \xdf\x11\x60\x95\x0d\x79\x22\x30\x2f\x63\x80\xd5\x98\xb0\xcf\x95\ \xe8\xc5\x70\x51\xdd\xf1\x64\xf2\x99\x3c\x6d\xb1\x1a\xc6\x82\xc9\ \x47\xba\x5c\x27\x2c\x17\xa2\x3f\x02\x5d\x0d\xa6\x41\x01\x88\xfd\ \x78\x25\x12\x45\x89\x2e\x40\xd6\xb9\xfe\x93\xb1\x87\xe5\x27\x21\ \xcd\x96\x43\x6b\x1d\xb1\x63\x0c\xa3\x0c\x45\xaf\x6f\xfb\x4f\x47\ \xf8\x27\x00\x8b\xbf\xc6\x4e\x39\xa2\x7f\x62\xaa\x3b\xe4\xbb\x1a\ \xf5\x4d\xdb\x21\xf2\xdd\x4d\xa0\x30\xe4\x07\x56\x4b\x72\xf1\x1a\ \x47\x55\x5f\x27\x69\x9f\x21\x96\x2f\x0b\x1d\xa6\x68\xc1\x6e\x40\ \x2a\x00\x58\x3b\x47\xb2\x28\x81\xfa\xc2\x6c\x96\xe9\xb4\x7c\xdc\ \xef\x47\x8a\x04\x56\x7e\xb5\x06\xea\x9f\xb2\x3b\xc8\x5e\x4f\xbc\ \xa9\xf0\xb5\xa5\xce\xde\xc1\x33\x01\xc0\x69\x41\x5c\x01\xc6\xb6\ \x67\xb0\x21\x6b\x05\x94\xa5\x58\x42\x69\xc2\x9f\x50\x97\x16\xcd\ \xe6\xd2\x8b\x56\x5c\x65\xde\xaa\xbf\xce\xde\x66\x99\x25\x62\x14\ \xa2\x12\x72\x86\x9f\x0a\xc0\x30\x02\x3d\xfb\x95\x15\x80\xbb\xeb\ \xb0\x7c\xc7\x8a\xaa\x9c\xbb\x70\x62\xf1\x21\x58\xfb\xf3\x06\x58\ \xfd\xed\x3a\xd8\xe3\xba\x13\x1e\xa4\x9d\x97\xbb\xf8\x53\x9c\x9b\ \xad\xd4\xb5\x2d\x99\xb4\x9a\x67\x02\x80\x03\x42\xb8\xf6\x5b\x45\ \x43\x57\x38\xb9\x57\xec\xac\x72\x42\x22\xd1\x49\xcb\x4b\x19\x03\ \x55\x6d\x10\x00\x2a\x00\xfb\xe6\x9e\x40\xa2\x28\x0b\x71\x1e\x86\ \xf0\x2a\xc5\xf3\xde\x02\xb8\x91\x7c\x53\xa9\xeb\x72\x7d\xd3\x9f\ \x77\x02\x50\xe6\x61\x82\x63\xc1\x5d\x55\x57\x95\xff\x3b\xee\xce\ \x05\x91\xc7\xa7\x6a\xd9\x22\xf4\x9c\x60\xf2\xd9\xd3\xf8\x17\x54\ \xf9\x98\x51\x01\xd0\xb4\x00\x84\x32\x90\xee\xe8\x53\x12\x97\x16\ \x5f\x87\xee\x16\x91\x4a\x05\x20\x3f\xe3\xbe\x52\xd7\x74\xc4\xf1\ \x0c\x38\x8e\xf6\xe1\x9d\x00\x54\x78\x99\x01\xd6\xca\xa3\x2d\x94\ \x59\x23\x8e\xa8\xda\x19\x8e\xcf\x55\xe2\x6c\xac\xfa\x08\xa0\xfd\ \x31\xc1\xe3\x4f\x88\xee\x47\x54\x00\x34\x2a\x00\xe8\x01\xa0\x38\ \xc1\x08\x06\xa0\x52\x97\x5f\xe5\x80\x0d\x46\xdc\x4a\x7f\x64\x6c\ \xfe\x6d\x1f\x5a\x88\xf3\x4c\x00\x30\x73\x2e\x6d\x95\x5d\xef\x87\ \x09\x41\x05\x43\x7f\x69\x3b\xae\x3a\x6a\xef\xbd\x8d\x64\x8f\x88\ \xda\x90\x99\x54\x00\x34\x2d\x00\xc9\xc6\xeb\x95\x23\x3d\xc1\x0c\ \xb4\xab\x59\xb9\x48\xa0\xfc\x66\x1f\xf9\x95\xbf\x96\xa4\x39\x6b\ \x71\x5d\x18\xbf\x04\x00\x31\xc0\x1f\x80\xd9\x48\x8c\x1d\x80\xdc\ \xc7\x6d\x45\x50\x73\x20\x4a\x4a\x7e\x21\x03\x75\x6c\x14\xea\xae\ \x25\xdb\x7c\x8b\x17\x7f\x45\x05\x40\xc3\x02\x80\x73\xf4\x48\x16\ \xd5\x02\x7d\xff\x25\xa2\xa1\x3b\x02\x77\xb7\x89\xe0\xe1\x8e\xbb\ \x2a\xbb\x8e\xb0\x29\x31\x10\xa0\x1f\xc5\x4b\x01\x28\x71\x1a\xe8\ \xaf\x9f\xe7\x36\x1b\xbb\x00\x59\x3c\xf6\x30\x52\x68\xbe\x7f\x00\ \x0a\x43\x7e\x7c\xf9\xb1\x38\xb9\xa7\x8e\x6b\xec\x28\xc9\x22\x0b\ \x40\xfc\xaf\x54\x00\x34\x2d\x00\xbb\x7e\x4d\x43\xa2\xa8\x0b\x77\ \x52\x6f\xa3\x10\x3c\xef\xc5\x10\x95\x6d\xda\x0f\xcf\xfb\x59\x41\ \x18\xf2\xab\x0c\xee\xef\x04\xf2\xa0\x15\x98\xec\x0d\x90\x3d\x7f\ \xa2\x4c\x0b\xf1\xb2\x54\x4f\x10\x67\x2c\x87\xa6\x1b\x3b\x70\xc3\ \x50\x5f\xa8\x5f\xb1\x39\x10\xf2\x3c\xe6\xc8\xea\x10\x54\x5b\x42\ \xae\xe5\xef\x83\xe4\xe3\xc7\x0a\x4b\x2a\x00\x9a\x16\x80\xa3\xf6\ \x67\x91\x28\xea\x46\x56\xf0\x35\xf8\x7b\xdb\x1d\x28\x38\xf9\x37\ \x94\x5e\x7b\x88\xf3\x03\x7d\xd6\xe1\x39\x87\xff\x82\x6b\x09\x37\ \xe1\x94\xa7\xea\x5f\x33\xdd\xf5\x02\x38\xe8\x79\xf1\x55\x00\x10\ \x65\x6e\x38\xf4\xa3\x0c\x70\x65\x99\xba\xae\xaf\xe1\xc2\x06\xf2\ \xcc\xc2\x0e\x5f\x2a\x00\x1a\x14\x00\x5c\xb0\x71\xd2\x03\x57\x77\ \xf3\x12\x3b\xe7\x1e\x01\x17\x3d\x7f\x1e\x0b\x00\x02\xcb\x77\x0a\ \x92\x1f\x17\x98\xd6\xaa\x93\x74\x87\x96\x90\xdb\xbb\x33\x93\xa8\ \x00\x68\x52\x00\xe2\x27\xa6\xf2\x95\xfc\x88\x15\x5f\x6f\x06\xaf\ \x31\x61\x7c\x17\x00\x44\x89\x8b\x31\x08\x87\x48\x7e\x6c\xc5\x0d\ \x50\x6f\x29\xae\x7c\xb5\x3b\x79\x66\xe1\xaf\xdd\x54\x00\x34\x29\ \x00\xab\x66\x6e\xe3\xb5\x00\x2c\x36\x4b\x02\xff\xb1\x91\x3c\x14\ \x00\xf2\x9e\x41\x91\xcd\x24\x79\xc4\xc7\x0d\xc5\x65\x52\x8f\x3d\ \xf5\xe2\x49\xd8\x8f\x2c\xeb\xbe\x2e\x52\x01\xd0\xa4\x00\x6c\xfd\ \xfe\x10\xaf\x05\xc0\xe7\xc3\x30\x08\xd1\x8f\xe5\xaf\x00\x10\x50\ \xe9\x33\x15\x9d\x74\x98\xf0\x1e\xfd\xf9\x73\x19\x30\xa3\xb6\x78\ \xd6\xaf\xf0\x44\xe2\x0f\x1b\x0a\x1c\x8c\xc9\x55\x89\x5e\x11\xee\ \xfd\xef\xc8\x3f\x01\xad\x37\xb7\x40\xc3\xfe\x10\xa8\x8b\xf8\x92\ \x0a\xc0\x70\xe1\x80\x65\x26\x7f\x05\xc0\xfd\x32\x08\xf4\xbd\x35\ \xeb\x08\x44\x81\x76\xde\x6d\x39\xc7\x65\x09\x00\xd9\xc7\xf0\xe2\ \x6a\x14\x02\x2a\x00\x6a\x44\x28\xd9\x03\x80\x17\xd8\x6f\x93\x01\ \x82\xd1\x7e\x54\x00\x34\x0b\x8c\x3a\x6a\xd3\x92\x14\x5a\x5a\x22\ \x59\x66\x41\x05\x40\x5d\x88\xfe\x68\x05\xaf\xc3\xff\x75\x3f\xee\ \x44\x13\x52\x2a\x00\x9a\x45\x79\xd8\x57\xd0\x55\x75\x4d\x0e\xe1\ \xc9\xfb\x0d\xc5\x2b\xac\xa8\x00\xa8\x03\xcb\xcc\x36\xf1\x5a\x00\ \xe2\x66\xae\x44\xc7\xe1\xe1\x14\x80\xf6\xec\xa3\x35\x9d\xc5\x67\ \x98\xec\xf6\x2e\x2c\x71\x49\x52\xed\xa1\x2e\x64\x96\xae\x92\x1f\ \xdf\x7b\xe7\x93\xd3\xca\x6d\x2e\x6a\xba\x07\x75\xd1\xdf\x53\x01\ \x50\x35\x36\x7e\xb5\x8f\xd7\x02\x10\x34\x21\x12\x82\xf4\x63\x86\ \x55\x00\x3a\x8b\x4f\xd7\x0f\x36\xda\x10\x42\xdb\xbd\x5d\x28\x06\ \xba\x26\x00\x4d\x19\x71\xaa\xd9\x0e\x7c\x7b\x1b\x15\x00\x55\x63\ \xcf\x9f\xe9\xbc\x16\x00\x67\xb2\x07\x80\xda\x05\x80\x00\xcc\x78\ \xe3\xde\x7e\x5d\x10\x80\x05\x9f\xc2\xb3\x36\xc2\xbe\x08\x05\x96\ \x84\x8a\x93\x7e\xa7\x02\xa0\x4a\x1c\x77\x3a\xcf\x5b\xf2\xa7\x09\ \xb2\xc0\x51\xcf\x5b\xf3\x02\x40\x00\xae\xe6\x8e\xf8\x82\xd7\xe4\ \x6f\x3c\x16\xcd\xf6\x77\x80\x33\x02\x45\x11\x7f\x40\x8e\xbd\x19\ \x14\x04\x7d\x0f\x0d\x57\xd8\x77\x43\x36\x9d\x4a\xa6\x02\xa0\x2a\ \x44\xbc\x97\xc2\xeb\xbb\xff\x96\xdf\xf6\x83\xeb\x98\x40\x2d\x15\ \x00\x04\x9a\x65\x8a\x97\x5b\xf1\x85\xf0\xb8\x5b\xb0\xf5\xc6\x26\ \xe8\x2a\x3f\x8f\xf5\x7d\x56\xf2\x3f\x38\x84\x4b\x4a\x5e\x6d\x50\ \x92\x9c\x5a\x49\x8e\x9e\x4a\xce\x50\x01\x50\x15\x92\xa6\xac\xe3\ \xb5\x00\xa4\x7c\xbe\x1e\x3d\x00\x86\x5b\x00\x98\x7a\x77\x27\x47\ \x01\x90\xe6\x07\x24\x9b\xdc\x46\x2a\xf1\xd1\x9a\xbc\xf5\xd6\x56\ \x78\xd6\x81\xae\x3f\x9c\xf1\x64\xd1\x1f\xb2\x17\x82\x38\x9a\xc3\ \xf3\x2e\x99\x23\xe4\xb8\x06\x8d\x0a\x80\x8a\xb0\x76\xce\x2e\x5e\ \x0b\x40\xb8\x51\x1c\x04\xea\x47\x0d\xbb\x00\x34\xdf\xdb\xdb\x3d\ \xf4\x3d\xf8\xb9\x20\xd9\xe8\x3a\x92\x88\x8f\xf5\xf9\xf6\xec\xc3\ \x0a\x9f\xe9\xb3\x6d\x8c\x88\x6d\xca\x5d\x35\x64\x03\x91\xba\xe0\ \x19\x54\x00\x54\x81\x1d\x3f\x1f\xe1\xb5\x00\xb8\xff\xa3\xcf\x02\ \x2c\x76\xd8\x05\x20\xcf\x7a\xa2\x24\xdb\xd6\x04\x8a\x16\xfd\x09\ \xb5\x87\xe3\xa0\xab\xf2\x0a\xe7\x7a\xb7\x24\xd5\x4e\xdb\x89\x8f\ \xdd\x79\xad\xd7\x37\x71\xb7\x1f\x23\x40\xe4\xfd\x85\x4c\xf2\x0b\ \x2d\x27\x13\x5c\x8d\xd0\x43\x90\x46\x00\xaa\xc2\x61\xdb\x33\xbc\ \x25\xff\x09\xb7\x4b\xe0\x38\x06\x3d\x00\x34\x22\x00\xaf\x2e\xe3\ \x7c\x9a\xec\x08\x5d\x15\x97\xe5\x91\x02\x3d\xfb\xc9\x9d\x6f\x9a\ \x47\xfd\x2e\x7f\xe8\x6d\xc4\x0d\x50\x4a\x43\x9c\xb9\x02\x8d\x4c\ \xf2\x04\x46\x90\xef\x6a\x0c\x22\x27\x43\xc8\xb6\x34\x80\xf2\x0d\ \xfe\xa4\xc7\xa0\x98\x52\x01\x50\x01\x16\xbc\x15\x8f\x7d\xf2\x7c\ \x15\x80\x5d\x16\x69\xe0\xac\xe7\xaf\x1d\x02\x20\xbd\xb3\x4d\x81\ \x86\x4b\x9b\x38\x35\xbd\x90\x6d\xb2\x34\x03\x71\xc2\xaf\xd0\x91\ \x77\x4c\xe5\x76\xef\xe2\x8c\x04\x28\xf4\x9d\x86\x5b\x8b\x0a\x7d\ \xa6\x41\xcd\xde\x30\xb4\x27\x27\x9a\x87\x64\xa5\x52\x01\x50\x05\ \x96\x4c\x58\xc5\xeb\xf0\x7f\xd5\xb7\x5b\xc0\x73\x6c\xa8\x46\x05\ \x80\x00\x5c\x87\x0d\xcf\xd8\x33\xe4\xdd\x75\xd7\x41\xbc\xe4\x27\ \xcd\x13\x3f\xee\x17\x68\xb9\xb6\x11\x73\x14\x43\xee\xde\x6b\x79\ \x00\x2d\xf7\x0f\xa0\xf1\x28\x6b\xd4\xd3\xfe\x08\xba\x6b\xae\xc1\ \xb3\x56\xf9\xfd\x02\x25\x4b\x2c\xa9\x00\xa8\x02\x2b\xa7\x6f\xe5\ \xb5\x00\x44\x4f\x4d\xc1\x75\xe4\x5a\x28\x00\x88\x8a\x8d\xfe\xf2\ \x09\xd4\x70\x17\xc4\x2b\xad\x87\x9f\xf8\x31\x3f\x40\xc3\x81\x30\ \xbc\xe3\x13\x85\x8a\x80\x9e\xfa\x3b\x50\x77\x2c\x09\x6b\xfa\x2f\ \x4a\x7c\xe4\xa8\x87\x08\xf6\x65\xa7\xb5\x54\x00\x94\xc7\xe6\xef\ \x0e\xf2\x5a\x00\xfc\x3e\x0a\x87\xe0\x71\xb1\x1a\x11\x00\xc6\x5d\ \xe7\x7c\xdf\xce\xbe\x52\x77\x13\x1c\x83\xcd\x9e\x27\x5b\x04\xaa\ \xf7\x45\x72\xe9\x7e\xc3\x99\x82\x86\x7d\x21\x98\x20\x94\x2c\xb7\ \x40\x88\x97\xce\x05\x71\xdc\xcf\x20\x4e\x99\x8b\xff\xdf\x67\xac\ \x29\x59\xef\x24\x45\xfd\x76\xdf\x3e\x60\x79\x51\xb2\xda\x0e\xc4\ \xc9\x7f\x60\x2f\x3d\x8e\xd7\x46\x7e\xdb\xf7\x58\xcc\x35\x48\x36\ \xb8\xe0\xfc\x7d\x5f\x83\x4d\xdb\x83\x7d\x38\x7d\xa7\x08\x39\x7b\ \x1a\xee\x41\xe5\xd6\x60\xdc\x52\x34\xc8\x78\xc4\x65\x26\xae\x00\ \x53\x82\xfc\x18\x45\x14\x2d\xfc\x0d\x84\x73\x3f\xa1\x11\x80\x2a\ \xb0\x7f\x7e\x06\x5f\xc9\x8f\xfe\x86\x82\x71\x84\x2d\x40\xc3\x3f\ \x0d\x88\xae\xbd\x4f\x5d\x8c\x18\x21\x98\x30\x88\x1c\x75\x27\x96\ \xca\x27\x80\xf6\x02\x23\x04\xc9\xe9\xd5\x90\x63\xc7\x6e\x4a\x5a\ \x1c\x6b\x45\x3e\xdb\x73\x40\xe5\xf6\x50\x7c\x9e\x1c\x8b\x89\x54\ \x00\x94\xc6\xf8\x38\x48\x77\xb9\xc8\x5b\x01\x38\x68\x9f\x09\x02\ \x3d\x5f\x6d\x11\x00\x29\x6a\xfd\xa7\xa2\x3b\xcf\xc0\x0a\xc1\x04\ \x42\x88\xac\xfd\xe8\x28\xce\x82\xa2\x85\xbf\x73\xf6\x1e\x2c\x49\ \xb0\xc5\x2a\xc7\xd0\x76\x42\x8a\xa0\x72\x47\xd8\x4b\xfe\x85\x93\ \xa9\x00\x28\x8b\xa8\x0f\x96\xf3\x3a\xfc\xdf\xf0\xd3\x6e\x70\x1b\ \x13\xac\x79\x01\x20\xa0\x58\x60\x38\xb0\x3a\x30\xdf\x00\x1a\xaf\ \x6d\x1d\x21\xc4\xc7\x95\xde\x48\x4a\xe1\xfc\x89\x1c\x7c\x07\x07\ \xfe\x4c\xbe\xcf\x97\xd2\x1d\x01\xf2\xd0\x59\x7a\xe1\xd5\x6e\x41\ \x5c\x86\x4a\x05\x40\x49\xa4\x98\x6e\xe4\xb5\x00\xc4\xcf\x5e\x05\ \x3e\x63\x17\x6a\xab\x00\x20\x9e\x08\x8c\x06\x45\x02\xb5\x47\x13\ \xb5\x9b\xf8\x9d\xd9\x20\xce\x58\x06\xb9\xce\x33\x48\x84\xc7\xf7\ \x51\x60\x3f\x19\xf7\x0b\xbe\x58\xfa\xf9\x6a\xd4\x83\x28\x0a\xff\ \x15\x24\x67\x52\xa1\xb3\xec\xd2\xcb\x0d\x45\xb8\x42\xac\xf1\xea\ \x56\x28\x49\x72\xc0\x24\xe2\x2b\x26\xa6\x74\x1c\x58\x15\x58\xff\ \xc5\x1e\x5e\x0b\x40\xc8\xa4\x68\x08\x1a\x17\xa3\xcd\x02\x80\x28\ \x72\x18\xec\xe3\x5f\xb6\xca\x1d\x7a\x9b\xee\x6b\x15\xf1\xbb\x6b\ \xae\xe3\xe6\x60\xa6\x47\x9f\x95\xf8\x68\xfb\xe5\x3f\x75\xd0\xfb\ \xec\x4b\x88\x0a\xe7\x11\xa3\x04\x24\x3a\x33\x0d\x88\x91\x10\x5b\ \x34\x51\xae\x1a\x07\x63\x2a\x00\xbb\x7f\x3f\xc6\x6b\x01\x70\x7d\ \x3b\x00\xdf\xa7\x76\x0b\x00\x02\x77\xf1\xc9\x18\x86\xc1\x16\xe2\ \x1e\xe9\x46\x5d\x8d\x02\xdb\x72\x9f\x26\x39\xb0\x2c\x15\x99\x04\ \xd5\xbe\x48\x7c\x22\x4a\xdd\x8c\xb5\x65\x6b\x11\x15\x80\x34\xc7\ \x2c\xde\x92\xff\xb8\xf3\x39\x70\x18\xed\x3d\x62\x04\xa0\x36\x60\ \x1a\xe4\x13\x3c\xfc\x85\xf3\x26\x62\xa8\x5c\xb1\x25\x18\xea\xd2\ \x97\xe2\x5e\xbf\xfa\x73\x6b\xa1\xf1\xc6\x8e\x81\xb8\xbe\x1d\xbf\ \xc7\x00\xf7\x00\x32\xe6\x9b\x03\xc0\x54\x1a\x70\xce\xbe\x35\x27\ \x1d\x7d\xf9\x9e\xf7\xe4\x29\x64\xc6\x51\x7b\x24\x1e\xaf\xe9\xc5\ \xf5\x61\x1b\x6f\x99\x1b\xf7\x33\x79\xa9\xeb\x90\x97\x96\x20\x8a\ \x1c\x55\x46\x7e\x2a\x00\x0b\xdf\x4d\xe2\xf5\xdd\x7f\xfb\x9f\x87\ \xc0\x45\x4f\x5b\x23\x00\xf2\x72\x4f\x91\x35\x8a\xc0\x30\x00\x5b\ \x92\xb1\xa6\x5e\xb9\x35\x04\x2b\x10\x3d\x92\x3b\x5c\x85\x00\x3b\ \xfb\x98\x92\x1f\x92\xb2\x56\x81\x6d\x42\x95\x5e\xa6\x78\x96\x97\ \x7b\x9d\x28\x30\x13\xa0\xcc\xdd\x98\xda\x82\xab\x12\x09\x93\xd7\ \xf0\x5a\x00\x96\x7e\xb9\x01\xbc\xc6\x2e\x18\x51\x02\x80\x60\xc8\ \x54\x28\x3d\x0e\x0c\x2b\xf0\xae\x5e\xb6\xc6\x9b\x11\x82\xdb\x9c\ \x44\xa0\xab\xfc\x02\x76\x0b\x2a\x4c\x40\x06\x65\xee\x26\x78\x74\ \x10\x0e\xec\x8d\xc0\x46\x9f\x3c\xeb\x49\xb8\xe2\x8c\x20\x30\xca\ \x80\x0a\x40\xea\xec\x1d\xbc\x16\x80\x08\xe3\x04\x08\xd0\x8f\xd4\ \x76\x01\x60\xdf\xf5\x3f\x17\x49\x31\xec\xc8\x71\x9c\x0a\x4d\x37\ \x77\x22\xc9\xb9\xb4\x29\x2b\x3f\xb5\x88\x62\x80\xf9\x03\x66\x2d\ \x39\xfe\xbb\x36\x40\xad\x6d\xce\x54\x00\xb6\xfd\x74\x98\xd7\x02\ \xe0\xf9\x4f\x1c\x00\x1a\xa9\x02\x20\xcd\x9a\x17\xda\x4f\x26\x9c\ \x97\xd5\x8f\xf2\x75\x3e\xf0\xac\x5d\xae\xbb\x0f\x3a\x00\x49\xd6\ \x0a\x46\x92\x89\x09\x15\x80\x43\xd6\xa7\x79\x4b\xfe\x0c\xb7\xcb\ \xe0\xa8\xe7\x35\xe2\x04\x80\x00\xac\xa3\x33\xa1\x32\x66\xc0\xf3\ \xed\x26\xf7\x25\x0b\x11\x4c\x88\x8c\xbb\xfe\x5e\x00\xff\xff\xc5\ \xf7\xf0\xe7\x0a\x18\x14\x3a\x30\xb0\xc7\xff\xc6\x3f\x63\x7e\x06\ \xdb\x68\xb9\x8a\x4a\x41\xc0\xb7\xd0\x55\x7d\x5d\x9e\x08\x60\x7b\ \xaf\x64\x9b\xf7\x48\x20\x3e\x15\x80\x30\x9e\xaf\x01\xdb\x6b\x95\ \x0e\x4e\x7a\x7e\x3c\x10\x00\xb5\x02\x43\x6d\x14\x16\x47\x43\x10\ \x59\x1b\x10\x8f\x1c\xb9\x02\x73\xac\x1c\x70\x69\xd7\xad\xdf\x13\ \xa4\xed\xc4\xa7\x02\x10\xf3\xf1\x4a\x5e\x87\xff\xa9\xdf\x6d\x07\ \x8f\x31\x21\x54\x00\x86\x1e\x69\xe0\x16\x61\xe1\x60\x21\xc0\xe9\ \xbe\xfa\x0b\x1b\xb8\x0c\x05\xe1\x54\x21\x15\x00\x2d\x16\x80\x15\ \xd3\x36\xf3\x5a\x00\x62\xa7\x2f\x03\xdf\xb1\x11\x54\x00\x14\x8e\ \x0c\xcc\x21\x8f\x50\x8e\xac\xde\x13\xc1\xa9\x57\xa0\xf1\x50\x38\ \x15\x00\x6d\x15\x80\x4d\xdf\xec\xe7\xb5\x00\xf8\x7f\xb2\x10\x82\ \xc7\xc5\x50\x01\x50\x1c\x98\x85\x2f\x76\x32\xc4\x72\xdc\x20\x13\ \x93\x2d\xc1\x78\xa7\x1f\xa1\x22\x40\x05\x60\xdf\xdc\x13\xfc\x5e\ \x03\x36\x5e\x55\x23\xc0\x74\x3b\x70\xb9\x97\xa9\x4c\xff\x82\xb2\ \xd5\x1e\xf2\x3b\x09\x9f\x89\xd0\x3c\x94\x0a\x80\x16\x09\x40\x28\ \x83\x74\xe7\x0b\xbc\x25\xff\x61\x87\xd3\x20\x18\xed\x4d\x05\x40\ \xb5\xc9\xc2\x57\xc7\x79\x11\x25\x89\xf6\xf0\xbc\x2b\x47\x8e\x83\ \x4f\x9e\x36\xed\x39\xa0\x02\x10\xf9\xfe\x52\x5e\xdf\xfd\x37\xfd\ \xba\x0f\xdc\xf4\x82\xa8\x00\xa8\x21\x41\x28\x6b\x56\xa1\x38\xce\ \x86\xb0\xbd\x47\x0a\xfc\xbe\x96\xec\x39\xa0\x02\x90\x6c\xb4\x81\ \xd7\x02\x90\x34\x27\x95\xb8\x06\x8c\x0a\x80\xf2\xdd\x7a\x85\x0e\ \x53\x64\x38\xfc\xd8\xc9\xb3\xf9\xc2\xcd\xc0\xd2\x8d\xbe\x9a\x03\ \x15\x80\xb5\x9f\xf1\x7b\x0d\x58\xe8\xe4\x18\x08\xd4\x8f\xd6\x49\ \x01\x10\x2f\xfe\x0a\x1a\xd3\x22\xa1\x23\x37\x0d\x7a\x9b\xd1\x4b\ \x00\x17\x73\xf6\x48\x6e\xa1\xd1\x27\xd6\xe8\xc3\x3e\x55\xf6\x75\ \xb0\x21\x69\x90\x08\x60\x24\x90\x23\x6f\x01\x2a\x1a\x91\x6a\x90\ \xf4\x54\x00\x76\xfe\x92\xc6\xef\x35\x60\xef\xe0\x16\x60\xdd\x12\ \x80\x90\x59\xd0\x74\x2a\x09\xd7\x65\x71\xe9\xdd\xaf\xdf\x1d\xa8\ \xf4\x6b\x16\x3b\x0f\xde\xe7\xf7\x34\x45\x80\xcd\x40\xac\xf6\x5e\ \x4f\x4e\x91\x45\x48\xfd\xa0\x02\x70\xc4\xfe\x2c\x6f\xc9\x9f\xee\ \x72\x01\x1c\xf4\xbc\x75\x4b\x00\x22\xbf\xc5\x55\xd9\x43\x9c\xeb\ \xc7\x2d\xbe\x8c\x70\x28\xf5\xda\x4f\x5d\x07\x9b\x7b\x94\xaf\xf7\ \x93\xbb\x2b\xb0\xed\xde\x4e\x4d\x10\x9e\x0a\x40\xf8\xdb\x89\x68\ \x95\xcd\x57\x01\xd8\x39\xf7\x08\x7a\x00\xe8\x8c\x00\x2c\xfa\x12\ \xba\x2b\x2f\x29\xec\xf0\xd3\x7a\x97\x21\x62\xd0\x74\x65\xae\x01\ \xcd\x3d\x14\xd9\x73\xd0\x98\x16\x35\xdc\x84\xa7\x02\x10\x3f\x31\ \x95\xd7\xe1\xff\x8a\xaf\x36\x81\xd7\x98\x30\x9d\x11\x80\xb6\x87\ \xfb\x95\xb6\xf9\x6a\x4c\x8f\xc1\xe7\x52\x75\x24\x20\x67\xcf\x01\ \x1e\x15\x86\xb9\x32\x40\x05\x60\xd5\xcc\x6d\xbc\x16\x80\xc5\x66\ \x49\xe0\x3f\x36\x52\x17\x04\x00\x6b\xeb\xca\x92\x5f\xba\x66\x3b\ \xfa\x7b\x7c\x4e\x15\x46\x02\x68\xf0\xd9\x74\x6b\x97\xfc\x95\x67\ \x8b\xbf\x1e\x2e\xc2\x53\x01\xd8\xfa\xfd\x21\x5e\x0b\x80\xf7\x07\ \x61\x10\x8c\xef\x95\xff\x02\xd0\x51\x90\x21\xaf\x0b\x0f\x7d\xff\ \xb8\xd8\x7c\x35\x67\x2d\x57\xc5\x35\xe1\xd6\xa3\x01\x56\x5e\x36\ \x46\xd0\x51\x72\x8e\xed\xb5\x31\x8a\x19\x26\xc2\x53\x01\x38\x60\ \x99\xc9\x57\xf2\xe3\x8a\x73\x81\x3e\x26\x00\xf9\x2f\x00\xd1\xdf\ \xb3\xfb\xf5\x3d\x3c\x02\x22\xcf\x39\xfd\x66\x9a\x8b\xfe\x84\x6e\ \xf1\x6d\xb6\xf2\x9c\x34\x17\xa0\x2c\x8a\x9d\x06\x96\x08\x45\x1e\ \x9f\x42\x6f\x13\xbb\xab\x71\xfd\x4e\xb5\xb7\x0b\x53\x01\x08\xe5\ \xb9\x07\xc0\x7e\x9b\x13\x20\x18\xed\xa7\x13\x02\x50\xbf\x3b\x80\ \x5c\x66\xab\xb8\x0c\xd9\x56\x83\x9a\x75\x70\x13\x0f\x4b\xef\x3e\ \xc1\xdb\x4f\x31\x14\x39\x0e\x14\x81\xe2\x18\x0b\xd6\xf2\x60\x6f\ \xcb\xdf\xd0\xfe\xe8\x00\x74\x57\x5f\x41\xb1\xe8\xae\xbd\x0e\xed\ \x39\x47\xa1\xe9\x54\x22\x88\xe3\x7f\xa5\x02\xa0\x0a\x44\x7f\xb4\ \x82\xd7\xe1\xff\xba\x1f\x77\x82\xfb\x98\x60\x9d\x10\x80\xa6\xcc\ \x78\x22\x99\xaa\x76\x47\x10\xdd\x7d\xd8\xce\xe4\x92\x75\x4e\x2a\ \xb8\x36\xf2\x9e\x83\xda\x23\xe4\x6b\x96\xbb\x15\xf9\xfe\x1e\x10\ \xc7\xfe\x44\x05\x40\x19\x2c\x33\xdb\xc4\x6b\x01\x58\x32\x63\x25\ \xf8\xea\x47\xe8\x82\x00\xe0\x99\x9d\x44\x98\xb2\x55\x6e\x44\x01\ \x10\x67\x10\x1f\x87\xeb\xc3\x55\x7a\x9d\x01\x03\x3d\x05\x84\x96\ \x93\xc9\xf9\x00\xf9\xc0\x28\x41\x89\xa1\x22\x2a\x00\x1b\xbf\xda\ \xc7\x6b\x01\x08\x9c\xb0\x18\x82\xf4\x63\x74\x42\x00\x1a\xd3\x63\ \x89\x44\xa9\x39\x14\xf7\x52\x26\x7e\xa0\x00\xb4\x3e\x3a\x42\x16\ \x80\x35\x0e\x6a\x19\x20\xca\x79\x69\x71\x68\x41\xf0\x0f\xe4\x63\ \x08\x17\xf4\x59\x8e\x6d\x71\xa7\x02\xa0\x08\xf6\xfc\x71\x9c\xdf\ \x1e\x00\x6f\xfa\xe3\xfb\xd4\x05\x01\x10\x6f\xf6\x20\x91\x04\xcf\ \xd0\x22\xf7\x99\x20\x72\x31\x86\x02\x4f\x53\x28\xf0\x30\x81\x3c\ \x81\x21\x2e\x00\xc1\x73\x38\x21\xcc\x16\x47\x7e\xa3\x96\x73\x76\ \x95\xb7\x99\x74\x1f\x60\xb6\xad\x09\x26\x23\x95\x2b\x5b\x3e\x56\ \x24\x2f\x40\x05\xe0\x98\xe0\x1c\x6f\xc9\x9f\x26\xc8\xc2\x16\x60\ \x5d\x11\x80\x8a\xe0\xcf\x58\xef\xa4\xdd\x75\x37\xa1\x62\x9d\x0b\ \x14\xfa\x4e\x83\xa2\x80\x59\x50\xb3\x37\x04\xa7\xf1\x08\x3f\x8f\ \x9b\x79\x6b\xfc\x70\xa7\x9f\x5a\x50\xea\x66\xa2\x58\x1e\x80\x80\ \xf6\xc7\x07\xa9\x00\x0c\x05\x11\xef\xa5\xf0\xfa\xee\xbf\xe5\xb7\ \xfd\xe0\x3a\x26\x50\x57\x04\x00\x97\x86\x34\xdf\x53\xa8\x0b\x90\ \x90\x38\x5c\x08\x15\x9e\xb8\x71\x57\x6d\x28\xf6\x9e\xc9\x71\xc7\ \x80\x90\xd3\x35\x4b\x96\x5b\x50\x01\xe0\x8a\xa4\x29\xeb\x78\x2d\ \x00\xc9\x9f\xad\x43\x0f\x00\x5d\x11\x80\x62\x81\x21\x14\x86\xfe\ \xcc\x3e\x74\xc3\x3d\xb9\x86\xa1\xb9\x74\xe5\xb6\x9a\x50\xbf\x27\ \x98\xf5\x3a\x1a\x2e\x6e\x80\x3c\x8f\x39\xfd\x5b\x91\xd3\x92\xd8\ \x9b\x97\xce\xad\xa0\x02\xc0\x15\x6b\x3e\xdd\xc9\x6b\x01\x08\x37\ \x5a\x02\x81\xfa\x51\x3a\x15\x01\x60\x56\x3f\x73\x85\xd2\x02\x50\ \x96\xea\x89\xcf\xa5\xee\x08\xa0\xf5\xce\x76\xd2\x35\xe0\x56\x63\ \x59\x55\x8b\x9a\x83\xe4\x64\x67\x57\xe9\x39\x2a\x00\x5c\xb1\xe3\ \xe7\xa3\xbc\x16\x00\xb7\xf7\xfa\x2c\xc0\x62\x75\x46\x00\xca\xdd\ \xf1\x4c\x0d\xc2\xf9\x06\xd0\x42\xcc\xec\xcb\x47\xdd\xb1\xa4\xfe\ \x09\x3e\xf5\xe5\x00\x10\x9d\x85\x27\x89\xd7\x51\x18\xf2\xa3\x2c\ \x01\xc0\xc8\x84\x70\x24\xc0\x9c\x06\x15\x00\x8e\x38\x6c\x7b\x86\ \xb7\xe4\x3f\xe1\x76\x09\x1c\xc7\xe0\x1a\x30\x9d\x11\x80\x6a\x3f\ \xf3\x01\xf5\x75\x52\x83\x0f\xd9\xa8\x53\x04\xd5\x7b\x17\xf5\x2f\ \x03\xb5\x98\xa8\xf6\xb6\xdb\xee\xaa\x2b\xa4\xeb\xc1\x99\x01\x42\ \xef\x02\x69\x2d\x19\x1e\x7f\xea\x82\x67\x50\x01\x90\x87\x05\x6f\ \xc5\x63\x9f\x3c\x5f\x05\x60\xb7\x45\x1a\x38\xeb\xf9\xeb\x92\x00\ \x20\x44\xfd\x06\x9d\xb8\xdb\x9f\xb3\x07\x80\xf0\x18\x14\x86\xfd\ \x32\x80\x64\x4f\x04\x46\x6a\x17\x80\xce\xa2\x4c\xd2\x35\xe1\xfe\ \x41\x19\xe4\x47\x61\x20\x25\x0e\x9f\xb5\x3f\xa2\x11\x00\x17\x2c\ \x99\xb0\x8a\xd7\xe1\xff\xaa\x6f\xb7\x80\xe7\xd8\x50\x9d\x13\x80\ \x72\x4f\x53\x24\x89\xc8\xfb\x0b\xbc\xa3\x13\xcf\xca\x15\x97\xa1\ \xf9\xde\x3e\xa8\x39\x18\x0d\x05\x81\xdf\xbd\x4a\x32\x5c\xfc\x51\ \xe3\x67\xae\x76\x01\x68\xb9\xb3\x83\x9c\x00\xbc\xb2\x05\x72\xac\ \x0c\x00\x7b\x17\x3c\x4c\xf0\xdf\x39\xd6\x93\x30\x4a\x21\x96\x2e\ \x69\x0e\x80\x1b\x56\x4e\xdb\x32\x2c\x44\xdc\x67\x7d\x02\x96\x7f\ \xb5\x11\xa2\xa7\xa6\xc0\x51\xc7\xe1\x3b\x72\x44\x99\xa7\x80\x9f\ \xfe\x22\x9e\x0b\x00\xb9\xdf\xbe\xfe\xfc\x7a\x72\x86\xbf\xf9\x6f\ \xb6\xf0\x1a\x51\xe2\x6c\x3c\x3c\xb6\xe2\xdb\x02\xd9\x8d\x49\xae\ \x6e\x80\x27\x61\x5f\x60\xf3\xd2\x93\x90\xcf\x40\x72\x32\x89\xb5\ \xca\x21\x39\xb9\x8c\x0a\x00\x17\x6c\xfe\xee\xa0\x5a\x88\x77\xd0\ \xee\x24\xa4\x7e\xb7\x0d\x16\x99\x26\x82\xe0\x4d\x6f\x98\xff\xba\ \xa3\x14\xf6\xe3\x3c\x60\xc3\xcf\x7b\x86\x45\x00\x7c\x3f\x5a\x00\ \xc1\xe3\x62\x75\x51\x00\x70\x82\x0f\xed\xb8\xd9\xdb\x82\x89\x28\ \xb4\x9b\x3c\x6c\xa3\xb7\x85\x9e\xb3\xe4\xee\x0f\x80\x67\x22\x0c\ \xed\x39\x1c\x65\xb0\xb3\x91\x0a\x00\x07\xec\x9f\x9f\xa1\x12\xa2\ \x1d\x71\x3c\x83\x13\x77\x48\xf8\xb7\x7c\x90\xe8\xf2\x10\x6e\x14\ \x8f\x46\x9d\x6a\x22\x3f\xfa\x1b\x3a\x8e\xc3\x0e\x40\x9d\x14\x80\ \x96\x6b\x1b\x59\x97\x71\xe4\x3a\xcf\x20\x92\xbf\xc8\x71\x0a\xee\ \xff\x1b\x26\x01\xc0\xcd\x42\xe2\x8c\x65\x2a\x69\x5c\x6a\xb9\x7f\ \x00\xb0\xb3\x90\x0a\x80\x1c\x8c\x8f\x63\x08\x78\x51\x69\xc2\x3b\ \xc9\x27\x3c\x11\xae\xef\xfa\xc1\x2e\x8b\x34\x35\x45\x21\x99\x20\ \xd0\xf3\xd5\x49\x01\x10\xc7\xfd\xc2\x7a\xf7\x97\x9c\x5e\x2d\x8b\ \xf8\x98\xf1\x2f\xf3\x30\x19\x76\xf3\x8d\x3c\x6b\x03\xc8\x71\x30\ \x83\xee\x9a\xeb\x4a\x5b\x98\xe5\xfb\x7f\x83\x2b\xcc\x69\x04\x20\ \x07\x51\x1f\x2c\xe7\x4c\xa6\xa3\x2f\x11\xde\xed\xbd\x80\x21\x13\ \xdd\x72\x94\x13\x38\x8c\xf6\x46\x57\x5e\x8b\xd7\x9d\x06\x7e\x6f\ \xb4\x00\xe2\x67\xaf\x82\x93\x2a\xae\x46\x6c\xf8\x69\x37\xb8\x8d\ \x09\xd6\x49\x01\x68\x7f\x7c\x88\xf5\xee\x9f\xe7\x36\xfb\x45\x92\ \x6f\x02\xde\x7d\xfb\x36\xfa\x94\x7b\x98\xa8\xff\xae\x4f\x40\xfe\ \xbf\xfc\x01\xb0\x83\x51\x7e\x4b\x30\xf9\x88\x50\xba\xdc\x05\x9f\ \x87\x79\x4f\x54\x00\xe4\x21\xc5\x74\x23\x99\xf0\x82\xb3\x48\xf8\ \xbe\x24\x9a\xc7\x3f\x83\x19\xd2\x0a\x86\x42\x78\x24\xb9\xfd\x68\ \x4f\xdc\xc3\x87\x73\xf8\xfa\xfd\xe7\xf0\xc0\x71\x51\x60\x37\xca\ \x63\xd0\x63\xbc\x3f\x08\xc5\xbb\xb6\x8a\x04\x00\x45\xc5\x67\xec\ \x42\x9d\x13\x00\xc9\x06\x17\x36\xa2\x60\xa8\xfd\x82\xfc\x6a\xcf\ \xf0\x73\xf7\x0b\x34\x7e\x69\x2c\xf8\x47\x9c\x0a\x1c\xea\x9d\xff\ \x05\xf9\x11\x45\x0e\x86\x54\x00\xe4\x61\xfd\x17\xfd\x89\xb8\x34\ \xa7\x2c\xd8\xf4\xcb\x5e\x88\x9d\xb1\x42\x21\xc2\x5b\xbe\x2e\x40\ \xc2\xbb\xea\x05\x22\xe9\x82\x5f\x10\x9e\x84\x60\x06\x7d\x3f\x3b\ \xff\x95\xd7\xb1\x1d\xeb\x0a\x6b\xbe\xdf\xae\x12\x01\x08\x9e\x14\ \x05\x41\xe3\x62\x74\x4b\x00\x42\x67\xa3\x65\x16\x1b\x51\x72\x9d\ \xa6\xbf\x38\xe7\x1b\x6a\xd5\x86\x61\x61\xbf\x3f\x01\x5e\x23\x53\ \xc1\x90\xb7\x49\x08\xd1\xf2\xe0\x10\x5a\x9a\x0d\x58\x40\x22\xbf\ \x75\x99\x0a\xc0\xea\x99\xdb\x21\x66\xea\x52\x26\xa4\x0f\x54\xe4\ \x0e\x8f\x77\x71\x97\x31\x01\x48\x78\x45\x33\xed\x7e\x63\x17\x81\ \xf5\x28\x17\x99\x09\xc2\xe3\xce\xca\x8d\x28\xbb\xbe\x8d\x4b\x40\ \x74\x4a\x00\x9a\x32\x13\x58\xc9\x52\xbd\x3f\x0a\x09\x22\x64\xc0\ \x90\x4e\xab\x96\x71\x16\x3a\xe0\x31\x60\x00\x98\x01\x20\x5c\x24\ \xd2\xf2\xe0\x30\x74\xd7\xdd\x82\xde\x96\x07\xd0\x53\x7f\x07\xda\ \x72\xd3\xb1\x55\xb9\x20\x78\x70\x9b\xb0\xc8\xca\x80\x8e\x03\x73\ \x41\x80\x7e\x14\x77\xc2\x33\xb0\x1d\xe5\xae\x14\xe1\x09\xc0\xbb\ \xb4\x93\x9e\xef\xab\xaf\x89\xd5\x84\x6d\x7f\x1e\x56\x88\xfc\xc7\ \x9d\xce\x61\xce\x41\x97\x04\x40\x9c\xfc\x07\x6b\x29\xad\xbb\xf6\ \x06\x64\x5b\x1b\x02\x9e\xb5\xed\xb1\xc4\xa7\x4d\xc0\x79\x83\xec\ \xf9\x13\xf0\xfa\x14\x04\x46\x11\x15\x5e\x66\x54\x00\xb8\xc0\x6b\ \xec\x02\xee\x84\x57\x7b\x28\x8d\xd7\x83\xc9\xc2\x01\xd7\x31\x4a\ \x80\xc7\x92\x21\x3a\x16\x33\xc2\x71\x10\x13\x8e\x3a\x22\x00\x18\ \xfa\x77\x95\x9f\x67\xbb\xfb\xe3\x72\x4e\x24\x09\x83\x2a\x1f\xcd\ \xdf\xfd\x09\x1d\x8c\xd2\xa3\x80\x02\xc0\xdd\x03\xd4\x12\x8c\x1b\ \x90\x20\xfd\x64\x13\x48\x09\xcf\xcc\xce\x6b\xea\xec\x8c\x51\x89\ \xdd\x68\xcf\x41\x82\xe4\xf5\x41\x08\x1c\xb0\xe1\xde\xb3\xb0\xf4\ \xcb\x0d\x28\x28\xbc\x17\x00\xf9\x35\x7f\x44\xf3\x8b\xda\x38\x03\ \x0c\xb5\xf1\x71\x5a\x8a\x0a\x14\x81\xa1\x45\x02\xc2\xa1\x76\x2d\ \x52\x01\xc0\x24\x1c\x8a\x00\x12\x9e\x1c\xd2\x6b\x00\xb1\x58\x3d\ \x98\x3f\x6a\x60\x5e\xc2\x5a\xcf\x05\x89\xcd\x45\x00\x22\x8c\x13\ \xc0\x5f\x3f\x52\x27\x04\xa0\x7e\x9b\x37\x7b\x86\xbc\xed\x11\xe4\ \xb9\x7f\x4a\x38\xfb\x6b\x25\x30\x42\xc9\x97\x0e\x34\xb1\x23\xd7\ \xd2\x40\x1a\xf6\x53\x01\xe0\x0b\xa4\x09\x42\xd7\x41\xd1\x40\xc8\ \xa4\x28\xf4\xf9\x63\x13\x00\xcf\x7f\x86\xe0\x73\xf0\x5d\x00\xc4\ \x29\x73\x19\x82\x3f\x64\x15\x80\x8a\xcd\x81\x48\x14\x15\x65\xfe\ \xd1\x83\xbf\x7e\xab\x17\x34\x1e\x0a\x87\x86\xfd\x21\x80\x76\xdc\ \x51\xdf\xa9\x45\x08\x2a\xbd\xcd\xa0\xc8\x61\x0a\xd6\xf5\x5f\x22\ \x3d\xba\x09\x33\xb3\x0e\xd8\xbb\x40\x17\x83\xf0\x17\x98\x7b\x70\ \xd6\xf3\x1b\x54\x2e\x74\x18\xef\x09\x9b\x7e\xdd\x2b\x93\xfc\x19\ \x6e\x97\xc1\x51\xcf\x8b\xef\x02\x80\xa4\xeb\x91\xdc\x62\x0f\xfd\ \xff\xda\x0f\x8f\x5f\x84\xd3\x4c\x82\x0d\xad\xb8\x15\x22\x7d\xe8\ \x2c\x24\x3c\x61\xdd\x38\x36\xe1\x74\x14\x66\x40\xc3\x9e\x60\xa8\ \x0b\x9a\xa1\x1e\x42\xbe\xb0\x12\x27\x34\x2b\x51\x01\xe0\x31\x30\ \x11\x69\x35\xb0\x5c\x88\xa5\xcb\xbe\x46\xa5\x13\xaf\x24\x08\xf7\ \x5a\x1d\x07\x27\x3d\x3f\x7e\x0b\x40\xf8\x1c\xe8\x2a\xcd\x62\x23\ \x3f\x2e\xfe\xcc\x15\xf4\x9b\x83\x94\xba\x29\x76\x46\x96\x2c\xb3\ \x80\xee\xea\xab\x9c\x9b\x72\x3a\x8b\x4f\x83\x38\xe9\xf7\x61\x21\ \x29\x15\x00\xdd\x01\x26\x26\xed\x47\x7b\xbd\x7a\x24\xc0\xd6\xe4\ \x3d\x56\xd2\xbd\x06\x38\x85\xe8\x31\x26\x94\xbf\x02\x10\x36\x07\ \x3a\x72\xd3\xe4\xba\xfa\x3c\x89\x9a\xaf\x74\x6d\xbc\x7e\x8b\x07\ \xeb\x4c\x01\x09\xcf\x5a\x1f\x80\x78\x85\x15\x15\x00\x2a\x00\xaa\ \x46\x2c\x92\xdb\xf2\x95\x23\x81\xd5\x68\x67\x48\x9e\xb3\x06\xdd\ \x8d\x62\xa6\x2d\x05\xdf\xb1\x11\xfc\x14\x80\x85\x9f\xcb\x5b\xfd\ \x8d\xa8\xdc\x1e\xf6\x72\x6d\x1c\x17\x70\x0c\x39\xec\x5f\x69\xcd\ \x9d\xfc\x04\x57\x1e\x75\x2e\xef\x44\x50\x01\xd0\x4d\xf8\xeb\x2f\ \xc6\xb2\xe5\xab\xd1\x40\xe0\x84\x45\xe0\xf5\x41\x28\x56\x35\xf8\ \x26\x00\xe2\x45\x5f\x41\x67\xc9\x19\xb9\xc4\xab\xbf\xb0\x71\xe0\ \xf6\x5d\x27\x23\x05\xa2\x8c\x4f\xa1\xbb\x0e\x5b\x8a\x95\x42\xe7\ \x93\x53\xe8\xcd\x47\x05\x80\x0a\x80\x1a\x12\x84\xb1\xd8\xb7\xf0\ \xaa\x08\xd8\x8c\x72\xc5\xef\xf3\x49\x00\xc4\x29\xf3\xb0\xc7\x9f\ \x4b\x5f\xbc\xd0\xa2\xbf\x7c\x96\x67\x35\x49\xa1\xb9\xf8\xc6\x63\ \xd1\x2a\x5b\x2c\x52\xbf\xc3\x97\x0a\x00\x15\x00\xb5\x26\x08\x07\ \xcc\x13\x38\x8e\xf6\xe1\x95\x00\xd4\xef\xf4\x87\x67\x9d\x42\xf9\ \x2b\xb1\x0a\x32\x07\x58\x7c\x65\xcf\x53\xb8\xe6\x2f\x57\x6c\x5a\ \xfe\x3e\x88\xab\xc6\xfb\xb6\x06\xb5\xe5\xb1\x1e\x49\xd0\xf2\x5b\ \xad\x64\xa5\x02\x40\x11\xac\x1f\x03\x82\xd1\x68\x3d\x86\x4d\x44\ \xbc\x10\x80\x88\x2f\xa1\xf5\xd6\x56\x6e\xfb\xf0\x8a\xce\xa0\xa1\ \xc6\xcb\xa1\x7f\x99\xbb\x89\xa2\x33\x05\xec\xab\xc2\x76\x86\xcb\ \x58\xd2\xb1\x84\x75\x4e\xbf\x6e\xe1\x17\x54\x00\x54\xf9\x15\xf4\ \x76\xd2\x98\xe0\xf1\x71\x7b\x99\x0f\xdb\x73\x2a\x00\xfd\xf0\x1c\ \x1b\x02\x3e\x63\x23\x46\xbc\x00\xd4\x6f\xf7\x81\xde\xc6\xbb\x9c\ \xc8\xdf\xd3\x70\x0f\x0d\x3e\x06\x5a\x79\x2b\xde\xf0\x53\xbf\x3b\ \x90\x7c\xe7\x7f\x78\x44\x76\x2b\xee\xbc\x89\xac\xfb\xfd\x25\xeb\ \x9d\x46\x12\xf9\x9f\xd7\x05\x99\xef\xad\x0d\x9a\x3d\xe6\x35\x6d\ \xff\x0a\x7d\x23\xde\x98\xf9\xc0\x5d\xa7\xe4\x27\x60\x84\x09\x80\ \x24\xd5\x1e\x13\x67\x43\x3c\x67\xe3\x36\xe0\xd6\xec\xe3\x50\xbe\ \xde\x17\x0a\x9d\x4c\x95\xf2\xc3\x6b\x3c\x1e\xc3\x52\x5d\x08\x25\ \xb6\xe4\xd6\x1d\x4b\x66\xc9\x03\xf8\x8d\x14\xf2\xdf\xab\x0d\x98\ \x3e\xfd\xb5\x91\xf5\x05\xff\x26\x74\x5c\xec\x1f\xcc\x07\xef\xe9\ \x88\x23\x0a\x15\x00\x0c\xf5\x1b\x0e\x2e\x20\x12\x5f\x81\x85\x9e\ \x98\xc4\x63\xba\xf7\x54\x2e\x00\x55\x3b\x17\x10\x04\x80\x75\xbd\ \x37\x46\x15\x5a\x4e\xfc\xca\xba\x80\xe9\xce\x10\xf9\xda\xbf\x7d\ \x6d\xa4\x7e\x45\xea\x47\xfe\xd7\xd0\xf1\x4b\x22\x99\x0f\x60\xbb\ \xf6\x13\x84\x0a\x40\x7d\x7a\x6c\x6e\x47\xde\x31\x42\xad\x5d\x69\ \x60\xf7\x9e\x24\xd5\x6e\x48\x65\xc6\x86\xbd\xc1\xac\x42\xd4\xfa\ \xe8\xc8\x8b\xbd\x83\x13\x21\xc7\x76\x32\x42\xd8\x37\xc3\x3f\x77\ \x22\x74\x3c\x39\x4b\x3e\x02\xac\x71\xd0\x56\xe2\x77\x89\x03\xa7\ \xaf\x94\x78\x99\xfe\xcf\xd7\xf8\xf2\x15\x3c\x2e\x71\x3c\x13\x11\ \xec\xa4\xf9\x01\xed\x16\x00\xa6\x65\xb6\x5e\x85\x84\x27\xee\xc8\ \x6b\xbd\xb9\x05\xc4\x8b\xbf\x92\x4d\xfa\x25\x3f\x41\xe3\xd1\xc5\ \x80\x42\x24\xdf\x7e\x0b\x51\xb5\x3d\x18\x17\x74\xf4\xc3\x0c\xc4\ \xe9\xc9\xac\xd7\x50\x17\xa1\x85\x49\xc0\x00\xf3\x13\xd5\x81\x33\ \xdf\x79\x8d\xaf\x5f\x61\xe3\xe3\x4c\x99\x0f\xe3\x4d\x4a\x46\x9d\ \x12\x00\xd2\xb6\x5c\x14\x82\xa6\x8c\x38\x68\x3e\xb7\x12\xda\xfe\ \xde\x0b\x3d\x0d\x77\x14\x7e\xbe\xb6\x9c\x34\xa8\x3b\x1a\xc9\x10\ \x7f\x09\x74\x14\x9e\x94\xbb\x6f\x90\x3c\xae\xab\x11\xdc\xaf\x0d\ \x30\x9f\xc9\x33\xba\x93\xf3\x03\xcc\x07\xd2\x86\x71\xe5\xad\xd2\ \x4e\xa2\x50\x01\xe0\x3b\xca\x56\xb9\x2b\x50\x92\x54\xcb\x34\xa1\ \x98\x49\xf0\xf9\xc0\x1f\x7f\xfc\xbb\xd7\x74\xed\x2b\x50\x2f\xf9\ \xbf\xbd\xc8\x0f\x74\x8c\x1c\x12\x51\x01\xe8\x69\xb8\x0b\xcd\x77\ \xf7\x62\x86\xbd\x62\xa3\x3f\x94\xc4\xdb\x42\x71\x8c\x05\xa2\x6c\ \x95\x1b\xd6\xdf\xc9\x8d\x38\x9a\x47\x5b\xde\x09\xcc\x0f\x94\xba\ \x69\x54\x00\xba\xfb\xce\xf9\xf5\x21\x86\xff\xeb\x35\x5d\xff\x0a\ \xd7\x8f\x7d\x03\xf3\x03\x94\xa4\x1a\x17\x80\xf6\xfc\x8c\x16\x99\ \x67\x76\x61\x1a\x94\xaf\xf5\x02\x91\xd7\x67\x5c\x2d\xb1\x70\x85\ \x36\xd3\x06\xac\x4d\xe4\xc7\x6a\x44\xbe\xdf\x57\x52\x9b\x6e\x4d\ \x9d\xf3\x6b\x82\xcc\xdf\xd3\x51\xba\x93\xbf\x42\xc7\xc7\xcc\x66\ \x26\xeb\x1e\x50\xa2\x6a\x4e\x00\xda\xb2\xd3\xba\xb1\x86\xdf\x2b\ \xc2\xbb\x38\xb3\xa2\x1b\xd7\x79\x2b\xe3\x8a\x5b\xba\xc2\x15\x6d\ \xb3\x55\x41\xe0\xae\xaa\x6b\x20\x39\xbd\x0a\x77\x09\x0c\x3d\xe7\ \xf0\x08\x8a\xc2\x7f\xed\xef\x14\xf4\x1b\x6e\x1b\xb2\xe9\x39\xb5\ \x41\xd3\xbf\xa6\x4c\x67\x2b\x1b\xbe\x16\xf9\x6f\xfb\xf2\x03\x0c\ \xaa\x87\x9f\x20\x54\x00\x9e\x2e\xb1\x6c\x2a\x0c\xf9\x91\xbc\xa2\ \x5b\x31\x60\x4b\xb0\xe4\xec\x1a\xf4\x04\x50\x84\xf4\x75\x27\x96\ \x42\xd1\xc2\xdf\xa5\x4e\x42\x05\x41\xdf\x43\x67\xf9\x65\xce\xcf\ \xd1\x5e\x78\x1a\xef\xfc\xfd\x03\x49\x06\xc3\x49\x7e\xc9\x50\xcf\ \xf9\x54\x08\x46\x45\xfe\xf7\x17\xf9\x81\xce\xe1\x23\x08\x15\x80\ \x3c\xeb\x89\x12\x24\x89\x9a\x90\xef\xfb\x25\x88\x33\x96\xb3\x45\ \x04\xf8\xbd\xa6\x5b\xbb\xb0\xc1\x87\x39\x46\x90\x5d\x77\xe7\x1b\ \x60\x0e\xa2\xa3\x38\x8b\x54\xea\x43\xe2\x97\xad\xf6\xc0\x1e\x81\ \x01\x56\xdd\xae\xc6\xc3\x76\xce\x6f\xf0\x9d\xfd\xbf\x29\xa3\x15\ \xfc\x0a\x1a\x1f\xf3\x4f\xe6\x83\x7b\x50\x7b\x48\x44\x05\x40\xf8\ \x2f\xd7\x5b\x34\xc0\x64\xfa\xfb\x91\x4c\xa5\x6e\x08\x28\x71\x36\ \x42\x8b\xef\x9c\x7e\xc2\x11\xfb\xf4\x45\x5e\x9f\x43\x49\x9c\x0d\ \x92\xf8\x69\xb2\x23\xee\xcf\xcf\x73\x9b\xa5\x88\xb0\xa0\xc3\x70\ \x49\xa2\x7d\xdf\x73\x21\x4a\x92\x1c\x48\xab\xc6\xf1\xda\xeb\x02\ \xd4\xbe\x7f\x30\x4b\x12\x32\xed\x63\xca\x60\x95\x4d\x1a\xc6\xcc\ \x61\xa6\x0d\x1f\x52\x12\x6b\x44\x00\x70\x45\x77\xb1\x93\x21\x54\ \x78\x99\x42\x2d\x47\xf2\x94\x7b\x98\x42\x9e\x25\x0a\x81\xb6\x00\ \x8f\x10\x95\xde\x6a\x4d\xfe\xe5\xd5\x06\x4d\xfb\x56\xad\x64\xa0\ \xf9\x81\xb8\x1a\x4a\x66\xf5\x0b\x40\xce\xfc\x09\xb8\xa2\x9b\x59\ \x8a\xa1\x4c\x9d\x1b\xa3\x02\xe1\x3c\xd5\x10\x58\x38\x6f\x02\xe4\ \xdb\x4e\x02\xa1\x22\x8f\x65\x80\x96\xdd\xea\x3c\xe7\x47\xce\xfe\ \xf7\x94\xa9\xea\x8e\x06\xde\x8c\xff\x3f\xc1\xfa\x4b\x12\x98\x0f\ \x75\x97\x6a\x09\x42\x05\xa0\xd4\xd5\xe8\x61\x99\x87\x89\xaa\xd7\ \x74\xa1\x09\x88\x88\x7d\xa9\x06\x09\xb8\x91\xa7\xc0\x76\x32\x94\ \xb9\x1b\x43\xad\xff\x4b\xeb\xba\xe6\x71\x8e\x2e\x30\x82\x51\x53\ \xe7\x5f\x0f\x33\xa6\xbb\xa1\x2a\x6c\xc6\xa8\x61\xa6\x01\xfd\x0a\ \x1b\x17\xf7\x3e\xd3\x3f\x70\x82\x12\x5b\x0b\x57\x83\x91\x57\x6c\ \x21\x99\x31\x9b\x4f\x24\x3c\x66\xe9\xa1\xc8\x71\x0a\x1e\x23\x48\ \xe7\xf5\x1a\x46\x0c\x9e\x38\x19\xb2\x09\x01\xee\x1d\x28\x71\x32\ \x22\x1c\x5b\x94\x46\x56\x7d\xa0\xf9\x27\x94\x89\x9a\x8e\x08\xf4\ \x63\x3e\x67\x76\xfb\x3f\xd6\x4a\xd2\x51\x01\x20\x90\xd7\xbc\x4f\ \x0c\x30\x81\x88\x89\x44\x17\x23\x6c\xcd\xad\x24\xe5\x18\xd8\x8f\ \x19\xf8\xb8\x12\x97\xbe\xe7\x32\x42\xf4\xfd\x77\xa5\x97\x19\xd4\ \xaa\xe7\xfa\x45\x4c\x3d\xff\x0f\xed\x62\x01\xcd\x0f\xfc\x7b\xa6\ \x6c\xe8\xcc\x7c\xd0\x6b\x29\xd1\xd5\x24\x00\x14\xf5\x4c\x59\x2f\ \xa4\xc0\xeb\x9b\xff\x44\x19\xf7\xff\xdb\xbb\x07\x18\x6b\xae\x28\ \x80\xe3\xa7\xb6\x3d\x77\x6a\xdb\x9a\x37\xfb\x29\xa8\xe3\x2f\x4e\ \xc3\xda\x1d\xdc\xa9\x27\x79\x73\x6b\xc6\x1b\xa7\xf1\xc6\xb5\x6d\ \xdb\xf6\x63\x6d\xcf\x66\xb6\xc6\xdb\xc7\xc1\xff\x97\x9c\xd8\xc9\ \xfc\x93\x7b\x16\x27\xa7\x4e\xb3\xe3\x75\x23\xeb\xfc\xab\xb4\x32\ \xdf\x0f\xe9\xc3\x21\x00\xcc\x8f\x2d\xdf\xb9\xfa\x23\xdf\xd9\x50\ \x8a\x01\xa1\x55\xdf\x3e\x54\xc9\xf5\x03\x7e\x34\x04\x80\xb9\xbd\ \xe1\x4f\xed\x26\x28\xee\x7e\x40\xdb\xe6\x79\x3e\xfe\x79\x06\x80\ \x79\xb5\x24\xef\x7c\x1c\xbd\xf7\xf4\x0a\xda\x36\xa7\x68\x65\x3e\ \x21\x00\x04\xe0\x7f\xe6\xf3\x96\xef\xc6\xbc\xf3\xcb\xf9\x63\xc3\ \xf5\xb2\xfd\xc0\x0f\x04\x80\x00\xfc\xd3\x3b\xbf\x71\xca\xfe\x1b\ \x09\xca\x2d\xdc\xa4\xbe\x63\x68\x25\x37\x12\x80\x0c\x01\xb8\xb3\ \x71\xba\xbb\xbb\x54\x0b\xb4\x95\x1c\xa1\x95\x79\x8d\x00\x54\x36\ \x00\xef\xb4\x03\xf7\x48\x01\xfb\x81\x5f\xe7\x53\x02\x50\x99\x00\ \x7c\x91\xbe\xf3\xdf\x8c\x17\xad\x2c\x29\xc0\xb3\xe2\xf5\x4b\xbf\ \x1f\x20\x00\x3f\xa5\xef\xfc\x7f\x3d\xaf\x05\x04\x96\xd9\x4b\x5b\ \xe6\x6e\x02\x50\xba\x79\xa8\x73\x7a\xed\x00\xe9\x19\xd8\x0f\xd8\ \xe6\x0d\x02\x50\xf4\x71\xdf\x4d\xdf\xf9\x3f\x8b\x2c\x23\xf3\x01\ \xc4\x3b\xc7\x2b\x16\x76\x3f\x40\x00\xbe\x4c\xdf\xf9\xef\x9e\x76\ \xe0\x2a\x32\x08\x20\xdc\xf4\x22\x2b\x54\x66\x5a\x2b\xf3\x23\x01\ \xc8\xfd\xfc\xd4\xf6\x6b\x33\x5d\xed\x6e\x26\xc0\x30\x45\x9b\x5c\ \xb0\xb7\x56\xe6\x5e\x02\x90\xdb\x79\xa4\x71\xfa\x81\x8e\x00\x63\ \xf8\xfd\x81\x37\x09\x40\x6e\xe6\x3d\xde\xf9\x18\xab\xd3\xec\xcb\ \x57\x89\x6c\xa3\xb5\x32\x9f\x11\x80\x89\xcd\x97\x4d\xdf\xb9\xb0\ \x79\xfc\xa2\xd5\x05\x98\x84\xb3\xec\x44\x4d\xe0\xec\x39\x01\xf0\ \x9c\x6b\x3f\x8c\x0e\xd8\x42\xf2\x00\x88\x36\xbd\x60\x5f\xad\xcc\ \xfd\x04\x60\xe4\xf3\x58\xd3\x73\x5d\x01\xf2\x78\xf6\x3c\x52\xc9\ \xd2\x48\x99\xb7\x08\xc0\xd0\xe7\xfd\x96\xe7\x1e\xfd\x73\x2c\xcb\ \x4a\x9e\x01\xb1\x15\xaf\x9a\x9d\x35\xfb\x8a\x00\x0c\x3c\xdf\xa6\ \xe7\xb5\x5a\x61\x6d\x0d\x29\x12\x20\x54\x17\xd9\x03\xec\x07\x08\ \x80\xe7\x5c\xfb\x91\xbf\x60\x4b\x29\x32\x20\xb4\x93\xfd\xb4\x32\ \x0f\x12\x80\x5e\xc7\x79\xbc\xe9\x39\x0b\xa4\x4c\xc0\x7e\x60\xf6\ \xac\x99\x95\x7c\x48\x00\xfe\x75\x5a\x9c\xd1\x2e\x35\xf8\x1b\x5d\ \xb2\x5a\xb6\x1f\xf8\x9a\x00\xfc\xf9\x8c\x76\xe7\xa4\xfd\xd7\x14\ \xa0\x0a\xce\xb2\x92\x4d\xb3\xfd\x40\x75\x03\x90\xbd\xf3\x1b\x81\ \xb3\xb5\x00\x55\x14\xd9\xf5\x45\x5a\x99\x27\xab\x17\x00\xf7\x85\ \x66\xe0\x1e\x2c\xc0\x1c\xce\x9e\x9b\x8f\xca\x1e\x80\xa6\x5f\x6b\ \xff\xc3\x3b\x1f\x40\xbc\x41\xbc\x7a\xb6\x1f\xf8\xa6\x74\x01\xc8\ \xde\xf9\x5d\xbd\xf7\x5a\xf2\xef\x00\x44\xf6\x05\xdb\x68\x75\xfe\ \x4c\x89\x02\x70\x6b\xc7\x9b\xda\x49\x7a\x07\x20\x52\xf5\x25\x91\ \x32\x4f\x17\x38\x00\x2f\x36\x83\xda\xa1\x32\x28\x80\xfd\xc0\xf9\ \x8d\x02\x05\xa0\x33\xfb\xce\x8f\x17\x2d\x2f\x83\x03\x70\xea\x16\ \xf1\xda\xa1\x65\x2e\xcc\xf6\x03\x79\x0d\xc0\xf7\xad\xc0\x99\xfe\ \xc0\x5b\xb4\xbe\x8c\x04\xc0\x7d\xc3\xed\xb2\xfd\x40\xde\x02\x70\ \x6b\xd7\x77\x76\x91\xf1\x00\x38\x7b\x1e\x2a\xf3\x6c\x0e\x02\xf0\ \x72\x2b\x70\x0f\x97\xb1\x03\xd8\x0f\x2c\x1f\xd9\xe6\x68\xad\x4c\ \x73\x02\x01\xe8\xb6\x7d\x57\xff\x1c\xef\xbc\xa2\x4c\x0e\x80\x68\ \xb3\x0b\xd6\xc9\xf6\x03\xdf\x8e\x3c\x00\xd9\x3b\xff\x23\xdf\xd9\ \x50\xf2\x03\x40\x68\xd5\xb7\x0f\x55\x72\xfd\x08\x03\x70\x7b\xc3\ \x9f\xda\x4d\xf2\x0d\x60\x3f\xa0\xad\xf3\x9f\x1b\x5e\x00\x9c\x57\ \x9a\x81\xbb\x54\x80\x82\xe1\xec\xb9\x32\x1f\x0f\x10\x80\xcf\xd3\ \xf3\x5a\xaf\x9e\x74\xc8\x4a\x02\xa0\x78\x4e\xb3\xe3\x75\xe7\xce\ \x9e\xcf\x23\x00\x3f\xa6\x67\xb4\x1b\xa7\xec\xbf\x91\x00\x28\x3e\ \xad\x2e\xdc\x3d\x54\xe6\x9c\x1e\x02\x70\x4e\xe3\x74\x77\x77\x01\ \x00\x00\x00\x00\x00\x00\x00\x14\xdf\x2f\x7a\xfa\x1a\x6e\x6a\x11\ \x2b\x47\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x18\x56\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x80\x00\x00\x00\x80\x08\x06\x00\x00\x00\xc3\x3e\x61\xcb\ \x00\x00\x18\x1d\x49\x44\x41\x54\x78\x01\xed\x5d\x05\x54\x5b\xd9\ \xd6\xce\xd3\xff\xb9\xd7\x02\xa3\xcf\x5f\x95\x7a\x03\x9d\xa1\xe3\ \xee\x75\x37\xea\x58\x0c\xa9\xa4\xc5\xa1\xee\xd4\xdd\x85\xa9\xbb\ \x97\xba\x63\x21\xb8\x13\xc1\x1d\xd2\xfd\x9f\xec\x95\x75\xe9\x49\ \x20\x0d\x90\x5e\x72\xe1\xee\xb5\xf6\x48\xe4\x76\x35\xdf\x77\xce\ \xd6\xb3\x8f\x80\x17\x5e\x78\xe1\xa5\x7d\x89\xfc\xf5\x45\x5d\x4d\ \x5f\xd3\xc9\x9d\xf1\xb5\x36\x2c\xbc\xc8\x1c\xc2\x1c\x7d\x1c\x02\ \x77\xc8\x1d\x82\x92\x4c\xdf\xd3\x48\x9d\x93\x35\x62\xd1\xf1\x3c\ \xc9\x3b\x6f\x09\x78\x69\x5b\xa2\x10\x2a\x7e\xe3\xe3\x18\x24\x27\ \xc0\x97\x12\x05\xa2\xc9\x0d\x12\x40\xe2\x0c\x44\x2b\xd4\x12\x51\ \xa8\x46\xe6\xfc\x7b\x01\x2f\x5c\x17\xf8\x09\x59\xf1\x43\x7d\x1c\ \x82\xd2\x8c\xc0\xc3\xcb\x09\xc0\x68\xb6\x46\xec\xe2\x06\x0a\xc1\ \x4f\x05\xbc\x70\x4f\x64\xaf\x05\xf6\x95\x39\x04\xdd\xa0\x81\xb7\ \x96\x00\x94\xde\x53\x8b\x5d\x5c\x04\x5c\x11\x1e\xf8\x30\x21\x01\ \x3e\x92\x80\xac\x27\x0a\x2d\x27\x00\xea\x73\xad\xc4\xf9\x40\xa1\ \x58\xf4\x86\xc0\x3e\x85\x17\x2f\xc7\xa5\xbf\x36\xda\xf9\x12\x0a\ \xec\x96\x12\x80\xd6\x72\x83\x7f\xa0\x9e\xe9\xfa\x3b\x81\xfd\x08\ \x2f\x72\x61\xe0\x97\x04\xd4\x54\x1a\x64\x1b\x13\x80\xd6\x2c\xad\ \xd4\x65\x1c\x08\x04\x3f\x11\xf0\xd2\x7a\x22\x15\x06\xf5\x26\xab\ \xfe\x1a\x0d\x2e\x1b\x04\x60\xf4\xae\x4e\x22\x1a\xc4\x23\xc1\xb2\ \xf8\xbd\x11\xd4\xc5\x68\xe7\xeb\x88\x42\xeb\x10\x80\xf6\x0f\x0a\ \xe4\x2e\xaf\xf3\xc8\xbc\x62\x51\x74\x55\xfc\x52\xee\x18\xe4\x41\ \xb4\x98\x02\xb4\xd5\x08\x40\x69\xb9\x46\xe2\xa2\x48\x55\xb8\xfe\ \x8a\x47\xea\xd5\xd9\xf9\x64\x1a\x48\x3b\x21\x00\xa5\x2e\x99\xbc\ \x7f\x60\x43\xf1\x11\x86\x38\x91\x44\xce\x15\x1a\x40\x7b\x25\x00\ \xa5\xb7\x75\xde\xce\x03\x79\x04\x9b\x29\xbe\x0e\xc1\x7f\xf5\x11\ \x06\xaf\xa0\xec\x3c\xa7\x08\x80\xaa\xd7\x48\x44\x3b\xd4\x52\xd7\ \xce\x3c\xa2\x56\x8a\x5b\x9f\xc8\x5f\xa0\x9d\x77\x08\x2a\xa2\x40\ \xe3\x1c\x01\x28\x2d\xe3\xfd\x03\xeb\xed\x7c\x12\x05\x16\xe7\x09\ \x40\x69\xba\xc1\x3f\x10\xd0\xc2\x8b\xaf\x30\xf8\x3f\x04\x94\x53\ \x34\x48\x6d\x8b\x00\x94\x8a\x9d\x2f\xe5\x7b\xbb\xf4\x14\xf0\xe9\ \x5b\xc5\x5f\x8c\x76\xbe\x96\x28\xb4\x13\x02\x50\xfe\x41\xbe\xc7\ \x80\x4e\xed\xd2\xce\x93\x0c\x9e\x1b\x01\x42\x43\x03\xd3\x5e\x08\ \x40\x69\xa1\x56\xe2\x22\x57\xcd\xf9\xf4\xff\xda\x49\x58\x17\xf0\ \x81\x5c\x18\x1c\xc3\x80\xc1\x13\xc0\xa8\xa2\x44\xb5\xd4\x65\x68\ \xdb\x2d\xd3\x0a\x03\xfe\x4d\x7e\xf8\x13\x14\x10\x3c\x01\xcc\x55\ \x2a\xba\x90\xe7\x35\xa8\x7b\xdb\x59\xf1\xaf\x87\xfc\x59\x26\x0c\ \x0a\x25\x3f\x7a\x35\x51\xe0\x09\x60\x95\xd6\x12\x22\x44\xe6\xfa\ \x0e\xee\xc0\xdd\xbc\xbd\x40\xf1\x73\xa3\x9d\x57\x9b\x01\xd0\x96\ \x09\x20\x7f\x07\x0a\xb6\x7b\x41\xd9\xed\x2d\x50\x11\x7b\x18\xca\ \x6e\x6e\x04\x5d\xe4\x34\xb2\xb2\x5d\x9a\xf3\xbc\x02\x83\x7f\x00\ \x8a\xae\xbf\xe4\x58\xf7\x6d\xd0\xfb\x72\x87\xe0\xa7\x16\x00\x68\ \x9b\x04\x58\xf8\x39\x54\xa5\x9e\x05\x78\xae\x32\xd3\xca\xf8\xa3\ \xa0\x99\xfb\x61\x73\x9f\xad\x24\x04\xfa\xc2\xfe\xeb\xf3\x8e\x01\ \xff\x24\xc0\x1f\x60\x01\x40\xfb\x23\x80\x6c\x30\x0d\x7e\x03\x5a\ \x11\x73\xb0\xa5\x3b\xcc\x79\xbb\x3c\xbf\xe0\xf9\xa6\xe2\x4f\x46\ \x3b\x5f\x45\x14\xda\x23\x01\x0a\xb6\x79\x52\x60\xd7\x15\x3f\x80\ \xd2\x27\x87\x41\x5f\xf1\x8c\x7a\x5d\xb7\x76\x72\x4b\x49\x50\x63\ \xf0\x0f\x72\xc4\xae\x7f\xb3\x07\x3b\xff\x53\xf2\x63\x8e\x23\xab\ \x3e\x9f\x05\xd0\xec\x9a\x00\x65\xf7\x76\x30\x20\xd7\x16\xde\x83\ \xf8\x69\x83\xe1\xd9\xb0\x6e\xa0\x74\xff\x10\x9e\x57\xc7\x32\xef\ \x95\x5c\x5a\x69\x2b\x47\x51\x47\xba\x95\x3d\x40\xe1\xfa\xf3\xd6\ \xf1\xee\x1d\x02\xde\x23\x65\xda\x27\x2c\x80\xc5\x09\x02\x54\x24\ \x44\x31\x20\x17\xdf\xde\x69\x00\x9f\xd1\x8a\xa4\xd3\xcc\x7b\x65\ \x77\xb7\xdb\x3a\x62\x88\x57\x4b\x9d\x3f\x63\x0f\x78\xc7\x90\x7f\ \x98\xdb\x79\x9e\x00\xc5\xd7\x37\xbe\xb0\xfd\x3f\x84\x24\xaf\x0f\ \x40\x39\xb5\x0f\xa4\xf8\x7f\x0d\xcf\x6b\xe2\x99\xf7\x0a\x4f\x2f\ \xb3\x25\xf8\xb4\x7f\x20\x1e\xfc\xbf\x57\x06\xbc\xa4\x53\xc4\x6f\ \x49\x58\xa7\x68\xd8\xce\xf3\x04\xc8\x5a\x31\x8d\xb2\xf5\xfa\xca\ \x18\xa8\x4a\x3b\x0b\xcf\x6b\xe3\xa9\xd7\x33\x03\x87\xbf\xca\xfc\ \x41\x0d\x09\x1b\x57\x14\xc8\xfb\xfc\xd1\xc6\x65\xda\xd0\xd7\x2d\ \xdb\x79\x9e\x00\x49\x73\x86\x80\xbe\x2a\xd6\x62\x14\x50\x74\x7d\ \x0b\x24\x4d\x72\x62\x23\x91\x94\x6f\xd3\x26\x55\xa9\x43\x50\x2f\ \x7b\x06\x34\x74\xe0\x32\x38\xbe\xe0\x20\x3c\x3d\x76\x09\x32\x1e\ \xdc\x81\xa2\xac\x27\x50\x51\x14\x0b\x25\xf9\xcf\x40\x93\xf4\x10\ \x54\xd7\x6e\x40\xf4\x96\xd3\x70\x58\xb6\x0f\x96\x7f\xb0\x06\x7c\ \x5f\x0f\xb1\x29\x01\xb4\xc1\xdf\x40\x75\xf6\x55\xcb\xe0\xdf\xd8\ \x02\xb1\x63\x7a\x43\xfa\xb4\x7e\x6c\x10\x00\xd4\x52\x51\xaf\x36\ \x4f\x80\xc8\xef\x37\x80\xea\xea\x0d\x78\xae\xc7\x1f\xd9\x6a\x2d\ \x4c\x7f\x5c\x23\x30\x91\x4a\xe5\x8f\x15\x95\x89\xc7\xa0\xec\xd6\ \x66\x28\x8a\x5a\x04\xba\xd5\x13\x40\x23\x7f\xd7\x32\xf0\x3e\xef\ \x42\xd1\x91\x05\x64\xbb\xa7\x43\xbd\xca\xb4\x0b\xa0\x39\xb6\x98\ \x80\xbe\x15\xff\x9d\x3c\x6f\x28\x3a\x83\x31\xc3\xbb\x43\xbe\x97\ \x88\x27\x40\x4b\x75\x61\xd7\x08\x78\x74\xe4\x22\x0d\x6c\x13\x94\ \xec\x0c\x75\x02\x13\xa9\xd5\xdd\xaa\x35\xfd\x9c\xbe\x2a\x06\x2a\ \x9e\xee\x07\xdd\x56\x77\x04\x1b\x7f\xdc\x79\x1f\x82\x6e\xfd\x54\ \x28\x39\xbf\x9c\x84\x7b\x77\x4d\x9f\x8d\xe0\xc7\x4f\x11\x21\xe0\ \x8c\x22\xf8\xdd\x20\x73\xd6\x80\xe6\x80\xc9\x13\x60\xee\xdf\xc3\ \x99\xff\x36\x6c\xe1\xba\xb4\xc7\x0d\xaf\xec\xbc\x47\x70\xf3\xd0\ \x31\x38\xb9\x66\x37\xec\x0b\xdc\x04\x87\xc3\xb7\xc1\x99\x0d\x7b\ \xe1\xd9\xe5\xf3\x50\xaa\x7d\x5a\xff\xb9\xdc\xc7\xcf\x05\x26\x52\ \xa3\x89\xae\xb3\x44\x1a\x7d\xc5\x53\xd0\x97\x35\xf8\xe7\x52\xe1\ \x5f\xdc\xc4\x81\x66\xe0\x27\x8c\xe9\x09\x39\x73\xac\x06\x1f\xeb\ \x06\x05\xbb\xa5\x50\x99\x10\x85\x24\xab\xce\xb9\x0c\xa5\x37\x36\ \x80\x36\xec\xfb\xf6\x45\x00\xc5\xbf\x96\xc2\xe6\xcf\xf7\xc3\xc9\ \x19\x57\x61\xcd\x90\x1d\xb0\xf2\xd3\xb5\x50\x5e\x40\x3b\x5a\xcf\ \xf5\x89\x10\x7d\xe4\x18\xf8\x0e\xf1\x83\x51\x1d\xa6\xc0\xc8\xbf\ \x4d\x6e\x54\xe7\x38\x79\xc3\xd2\xf1\xe1\xb0\x3f\x68\x8b\x5e\x60\ \x22\xea\x43\x41\x7a\x03\x80\x55\xe9\x17\x89\xe7\xae\x6c\xd2\x8e\ \x52\x93\x7f\x13\x32\x57\xcf\x61\x00\x4f\x9d\xda\x17\xf2\x3c\x07\ \x41\xf6\x9c\x81\x40\x4a\xbb\x56\x00\x46\x99\x15\x4c\x19\x37\xf0\ \xe7\x60\x38\x59\xb0\x4b\xd2\xf6\x09\xb0\xe8\xbf\xcb\x61\xeb\x57\ \x87\xe0\xe4\xcc\x6b\x70\x6a\x16\x2a\x5c\x59\x70\x13\x2a\x8b\xe8\ \x90\x2a\x23\xf6\x26\xf8\xbd\xef\xcf\x00\x3c\xfa\x6f\x53\x61\x52\ \xc7\x39\x30\xb5\x93\x27\x4c\xeb\x2c\x06\xb7\x4e\xde\x30\x85\xfc\ \xf7\x84\x8e\xb3\x60\x14\x79\xaf\xfe\x73\x6e\x66\x26\x20\x6e\x54\ \x8f\x1a\x23\x80\xe8\xac\xa5\x06\x8e\x01\xed\x89\xa5\x64\x05\xde\ \x6f\x14\x78\x92\xe0\x21\xc0\xcf\x86\x98\xd1\x4e\x0c\xf8\x69\x6e\ \x7d\x5b\xb4\x75\x97\x5e\x5d\x6b\x89\x6c\x84\x9c\x09\xa0\x5b\x3a\ \xaa\x6d\x12\x20\xa0\xeb\x72\xd8\xfe\xcd\x61\x38\x89\xa0\xd7\xeb\ \x59\xaf\xeb\x50\x9a\x4d\x83\xff\xe0\xf4\x69\x98\xf0\xda\x74\x04\ \xd4\x00\xba\x67\x97\xb9\x20\x6b\xfc\xd9\xf8\x9e\x57\x97\x05\x30\ \xbb\xb3\x0f\x4c\xed\xe8\x5d\x68\x81\x00\x94\xc6\x8c\xea\x05\x19\ \xcb\xa6\xe1\xce\x60\xb6\xf2\x35\xb7\x41\x25\xfe\x0c\x3f\x17\x37\ \xaa\x3b\x64\xcf\x1e\xd0\xe2\x6a\xe2\xf3\xba\xfa\xdd\xa7\xae\xf4\ \x31\xe8\xce\xac\x84\xd2\x87\xfb\xa9\x3f\xb7\xfc\xd1\xee\xb6\x45\ \x80\xe0\x1e\xab\x61\xe7\xf7\x51\x08\x76\x43\x9a\x76\x99\xb6\xbd\ \xca\x1b\xd7\x60\xac\xd0\x0d\xc6\x76\x98\x8e\xc0\xdb\x22\x0f\x90\ \xeb\x39\x30\xc3\xb0\x65\x67\x4c\xef\x07\xc9\x93\xfb\x40\xfc\xe8\ \x1e\x34\x11\x46\xf4\x80\xac\xb5\xee\x04\x94\x47\x74\xd1\x87\xfc\ \x7f\x76\xf8\x18\x50\x7b\xb7\xdc\x71\x2b\xd8\x29\xa6\x9e\x9d\xb2\ \x60\x18\xf3\xe7\x17\x5e\xdb\xc2\xbc\x4e\x7c\x91\x36\x41\x00\x04\ \x7e\xcf\xb0\x63\x66\x80\xdf\x0c\xbf\x0d\x09\x3f\x3e\x84\xd4\x0b\ \x8f\x51\xe1\x85\x30\xaf\x2c\x37\x1e\xce\x78\x5e\x05\xdf\x1e\x81\ \x20\x75\x08\x78\xa5\x89\xa0\x3c\x4f\x11\xda\x73\xb2\x3b\xd4\x3b\ \x74\x33\x5c\xa1\x2c\xf6\x47\xb3\x68\x41\xbb\x64\x44\x8b\x09\x50\ \x74\xd0\x9f\x7a\x6e\xec\xb8\xbe\xcc\x9f\x9b\xbb\x6b\x1e\xf3\x3a\ \xd9\x25\xd0\x51\xe4\x26\x01\x1c\x83\x21\xbc\xcf\x7a\xd8\x3f\xfa\ \x94\x19\xf0\x57\x17\x46\x43\x81\x2a\xd6\x82\xfd\x53\xc1\xcd\xb0\ \x5b\xf8\xd9\x13\xd3\x2f\x23\x81\xd8\xc8\x04\xaa\x89\xa6\x93\x9d\ \x21\x76\x24\x12\x01\x62\xc8\xbf\x0b\x2f\x47\xd2\x3b\x41\xe1\x3d\ \xdc\xc2\x5b\x14\xba\xad\x9e\x4c\x3d\x53\x7b\x7c\x31\xa8\x66\x0f\ \x84\x14\xdf\x4f\x48\x24\x70\x8d\x79\x9d\x24\x9d\xb8\xb7\x03\xf8\ \xbc\x16\x0c\x4b\x07\x6e\x84\x43\x63\xcf\x34\xb8\xcd\x5f\x0f\x8e\ \x86\x9a\xb2\x44\xcb\x0e\x50\x9d\x0a\xee\xad\xbb\xc3\x7c\xe7\xc7\ \xa9\x17\xd1\x61\x64\x2b\x15\xac\x16\x8b\x48\x2a\xb7\x37\xe0\xaa\ \x1c\x6e\x46\x02\xcc\xfd\x97\x9c\x5b\x4a\x72\x04\xcb\x88\xb7\x2e\ \x06\x8d\xdf\x90\x26\x11\x20\x63\xf6\x20\xa8\xd5\xdd\xa1\xff\xce\ \x64\x77\x81\x3a\x3a\x2a\xc9\x3f\xb0\x88\x7b\x04\xd8\xfc\xc5\x81\ \x46\x6d\xfc\x19\xf7\x6b\x50\x96\x47\x3b\x79\x7a\x12\x8a\x69\xd2\ \xef\x43\x55\x79\x1c\xbd\xd2\x2a\x13\xe1\xa2\xdf\x0d\xe6\xbb\x51\ \x53\x2e\x92\x70\x71\x09\xab\xb5\x80\xcc\x19\xfd\x09\x01\xba\x93\ \x9d\xa0\x27\x94\xc5\x44\xd1\x44\xa5\x9b\x42\xb0\x1f\xd0\x6a\x02\ \xcc\xe8\x07\x69\xe1\x93\x28\x47\xd0\x54\x2b\x53\xcf\x43\xdc\xf8\ \x7e\xdc\x23\xc0\x81\x31\xa7\x1a\x25\xc0\x93\x9d\xf7\xa8\xbf\xe4\ \xa3\xf3\x67\x60\x46\x57\x77\xf4\xf0\x27\xbf\x3d\x13\x4e\x47\xee\ \xa3\xde\x4f\xbb\xf4\x84\xfa\xfe\x91\x89\xe7\x60\xfe\x3f\x22\x58\ \x2d\x06\x11\x8f\x1f\x53\xbb\xc9\xf3\x87\x35\x00\x14\x1d\xbb\x6b\ \x57\x8c\xb5\xea\x99\x79\x1e\x98\x44\x82\xf2\xf8\x63\x0d\x3d\x0b\ \xa3\x81\xf8\xa9\x2e\xa0\x1c\xef\xc4\x2d\x02\xf8\x12\xbb\x7f\x62\ \xc6\x95\x46\x09\x90\xf7\xf0\x19\x95\xcd\x23\xe1\x1d\x13\xd7\x8f\ \x32\xc6\xee\xcf\xae\x9c\xaf\x0f\x83\xf2\xe3\xcd\x9e\x71\x68\xdc\ \x59\x98\xfb\x76\x38\x0b\x04\xa0\x49\x50\x72\x6f\x8f\x59\x19\xb8\ \xae\xec\x09\x6d\x1a\x52\xcf\x58\xfd\x4c\xd5\x84\x3e\xa0\x2f\x7f\ \x5a\xef\xf4\x3e\x3d\x0c\x39\x5b\xe4\xa0\x92\x7d\xc9\x38\x84\x59\ \x73\x06\x70\x8b\x00\x01\xc4\x4e\xd3\x80\xd1\x5a\x9c\x51\xbf\xfd\ \x5f\xdf\x1f\x45\x12\x37\xb3\x41\x2c\x5c\x88\xdf\x95\x38\x2c\x22\ \x71\xbb\x27\x6c\xf5\x61\x12\x24\x18\x15\x9c\x76\x37\x7b\x0e\x3a\ \x96\xfe\x6f\x84\xb2\x57\x0e\xf6\x7f\x9f\xda\xae\x4b\xee\xef\x23\ \x49\xa1\xde\xe8\x24\x16\x5c\x5c\x4f\x3b\x74\x81\x5f\x5b\xf7\xcc\ \xd0\xef\xa9\xef\xa5\x2f\x9e\x42\x85\xa3\xa9\x54\xa2\x89\x23\x04\ \x58\x32\x70\x93\x45\x02\x14\xa5\xd5\xdb\xf9\x7b\xc7\x4e\x35\x10\ \xde\x05\xc2\x81\x85\x5b\x28\x67\xb0\xb1\x67\xed\x1d\x71\x9c\x94\ \x7b\x43\x59\x21\x80\x36\xf4\x3b\xda\x34\x85\x8e\x67\x80\x4a\x92\ \x7f\x45\x37\x83\xae\x9e\x68\x5d\x2e\x60\xeb\x1c\x3a\xe7\x31\xe7\ \x03\x2c\x24\x29\xc7\xf5\x82\xec\x59\xfd\xb9\x59\x0b\x58\xff\xd1\ \x2e\x8b\x04\xc8\x8c\xae\xdf\xf2\x6a\xab\x12\x20\x6c\x10\xed\xd9\ \xcf\xfb\x57\x18\xe4\x27\xdd\xaf\xb7\x85\x39\xf1\x16\x9f\xb7\x64\ \xc0\x06\x76\x08\xb0\xe0\x63\x0a\x2c\x4d\x54\x04\xc4\x4f\x70\x82\ \x78\x12\x29\xe4\xee\x9c\x4b\xef\x00\xe1\xc3\xac\x23\xc0\x5e\x19\ \xbd\x03\xcc\x12\x71\xbf\x1a\xb8\x73\xe8\x51\x8b\x80\xdd\x59\x79\ \x9b\xae\xea\x65\x3e\x81\xbd\xb3\x76\x22\x11\x36\x8d\xda\x04\x19\ \x0f\xe9\xd0\x48\x79\xec\xa1\xc5\xe7\x2d\xfa\xcf\x32\x36\x08\x80\ \x89\xa2\x8a\xa4\x33\x2f\x98\xa6\x44\x28\x8f\x3b\x02\x65\x8f\xf7\ \x01\xe8\x95\x54\xca\x38\xc7\x43\x64\x1d\x01\x0e\xce\x7f\xe1\x79\ \x4a\xc8\x76\x1f\xc4\x7d\x02\x44\x4d\xbd\x80\xc0\x58\xd2\xfc\xa7\ \x31\x56\x55\xde\x2a\x34\x09\x70\x4e\x7c\xbd\xb1\xe7\x60\x82\xc8\ \xdd\x61\x2e\x1b\x04\x40\xaf\x3d\x35\x78\x1c\x01\xca\x72\xfe\x22\ \x73\x8d\xbb\xd5\xe5\xe0\x82\xa3\x01\xf5\xa6\xae\x3a\x0e\xd4\xde\ \x1c\x27\xc0\xfc\x7f\x2c\x66\xc0\xb1\xa4\xe7\x25\x37\xa0\x28\x35\ \xce\xe2\x0f\x59\x55\x94\x00\xd7\x02\xa3\x2d\x3e\x67\xd7\xa8\x28\ \xf0\x12\xce\x67\xc7\x09\x14\x8b\x30\x14\xcc\xde\x2c\x6b\xb8\x84\ \xac\x57\x62\xd2\x86\xf8\x04\x56\x77\x02\x69\xf7\xf8\xd1\x66\xc5\ \xe7\x5d\x4e\x13\x00\xd3\xbe\x08\x8e\x15\x7a\xc6\xf3\x3a\xa8\x4e\ \x3e\x22\x19\x41\xa5\x49\x52\x48\x05\x59\xc4\x4f\xb8\xe8\x73\xe3\ \xa5\xcf\x58\xf5\xd9\x16\x90\x39\x04\xb2\xd5\x14\xca\x64\x06\x13\ \xbd\x3e\x01\xdd\xe9\x15\x50\xae\x3c\x89\x65\xe2\x82\x0b\xeb\x20\ \xc9\xf7\x5b\xc0\xf7\xc6\xf7\xb2\xfa\x79\xf9\x1b\x69\x27\x50\xab\ \xf8\x94\xdb\x04\x58\x3d\x64\x3b\x02\xd3\x14\x3d\x3d\xe7\x1a\x44\ \x47\xdc\x82\x07\x1b\xee\xc2\xed\xe5\xb7\xe1\xac\xf7\x75\xab\xbf\ \x1b\xe8\xbc\x8c\xcd\xae\x60\x5c\xd9\xa4\x14\x8c\x40\x37\xa4\xb1\ \x23\xbb\xa3\xaf\x60\xed\xf3\x72\x96\x4d\xa2\x09\xb0\x7c\x0c\xb7\ \x09\xb0\xf5\x9b\x83\x08\x0c\x5b\xea\xd3\x35\x90\xf5\xb6\xf0\x3c\ \x2f\x11\xae\x72\xb3\x56\xb0\xb1\xbd\xd0\x4f\x68\xd2\xf9\x02\xd9\ \x47\x14\x01\x0a\x76\xcb\xb8\x4d\x80\x83\xe3\x4f\xb3\x06\xfe\xc9\ \x59\x57\xc1\xfd\x35\xff\x56\x3b\x1e\x9e\xeb\x39\x08\xeb\x04\x19\ \x44\x73\x3c\x9a\xe7\xbc\x65\x92\x58\xbf\xb6\xe0\x6e\xbd\xf9\x2b\ \x7f\x02\xd5\x19\x17\xa0\xf4\xda\x7a\xd0\x86\x7c\xc7\x2d\x02\xf8\ \x91\xac\xdc\xa9\x99\x57\x59\x23\xc0\x81\x89\xa7\xc0\xa3\xcb\x3c\ \x4e\x4f\x08\xc9\xf5\x72\x81\xaa\x2c\xea\x8c\x01\x15\x15\x90\xe1\ \x13\xdc\x21\x40\x50\x8f\x55\xac\x6e\xff\x1b\xbe\xd9\x8d\x59\xc4\ \xe6\x10\xa0\x3a\xf3\x62\x95\xa1\x9c\x5b\x7a\x79\x15\x68\x57\x8e\ \x6b\x35\x02\x94\x5c\x5a\xfd\x92\x9e\xc0\x78\xd0\x2e\x19\xce\x0d\ \x02\x2c\x1f\xbc\x99\x55\x02\x84\xb9\xae\x6e\x6e\x31\xc8\xec\x5c\ \x40\x65\xd2\x09\xd0\x46\x0c\x63\x13\x7c\xac\x17\x3c\xaf\x66\x42\ \x61\x2c\x2a\xe9\xce\xad\x81\xd2\xa7\x87\xe9\x9e\xc0\x07\xbb\xb8\ \x41\x80\x0d\x9f\xef\x65\x95\x00\xf3\x9c\x42\x9b\x43\x00\xfa\x5c\ \x00\xdd\xea\x85\x83\x1f\x5e\x21\xe8\xe8\xe5\x97\x5e\x5f\x0f\x35\ \xf9\xd7\xcd\x56\x7b\xca\xc2\x91\x8c\x43\x49\x4e\x19\x71\xaf\x27\ \x70\xcf\xa8\xe3\xac\x12\xc0\xfb\xed\x05\xcd\x26\x40\xde\x9e\x79\ \x7a\x43\xbd\xdd\xb8\x02\xa9\xf4\x6e\xe1\x1e\x99\xed\x81\x0f\xfe\ \x16\xca\x9f\xec\xb3\xb8\xdd\xc7\x8e\xed\xd3\x60\x4f\x20\x70\xa1\ \x27\x90\x1c\x25\x87\xe3\xd3\x2f\xb1\x06\xfe\x8f\x6e\x17\xc0\xbd\ \x8b\x7f\x33\x08\x40\xb7\x85\x27\x4c\x7f\x07\x0a\xaf\x6c\x34\x69\ \x45\x53\x92\x9d\xc0\xc3\x36\xe0\xfb\xbe\x47\xda\xc6\x96\x30\x33\ \x03\x2c\xa9\x26\x2a\x14\x12\x67\x0e\x80\x64\xd9\x87\xc4\x31\xbc\ \xc2\xbc\x4e\xfa\x03\xed\x7f\x07\x58\xf8\x9f\x65\xac\xae\xfe\xad\ \xc3\x0e\x81\x58\xa8\x68\x31\x01\x8c\x0a\x79\xbb\xe7\x99\x1d\xc8\ \x68\x01\x09\xb0\x74\x5c\x7c\x2a\x04\xea\x8a\xee\x35\xd8\x35\x44\ \x4e\x25\x61\x4a\xb9\x46\x1d\x6d\x66\x86\x4c\x5b\xc4\xf2\x0f\x06\ \xd9\x3f\x01\x96\x0c\xd8\xc8\x2a\x01\x96\x7d\xb4\xa1\x45\x2d\x61\ \xf9\x5e\x83\xd2\x0c\x1d\x3e\x49\x13\x49\x43\x87\x91\x04\xf9\x07\ \x82\xcc\x7b\xf2\x54\x27\xf0\x9c\xde\x0b\x8a\x27\x8a\xcb\x1f\xee\ \x36\x38\x67\xf8\xff\x25\x17\x96\x93\xb8\x7d\x1d\x94\xdf\xdf\x81\ \xe7\xfb\xea\x4a\x1e\x36\x7e\x9e\x30\x7a\x3b\x24\xcc\x7a\x8f\x21\ \xde\x4b\x7a\x02\xf1\x80\x4a\xdc\x84\xfe\xf6\x4f\x80\x75\x1f\xee\ \x64\x95\x00\x8b\x06\x2c\xb6\x59\x4f\x60\x8e\xfb\x40\xa6\x05\x5c\ \x7b\x6a\x05\x0d\x82\x6d\x14\x63\xfd\xd4\x45\xa3\x4c\x4e\x10\x63\ \x4a\x19\xbb\x80\x6a\x8b\x1e\x98\x7c\x07\xdb\xc3\x70\xf8\x54\xe2\ \xb8\x5e\xf6\x4f\x80\xed\x3f\x1c\x61\x95\x00\xb2\x7f\x2f\xb4\x69\ \x53\x28\x39\xdc\xc9\x90\x20\x6f\xcf\x7c\xda\x39\x6c\xbe\x62\x57\ \x2f\x9e\x27\x1c\x55\x9f\x3a\x56\x4d\x74\xc2\x83\xa4\x6a\xb1\xa1\ \x27\xd0\x89\x39\x9f\x98\xb1\xd4\x0d\xab\x89\xb9\x3b\xfc\x20\xc9\ \xef\xdb\xfa\x9e\xc0\xd9\x1c\xe8\x09\x3c\x3a\xf9\x3c\x6b\xe0\x9f\ \x98\x41\x52\xc0\x42\x5f\x5b\x77\x05\x63\x3a\x37\x76\x04\xae\x4a\ \xdc\xa6\x0d\x26\xa1\xe4\xc1\x3e\x6c\x02\x69\x4c\x2b\x53\xce\x41\ \xad\xee\x6e\x63\x3d\x02\xf8\x99\x84\x19\xef\xe2\x33\xe3\x47\x53\ \xc7\xc6\x99\xe2\x52\xfc\x98\x9e\x8d\x16\x97\x52\xa7\x72\xa0\x27\ \x70\xde\xdb\x11\xac\xae\xfe\x3d\x63\x8e\x81\x57\x97\xf9\xb6\x25\ \x00\x65\x0e\x90\x04\x4d\xd2\xb8\x89\x03\x20\x35\x60\x14\x26\x72\ \x4c\x3c\x7e\xcc\xf5\x67\x87\x8e\x04\x75\x63\x15\x46\x6f\x67\x48\ \x99\xd2\xb7\xfe\x24\x92\x71\xc6\x40\x16\x57\x7a\x02\xc3\x9c\xd6\ \xb2\x4a\x80\x35\x5f\x6c\x07\xa9\x43\xa0\xad\x09\x40\x99\x03\xd5\ \x04\xac\xfb\x37\x47\xb1\xa5\xbb\x2a\xe3\x52\x73\xf2\xfa\x78\x1a\ \x49\xcd\xb5\x6a\xe0\x2a\xd7\x6d\xac\x12\x20\xc4\x65\x05\x2b\x27\ \x83\xf2\xbd\x45\x68\x7f\x33\x67\xf6\x6f\x44\xb1\x12\x88\xad\xdb\ \x49\x13\x9d\xa8\x13\xc6\x86\xa4\x4e\xc9\xfd\xbd\xb4\x49\x20\xde\ \x7e\xe1\x5e\x79\xdb\x1b\x11\xb3\xf9\xeb\xfd\xac\x12\xc0\xaf\x7b\ \xa0\xdd\xde\x17\x90\xeb\x31\xc8\x70\x92\x87\x39\x62\xae\x3b\xb3\ \xca\x34\xd3\x88\x83\xa6\xda\x14\x01\xf6\x8f\x3d\xc9\x2a\x01\x3c\ \xdf\xf0\xb7\xfb\x0b\x23\xd2\xa7\xf5\x65\xf2\x0b\x79\xfb\x16\x22\ \xf0\x14\x09\x0e\xcf\x6b\x1b\x04\x20\x33\xf8\x70\xa6\x0f\x5b\xe0\ \x1f\x9a\x74\x86\xf4\x00\xcc\xe5\xc2\x8d\x21\x68\x3e\x62\x8c\x51\ \x45\xf6\x46\xb1\x09\x09\x94\x58\x78\xe2\x3c\x01\x02\xbb\xae\x60\ \x75\xf5\x6f\xfc\x6e\x0f\x48\x84\x0b\x5b\x8f\x00\x8b\xbe\x84\xc2\ \xfd\xbe\x50\x7c\x32\x14\x0a\x77\xcb\xf0\xb0\x88\xa5\xcf\x63\x54\ \x61\x24\x41\xce\x56\xb9\x69\xad\x1f\x47\xd0\x71\x9a\x00\xcb\x44\ \xec\xf6\x00\x44\x0c\x59\xdb\x3a\xa3\x62\xe5\xef\xe2\xe8\x77\x93\ \xb4\x2d\x7a\xf7\xc5\xa7\xc2\x41\x23\x1d\x0c\x96\xa2\x8a\xb8\xd1\ \x18\xeb\x63\xa2\x87\xca\xfd\x97\x3f\x06\x6d\xc4\x50\xee\x12\x20\ \xf2\xd3\x3d\xac\x12\x60\x7e\x9f\xb0\xd6\x20\x00\xe6\xff\x2d\x0e\ \xa3\x3c\xbf\xec\x65\x8d\xa4\xcc\xe8\x19\xf5\xd1\x70\x7a\xf8\x54\ \xce\x15\xac\x1a\x72\x92\x00\xbb\x46\x44\xb1\x4a\x00\xc9\x3f\x17\ \xb0\x4e\x00\xdd\xba\xa9\x0d\xf4\xeb\xc5\x9a\x85\x78\xcc\x60\x47\ \x0b\x3b\x01\x92\x80\xd4\x00\x0a\xaf\x51\x84\xc2\xcb\xa6\x38\x47\ \x00\x1f\xa2\xc7\xdc\x2e\xb2\x06\xfe\x31\xd2\x6f\x30\x47\xe8\xcf\ \x2e\x01\xe8\xd5\x8f\x13\xc2\x49\x51\x07\x47\xc6\xa4\x2f\x75\xa3\ \x1c\x3b\x62\x0a\xac\x09\x13\x31\xd3\x48\x72\xff\xd4\xc5\x12\xf8\ \xfd\xe3\x41\x18\x22\x92\xa9\xa1\xd8\x11\x6c\xef\x04\xc0\xc9\x9e\ \x6c\xae\xfe\x1d\x23\x0e\x83\xb7\x90\xfd\x1d\xa0\x32\xf1\x38\x35\ \x13\xe0\x85\xac\x1f\xd6\x02\x98\x55\x7c\x67\xab\x75\xb9\x02\x74\ \x0c\xbb\xe1\x51\x70\x7d\x65\x23\x67\x24\xf5\x4a\x2c\x39\x6b\xe6\ \x7f\x64\xbf\x04\x88\xe8\xb7\x9e\x55\x02\x2c\xff\x64\x33\x0e\x80\ \x64\x7d\x07\x78\xb8\x8f\xea\xce\x89\x1f\xdf\x07\x8f\x85\x27\xb8\ \x89\xa8\x39\x82\xc5\x97\xd6\x34\x6d\xe4\x0c\xed\x0f\x34\xa8\xd5\ \x19\xe7\x89\x7f\x30\xc4\x3e\x09\xb0\xf6\x83\x1d\xac\x12\x20\x60\ \xd0\xd2\x56\xb9\x30\x22\x7f\xb7\x3f\x5d\xdb\x27\xa0\x14\x5e\x58\ \x69\xd6\xd8\x99\xb3\x7a\x46\x93\xb6\xed\xb4\xe9\x03\xa0\x3a\xef\ \xa6\x59\xa6\xd0\xac\x89\xe4\x44\xb0\x7d\x12\x60\xdb\xf7\x87\x58\ \x25\x80\xfc\x7f\x01\xad\x42\x00\x95\xdb\x20\x3c\xf3\x6f\xb1\xee\ \x9f\x72\x16\x92\x27\x37\x6d\x6e\xb0\x76\x2d\x35\x27\x10\x2b\x89\ \xb1\xe3\xfb\x81\x4a\xfa\x05\x54\xe7\x47\xd7\x47\x09\xb9\x76\x3a\ \x27\xf0\xf0\xc4\xb3\x6c\x81\x8f\x27\x8e\xdc\x1d\xfd\x5a\x83\x00\ \xd8\x36\xa6\x92\x7d\x45\x75\xed\x50\x3b\x42\xe6\x15\x50\xce\x1a\ \x82\xb5\xfb\x16\x4d\x0a\x1d\xdf\x9f\xf1\x2d\x48\x53\x8a\x7d\x4f\ \x0a\xf5\x7f\x33\x0c\xcf\xe6\xb1\x45\x80\xfd\xe3\x4e\x80\xa7\x70\ \x1e\x7b\x04\xa0\xed\x35\x7a\xed\xd4\x19\x3e\xe2\xbc\x95\x27\x9c\ \x20\xed\xdb\x73\x99\x11\xaf\xb9\x4d\x3c\x1f\x58\xb0\x83\x9e\x15\ \x9c\xec\xfb\x2d\xf1\x2f\x9c\x20\x8e\xf4\x02\x14\x5c\x8a\xa4\xce\ \x0b\xda\xdd\x0e\x10\xd2\x73\x8d\xb5\xe0\x61\xbb\xf8\xe6\x1f\xf6\ \x43\xf0\x3b\x2b\xc0\xf3\x7f\xfe\xb0\xfe\xeb\xa6\xf7\x0f\xae\x23\ \xdf\x79\xd5\xb3\x82\x2d\xa9\xda\xc4\x0f\xc8\x5c\x39\xa3\x59\x9d\ \x3b\x14\xb1\xfc\x3f\x33\x78\xfb\xd4\x05\x94\xba\xd3\x8b\xa1\xe4\ \xee\x76\x3a\xc9\x74\xd7\x0e\x4f\x06\xad\x1a\xbc\xd5\x62\xcb\xd6\ \xae\x51\x47\x21\xe2\xbd\x35\xe0\xdd\x6d\x1e\x8c\xe9\xe8\x66\x76\ \x99\x83\x7f\xdf\x10\x38\x36\xcd\xfa\x1c\x42\xe8\xbb\xab\x5b\xeb\ \xd6\x30\xf4\xc2\xc9\xea\xa7\x66\xf7\x92\x52\x2f\xd3\xd8\x99\x36\ \xad\x6f\x33\x2b\x86\xfd\xc8\x4a\xdf\xf0\x92\xb3\x81\x4a\x48\xf6\ \xfb\xce\xfe\x08\xb0\xe9\xcb\xfd\x8d\x03\xde\x69\x9a\x09\xe0\x53\ \x60\x7c\xc7\x99\x78\xa9\x83\x7b\x17\x3f\x98\xd8\x71\x36\xbe\x3e\ \xf5\x75\x0f\xd8\x36\xdc\xba\x79\x02\xfe\x3d\x43\x5a\x8d\x00\xc5\ \xa7\xa9\x50\x0d\xc7\xc8\x27\x8e\xed\x89\x0d\x21\xa4\x69\xa4\x25\ \xd5\x42\x74\xfa\xe8\x69\xe4\xf4\xd9\x84\xac\xf5\x9e\x58\x47\xb0\ \x3b\x02\xac\xfb\x6c\x17\x84\x0f\x31\x00\x3e\x17\xc6\x76\x46\xc0\ \x29\x1d\xd7\x61\x06\xde\xe4\x31\x87\x00\x2e\x15\x9a\xcf\x01\x9c\ \xd5\xd9\x07\x46\x13\x62\x8c\xee\x38\x05\x42\x5d\x57\xc1\xc9\x97\ \x1c\x2b\xf7\x7e\x6b\x7e\xab\x10\x40\x1b\x64\x38\xc0\x19\x4b\xad\ \xfe\xd8\x91\x3d\xad\xb3\xf7\x56\xcc\x1c\x22\x7d\x80\xd8\x31\x9c\ \xbd\xc1\x1b\x2a\x12\x4f\xe2\xa5\x11\x35\xda\x3b\x78\xed\x5c\x92\ \xcf\x37\x46\xf3\xd2\xc7\xfe\x08\x40\xa6\x79\x52\x80\x93\xcb\x1c\ \x2c\x00\xde\xb0\x92\xac\x1e\x12\x05\xef\xf7\xf9\xb7\x0f\xec\x1f\ \xdf\x70\x63\xc9\xd1\x29\xe7\xf1\x18\x18\xeb\x04\x90\x0e\xc6\x83\ \x1e\xd4\x70\xc8\x90\xf1\xd8\xca\x6d\xb3\x9e\x81\x99\xe8\xf9\x37\ \xa6\xd8\x49\xac\xf6\x16\xd9\x1f\x01\xa6\x74\xf2\x00\xb7\x4e\x5e\ \x08\x78\x0b\x9c\x33\x1c\xf0\x44\xee\xfa\x41\x33\x31\xb6\xf3\x74\ \x58\xf1\x89\xd9\x94\x51\x74\x20\xb1\x07\x80\x65\x02\x14\x9f\x08\ \x31\xb9\x0c\x72\x2b\xe5\xed\xdb\x48\xd1\x94\x18\x7d\x0a\x4a\x0d\ \xd3\x42\xc9\x09\xa6\x76\x71\x6b\x18\x76\xf9\x8c\xed\x80\xa6\x04\ \xe4\xbd\x16\xe1\xaa\x67\x26\x81\x7e\x10\xc9\xfa\xd5\xb1\xba\x8d\ \x33\x0c\xf1\x37\x35\x0a\x3e\x7e\xda\x3b\xd8\xfc\x69\x69\xc7\x28\ \xd8\x32\x1b\xca\xa2\x37\x41\xc5\xb3\x03\x78\x6c\x4c\xb7\x61\xba\ \x75\x00\x8a\x91\x08\x86\x68\x02\x9d\xca\x1c\xf7\x01\xed\xef\xde\ \x40\x19\x31\x1d\x93\x3b\x7a\x20\x09\xa6\xbc\xee\x0e\x5b\x87\x1e\ \x40\x02\x2c\xe8\xb7\x98\x55\x02\xe8\x96\x8f\xa6\x8b\x34\x7a\x25\ \xde\x2a\x16\x3b\xc2\xc2\x4d\xa0\xf3\x3e\x64\x8a\x46\xa6\x5a\x11\ \x77\x04\x07\x4d\xf3\x17\x47\x5a\xa9\xb3\x8d\x0e\xe2\xc8\x0e\x53\ \x20\x70\xf0\x32\x90\xfe\x53\xc1\x1a\x01\x74\xab\x26\x60\xe2\x85\ \x3a\x9d\xbb\x3f\x00\xb7\x64\x72\xc1\x54\xa3\x17\x3f\x92\x6b\x68\ \x2d\x4f\x3c\x8d\x3d\x8c\x9f\xe3\x09\x60\xa5\x4a\x1c\x16\xc2\x84\ \x0e\xb3\x70\x37\x20\x84\x60\x83\x00\x38\x18\xc2\xf4\x4c\x20\x19\ \x00\x89\x0d\x1c\xaa\xc6\x87\x3f\xe2\xb6\x4f\xb7\x79\x3d\xc1\x83\ \x9d\xcc\x3d\x00\x46\xd5\x45\xba\xb1\x4a\x00\xee\x5f\x1f\x2f\x0c\ \x84\x69\x9d\x24\x18\x2d\xbc\x42\x02\xe0\xac\x9e\xf2\xc7\x7b\xcd\ \xab\x70\x77\x77\xa3\x73\x46\xba\x78\x2c\xc6\xfb\xc4\xe6\x53\xd7\ \xca\x19\x6e\x1a\x43\x27\xce\xfd\x43\xca\x94\x90\x4b\x22\x39\x71\ \x7d\x3c\x8a\xa4\x53\xc4\x6f\xc9\xd4\x0f\x05\xf9\xf1\x2a\x89\x02\ \x67\xd5\x02\x01\xb4\x01\x5f\x41\xf1\x99\xc5\x78\x51\x53\xc3\x97\ \x56\x29\x71\x60\x93\x76\x8b\xbb\xc5\xed\xbb\xe2\xd9\xc1\x7a\x90\ \x1f\x1d\x60\xbc\x78\xd3\xeb\x60\xca\x1f\xee\xb1\x35\xf0\x35\x5a\ \x89\xcb\x8a\x02\x79\x9f\x3f\x0a\x5e\x95\xf8\x38\x86\xfc\x83\xec\ \x06\x07\xda\x1a\x01\x48\x7c\x5f\xc1\xe4\xe1\xad\xd0\x4a\x95\xc9\ \x14\x31\xdf\x21\x18\x29\x94\x45\x6f\xa4\xb6\x7a\x43\x22\x27\xc9\ \xfb\x43\x50\x92\xe4\x4d\x92\xe4\x63\xd0\x57\x3c\xab\x0f\x23\x2f\ \xae\xb6\x25\xf8\xe7\x75\xe2\xc1\xff\x13\xb0\x25\xa4\x13\x67\x08\ \xe9\x01\x7c\xd2\x26\x08\x40\x8f\x89\xb3\x5e\xeb\x94\x78\x55\x5c\ \x4d\xde\x35\xd3\xab\xdd\xcc\x7c\x80\x0a\xe5\x8f\x66\xed\x5e\x59\ \x8b\x27\xd8\x02\xf8\x78\x12\x32\x7e\x2a\x68\x0d\x51\x08\x14\x3f\ \x25\x3f\xe6\x38\xda\x3f\xe0\x80\x5a\x1e\x13\x87\x13\xbf\x35\x51\ \xe1\xa4\xc2\x37\x13\x54\x92\x2f\xf0\x74\xaf\xe1\x76\x30\xf5\xa1\ \x60\xaa\xe5\xab\x25\x5a\xf2\x70\x3f\x0e\x86\x68\x01\xf0\x3a\xb5\ \xd8\xc5\x03\x14\xae\x3f\x17\xb4\xb6\x78\xbe\xa9\xf8\x93\x4c\x18\ \x14\x4a\x7e\xd8\x2a\xae\x12\x40\x7b\x6c\x89\xde\x30\x89\x23\xd1\ \xf3\x23\xcb\xa9\xd8\xa9\xce\x78\x21\x54\x23\xe6\x02\x57\x79\x51\ \xf4\x76\x2c\x0f\x97\xc5\x1c\x6d\xf8\xfa\xb7\xc7\x07\xb1\xd9\xa3\ \x39\x25\x63\xa2\x35\x1a\xa9\x28\x32\x47\xec\xfa\x37\x81\xbd\x89\ \xd4\x31\xe0\x9f\x1c\xf0\x0f\x2c\x4f\x09\xa3\x47\xbd\x63\xfa\x35\ \x91\xa8\x92\x34\x65\xc4\xbc\x78\xe6\x5f\xf2\xb9\x61\x47\xc0\x22\ \x4d\xe1\x95\x0d\x98\x1b\x48\x0d\x1a\x8b\x8d\x22\xcc\x33\x0c\xa5\ \xe1\xb0\x89\x50\x70\x7e\x8d\xe1\x7a\x39\x6c\xf1\x4a\x0b\x1e\x87\ \xaf\xc7\x10\x25\x87\x43\x9a\x6e\xe7\xe5\xce\x5d\x05\xf6\x2d\xe8\ \x1f\xbc\x4f\x88\xf0\x94\x6b\x04\x20\xc0\x20\xd8\x58\xd6\x35\xcf\ \xb9\x63\xd6\x2f\x65\x32\x33\x45\xcc\x92\x32\xe7\xff\x1a\x51\xbc\ \x25\xb4\x09\xc0\x27\x68\x24\xa2\xcf\x05\xdc\x11\xf4\x0f\x7e\x4e\ \xc2\x46\x37\xf2\x63\xab\x39\x40\x00\xbc\x3e\x9e\x38\x53\xd6\xf6\ \xf1\x23\x51\xcc\x2b\x75\x3d\x20\x65\x4a\x6f\xc3\xc0\x27\x66\xf4\ \x3b\x21\x96\xd9\x67\xb2\x66\x59\x9d\xd7\x2f\x20\x61\x9d\x1c\x14\ \x5d\x7f\x29\xe0\xaa\xf8\xbc\x1e\xf2\x67\xa3\x7f\x50\x6d\xef\x79\ \x80\x66\xdc\x16\x82\x43\x9e\x72\x3d\x06\x5a\x4c\x0c\x91\x6a\x21\ \x36\x7b\x34\xa1\x6a\x58\x6b\xb0\xf3\xb9\xbe\x83\x3b\x08\xda\x8a\ \x90\xc2\xce\xbf\xc9\x0f\x7f\x82\xdb\x04\x60\x41\xa5\xa2\x0b\x79\ \x5e\x83\xba\x0b\xda\xaa\xf8\x08\x03\x3e\x90\x0b\x83\x63\x78\x02\ \x98\xaa\x28\x51\x2d\x75\x19\x2a\x68\x0f\xe2\xd6\x27\xf2\x17\x46\ \xff\x40\x23\xe7\x09\x50\x68\xb0\xf3\xaa\x39\x9f\xfe\x9f\xa0\xbd\ \x89\x97\xa3\xe2\x2f\x3e\xc2\xe0\x15\x04\x90\xda\x76\x48\x00\x3d\ \x59\xf5\x3b\xf2\x24\xa2\x8e\x82\xf6\x2e\xbe\xc2\xe0\xff\x10\x50\ \x4e\xb5\x1b\x02\x88\x9d\x2f\xe5\x7b\xbb\xf4\x14\xf0\xd2\x80\x7f\ \xe0\x18\x14\xdb\x86\x09\xa0\x62\xc1\xce\x73\xdf\x3f\x20\x24\xf0\ \x20\x20\x15\xb5\x21\x02\x94\x69\x24\x2e\x8a\x54\x85\xeb\xaf\x78\ \x84\xad\x14\x5f\x87\xe0\xbf\x1a\xfd\x83\x3a\x0e\x13\x00\xed\xbc\ \x5a\xea\xda\x99\x47\xb4\xd9\x66\x21\xc4\x89\x94\x9d\xaf\x70\x90\ \x00\xb7\x89\x77\x3f\x80\x47\xd0\x66\x6d\x69\x81\x5f\x1a\x80\xe3\ \x00\x01\x32\xb4\x52\x97\x71\x20\x10\xfc\x84\x47\xcd\xc6\xa2\xe8\ \xaa\xf8\xa5\xc1\x3f\x20\x5a\x6c\x87\x04\x60\xcb\xce\xf3\xe2\xf7\ \x46\x50\x17\x52\x71\x8c\x44\xff\xa0\xf5\x09\xf0\x5c\x2b\x71\x3e\ \x80\x4d\x98\xec\x0a\x2f\x52\x61\x50\x6f\x92\x51\xbc\xd6\x6a\x04\ \x10\xbb\xdc\xd1\x49\x44\x83\x78\x24\xec\xc1\x3f\x70\x0c\x4a\x61\ \x91\x00\x59\xbc\x9d\xb7\xbb\xb4\xf2\xd2\x5f\x93\xdd\x40\x4e\xc0\ \x2d\x79\x85\x04\x28\x57\x4b\x44\xa1\xea\x99\xae\xbf\x13\xd8\xa7\ \xf0\x22\x7b\x2d\x4c\x68\xf4\x0f\xf4\x36\x24\x00\xda\xf9\x42\xb1\ \xe8\x0d\x01\x57\x84\x27\x42\x60\x5f\x42\x84\x1b\x36\x20\xc0\xbd\ \x7c\xe9\x60\x67\x01\x17\x85\x17\xf8\x89\x8f\x43\xe0\x50\x92\x48\ \x4a\x6b\x06\x01\xb2\x89\x93\xe7\x06\x0a\xc1\x4f\x05\xdc\x16\x5e\ \x14\x42\xc5\x6f\x8c\xfe\x41\xa9\x15\x04\xa8\x30\xd8\x79\x8d\xcc\ \xf9\xf7\x82\xb6\x25\xbc\xf8\x3b\x06\x3a\x90\x1d\x61\x07\x21\x40\ \x52\x83\x04\x10\x8b\x8e\xe7\x49\xde\x79\x4b\xd0\xb6\x85\x17\xf9\ \xeb\x8b\xcc\x7a\xeb\xb1\xdf\xbe\x5d\x09\x2f\xbc\xf0\xc2\xcb\xff\ \x03\x60\x37\xfd\xc4\xa3\x8a\xed\x35\x00\x00\x00\x00\x49\x45\x4e\ \x44\xae\x42\x60\x82\ \x00\x00\x01\x36\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ \x00\x00\x00\xfd\x49\x44\x41\x54\x78\x01\x63\x20\x05\x14\xca\xf4\ \x71\x3e\x2e\xb4\xe4\x64\xa0\x36\x68\x10\x6d\xe0\xa9\x90\x69\x6d\ \xa8\x90\x69\x9b\xfc\xaa\xc4\x6a\xea\x9b\x12\x9b\x89\xef\x0b\x1c\ \x04\xa8\x60\xf4\x7f\xc6\x72\xe9\xd6\x38\x20\x7e\x01\xc4\xff\xcb\ \xa4\x5b\x67\xbe\x2e\xb5\x9a\xf9\xba\xc4\xfa\x3f\x10\xbf\x7d\x55\ \x6c\x93\xff\x3f\x34\x94\x99\x2c\xa3\x2b\x64\xdb\x4d\x81\x86\x1e\ \x07\x19\x0c\xc2\x18\x16\x20\xf0\x39\xa0\x45\x36\x44\x1b\x5c\x26\ \xdb\x29\x55\x21\xdd\xb2\x08\x68\xe0\x3f\x84\xe1\x98\x16\x60\xe0\ \x62\xab\xcd\xef\x8b\xad\xe4\x71\x1a\x9c\x66\x3c\x93\xb5\x5c\xa6\ \x35\x1f\x68\xd0\x27\x98\x81\x04\x2d\xc0\xc4\x5f\x5f\x97\xd8\x34\ \xdc\x6f\x70\xe0\xc0\xb0\x00\x68\xc0\x1d\x98\x41\x64\x5b\x80\xc0\ \x77\xb0\x59\xf0\x9f\x8a\x16\xfc\xa7\x83\x05\xa3\x16\x8c\x5a\x30\ \x6a\x01\xbd\x8b\x0a\x44\x61\x07\xc4\x1f\xa9\x5c\xd8\x61\x16\xd7\ \x20\x83\x80\x06\xfe\x25\xc1\x82\x7f\x6f\x4a\xac\x57\xbd\x2b\xb7\ \x91\xa3\x41\x85\x63\x75\x16\x54\xe1\x50\xbd\xca\x7c\x55\x62\xfd\ \x06\x56\x65\xd2\xa4\xd2\x7f\x57\x6e\xcc\x4f\x8c\x5e\x9a\x37\x5b\ \x00\x76\x3d\x36\x28\xad\x67\x1e\x38\x00\x00\x00\x00\x49\x45\x4e\ \x44\xae\x42\x60\x82\ " qt_resource_name = "\ \x00\x06\ \x07\x03\x7d\xc3\ \x00\x69\ \x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\ \x00\x05\ \x00\x35\xbb\x54\ \x00\x32\ \x00\x34\x00\x78\x00\x32\x00\x34\ \x00\x10\ \x00\xe0\x1d\x47\ \x00\x66\ \x00\x69\x00\x6c\x00\x65\x00\x2d\x00\x70\x00\x65\x00\x6e\x00\x64\x00\x69\x00\x6e\x00\x67\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0e\ \x08\x53\x8e\xe7\ \x00\x74\ \x00\x72\x00\x61\x00\x63\x00\x6b\x00\x2d\x00\x64\x00\x61\x00\x74\x00\x61\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x12\ \x08\xc2\x5a\x27\ \x00\x43\ \x00\x6f\x00\x76\x00\x65\x00\x72\x00\x41\x00\x72\x00\x74\x00\x53\x00\x68\x00\x61\x00\x64\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\ \x00\x67\ \x00\x07\ \x04\x5f\xb4\x38\ \x00\x31\ \x00\x32\x00\x38\x00\x78\x00\x31\x00\x32\x00\x38\ \x00\x08\ \x0a\x85\x58\x07\ \x00\x73\ \x00\x74\x00\x61\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x09\ \x09\x6a\x86\x67\ \x00\x61\ \x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x15\ \x07\x05\x4c\x07\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x70\x00\x65\x00\x6e\x00\x64\x00\x69\x00\x6e\x00\x67\x00\x2d\x00\x31\x00\x30\x00\x30\ \x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x07\ \x05\x8d\xb5\xe6\ \x00\x32\ \x00\x35\x00\x36\x00\x78\x00\x32\x00\x35\x00\x36\ \x00\x05\ \x00\x36\x9b\x62\ \x00\x33\ \x00\x32\x00\x78\x00\x33\x00\x32\ \x00\x0f\ \x05\xff\xc8\x27\ \x00\x74\ \x00\x72\x00\x61\x00\x63\x00\x6b\x00\x2d\x00\x61\x00\x75\x00\x64\x00\x69\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x05\ \x00\x37\xfb\x78\ \x00\x34\ \x00\x38\x00\x78\x00\x34\x00\x38\ \x00\x0d\ \x0d\xdc\xf1\x67\ \x00\x73\ \x00\x74\x00\x61\x00\x72\x00\x2d\x00\x67\x00\x72\x00\x61\x00\x79\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0c\ \x0e\xf2\x04\x27\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x35\x00\x30\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x14\ \x0a\xb0\x36\xe7\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x70\x00\x65\x00\x6e\x00\x64\x00\x69\x00\x6e\x00\x67\x00\x2d\x00\x35\x00\x30\x00\x2e\ \x00\x70\x00\x6e\x00\x67\ \x00\x0c\ \x0e\xe2\x04\x27\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x36\x00\x30\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x14\ \x0a\xa0\x36\xe7\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x70\x00\x65\x00\x6e\x00\x64\x00\x69\x00\x6e\x00\x67\x00\x2d\x00\x36\x00\x30\x00\x2e\ \x00\x70\x00\x6e\x00\x67\ \x00\x0a\ \x0a\xcb\x27\x16\ \x00\x6c\ \x00\x6f\x00\x61\x00\x64\x00\x65\x00\x72\x00\x2e\x00\x67\x00\x69\x00\x66\ \x00\x05\ \x00\x35\x9b\x52\ \x00\x32\ \x00\x32\x00\x78\x00\x32\x00\x32\ \x00\x0c\ \x0e\xd2\x04\x27\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x37\x00\x30\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x14\ \x0a\x90\x36\xe7\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x70\x00\x65\x00\x6e\x00\x64\x00\x69\x00\x6e\x00\x67\x00\x2d\x00\x37\x00\x30\x00\x2e\ \x00\x70\x00\x6e\x00\x67\ \x00\x08\ \x00\x28\x5a\xe7\ \x00\x66\ \x00\x69\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0c\ \x0e\xc2\x04\x27\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x38\x00\x30\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x14\ \x0a\x80\x36\xe7\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x70\x00\x65\x00\x6e\x00\x64\x00\x69\x00\x6e\x00\x67\x00\x2d\x00\x38\x00\x30\x00\x2e\ \x00\x70\x00\x6e\x00\x67\ \x00\x0f\ \x05\xbf\x20\xa7\ \x00\x74\ \x00\x72\x00\x61\x00\x63\x00\x6b\x00\x2d\x00\x76\x00\x69\x00\x64\x00\x65\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x05\ \x00\x34\xdb\x46\ \x00\x31\ \x00\x36\x00\x78\x00\x31\x00\x36\ \x00\x0c\ \x0e\xb2\x04\x27\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x39\x00\x30\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0f\ \x03\x8a\x2a\x47\ \x00\x74\ \x00\x72\x00\x61\x00\x63\x00\x6b\x00\x2d\x00\x73\x00\x61\x00\x76\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0d\ \x03\x26\x60\x87\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x31\x00\x30\x00\x30\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x14\ \x0a\xf0\x36\xe7\ \x00\x6d\ \x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x70\x00\x65\x00\x6e\x00\x64\x00\x69\x00\x6e\x00\x67\x00\x2d\x00\x39\x00\x30\x00\x2e\ \x00\x70\x00\x6e\x00\x67\ \x00\x13\ \x06\x89\x11\xa7\ \x00\x70\ \x00\x69\x00\x63\x00\x61\x00\x72\x00\x64\x00\x2d\x00\x61\x00\x75\x00\x74\x00\x6f\x00\x2d\x00\x74\x00\x61\x00\x67\x00\x2e\x00\x70\ \x00\x6e\x00\x67\ \x00\x17\ \x0d\x58\x3e\xe7\ \x00\x61\ \x00\x70\x00\x70\x00\x6c\x00\x69\x00\x63\x00\x61\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x73\x00\x2d\x00\x73\x00\x79\x00\x73\x00\x74\ \x00\x65\x00\x6d\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x17\ \x06\x8b\xba\x67\ \x00\x70\ \x00\x72\x00\x65\x00\x66\x00\x65\x00\x72\x00\x65\x00\x6e\x00\x63\x00\x65\x00\x73\x00\x2d\x00\x64\x00\x65\x00\x73\x00\x6b\x00\x74\ \x00\x6f\x00\x70\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0f\ \x02\x69\x3c\x47\ \x00\x72\ \x00\x65\x00\x6d\x00\x6f\x00\x76\x00\x65\x00\x2d\x00\x69\x00\x74\x00\x65\x00\x6d\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0a\ \x0a\xc8\xfb\x07\ \x00\x66\ \x00\x6f\x00\x6c\x00\x64\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0f\ \x02\x30\x8b\xe7\ \x00\x6c\ \x00\x69\x00\x73\x00\x74\x00\x2d\x00\x72\x00\x65\x00\x6d\x00\x6f\x00\x76\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x15\ \x00\x24\xf9\x87\ \x00\x61\ \x00\x63\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x2d\x00\x67\x00\x6f\x00\x2d\x00\x64\x00\x6f\x00\x77\x00\x6e\x00\x2d\x00\x31\x00\x36\ \x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0a\ \x08\x89\x88\x67\ \x00\x70\ \x00\x69\x00\x63\x00\x61\x00\x72\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x17\ \x08\x1f\x12\x27\ \x00\x6d\ \x00\x65\x00\x64\x00\x69\x00\x61\x00\x2d\x00\x6f\x00\x70\x00\x74\x00\x69\x00\x63\x00\x61\x00\x6c\x00\x2d\x00\x65\x00\x72\x00\x72\ \x00\x6f\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x16\ \x08\x9b\xc7\x87\ \x00\x6c\ \x00\x6f\x00\x6f\x00\x6b\x00\x75\x00\x70\x00\x2d\x00\x6d\x00\x75\x00\x73\x00\x69\x00\x63\x00\x62\x00\x72\x00\x61\x00\x69\x00\x6e\ \x00\x7a\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x11\ \x01\xa6\xc4\x87\ \x00\x64\ \x00\x6f\x00\x63\x00\x75\x00\x6d\x00\x65\x00\x6e\x00\x74\x00\x2d\x00\x6f\x00\x70\x00\x65\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \ \x00\x20\ \x0a\x57\x7b\xa7\ \x00\x6d\ \x00\x65\x00\x64\x00\x69\x00\x61\x00\x2d\x00\x6f\x00\x70\x00\x74\x00\x69\x00\x63\x00\x61\x00\x6c\x00\x2d\x00\x73\x00\x61\x00\x76\ \x00\x65\x00\x64\x00\x2d\x00\x6d\x00\x6f\x00\x64\x00\x69\x00\x66\x00\x69\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x17\ \x0d\xcd\xf8\x07\ \x00\x6d\ \x00\x65\x00\x64\x00\x69\x00\x61\x00\x2d\x00\x6f\x00\x70\x00\x74\x00\x69\x00\x63\x00\x61\x00\x6c\x00\x2d\x00\x73\x00\x61\x00\x76\ \x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x14\ \x0e\xe8\x99\x87\ \x00\x70\ \x00\x69\x00\x63\x00\x61\x00\x72\x00\x64\x00\x2d\x00\x65\x00\x64\x00\x69\x00\x74\x00\x2d\x00\x74\x00\x61\x00\x67\x00\x73\x00\x2e\ \x00\x70\x00\x6e\x00\x67\ \x00\x12\ \x02\xd2\xa5\xe7\ \x00\x70\ \x00\x69\x00\x63\x00\x61\x00\x72\x00\x64\x00\x2d\x00\x61\x00\x6e\x00\x61\x00\x6c\x00\x79\x00\x7a\x00\x65\x00\x2e\x00\x70\x00\x6e\ \x00\x67\ \x00\x0e\ \x0c\xee\xc3\x07\ \x00\x70\ \x00\x6c\x00\x61\x00\x79\x00\x2d\x00\x6d\x00\x75\x00\x73\x00\x69\x00\x63\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x10\ \x08\x15\x13\x67\ \x00\x76\ \x00\x69\x00\x65\x00\x77\x00\x2d\x00\x72\x00\x65\x00\x66\x00\x72\x00\x65\x00\x73\x00\x68\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0c\ \x06\x4c\xbf\x47\ \x00\x61\ \x00\x64\x00\x64\x00\x2d\x00\x69\x00\x74\x00\x65\x00\x6d\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x10\ \x0f\xcb\x90\x67\ \x00\x64\ \x00\x69\x00\x61\x00\x6c\x00\x6f\x00\x67\x00\x2d\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0e\ \x0c\xaa\xc0\xa7\ \x00\x65\ \x00\x64\x00\x69\x00\x74\x00\x2d\x00\x70\x00\x61\x00\x73\x00\x74\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x1a\ \x0c\x7d\x14\x87\ \x00\x61\ \x00\x63\x00\x6f\x00\x75\x00\x73\x00\x74\x00\x69\x00\x64\x00\x2d\x00\x66\x00\x69\x00\x6e\x00\x67\x00\x65\x00\x72\x00\x70\x00\x72\ \x00\x69\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x1a\ \x04\x78\x27\x87\ \x00\x6d\ \x00\x65\x00\x64\x00\x69\x00\x61\x00\x2d\x00\x6f\x00\x70\x00\x74\x00\x69\x00\x63\x00\x61\x00\x6c\x00\x2d\x00\x6d\x00\x6f\x00\x64\ \x00\x69\x00\x66\x00\x69\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0c\ \x07\xb1\x59\x27\ \x00\x65\ \x00\x64\x00\x69\x00\x74\x00\x2d\x00\x63\x00\x75\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x11\ \x0f\xe3\xd5\x67\ \x00\x64\ \x00\x6f\x00\x63\x00\x75\x00\x6d\x00\x65\x00\x6e\x00\x74\x00\x2d\x00\x73\x00\x61\x00\x76\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ \ \x00\x11\ \x00\xbd\x49\x67\ \x00\x6d\ \x00\x65\x00\x64\x00\x69\x00\x61\x00\x2d\x00\x6f\x00\x70\x00\x74\x00\x69\x00\x63\x00\x61\x00\x6c\x00\x2e\x00\x70\x00\x6e\x00\x67\ \ \x00\x12\ \x09\x17\x4c\x27\ \x00\x70\ \x00\x69\x00\x63\x00\x61\x00\x72\x00\x64\x00\x2d\x00\x63\x00\x6c\x00\x75\x00\x73\x00\x74\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\ \x00\x67\ \x00\x0e\ \x0d\x8b\x39\xe7\ \x00\x65\ \x00\x64\x00\x69\x00\x74\x00\x2d\x00\x63\x00\x6c\x00\x65\x00\x61\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x11\ \x0e\xfe\x4a\xe7\ \x00\x73\ \x00\x79\x00\x73\x00\x74\x00\x65\x00\x6d\x00\x2d\x00\x73\x00\x65\x00\x61\x00\x72\x00\x63\x00\x68\x00\x2e\x00\x70\x00\x6e\x00\x67\ \ " qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x1e\x00\x00\x00\x02\ \x00\x00\x02\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x09\ \x00\x00\x03\x12\x00\x02\x00\x00\x00\x1b\x00\x00\x00\x38\ \x00\x00\x02\x30\x00\x02\x00\x00\x00\x13\x00\x00\x00\x25\ \x00\x00\x00\x12\x00\x02\x00\x00\x00\x01\x00\x00\x00\x24\ \x00\x00\x01\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x23\ \x00\x00\x01\x4e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x22\ \x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x03\x64\x00\x00\x00\x00\x00\x01\x00\x00\x50\x29\ \x00\x00\x03\x40\x00\x00\x00\x00\x00\x01\x00\x00\x4f\x94\ \x00\x00\x00\x94\x00\x02\x00\x00\x00\x01\x00\x00\x00\x21\ \x00\x00\x01\x06\x00\x02\x00\x00\x00\x01\x00\x00\x00\x20\ \x00\x00\x02\xee\x00\x00\x00\x00\x00\x01\x00\x00\x4c\xea\ \x00\x00\x01\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x41\xdb\ \x00\x00\x00\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x41\x74\ \x00\x00\x00\x48\x00\x00\x00\x00\x00\x01\x00\x00\x01\x1c\ \x00\x00\x00\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x03\x6c\ \x00\x00\x00\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x3e\xbf\ \x00\x00\x02\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x4c\x7e\ \x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00\x3c\x9c\ \x00\x00\x02\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x49\xa7\ \x00\x00\x01\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x46\x29\ \x00\x00\x01\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x45\x4d\ \x00\x00\x02\x16\x00\x00\x00\x00\x00\x01\x00\x00\x46\x95\ \x00\x00\x03\x84\x00\x00\x00\x00\x00\x01\x00\x00\x50\x9d\ \x00\x00\x01\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x42\xeb\ \x00\x00\x03\x22\x00\x00\x00\x00\x00\x01\x00\x00\x4f\x1c\ \x00\x00\x02\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x4c\x04\ \x00\x00\x02\x40\x00\x00\x00\x00\x00\x01\x00\x00\x49\x3a\ \x00\x00\x01\xca\x00\x00\x00\x00\x00\x01\x00\x00\x45\xaf\ \x00\x00\x01\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x44\xe2\ \x00\x00\x04\xd8\x00\x00\x00\x00\x00\x01\x00\x00\xdc\x24\ \x00\x00\x04\xd8\x00\x00\x00\x00\x00\x01\x00\x01\x0f\x67\ \x00\x00\x04\xd8\x00\x00\x00\x00\x00\x01\x00\x00\xd3\x08\ \x00\x00\x04\xd8\x00\x00\x00\x00\x00\x01\x00\x00\xd8\x6d\ \x00\x00\x04\xd8\x00\x00\x00\x00\x00\x01\x00\x01\x27\xc1\ \x00\x00\x07\xba\x00\x00\x00\x00\x00\x01\x00\x00\xc7\x29\ \x00\x00\x05\x58\x00\x00\x00\x00\x00\x01\x00\x00\xa1\xb1\ \x00\x00\x04\x84\x00\x00\x00\x00\x00\x01\x00\x00\x95\x2b\ \x00\x00\x06\x28\x00\x00\x00\x00\x00\x01\x00\x00\xb1\x8d\ \x00\x00\x07\x3a\x00\x00\x00\x00\x00\x01\x00\x00\xba\x7b\ \x00\x00\x03\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x8e\x13\ \x00\x00\x04\x12\x00\x00\x00\x00\x00\x01\x00\x00\x90\xd1\ \x00\x00\x04\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x97\x16\ \x00\x00\x05\x26\x00\x00\x00\x00\x00\x01\x00\x00\x9c\x3c\ \x00\x00\x07\xe2\x00\x00\x00\x00\x00\x01\x00\x00\xcc\x2b\ \x00\x00\x05\x80\x00\x00\x00\x00\x00\x01\x00\x00\xa5\x41\ \x00\x00\x04\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x92\x8b\ \x00\x00\x07\x00\x00\x00\x00\x00\x00\x01\x00\x00\xb7\x10\ \x00\x00\x06\x52\x00\x00\x00\x00\x00\x01\x00\x00\xb3\xb3\ \x00\x00\x08\x0c\x00\x00\x00\x00\x00\x01\x00\x00\xce\xa6\ \x00\x00\x05\xc6\x00\x00\x00\x00\x00\x01\x00\x00\xad\x5d\ \x00\x00\x05\xfa\x00\x00\x00\x00\x00\x01\x00\x00\xa9\xbe\ \x00\x00\x08\x2e\x00\x00\x00\x00\x00\x01\x00\x00\xc2\xd6\ \x00\x00\x07\x92\x00\x00\x00\x00\x00\x01\x00\x00\xbf\xa7\ \x00\x00\x04\xa8\x00\x00\x00\x00\x00\x01\x00\x00\x5a\xf0\ \x00\x00\x07\xba\x00\x00\x00\x00\x00\x01\x00\x00\x86\x55\ \x00\x00\x05\x58\x00\x00\x00\x00\x00\x01\x00\x00\x64\xc6\ \x00\x00\x04\x84\x00\x00\x00\x00\x00\x01\x00\x00\x58\x19\ \x00\x00\x04\x46\x00\x00\x00\x00\x00\x01\x00\x00\x55\x94\ \x00\x00\x06\x28\x00\x00\x00\x00\x00\x01\x00\x00\x6e\xc5\ \x00\x00\x07\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x7e\xa6\ \x00\x00\x06\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x76\x06\ \x00\x00\x03\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x51\x06\ \x00\x00\x04\x12\x00\x00\x00\x00\x00\x01\x00\x00\x54\x44\ \x00\x00\x07\x74\x00\x00\x00\x00\x00\x01\x00\x00\x81\xac\ \x00\x00\x06\x74\x00\x00\x00\x00\x00\x01\x00\x00\x73\x2c\ \x00\x00\x04\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x5d\xef\ \x00\x00\x04\xd8\x00\x00\x00\x00\x00\x01\x00\x00\x5d\x16\ \x00\x00\x05\x26\x00\x00\x00\x00\x00\x01\x00\x00\x60\xf8\ \x00\x00\x07\xe2\x00\x00\x00\x00\x00\x01\x00\x00\x89\x0f\ \x00\x00\x05\x80\x00\x00\x00\x00\x00\x01\x00\x00\x66\x64\ \x00\x00\x04\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x56\x9b\ \x00\x00\x07\x00\x00\x00\x00\x00\x00\x01\x00\x00\x7b\xab\ \x00\x00\x06\xde\x00\x00\x00\x00\x00\x01\x00\x00\x79\xd5\ \x00\x00\x06\x52\x00\x00\x00\x00\x00\x01\x00\x00\x71\x09\ \x00\x00\x03\xde\x00\x00\x00\x00\x00\x01\x00\x00\x53\x4e\ \x00\x00\x08\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x8c\x01\ \x00\x00\x05\xc6\x00\x00\x00\x00\x00\x01\x00\x00\x69\x4e\ \x00\x00\x05\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x6b\xee\ \x00\x00\x06\xb8\x00\x00\x00\x00\x00\x01\x00\x00\x77\xad\ \x00\x00\x07\x92\x00\x00\x00\x00\x00\x01\x00\x00\x84\x0d\ " def qInitResources(): QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) def qCleanupResources(): QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() picard-release-1.4.2/picard/script.py000066400000000000000000000575771310410472100175340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006-2007 Lukáš Lalinský # Copyright (C) 2007 Javier Kohen # Copyright (C) 2008 Philipp Wolfer # # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import re import operator from collections import namedtuple from inspect import getargspec from picard.metadata import Metadata from picard.metadata import MULTI_VALUED_JOINER from picard.plugin import ExtensionPoint class ScriptError(Exception): pass class ParseError(ScriptError): pass class EndOfFile(ParseError): pass class SyntaxError(ParseError): pass class UnknownFunction(ScriptError): pass class ScriptText(unicode): def eval(self, state): return self class ScriptVariable(object): def __init__(self, name): self.name = name def __repr__(self): return '' % self.name def eval(self, state): name = self.name if name.startswith(u"_"): name = u"~" + name[1:] return state.context.get(name, u"") FunctionRegistryItem = namedtuple("FunctionRegistryItem", ["function", "eval_args", "argcount"]) Bound = namedtuple("Bound", ["lower", "upper"]) class ScriptFunction(object): def __init__(self, name, args, parser): try: argnum_bound = parser.functions[name].argcount argcount = len(args) if argnum_bound and not (argnum_bound.lower <= argcount and (argnum_bound.upper is None or len(args) <= argnum_bound.upper)): raise ScriptError( "Wrong number of arguments for $%s: Expected %s, got %i at position %i, line %i" % (name, str(argnum_bound.lower) if argnum_bound.upper is None else "%i - %i" % (argnum_bound.lower, argnum_bound.upper), argcount, parser._x, parser._y)) except KeyError: raise UnknownFunction("Unknown function '%s'" % name) self.name = name self.args = args def __repr__(self): return "" % (self.name, self.args) def eval(self, parser): function, eval_args, num_args = parser.functions[self.name] if eval_args: args = [arg.eval(parser) for arg in self.args] else: args = self.args return function(parser, *args) class ScriptExpression(list): def eval(self, state): result = [] for item in self: result.append(item.eval(state)) return "".join(result) def isidentif(ch): return ch.isalnum() or ch == '_' class ScriptParser(object): r"""Tagger script parser. Grammar: text ::= [^$%] | '\$' | '\%' | '\(' | '\)' | '\,' argtext ::= [^$%(),] | '\$' | '\%' | '\(' | '\)' | '\,' identifier ::= [a-zA-Z0-9_] variable ::= '%' identifier '%' function ::= '$' identifier '(' (argument (',' argument)*)? ')' expression ::= (variable | function | text)* argument ::= (variable | function | argtext)* """ _function_registry = ExtensionPoint() _cache = {} def __raise_eof(self): raise EndOfFile("Unexpected end of script at position %d, line %d" % (self._x, self._y)) def __raise_char(self, ch): #line = self._text[self._line:].split("\n", 1)[0] #cursor = " " * (self._pos - self._line - 1) + "^" #raise SyntaxError("Unexpected character '%s' at position %d, line %d\n%s\n%s" % (ch, self._x, self._y, line, cursor)) raise SyntaxError("Unexpected character '%s' at position %d, line %d" % (ch, self._x, self._y)) def read(self): try: ch = self._text[self._pos] except IndexError: return None else: self._pos += 1 self._px = self._x self._py = self._y if ch == '\n': self._line = self._pos self._x = 1 self._y += 1 else: self._x += 1 return ch def unread(self): self._pos -= 1 self._x = self._px self._y = self._py def parse_arguments(self): results = [] while True: result, ch = self.parse_expression(False) results.append(result) if ch == ')': # Only an empty expression as first argument # is the same as no argument given. if len(results) == 1 and results[0] == []: return [] return results def parse_function(self): start = self._pos while True: ch = self.read() if ch == '(': name = self._text[start:self._pos-1] if name not in self.functions: raise UnknownFunction("Unknown function '%s'" % name) return ScriptFunction(name, self.parse_arguments(), self) elif ch is None: self.__raise_eof() elif not isidentif(ch): self.__raise_char(ch) def parse_variable(self): begin = self._pos while True: ch = self.read() if ch == '%': return ScriptVariable(self._text[begin:self._pos-1]) elif ch is None: self.__raise_eof() elif not isidentif(ch) and ch != ':': self.__raise_char(ch) def parse_text(self, top): text = [] while True: ch = self.read() if ch == "\\": ch = self.read() if ch == 'n': text.append('\n') elif ch == 't': text.append('\t') elif ch not in "$%(),\\": self.__raise_char(ch) else: text.append(ch) elif ch is None: break elif not top and ch == '(': self.__raise_char(ch) elif ch in '$%' or (not top and ch in ',)'): self.unread() break else: text.append(ch) return ScriptText("".join(text)) def parse_expression(self, top): tokens = ScriptExpression() while True: ch = self.read() if ch is None: if top: break else: self.__raise_eof() elif not top and ch in ',)': break elif ch == '$': tokens.append(self.parse_function()) elif ch == '%': tokens.append(self.parse_variable()) else: self.unread() tokens.append(self.parse_text(top)) return (tokens, ch) def load_functions(self): self.functions = {} for name, item in ScriptParser._function_registry: self.functions[name] = item def parse(self, script, functions=False): """Parse the script.""" self._text = script self._pos = 0 self._px = self._x = 1 self._py = self._y = 1 self._line = 0 if not functions: self.load_functions() return self.parse_expression(True)[0] def eval(self, script, context=None, file=None): """Parse and evaluate the script.""" self.context = context if context is not None else Metadata() self.file = file self.load_functions() key = hash(script) if key not in ScriptParser._cache: ScriptParser._cache[key] = self.parse(script, True) return ScriptParser._cache[key].eval(self) def register_script_function(function, name=None, eval_args=True, check_argcount=True): """Registers a script function. If ``name`` is ``None``, ``function.__name__`` will be used. If ``eval_args`` is ``False``, the arguments will not be evaluated before being passed to ``function``. If ``check_argcount`` is ``False`` the number of arguments passed to the function will not be verified.""" args, varargs, keywords, defaults = getargspec(function) args = len(args) - 1 # -1 for the parser varargs = varargs is not None defaults = len(defaults) if defaults else 0 argcount = Bound(args - defaults, args if not varargs else None) if name is None: name = function.__name__ ScriptParser._function_registry.register(function.__module__, (name, FunctionRegistryItem( function, eval_args, argcount if argcount and check_argcount else False) ) ) def _compute_int(operation, *args): return str(reduce(operation, map(int, args))) def _compute_logic(operation, *args): return operation(args) def func_if(parser, _if, _then, _else=None): """If ``if`` is not empty, it returns ``then``, otherwise it returns ``else``.""" if _if.eval(parser): return _then.eval(parser) elif _else: return _else.eval(parser) return '' def func_if2(parser, *args): """Returns first non empty argument.""" for arg in args: arg = arg.eval(parser) if arg: return arg return '' def func_noop(parser, *args): """Does nothing :)""" return '' def func_left(parser, text, length): """Returns first ``num`` characters from ``text``.""" try: return text[:int(length)] except ValueError: return "" def func_right(parser, text, length): """Returns last ``num`` characters from ``text``.""" try: return text[-int(length):] except ValueError: return "" def func_lower(parser, text): """Returns ``text`` in lower case.""" return text.lower() def func_upper(parser, text): """Returns ``text`` in upper case.""" return text.upper() def func_pad(parser, text, length, char): try: return char * (int(length) - len(text)) + text except ValueError: return "" def func_strip(parser, text): return re.sub(r"\s+", " ", text).strip() def func_replace(parser, text, old, new): return text.replace(old, new) def func_in(parser, text, needle): if needle in text: return "1" else: return "" def func_inmulti(parser, text, value, separator=MULTI_VALUED_JOINER): """Splits ``text`` by ``separator``, and returns true if the resulting list contains ``value``.""" return func_in(parser, text.split(separator) if separator else [text], value) def func_rreplace(parser, text, old, new): return re.sub(old, new, text) def func_rsearch(parser, text, pattern): match = re.search(pattern, text) if match: try: return match.group(1) except IndexError: return match.group(0) return u"" def func_num(parser, text, length): try: format = "%%0%dd" % min(int(length), 20) except ValueError: return "" try: value = int(text) except ValueError: value = 0 return format % value def func_unset(parser, name): """Unsets the variable ``name``.""" if name.startswith("_"): name = "~" + name[1:] # Allow wild-card unset for certain keys if name in ('performer:*', 'comment:*', 'lyrics:*'): name = name[:-1] for key in parser.context.keys(): if key.startswith(name): del parser.context[key] return "" try: del parser.context[name] except KeyError: pass return "" def func_set(parser, name, value): """Sets the variable ``name`` to ``value``.""" if value: if name.startswith("_"): name = "~" + name[1:] parser.context[name] = value else: func_unset(parser, name) return "" def func_setmulti(parser, name, value, separator=MULTI_VALUED_JOINER): """Sets the variable ``name`` to ``value`` as a list; splitting by the passed string, or "; " otherwise.""" return func_set(parser, name, value.split(separator) if value and separator else value) def func_get(parser, name): """Returns the variable ``name`` (equivalent to ``%name%``).""" if name.startswith("_"): name = "~" + name[1:] return parser.context.get(name, u"") def func_copy(parser, new, old): """Copies content of variable ``old`` to variable ``new``.""" if new.startswith("_"): new = "~" + new[1:] if old.startswith("_"): old = "~" + old[1:] parser.context[new] = parser.context.getall(old)[:] return "" def func_copymerge(parser, new, old): """Copies content of variable ``old`` and appends it into variable ``new``, removing duplicates. This is normally used to merge a multi-valued variable into another, existing multi-valued variable.""" if new.startswith("_"): new = "~" + new[1:] if old.startswith("_"): old = "~" + old[1:] newvals = parser.context.getall(new) oldvals = parser.context.getall(old) parser.context[new] = newvals + list(set(oldvals) - set(newvals)) return "" def func_trim(parser, text, char=None): """Trims all leading and trailing whitespaces from ``text``. The optional second parameter specifies the character to trim.""" if char: return text.strip(char) else: return text.strip() def func_add(parser, x, y, *args): """Adds ``y`` to ``x``. Can be used with an arbitrary number of arguments. Eg: $add(x, y, z) = ((x + y) + z) """ try: return _compute_int(operator.add, x, y, *args) except ValueError: return "" def func_sub(parser, x, y, *args): """Subtracts ``y`` from ``x``. Can be used with an arbitrary number of arguments. Eg: $sub(x, y, z) = ((x - y) - z) """ try: return _compute_int(operator.sub, x, y, *args) except ValueError: return "" def func_div(parser, x, y, *args): """Divides ``x`` by ``y``. Can be used with an arbitrary number of arguments. Eg: $div(x, y, z) = ((x / y) / z) """ try: return _compute_int(operator.div, x, y, *args) except ValueError: return "" def func_mod(parser, x, y, *args): """Returns the remainder of ``x`` divided by ``y``. Can be used with an arbitrary number of arguments. Eg: $mod(x, y, z) = ((x % y) % z) """ try: return _compute_int(operator.mod, x, y, *args) except ValueError: return "" def func_mul(parser, x, y, *args): """Multiplies ``x`` by ``y``. Can be used with an arbitrary number of arguments. Eg: $mul(x, y, z) = ((x * y) * z) """ try: return _compute_int(operator.mul, x, y, *args) except ValueError: return "" def func_or(parser, x, y, *args): """Returns true, if either ``x`` or ``y`` not empty. Can be used with an arbitrary number of arguments. The result is true if ANY of the arguments is not empty. """ if _compute_logic(any, x, y, *args): return "1" else: return "" def func_and(parser, x, y, *args): """Returns true, if both ``x`` and ``y`` are not empty. Can be used with an arbitrary number of arguments. The result is true if ALL of the arguments are not empty. """ if _compute_logic(all, x, y, *args): return "1" else: return "" def func_not(parser, x): """Returns true, if ``x`` is empty.""" if not x: return "1" else: return "" def func_eq(parser, x, y): """Returns true, if ``x`` equals ``y``.""" if x == y: return "1" else: return "" def func_ne(parser, x, y): """Returns true, if ``x`` not equals ``y``.""" if x != y: return "1" else: return "" def func_lt(parser, x, y): """Returns true, if ``x`` is lower than ``y``.""" try: if int(x) < int(y): return "1" except ValueError: pass return "" def func_lte(parser, x, y): """Returns true, if ``x`` is lower than or equals ``y``.""" try: if int(x) <= int(y): return "1" except ValueError: pass return "" def func_gt(parser, x, y): """Returns true, if ``x`` is greater than ``y``.""" try: if int(x) > int(y): return "1" except ValueError: pass return "" def func_gte(parser, x, y): """Returns true, if ``x`` is greater than or equals ``y``.""" try: if int(x) >= int(y): return "1" except ValueError: pass return "" def func_len(parser, text=""): return str(len(text)) def func_performer(parser, pattern="", join=", "): values = [] for name, value in parser.context.items(): if name.startswith("performer:") and pattern in name: values.append(value) return join.join(values) def func_matchedtracks(parser, arg): if parser.file and parser.file.parent: return str(parser.file.parent.album.get_num_matched_tracks()) return "0" def func_is_complete(parser): if (parser.file and parser.file.parent and parser.file.parent.album.is_complete()): return "1" return "0" def func_firstalphachar(parser, text="", nonalpha="#"): if len(text) == 0: return nonalpha firstchar = text[0] if firstchar.isalpha(): return firstchar.upper() else: return nonalpha def func_initials(parser, text=""): return "".join(a[:1] for a in text.split(" ") if a[:1].isalpha()) def func_firstwords(parser, text, length): try: length = int(length) except ValueError as e: length = 0 if len(text) <= length: return text else: if text[length] == ' ': return text[:length] return text[:length].rsplit(' ', 1)[0] def func_startswith(parser, text, prefix): if text.startswith(prefix): return "1" return "0" def func_endswith(parser, text, suffix): if text.endswith(suffix): return "1" return "0" def func_truncate(parser, text, length): try: length = int(length) except ValueError as e: length = None return text[:length].rstrip() def func_swapprefix(parser, text, *prefixes): """ Moves the specified prefixes to the end of text. If no prefix is specified 'A' and 'The' are taken as default. """ # Inspired by the swapprefix plugin by Philipp Wolfer. text, prefix = _delete_prefix(parser, text, *prefixes) if prefix != '': return text + ', ' + prefix return text def func_delprefix(parser, text, *prefixes): """ Deletes the specified prefixes. If no prefix is specified 'A' and 'The' are taken as default. """ # Inspired by the swapprefix plugin by Philipp Wolfer. return _delete_prefix(parser, text, *prefixes)[0] def _delete_prefix(parser, text, *prefixes): """ Worker function to deletes the specified prefixes. Returns remaining string and deleted part separately. If no prefix is specified 'A' and 'The' used. """ # Inspired by the swapprefix plugin by Philipp Wolfer. if not prefixes: prefixes = ('A', 'The') text = text.strip() match = re.match('(' + r'\s+)|('.join(prefixes) + r'\s+)', text) if match: pref = match.group() return text[len(pref):], pref.strip() return text, '' def func_eq_any(parser, x, *args): """ Return True if one string matches any of one or more other strings. $eq_any(a,b,c ...) is functionally equivalent to $or($eq(a,b),$eq(a,c) ...) Example: $if($eq_any(%artist%,foo,bar,baz),$set(engineer,test)) """ # Inspired by the eq2 plugin by Brian Schweitzer. return '1' if x in args else '' def func_ne_all(parser, x, *args): """ Return True if one string doesn't match all of one or more other strings. $ne_all(a,b,c ...) is functionally equivalent to $and($ne(a,b),$ne(a,c) ...) Example: $if($ne_all(%artist%,foo,bar,baz),$set(engineer,test)) """ # Inspired by the ne2 plugin by Brian Schweitzer. return '1' if x not in args else '' def func_eq_all(parser, x, *args): """ Return True if all string are equal. $eq_all(a,b,c ...) is functionally equivalent to $and($eq(a,b),$eq(a,c) ...) Example: $if($eq_all(%albumartist%,%artist%,Justin Bieber),$set(engineer,Meat Loaf)) """ for i in args: if x != i: return '' return '1' def func_ne_any(parser, x, *args): """ Return True if all strings are not equal. $ne_any(a,b,c ...) is functionally equivalent to $or($ne(a,b),$ne(a,c) ...) Example: $if($ne_any(%albumartist%,%trackartist%,%composer%),$set(lyricist,%composer%)) """ return func_not(parser, func_eq_all(parser, x, *args)) register_script_function(func_if, "if", eval_args=False) register_script_function(func_if2, "if2", eval_args=False) register_script_function(func_noop, "noop", eval_args=False) register_script_function(func_left, "left") register_script_function(func_right, "right") register_script_function(func_lower, "lower") register_script_function(func_upper, "upper") register_script_function(func_pad, "pad") register_script_function(func_strip, "strip") register_script_function(func_replace, "replace") register_script_function(func_rreplace, "rreplace") register_script_function(func_rsearch, "rsearch") register_script_function(func_num, "num") register_script_function(func_unset, "unset") register_script_function(func_set, "set") register_script_function(func_setmulti, "setmulti") register_script_function(func_get, "get") register_script_function(func_trim, "trim") register_script_function(func_add, "add") register_script_function(func_sub, "sub") register_script_function(func_div, "div") register_script_function(func_mod, "mod") register_script_function(func_mul, "mul") register_script_function(func_or, "or") register_script_function(func_and, "and") register_script_function(func_not, "not") register_script_function(func_eq, "eq") register_script_function(func_ne, "ne") register_script_function(func_lt, "lt") register_script_function(func_lte, "lte") register_script_function(func_gt, "gt") register_script_function(func_gte, "gte") register_script_function(func_in, "in") register_script_function(func_inmulti, "inmulti") register_script_function(func_copy, "copy") register_script_function(func_copymerge, "copymerge") register_script_function(func_len, "len") register_script_function(func_performer, "performer") register_script_function(func_matchedtracks, "matchedtracks") register_script_function(func_is_complete, "is_complete") register_script_function(func_firstalphachar, "firstalphachar") register_script_function(func_initials, "initials") register_script_function(func_firstwords, "firstwords") register_script_function(func_startswith, "startswith") register_script_function(func_endswith, "endswith") register_script_function(func_truncate, "truncate") register_script_function(func_swapprefix, "swapprefix", check_argcount=False) register_script_function(func_delprefix, "delprefix", check_argcount=False) register_script_function(func_eq_any, "eq_any", check_argcount=False) register_script_function(func_ne_all, "ne_all", check_argcount=False) register_script_function(func_eq_all, "eq_all", check_argcount=False) register_script_function(func_ne_any, "ne_any", check_argcount=False) picard-release-1.4.2/picard/similarity.py000066400000000000000000000041601310410472100203720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import re from picard.util import strip_non_alnum from picard.util.astrcmp import astrcmp def normalize(orig_string): """Strips non-alphanumeric characters from a string unless doing so would make it blank.""" string = strip_non_alnum(orig_string.lower()) if not string: string = orig_string return string def similarity(a1, b1): """Calculates similarity of single words as a function of their edit distance.""" a2 = normalize(a1) if a2: b2 = normalize(b1) else: b2 = "" return astrcmp(a2, b2) _split_words_re = re.compile(r'\W+', re.UNICODE) def similarity2(a, b): """Calculates similarity of a multi-word strings.""" alist = filter(bool, _split_words_re.split(a.lower())) blist = filter(bool, _split_words_re.split(b.lower())) total = 0 score = 0.0 if len(alist) > len(blist): alist, blist = blist, alist for a in alist: ms = 0.0 mp = None for position, b in enumerate(blist): s = astrcmp(a, b) if s > ms: ms = s mp = position if mp is not None: score += ms if ms > 0.6: del blist[mp] total += 1 total += len(blist) * 0.4 if total: return score / total else: return 0 picard-release-1.4.2/picard/tagger.py000066400000000000000000000702131310410472100174570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2004 Robert Kaye # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from __future__ import print_function import sip sip.setapi("QString", 2) sip.setapi("QVariant", 2) from PyQt4 import QtGui, QtCore import argparse import os.path import platform import re import shutil import signal import sys from functools import partial from itertools import chain # A "fix" for http://python.org/sf/1438480 def _patched_shutil_copystat(src, dst): try: _orig_shutil_copystat(src, dst) except OSError: pass _orig_shutil_copystat = shutil.copystat shutil.copystat = _patched_shutil_copystat import picard.resources from picard.i18n import setup_gettext from picard import (PICARD_APP_NAME, PICARD_ORG_NAME, PICARD_FANCY_VERSION_STR, log, acoustid, config) from picard.album import Album, NatAlbum from picard.browser.browser import BrowserIntegration from picard.browser.filelookup import FileLookup from picard.cluster import Cluster, ClusterList, UnmatchedFiles from picard.const import USER_DIR, USER_PLUGIN_DIR from picard.dataobj import DataObject from picard.disc import Disc from picard.file import File from picard.formats import open as open_file from picard.track import Track, NonAlbumTrack from picard.releasegroup import ReleaseGroup from picard.collection import load_user_collections from picard.ui.mainwindow import MainWindow from picard.ui.itemviews import BaseTreeView from picard.plugin import PluginManager from picard.acoustidmanager import AcoustIDManager from picard.config_upgrade import upgrade_config from picard.util import ( decode_filename, encode_filename, thread, mbid_validate, check_io_encoding, uniqify, is_hidden, versions, ) from picard.webservice import XmlWebService from picard.ui.searchdialog import ( TrackSearchDialog, AlbumSearchDialog, ArtistSearchDialog ) class Tagger(QtGui.QApplication): tagger_stats_changed = QtCore.pyqtSignal() listen_port_changed = QtCore.pyqtSignal(int) cluster_added = QtCore.pyqtSignal(Cluster) cluster_removed = QtCore.pyqtSignal(Cluster) album_added = QtCore.pyqtSignal(Album) album_removed = QtCore.pyqtSignal(Album) __instance = None def __init__(self, picard_args, unparsed_args, localedir, autoupdate): # Set the WM_CLASS to 'MusicBrainz-Picard' so desktop environments # can use it to look up the app QtGui.QApplication.__init__(self, ['MusicBrainz-Picard'] + unparsed_args) self.__class__.__instance = self config._setup(self, picard_args.config_file) self._cmdline_files = picard_args.FILE self._autoupdate = autoupdate self._debug = False # FIXME: Figure out what's wrong with QThreadPool.globalInstance(). # It's a valid reference, but its start() method doesn't work. self.thread_pool = QtCore.QThreadPool(self) # Use a separate thread pool for file saving, with a thread count of 1, # to avoid race conditions in File._save_and_rename. self.save_thread_pool = QtCore.QThreadPool(self) self.save_thread_pool.setMaxThreadCount(1) if not sys.platform == "win32": # Set up signal handling # It's not possible to call all available functions from signal # handlers, therefore we need to set up a QSocketNotifier to listen # on a socket. Sending data through a socket can be done in a # signal handler, so we use the socket to notify the application of # the signal. # This code is adopted from # https://qt-project.org/doc/qt-4.8/unix-signals.html # To not make the socket module a requirement for the Windows # installer, import it here and not globally import socket self.signalfd = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0) self.signalnotifier = QtCore.QSocketNotifier(self.signalfd[1].fileno(), QtCore.QSocketNotifier.Read, self) self.signalnotifier.activated.connect(self.sighandler) signal.signal(signal.SIGHUP, self.signal) signal.signal(signal.SIGINT, self.signal) signal.signal(signal.SIGTERM, self.signal) # Setup logging self.debug(picard_args.debug or "PICARD_DEBUG" in os.environ) log.debug("Starting Picard from %r", os.path.abspath(__file__)) log.debug("Platform: %s %s %s", platform.platform(), platform.python_implementation(), platform.python_version()) log.debug("Versions: %s", versions.as_string()) log.debug("Configuration file path: %r", config.config.fileName()) # TODO remove this before the final release if sys.platform == "win32": olduserdir = "~\\Local Settings\\Application Data\\MusicBrainz Picard" else: olduserdir = "~/.picard" olduserdir = os.path.expanduser(olduserdir) if os.path.isdir(olduserdir): log.info("Moving %r to %r", olduserdir, USER_DIR) try: shutil.move(olduserdir, USER_DIR) except: pass log.debug("User directory: %r", os.path.abspath(USER_DIR)) # for compatibility with pre-1.3 plugins QtCore.QObject.tagger = self QtCore.QObject.config = config QtCore.QObject.log = log check_io_encoding() # Must be before config upgrade because upgrade dialogs need to be # translated setup_gettext(localedir, config.setting["ui_language"], log.debug) upgrade_config() self.xmlws = XmlWebService() load_user_collections() # Initialize fingerprinting self._acoustid = acoustid.AcoustIDClient() self._acoustid.init() # Load plugins self.pluginmanager = PluginManager() if hasattr(sys, "frozen"): self.pluginmanager.load_plugindir(os.path.join(os.path.dirname(sys.argv[0]), "plugins")) else: mydir = os.path.dirname(os.path.abspath(__file__)) self.pluginmanager.load_plugindir(os.path.join(mydir, "plugins")) self.pluginmanager.load_plugindir(os.path.join(mydir, os.pardir, "contrib", "plugins")) if not os.path.exists(USER_PLUGIN_DIR): os.makedirs(USER_PLUGIN_DIR) self.pluginmanager.load_plugindir(USER_PLUGIN_DIR) self.pluginmanager.query_available_plugins() self.acoustidmanager = AcoustIDManager() self.browser_integration = BrowserIntegration() self.files = {} self.clusters = ClusterList() self.albums = {} self.release_groups = {} self.mbid_redirects = {} self.unmatched_files = UnmatchedFiles() self.nats = None self.window = MainWindow() self.exit_cleanup = [] self.stopping = False def register_cleanup(self, func): self.exit_cleanup.append(func) def run_cleanup(self): for f in self.exit_cleanup: f() def debug(self, debug): if self._debug == debug: return if debug: log.log_levels = log.log_levels | log.LOG_DEBUG log.debug("Debug mode on") else: log.debug("Debug mode off") log.log_levels = log.log_levels & ~log.LOG_DEBUG self._debug = debug def move_files_to_album(self, files, albumid=None, album=None): """Move `files` to tracks on album `albumid`.""" if album is None: album = self.load_album(albumid) if album.loaded: album.match_files(files) else: for file in list(files): file.move(album.unmatched_files) def move_file_to_album(self, file, albumid): """Move `file` to a track on album `albumid`.""" self.move_files_to_album([file], albumid) def move_file_to_track(self, file, albumid, recordingid): """Move `file` to recording `recordingid` on album `albumid`.""" album = self.load_album(albumid) file.move(album.unmatched_files) album.run_when_loaded(partial(album.match_file, file, recordingid)) def create_nats(self): if self.nats is None: self.nats = NatAlbum() self.albums["NATS"] = self.nats self.album_added.emit(self.nats) return self.nats def move_file_to_nat(self, file, recordingid, node=None): self.create_nats() file.move(self.nats.unmatched_files) nat = self.load_nat(recordingid, node=node) nat.run_when_loaded(partial(file.move, nat)) if nat.loaded: self.nats.update() def exit(self): log.debug("Picard stopping") self.stopping = True self._acoustid.done() self.thread_pool.waitForDone() self.save_thread_pool.waitForDone() self.browser_integration.stop() self.xmlws.stop() self.run_cleanup() QtCore.QCoreApplication.processEvents() def _run_init(self): if self._cmdline_files: files = [] for file in self._cmdline_files: if os.path.isdir(file): self.add_directory(decode_filename(file)) else: files.append(decode_filename(file)) if files: self.add_files(files) del self._cmdline_files def run(self): if config.setting["browser_integration"]: self.browser_integration.start() self.window.show() QtCore.QTimer.singleShot(0, self._run_init) res = self.exec_() self.exit() return res def event(self, event): if isinstance(event, thread.ProxyToMainEvent): event.run() elif event.type() == QtCore.QEvent.FileOpen: f = str(event.file()) self.add_files([f]) # We should just return True here, except that seems to # cause the event's sender to get a -9874 error, so # apparently there's some magic inside QFileOpenEvent... return 1 return QtGui.QApplication.event(self, event) def _file_loaded(self, file, target=None): if file is not None and not file.has_error(): recordingid = file.metadata.getall('musicbrainz_recordingid')[0] \ if 'musicbrainz_recordingid' in file.metadata else '' if target is not None: self.move_files([file], target) elif not config.setting["ignore_file_mbids"]: albumid = file.metadata.getall('musicbrainz_albumid')[0] \ if 'musicbrainz_albumid' in file.metadata else '' if mbid_validate(albumid): if mbid_validate(recordingid): self.move_file_to_track(file, albumid, recordingid) else: self.move_file_to_album(file, albumid) elif mbid_validate(recordingid): self.move_file_to_nat(file, recordingid) elif config.setting['analyze_new_files'] and file.can_analyze(): self.analyze([file]) elif config.setting['analyze_new_files'] and file.can_analyze(): self.analyze([file]) def move_files(self, files, target): if target is None: log.debug("Aborting move since target is invalid") return if isinstance(target, (Track, Cluster)): for file in files: file.move(target) elif isinstance(target, File): for file in files: file.move(target.parent) elif isinstance(target, Album): self.move_files_to_album(files, album=target) elif isinstance(target, ClusterList): self.cluster(files) def add_files(self, filenames, target=None): """Add files to the tagger.""" ignoreregex = None pattern = config.setting['ignore_regex'] if pattern: ignoreregex = re.compile(pattern) ignore_hidden = config.setting["ignore_hidden_files"] new_files = [] for filename in filenames: filename = os.path.normpath(os.path.realpath(filename)) if ignore_hidden and is_hidden(filename): log.debug("File ignored (hidden): %r" % (filename)) continue if ignoreregex is not None and ignoreregex.search(filename): log.info("File ignored (matching %r): %r" % (pattern, filename)) continue if filename not in self.files: file = open_file(filename) if file: self.files[filename] = file new_files.append(file) if new_files: log.debug("Adding files %r", new_files) new_files.sort(key=lambda x: x.filename) if target is None or target is self.unmatched_files: self.unmatched_files.add_files(new_files) target = None for file in new_files: file.load(partial(self._file_loaded, target=target)) def add_directory(self, path): if config.setting['recursively_add_files']: self._add_directory_recursive(path) else: self._add_directory_non_recursive(path) def _add_directory_recursive(self, path): ignore_hidden = config.setting["ignore_hidden_files"] walk = os.walk(unicode(path)) def get_files(): try: root, dirs, files = next(walk) if ignore_hidden: dirs[:] = [d for d in dirs if not is_hidden(os.path.join(root, d))] except StopIteration: return None else: number_of_files = len(files) if number_of_files: mparms = { 'count': number_of_files, 'directory': root, } log.debug("Adding %(count)d files from '%(directory)r'" % mparms) self.window.set_statusbar_message( ungettext( "Adding %(count)d file from '%(directory)s' ...", "Adding %(count)d files from '%(directory)s' ...", number_of_files), mparms, translate=None, echo=None ) return (os.path.join(root, f) for f in files) def process(result=None, error=None): if result: if error is None: self.add_files(result) thread.run_task(get_files, process) process(True, False) def _add_directory_non_recursive(self, path): files = [] for f in os.listdir(path): listing = os.path.join(path, f) if os.path.isfile(listing): files.append(listing) number_of_files = len(files) if number_of_files: mparms = { 'count': number_of_files, 'directory': path, } log.debug("Adding %(count)d files from '%(directory)r'" % mparms) self.window.set_statusbar_message( ungettext( "Adding %(count)d file from '%(directory)s' ...", "Adding %(count)d files from '%(directory)s' ...", number_of_files), mparms, translate=None, echo=None ) # Function call only if files exist self.add_files(files) def get_file_lookup(self): """Return a FileLookup object.""" return FileLookup(self, config.setting["server_host"], config.setting["server_port"], self.browser_integration.port) def copy_files(self, objects): mimeData = QtCore.QMimeData() mimeData.setUrls([QtCore.QUrl.fromLocalFile(f.filename) for f in (self.get_files_from_objects(objects))]) self.clipboard().setMimeData(mimeData) def paste_files(self, target): mimeData = self.clipboard().mimeData() if mimeData.hasUrls(): BaseTreeView.drop_urls(mimeData.urls(), target) def search(self, text, type, adv=False): """Search on the MusicBrainz website.""" lookup = self.get_file_lookup() if config.setting["builtin_search"]: if type == "track" and not lookup.mbidLookup(text, 'recording'): dialog = TrackSearchDialog(self.window) dialog.search(text) dialog.exec_() elif type == "album" and not lookup.mbidLookup(text, 'release'): dialog = AlbumSearchDialog(self.window) dialog.search(text) dialog.exec_() elif type == "artist" and not lookup.mbidLookup(text, 'artist'): dialog = ArtistSearchDialog(self.window) dialog.search(text) dialog.exec_() else: getattr(lookup, type + "Search")(text, adv) def collection_lookup(self): """Lookup the users collections on the MusicBrainz website.""" lookup = self.get_file_lookup() lookup.collectionLookup(config.persist["oauth_username"]) def browser_lookup(self, item): """Lookup the object's metadata on the MusicBrainz website.""" lookup = self.get_file_lookup() metadata = item.metadata # Only lookup via MB IDs if matched to a DataObject; otherwise ignore and use metadata details if isinstance(item, DataObject): itemid = item.id if isinstance(item, Track): lookup.recordingLookup(itemid) elif isinstance(item, Album): lookup.albumLookup(itemid) else: lookup.tagLookup( metadata["albumartist"] if item.is_album_like() else metadata["artist"], metadata["album"], metadata["title"], metadata["tracknumber"], '' if item.is_album_like() else str(metadata.length), item.filename if isinstance(item, File) else '') def get_files_from_objects(self, objects, save=False): """Return list of files from list of albums, clusters, tracks or files.""" return uniqify(chain(*[obj.iterfiles(save) for obj in objects])) def save(self, objects): """Save the specified objects.""" files = self.get_files_from_objects(objects, save=True) for file in files: file.save() def load_album(self, id, discid=None): id = self.mbid_redirects.get(id, id) album = self.albums.get(id) if album: log.debug("Album %s already loaded.", id) return album album = Album(id, discid=discid) self.albums[id] = album self.album_added.emit(album) album.load() return album def load_nat(self, id, node=None): self.create_nats() nat = self.get_nat_by_id(id) if nat: log.debug("NAT %s already loaded.", id) return nat nat = NonAlbumTrack(id) self.nats.tracks.append(nat) self.nats.update(True) if node: nat._parse_recording(node) else: nat.load() return nat def get_nat_by_id(self, id): if self.nats is not None: for nat in self.nats.tracks: if nat.id == id: return nat def get_release_group_by_id(self, id): return self.release_groups.setdefault(id, ReleaseGroup(id)) def remove_files(self, files, from_parent=True): """Remove files from the tagger.""" for file in files: if file.filename in self.files: file.clear_lookup_task() self._acoustid.stop_analyze(file) del self.files[file.filename] file.remove(from_parent) def remove_album(self, album): """Remove the specified album.""" log.debug("Removing %r", album) album.stop_loading() self.remove_files(self.get_files_from_objects([album])) del self.albums[album.id] if album.release_group: album.release_group.remove_album(album.id) if album == self.nats: self.nats = None self.album_removed.emit(album) def remove_cluster(self, cluster): """Remove the specified cluster.""" if not cluster.special: log.debug("Removing %r", cluster) files = list(cluster.files) cluster.files = [] cluster.clear_lookup_task() self.remove_files(files, from_parent=False) self.clusters.remove(cluster) self.cluster_removed.emit(cluster) def remove(self, objects): """Remove the specified objects.""" files = [] for obj in objects: if isinstance(obj, File): files.append(obj) elif isinstance(obj, Track): files.extend(obj.linked_files) elif isinstance(obj, Album): self.window.set_statusbar_message( N_("Removing album %(id)s: %(artist)s - %(album)s"), { 'id': obj.id, 'artist': obj.metadata['albumartist'], 'album': obj.metadata['album'] } ) self.remove_album(obj) elif isinstance(obj, UnmatchedFiles): files.extend(list(obj.files)) elif isinstance(obj, Cluster): self.remove_cluster(obj) if files: self.remove_files(files) def _lookup_disc(self, disc, result=None, error=None): self.restore_cursor() if error is not None: QtGui.QMessageBox.critical(self.window, _(u"CD Lookup Error"), _(u"Error while reading CD:\n\n%s") % error) else: disc.lookup() def lookup_cd(self, action): """Reads CD from the selected drive and tries to lookup the DiscID on MusicBrainz.""" if isinstance(action, QtGui.QAction): device = unicode(action.text()) elif config.setting["cd_lookup_device"] != '': device = config.setting["cd_lookup_device"].split(",", 1)[0] else: # rely on python-discid auto detection device = None disc = Disc() self.set_wait_cursor() thread.run_task( partial(disc.read, encode_filename(device)), partial(self._lookup_disc, disc)) @property def use_acoustid(self): return config.setting["fingerprinting_system"] == "acoustid" def analyze(self, objs): """Analyze the file(s).""" files = self.get_files_from_objects(objs) for file in files: file.set_pending() if self.use_acoustid: self._acoustid.analyze(file, partial(file._lookup_finished, 'acoustid')) # ======================================================================= # Metadata-based lookups # ======================================================================= def autotag(self, objects): for obj in objects: if obj.can_autotag(): obj.lookup_metadata() # ======================================================================= # Clusters # ======================================================================= def cluster(self, objs): """Group files with similar metadata to 'clusters'.""" log.debug("Clustering %r", objs) if len(objs) <= 1 or self.unmatched_files in objs: files = list(self.unmatched_files.files) else: files = self.get_files_from_objects(objs) fcmp = lambda a, b: ( cmp(a.discnumber, b.discnumber) or cmp(a.tracknumber, b.tracknumber) or cmp(a.base_filename, b.base_filename)) for name, artist, files in Cluster.cluster(files, 1.0): QtCore.QCoreApplication.processEvents() cluster = self.load_cluster(name, artist) for file in sorted(files, fcmp): file.move(cluster) def load_cluster(self, name, artist): for cluster in self.clusters: cm = cluster.metadata if name == cm["album"] and artist == cm["albumartist"]: return cluster cluster = Cluster(name, artist) self.clusters.append(cluster) self.cluster_added.emit(cluster) return cluster # ======================================================================= # Utils # ======================================================================= def set_wait_cursor(self): """Sets the waiting cursor.""" QtGui.QApplication.setOverrideCursor( QtGui.QCursor(QtCore.Qt.WaitCursor)) def restore_cursor(self): """Restores the cursor set by ``set_wait_cursor``.""" QtGui.QApplication.restoreOverrideCursor() def refresh(self, objs): for obj in objs: if obj.can_refresh(): obj.load(priority=True, refresh=True) def bring_tagger_front(self): self.window.setWindowState(self.window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) self.window.raise_() self.window.activateWindow() @classmethod def instance(cls): return cls.__instance def signal(self, signum, frame): log.debug("signal %i received", signum) # Send a notification about a received signal from the signal handler # to Qt. self.signalfd[0].sendall("a") def sighandler(self): self.signalnotifier.setEnabled(False) self.exit() self.quit() self.signalnotifier.setEnabled(True) def version(): print("%s %s %s" % (PICARD_ORG_NAME, PICARD_APP_NAME, PICARD_FANCY_VERSION_STR)) def longversion(): print(versions.as_string()) def process_picard_args(): parser = argparse.ArgumentParser( epilog="If one of the filenames begins with a hyphen, use -- to separate the options from the filenames." ) parser.add_argument("-c", "--config-file", action='store', default=None, help="location of the configuration file") parser.add_argument("-d", "--debug", action='store_true', help="enable debug-level logging") parser.add_argument('-v', '--version', action='store_true', help="display version information and exit") parser.add_argument("-V", "--long-version", action='store_true', help="display long version information and exit") parser.add_argument('FILE', nargs='*') picard_args, unparsed_args = parser.parse_known_args() return picard_args, unparsed_args def main(localedir=None, autoupdate=True): # Some libs (ie. Phonon) require those to be set QtGui.QApplication.setApplicationName(PICARD_APP_NAME) QtGui.QApplication.setOrganizationName(PICARD_ORG_NAME) signal.signal(signal.SIGINT, signal.SIG_DFL) picard_args, unparsed_args = process_picard_args() if picard_args.version: return version() if picard_args.long_version: return longversion() tagger = Tagger(picard_args, unparsed_args, localedir, autoupdate) tagger.startTimer(1000) sys.exit(tagger.run()) picard-release-1.4.2/picard/track.py000066400000000000000000000257361310410472100173240ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2004 Robert Kaye # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from functools import partial from PyQt4 import QtCore from picard import config, log from picard.metadata import Metadata, run_track_metadata_processors from picard.dataobj import DataObject from picard.util.textencoding import asciipunct from picard.mbxml import recording_to_metadata from picard.script import ScriptParser from picard.const import VARIOUS_ARTISTS_ID, SILENCE_TRACK_TITLE, DATA_TRACK_TITLE from picard.ui.item import Item from picard.util.imagelist import ImageList, update_metadata_images import traceback _TRANSLATE_TAGS = { "hip hop": u"Hip-Hop", "synth-pop": u"Synthpop", "electronica": u"Electronic", } class TrackArtist(DataObject): def __init__(self, id): DataObject.__init__(self, id) class Track(DataObject, Item): metadata_images_changed = QtCore.pyqtSignal() def __init__(self, id, album=None): DataObject.__init__(self, id) self.album = album self.linked_files = [] self.num_linked_files = 0 self.metadata = Metadata() self.orig_metadata = Metadata() self._track_artists = [] def __repr__(self): return '' % (self.id, self.metadata["title"]) def add_file(self, file): if file not in self.linked_files: self.linked_files.append(file) self.num_linked_files += 1 self.album._add_file(self, file) self.update_file_metadata(file) file.metadata_images_changed.connect(self.update_orig_metadata_images) def update_file_metadata(self, file): if file not in self.linked_files: return file.copy_metadata(self.metadata) file.metadata['~extension'] = file.orig_metadata['~extension'] file.metadata.changed = True file.update(signal=False) self.update() def remove_file(self, file): if file not in self.linked_files: return self.linked_files.remove(file) self.num_linked_files -= 1 file.copy_metadata(file.orig_metadata) self.album._remove_file(self, file) file.metadata_images_changed.disconnect(self.update_orig_metadata_images) self.update() def update(self): if self.item: self.item.update() self.update_orig_metadata_images() def iterfiles(self, save=False): for file in self.linked_files: yield file def is_linked(self): return self.num_linked_files > 0 def can_save(self): """Return if this object can be saved.""" for file in self.linked_files: if file.can_save(): return True return False def can_remove(self): """Return if this object can be removed.""" for file in self.linked_files: if file.can_remove(): return True return False def can_edit_tags(self): """Return if this object supports tag editing.""" return True def can_view_info(self): return self.num_linked_files == 1 or self.metadata.images def column(self, column): m = self.metadata if column == 'title': prefix = "%s-" % m['discnumber'] if m['discnumber'] and m['totaldiscs'] != "1" else "" return u"%s%s %s" % (prefix, m['tracknumber'].zfill(2), m['title']) return m[column] def is_video(self): return self.metadata['~video'] == '1' def is_pregap(self): return self.metadata['~pregap'] == '1' def is_data(self): return self.metadata['~datatrack'] == '1' def is_silence(self): return self.metadata['~silence'] == '1' def is_complete(self): return self.ignored_for_completeness() or self.num_linked_files == 1 def ignored_for_completeness(self): if (config.setting['completeness_ignore_videos'] and self.is_video()) \ or (config.setting['completeness_ignore_pregap'] and self.is_pregap()) \ or (config.setting['completeness_ignore_data'] and self.is_data()) \ or (config.setting['completeness_ignore_silence'] and self.is_silence()): return True return False def append_track_artist(self, id): """Append artist id to the list of track artists and return an TrackArtist instance""" track_artist = TrackArtist(id) self._track_artists.append(track_artist) return track_artist def _customize_metadata(self): tm = self.metadata # Custom VA name if tm['musicbrainz_artistid'] == VARIOUS_ARTISTS_ID: tm['artistsort'] = tm['artist'] = config.setting['va_name'] if tm['title'] == DATA_TRACK_TITLE: tm['~datatrack'] = '1' if tm['title'] == SILENCE_TRACK_TITLE: tm['~silence'] = '1' if config.setting['folksonomy_tags']: self._convert_folksonomy_tags_to_genre() # Convert Unicode punctuation if config.setting['convert_punctuation']: tm.apply_func(asciipunct) def _convert_folksonomy_tags_to_genre(self): # Combine release and track tags tags = dict(self.folksonomy_tags) self.merge_folksonomy_tags(tags, self.album.folksonomy_tags) if self.album.release_group: self.merge_folksonomy_tags(tags, self.album.release_group.folksonomy_tags) if not tags and config.setting['artists_tags']: # For compilations use each track's artists to look up tags if self.metadata['musicbrainz_albumartistid'] == VARIOUS_ARTISTS_ID: for artist in self._track_artists: self.merge_folksonomy_tags(tags, artist.folksonomy_tags) else: for artist in self.album.get_album_artists(): self.merge_folksonomy_tags(tags, artist.folksonomy_tags) # Ignore tags with zero or lower score tags = dict((name, count) for name, count in tags.items() if count > 0) if not tags: return # Convert counts to values from 0 to 100 maxcount = max(tags.values()) taglist = [] for name, count in tags.items(): taglist.append((100 * count / maxcount, name)) taglist.sort(reverse=True) # And generate the genre metadata tag maxtags = config.setting['max_tags'] minusage = config.setting['min_tag_usage'] ignore_tags = self._get_ignored_folksonomy_tags() genre = [] for usage, name in taglist[:maxtags]: if name.lower() in ignore_tags: continue if usage < minusage: break name = _TRANSLATE_TAGS.get(name, name.title()) genre.append(name) join_tags = config.setting['join_tags'] if join_tags: genre = [join_tags.join(genre)] self.metadata['genre'] = genre def _get_ignored_folksonomy_tags(self): tags = [] ignore_tags = config.setting['ignore_tags'] if ignore_tags: tags = [s.strip().lower() for s in ignore_tags.split(',')] return tags def update_orig_metadata_images(self): update_metadata_images(self) def keep_original_images(self): for file in self.linked_files: file.keep_original_images() if self.linked_files: self.update_orig_metadata_images() self.metadata.images = self.orig_metadata.images[:] else: self.metadata.images = [] self.update() class NonAlbumTrack(Track): def __init__(self, id): Track.__init__(self, id, self.tagger.nats) self.callback = None self.loaded = False def can_refresh(self): return True def column(self, column): if column == "title": return self.metadata["title"] return Track.column(self, column) def load(self, priority=False, refresh=False): self.metadata.copy(self.album.metadata) self.metadata["title"] = u"[loading track information]" self.loaded = False self.tagger.nats.update(True) mblogin = False inc = ["artist-credits", "artists", "aliases"] if config.setting["track_ars"]: inc += ["artist-rels", "url-rels", "recording-rels", "work-rels", "work-level-rels"] if config.setting["folksonomy_tags"]: if config.setting["only_my_tags"]: mblogin = True inc += ["user-tags"] else: inc += ["tags"] if config.setting["enable_ratings"]: mblogin = True inc += ["user-ratings"] self.tagger.xmlws.get_track_by_id(self.id, partial(self._recording_request_finished), inc, mblogin=mblogin, priority=priority, refresh=refresh) def _recording_request_finished(self, document, http, error): if error: log.error("%r", unicode(http.errorString())) return try: recording = document.metadata[0].recording[0] self._parse_recording(recording) for file in self.linked_files: self.update_file_metadata(file) except: log.error(traceback.format_exc()) def _parse_recording(self, recording): m = self.metadata recording_to_metadata(recording, m, self) self._customize_metadata() run_track_metadata_processors(self.album, m, None, recording) if config.setting["enable_tagger_scripts"]: for s_pos, s_name, s_enabled, s_text in config.setting["list_of_scripts"]: if s_enabled and s_text: parser = ScriptParser() try: parser.eval(s_text, m) except: log.error(traceback.format_exc()) m.strip_whitespace() self.loaded = True if self.callback: self.callback() self.callback = None self.tagger.nats.update(True) def run_when_loaded(self, func): if self.loaded: func() else: self.callback = func picard-release-1.4.2/picard/ui/000077500000000000000000000000001310410472100162465ustar00rootroot00000000000000picard-release-1.4.2/picard/ui/__init__.py000066400000000000000000000020431310410472100203560ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtGui class PicardDialog(QtGui.QDialog): flags = QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint def __init__(self, parent=None): QtGui.QDialog.__init__(self, parent, self.flags) picard-release-1.4.2/picard/ui/cdlookup.py000066400000000000000000000063561310410472100204520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtGui from picard.ui import PicardDialog from picard.ui.ui_cdlookup import Ui_Dialog from picard.mbxml import artist_credit_from_node, label_info_from_node class CDLookupDialog(PicardDialog): def __init__(self, releases, disc, parent=None): PicardDialog.__init__(self, parent) self.releases = releases self.disc = disc self.ui = Ui_Dialog() self.ui.setupUi(self) self.ui.release_list.setSortingEnabled(True) self.ui.release_list.setHeaderLabels([_(u"Album"), _(u"Artist"), _(u"Date"), _(u"Country"), _(u"Labels"), _(u"Catalog #s"), _(u"Barcode")]) if self.releases: for release in self.releases: labels, catalog_numbers = label_info_from_node(release.label_info_list[0]) date = release.date[0].text if "date" in release.children else "" country = release.country[0].text if "country" in release.children else "" barcode = release.barcode[0].text if "barcode" in release.children else "" item = QtGui.QTreeWidgetItem(self.ui.release_list) item.setText(0, release.title[0].text) item.setText(1, artist_credit_from_node(release.artist_credit[0])[0]) item.setText(2, date) item.setText(3, country) item.setText(4, ", ".join(labels)) item.setText(5, ", ".join(catalog_numbers)) item.setText(6, barcode) item.setData(0, QtCore.Qt.UserRole, release.id) self.ui.release_list.setCurrentItem(self.ui.release_list.topLevelItem(0)) self.ui.ok_button.setEnabled(True) for i in range(self.ui.release_list.columnCount() - 1): self.ui.release_list.resizeColumnToContents(i) # Sort by descending date, then ascending country self.ui.release_list.sortByColumn(3, QtCore.Qt.AscendingOrder) self.ui.release_list.sortByColumn(2, QtCore.Qt.DescendingOrder) self.ui.lookup_button.clicked.connect(self.lookup) def accept(self): release_id = self.ui.release_list.currentItem().data(0, QtCore.Qt.UserRole) self.tagger.load_album(release_id, discid=self.disc.id) QtGui.QDialog.accept(self) def lookup(self): lookup = self.tagger.get_file_lookup() lookup.discLookup(self.disc.submission_url) QtGui.QDialog.accept(self) picard-release-1.4.2/picard/ui/collectionmenu.py000066400000000000000000000061641310410472100216470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2013 Michael Wiencek # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import locale from PyQt4 import QtCore, QtGui from picard.collection import user_collections, load_user_collections class CollectionMenu(QtGui.QMenu): def __init__(self, albums, *args): QtGui.QMenu.__init__(self, *args) self.ids = set(a.id for a in albums) self.update_collections() def update_collections(self): self.clear() for id, collection in sorted(user_collections.iteritems(), key=lambda k_v: (locale.strxfrm(k_v[1].name.encode('utf-8')), k_v[0])): action = QtGui.QWidgetAction(self) action.setDefaultWidget(CollectionCheckBox(self, collection)) self.addAction(action) self.addSeparator() self.refresh_action = self.addAction(_("Refresh List")) def refresh_list(self): self.refresh_action.setEnabled(False) load_user_collections(self.update_collections) def mouseReleaseEvent(self, event): # Not using self.refresh_action.triggered because it closes the menu if self.actionAt(event.pos()) == self.refresh_action and self.refresh_action.isEnabled(): self.refresh_list() class CollectionCheckBox(QtGui.QCheckBox): def __init__(self, menu, collection): self.menu = menu self.collection = collection QtGui.QCheckBox.__init__(self, self.label()) releases = collection.releases & menu.ids if len(releases) == len(menu.ids): self.setCheckState(QtCore.Qt.Checked) elif not releases: self.setCheckState(QtCore.Qt.Unchecked) else: self.setCheckState(QtCore.Qt.PartiallyChecked) def nextCheckState(self): ids = self.menu.ids if ids & self.collection.pending: return diff = ids - self.collection.releases if diff: self.collection.add_releases(diff, self.updateText) self.setCheckState(QtCore.Qt.Checked) else: self.collection.remove_releases(ids & self.collection.releases, self.updateText) self.setCheckState(QtCore.Qt.Unchecked) def updateText(self): self.setText(self.label()) def label(self): c = self.collection return ungettext("%s (%i release)", "%s (%i releases)", c.size) % (c.name, c.size) picard-release-1.4.2/picard/ui/coverartbox.py000066400000000000000000000463251310410472100211700ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006,2011 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import sys from functools import partial from PyQt4 import QtCore, QtGui, QtNetwork from picard import config, log from picard.album import Album from picard.coverart.image import CoverArtImage, CoverArtImageError from picard.track import Track from picard.file import File from picard.util import imageinfo from picard.util.lrucache import LRUCache from picard.const import MAX_COVERS_TO_STACK if sys.platform == 'darwin': try: from Foundation import NSURL NSURL_IMPORTED = True except ImportError: NSURL_IMPORTED = False log.warning("Unable to import NSURL, file drag'n'drop might not work correctly") class ActiveLabel(QtGui.QLabel): """Clickable QLabel.""" clicked = QtCore.pyqtSignal() image_dropped = QtCore.pyqtSignal(QtCore.QUrl, QtCore.QByteArray) def __init__(self, active=True, drops=False, *args): QtGui.QLabel.__init__(self, *args) self.setMargin(0) self.setActive(active) self.setAcceptDrops(drops) def setActive(self, active): self.active = active if self.active: self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) else: self.setCursor(QtGui.QCursor()) def mouseReleaseEvent(self, event): if self.active and event.button() == QtCore.Qt.LeftButton: self.clicked.emit() def dragEnterEvent(self, event): for url in event.mimeData().urls(): if url.scheme() in ('https', 'http', 'file'): event.acceptProposedAction() break def dropEvent(self, event): accepted = False # Chromium includes the actual data of the dragged image in the drop event. This # is useful for Google Images, where the url links to the page that contains the image # so we use it if the downloaded url is not an image. dropped_data = event.mimeData().data('application/octet-stream') for url in event.mimeData().urls(): if url.scheme() in ('https', 'http', 'file'): accepted = True self.image_dropped.emit(url, dropped_data) if accepted: event.acceptProposedAction() class CoverArtThumbnail(ActiveLabel): def __init__(self, active=False, drops=False, pixmap_cache=None, *args, **kwargs): super(CoverArtThumbnail, self).__init__(active, drops, *args, **kwargs) self.data = None self.has_common_images = None self.shadow = QtGui.QPixmap(":/images/CoverArtShadow.png") self.release = None self.setPixmap(self.shadow) self.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter) self.clicked.connect(self.open_release_page) self.related_images = [] self._pixmap_cache = pixmap_cache self.current_pixmap_key = None def __eq__(self, other): if len(self.data) or len(other.data): return self.current_pixmap_key == other.current_pixmap_key else: return True def show(self): self.set_data(self.data, True) def decorate_cover(self, pixmap): offx, offy, w, h = (1, 1, 121, 121) cover = QtGui.QPixmap(self.shadow) pixmap = pixmap.scaled(w, h, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) painter = QtGui.QPainter(cover) bgcolor = QtGui.QColor.fromRgb(0, 0, 0, 128) painter.fillRect(QtCore.QRectF(offx, offy, w, h), bgcolor) x = offx + (w - pixmap.width()) / 2 y = offy + (h - pixmap.height()) / 2 painter.drawPixmap(x, y, pixmap) painter.end() return cover def set_data(self, data, force=False, has_common_images=True): if not force and self.data == data and self.has_common_images == has_common_images: return self.data = data self.has_common_images = has_common_images if not force and self.parent().isHidden(): return if not self.data: self.setPixmap(self.shadow) self.current_pixmap_key = None return if len(self.data) == 1: has_common_images = True w, h, displacements = (128, 128, 20) key = hash(tuple(sorted(self.data)) + (has_common_images,)) try: pixmap = self._pixmap_cache[key] except KeyError: if len(self.data) == 1: pixmap = QtGui.QPixmap() pixmap.loadFromData(self.data[0].data) pixmap = self.decorate_cover(pixmap) else: limited = len(self.data) > MAX_COVERS_TO_STACK if limited: data_to_paint = data[:MAX_COVERS_TO_STACK - 1] offset = displacements * len(data_to_paint) else: data_to_paint = data offset = displacements * (len(data_to_paint) - 1) stack_width, stack_height = (w + offset, h + offset) pixmap = QtGui.QPixmap(stack_width, stack_height) bgcolor = self.palette().color(QtGui.QPalette.Window) painter = QtGui.QPainter(pixmap) painter.fillRect(QtCore.QRectF(0, 0, stack_width, stack_height), bgcolor) cx = stack_width - w / 2 cy = h / 2 if limited: x, y = (cx - self.shadow.width() / 2, cy - self.shadow.height() / 2) for i in range(3): painter.drawPixmap(x, y, self.shadow) x -= displacements / 3 y += displacements / 3 cx -= displacements cy += displacements else: cx = stack_width - w / 2 cy = h / 2 for image in reversed(data_to_paint): if isinstance(image, QtGui.QPixmap): thumb = image else: thumb = QtGui.QPixmap() thumb.loadFromData(image.data) thumb = self.decorate_cover(thumb) x, y = (cx - thumb.width() / 2, cy - thumb.height() / 2) painter.drawPixmap(x, y, thumb) cx -= displacements cy += displacements if not has_common_images: color = QtGui.QColor("darkgoldenrod") border_length = 10 for k in range(border_length): color.setAlpha(255 - k * 255 / border_length) painter.setPen(color) painter.drawLine(x, y - k - 1, x + 121 + k + 1, y - k - 1) painter.drawLine(x + 121 + k + 2, y - 1 - k, x + 121 + k + 2, y + 121 + 4) for k in range(5): bgcolor.setAlpha(80 + k * 255 / 7) painter.setPen(bgcolor) painter.drawLine(x + 121 + 2, y + 121 + 2 + k, x + 121 + border_length + 2, y + 121 + 2 + k) painter.end() pixmap = pixmap.scaled(w, h, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) self._pixmap_cache[key] = pixmap self.setPixmap(pixmap) self.current_pixmap_key = key def set_metadata(self, metadata): data = None self.related_images = [] if metadata and metadata.images: self.related_images = metadata.images data = [image for image in metadata.images if image.is_front_image()] if not data: # There's no front image, choose the first one available data = [metadata.images[0]] has_common_images = getattr(metadata, 'has_common_images', True) self.set_data(data, has_common_images=has_common_images) release = None if metadata: release = metadata.get("musicbrainz_albumid", None) if release: self.setActive(True) text = _(u"View release on MusicBrainz") else: self.setActive(False) text = "" if hasattr(metadata, 'has_common_images'): if has_common_images: note = _(u'Common images on all tracks') else: note = _(u'Tracks contain different images') if text: text += '
' text += '%s' % note self.setToolTip(text) self.release = release def open_release_page(self): lookup = self.tagger.get_file_lookup() lookup.albumLookup(self.release) def set_image_replace(obj, coverartimage): obj.metadata.set_front_image(coverartimage) def set_image_append(obj, coverartimage): obj.metadata.append_image(coverartimage) class CoverArtBox(QtGui.QGroupBox): def __init__(self, parent): QtGui.QGroupBox.__init__(self, "") self.layout = QtGui.QVBoxLayout() self.layout.setSpacing(6) self.parent = parent # Kills off any borders self.setStyleSheet('''QGroupBox{background-color:none;border:1px;}''') self.setFlat(True) self.item = None self.pixmap_cache = LRUCache(40) self.cover_art_label = QtGui.QLabel('') self.cover_art_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter) self.cover_art = CoverArtThumbnail(False, True, self.pixmap_cache, parent) self.cover_art.image_dropped.connect(self.fetch_remote_image) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.orig_cover_art_label = QtGui.QLabel('') self.orig_cover_art = CoverArtThumbnail(False, False, self.pixmap_cache, parent) self.orig_cover_art_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter) self.show_details_button = QtGui.QPushButton(_(u'Show more details'), self) self.layout.addWidget(self.cover_art_label) self.layout.addWidget(self.cover_art) self.layout.addWidget(self.orig_cover_art_label) self.layout.addWidget(self.orig_cover_art) self.layout.addWidget(self.show_details_button) self.layout.addSpacerItem(spacerItem) self.setLayout(self.layout) self.orig_cover_art.setHidden(True) self.show_details_button.setHidden(True) self.show_details_button.clicked.connect(self.show_cover_art_info) def show_cover_art_info(self): self.parent.view_info(default_tab=1) def update_display(self, force=False): if self.isHidden(): if not force: # If the Cover art box is hidden and selection is updated # we should not update the display of child widgets return else: # Coverart box display was toggled. # Update the pixmaps and display them self.cover_art.show() self.orig_cover_art.show() # We want to show the 2 coverarts only if they are different # and orig_cover_art data is set and not the default cd shadow if self.orig_cover_art.data is None or self.cover_art == self.orig_cover_art: self.show_details_button.setVisible(bool(self.item and self.item.can_view_info())) self.orig_cover_art.setVisible(False) self.cover_art_label.setText('') self.orig_cover_art_label.setText('') else: self.show_details_button.setVisible(True) self.orig_cover_art.setVisible(True) self.cover_art_label.setText(_(u'New Cover Art')) self.orig_cover_art_label.setText(_(u'Original Cover Art')) def show(self): self.update_display(True) super(CoverArtBox, self).show() def set_metadata(self, metadata, orig_metadata, item): if not metadata or not metadata.images: self.cover_art.set_metadata(orig_metadata) else: self.cover_art.set_metadata(metadata) self.orig_cover_art.set_metadata(orig_metadata) self.item = item self.update_display() def fetch_remote_image(self, url, fallback_data=None): if self.item is None: return if url.scheme() in ('https', 'http'): path = url.encodedPath() if url.hasQuery(): path += '?' + url.encodedQuery() if url.scheme() == 'https': port = 443 else: port = 80 self.tagger.xmlws.get(str(url.encodedHost()), url.port(port), str(path), partial(self.on_remote_image_fetched, url, fallback_data=fallback_data), xml=False, priority=True, important=True) elif url.scheme() == 'file': log.debug("Dropped the URL: %r", url.toString(QtCore.QUrl.RemoveUserInfo)) if sys.platform == 'darwin' and unicode(url.path()).startswith('/.file/id='): # Workaround for https://bugreports.qt.io/browse/QTBUG-40449 # OSX Urls follow the NSURL scheme and need to be converted if NSURL_IMPORTED: path = os.path.normpath(os.path.realpath(unicode(NSURL.URLWithString_(str(url.toString())).filePathURL().path()).rstrip("\0"))) log.debug('OSX NSURL path detected. Dropped File is: %r', path) else: log.error("Unable to get appropriate file path for %r", url.toString(QtCore.QUrl.RemoveUserInfo)) else: # Dropping a file from iTunes gives a path with a NULL terminator path = os.path.normpath(os.path.realpath(unicode(url.toLocalFile()).rstrip("\0"))) if path and os.path.exists(path): mime = 'image/png' if path.lower().endswith('.png') else 'image/jpeg' with open(path, 'rb') as f: data = f.read() self.load_remote_image(url, mime, data) def on_remote_image_fetched(self, url, data, reply, error, fallback_data=None): mime = reply.header(QtNetwork.QNetworkRequest.ContentTypeHeader) if mime in ('image/jpeg', 'image/png'): self.load_remote_image(url, mime, data) elif url.hasQueryItem("imgurl"): # This may be a google images result, try to get the URL which is encoded in the query url = QtCore.QUrl(url.queryItemValue("imgurl")) self.fetch_remote_image(url) else: log.warning("Can't load remote image with MIME-Type %s", mime) if fallback_data: # Tests for image format obtained from file-magic try: mime = imageinfo.identify(fallback_data)[2] except imageinfo.IdentificationError as e: log.error("Unable to identify dropped data format: %s" % e) else: log.debug("Trying the dropped %s data", mime) self.load_remote_image(url, mime, fallback_data) def load_remote_image(self, url, mime, data): try: coverartimage = CoverArtImage( url=url.toString(), types=[u'front'], data=data ) except CoverArtImageError as e: log.warning("Can't load image: %s" % unicode(e)) return if config.setting["load_image_behavior"] == 'replace': set_image = set_image_replace else: set_image = set_image_append if isinstance(self.item, Album): album = self.item album.enable_update_metadata_images(False) set_image(album, coverartimage) for track in album.tracks: set_image(track, coverartimage) track.metadata_images_changed.emit() for file in album.iterfiles(): set_image(file, coverartimage) file.metadata_images_changed.emit() file.update() album.enable_update_metadata_images(True) album.update_metadata_images() album.update(False) elif isinstance(self.item, Track): track = self.item track.album.enable_update_metadata_images(False) set_image(track, coverartimage) track.metadata_images_changed.emit() for file in track.iterfiles(): set_image(file, coverartimage) file.metadata_images_changed.emit() file.update() track.album.enable_update_metadata_images(True) track.album.update_metadata_images() track.album.update(False) elif isinstance(self.item, File): file = self.item set_image(file, coverartimage) file.metadata_images_changed.emit() file.update() self.cover_art.set_metadata(self.item.metadata) self.show() def set_load_image_behavior(self, behavior): config.setting["load_image_behavior"] = behavior def contextMenuEvent(self, event): menu = QtGui.QMenu(self) if self.show_details_button.isVisible(): name = _(u'Show more details...') show_more_details_action = QtGui.QAction(name, self.parent) show_more_details_action.triggered.connect(self.show_cover_art_info) menu.addAction(show_more_details_action) if self.orig_cover_art.isVisible(): name = _(u'Keep original cover art') use_orig_value_action = QtGui.QAction(name, self.parent) use_orig_value_action.triggered.connect(self.item.keep_original_images) menu.addAction(use_orig_value_action) if not menu.isEmpty(): menu.addSeparator() load_image_behavior_group = QtGui.QActionGroup(self.parent, exclusive=True) action = load_image_behavior_group.addAction(QtGui.QAction(_(u'Replace front cover art on drop'), self.parent, checkable=True)) action.triggered.connect(partial(self.set_load_image_behavior, behavior='replace')) if config.setting["load_image_behavior"] == 'replace': action.setChecked(True) menu.addAction(action) action = load_image_behavior_group.addAction(QtGui.QAction(_(u'Append front cover art on drop'), self.parent, checkable=True)) action.triggered.connect(partial(self.set_load_image_behavior, behavior='append')) if config.setting["load_image_behavior"] == 'append': action.setChecked(True) menu.addAction(action) menu.exec_(event.globalPos()) event.accept() picard-release-1.4.2/picard/ui/edittagdialog.py000066400000000000000000000162511310410472100214260ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2011 Michael Wiencek # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtGui from picard.util.tags import TAG_NAMES from picard.ui import PicardDialog from picard.ui.ui_edittagdialog import Ui_EditTagDialog class EditTagDialog(PicardDialog): def __init__(self, window, tag): PicardDialog.__init__(self, window) self.ui = Ui_EditTagDialog() self.ui.setupUi(self) self.window = window self.value_list = self.ui.value_list self.metadata_box = window.metadata_box self.tag = tag self.modified_tags = {} self.different = False self.default_tags = sorted( set(TAG_NAMES.keys() + self.metadata_box.tag_diff.tag_names)) if len(self.metadata_box.files) == 1: current_file = list(self.metadata_box.files)[0] self.default_tags = filter(lambda x: current_file.supports_tag(x), self.default_tags) tag_names = self.ui.tag_names tag_names.editTextChanged.connect(self.tag_changed) tag_names.addItem("") visible_tags = [tn for tn in self.default_tags if not tn.startswith("~")] tag_names.addItems(visible_tags) self.completer = QtGui.QCompleter(visible_tags, tag_names) self.completer.setCompletionMode(QtGui.QCompleter.PopupCompletion) tag_names.setCompleter(self.completer) self.tag_changed(tag) self.value_selection_changed() self.ui.edit_value.clicked.connect(self.edit_value) self.ui.add_value.clicked.connect(self.add_value) self.ui.remove_value.clicked.connect(self.remove_value) self.value_list.itemChanged.connect(self.value_edited) self.value_list.itemSelectionChanged.connect(self.value_selection_changed) def edit_value(self): item = self.value_list.currentItem() if item: self.value_list.editItem(item) def add_value(self): self._modified_tag().append("") item = QtGui.QListWidgetItem() item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable) self.value_list.addItem(item) self.value_list.editItem(item) def remove_value(self): value_list = self.value_list row = value_list.row(value_list.currentItem()) if row == 0 and self.different: self.different = False self.ui.add_value.setEnabled(True) value_list.takeItem(row) del self._modified_tag()[row] def disable_all(self): self.value_list.clear() self.value_list.setEnabled(False) self.ui.add_value.setEnabled(False) def enable_all(self): self.value_list.setEnabled(True) self.ui.add_value.setEnabled(True) def tag_changed(self, tag): tag_names = self.ui.tag_names tag_names.editTextChanged.disconnect(self.tag_changed) flags = QtCore.Qt.MatchFixedString | QtCore.Qt.MatchCaseSensitive # if the previous tag was new and has no value, remove it from the QComboBox. # e.g. typing "XYZ" should not leave "X" or "XY" in the QComboBox. if self.tag and self.tag not in self.default_tags and self._modified_tag() == [""]: tag_names.removeItem(tag_names.findText(self.tag, flags)) row = tag_names.findText(tag, flags) self.tag = unicode(tag) if row <= 0: if tag: # add custom tags to the QComboBox immediately tag_names.addItem(tag) tag_names.model().sort(0) row = tag_names.findText(tag, flags) else: # the QLineEdit is empty, disable everything self.disable_all() tag_names.setCurrentIndex(0) tag_names.editTextChanged.connect(self.tag_changed) return self.enable_all() tag_names.setCurrentIndex(row) self.value_list.clear() values = self.modified_tags.get(self.tag, None) if values is None: new_tags = self.metadata_box.tag_diff.new display_value, self.different = new_tags.display_value(self.tag) values = [display_value] if self.different else new_tags[self.tag] self.ui.add_value.setEnabled(not self.different) self._add_value_items(values) self.value_list.setCurrentItem(self.value_list.item(0), QtGui.QItemSelectionModel.SelectCurrent) tag_names.editTextChanged.connect(self.tag_changed) def _add_value_items(self, values): values = [v for v in values if v] or [""] for value in values: item = QtGui.QListWidgetItem(value) item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable) font = item.font() font.setItalic(self.different) item.setFont(font) self.value_list.addItem(item) def value_edited(self, item): row = self.value_list.row(item) value = unicode(item.text()) if row == 0 and self.different: self.modified_tags[self.tag] = [value] self.different = False font = item.font() font.setItalic(False) item.setFont(font) self.ui.add_value.setEnabled(True) else: self._modified_tag()[row] = value # add tags to the completer model once they get values cm = self.completer.model() if self.tag not in cm.stringList(): cm.insertRows(0, 1) cm.setData(cm.index(0, 0), self.tag) cm.sort(0) def value_selection_changed(self): selection = len(self.value_list.selectedItems()) > 0 self.ui.edit_value.setEnabled(selection) self.ui.remove_value.setEnabled(selection) def _modified_tag(self): return self.modified_tags.setdefault(self.tag, list(self.metadata_box.tag_diff.new[self.tag]) or [""]) def accept(self): self.window.ignore_selection_changes = True for tag, values in self.modified_tags.items(): self.modified_tags[tag] = [v for v in values if v] modified_tags = self.modified_tags.items() for obj in self.metadata_box.objects: for tag, values in modified_tags: obj.metadata[tag] = list(values) obj.update() self.window.ignore_selection_changes = False self.window.update_selection() QtGui.QDialog.accept(self) picard-release-1.4.2/picard/ui/filebrowser.py000066400000000000000000000137321310410472100211510ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006-2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # import os import sys from PyQt4 import QtCore, QtGui from picard import config from picard.formats import supported_formats from picard.util import find_existing_path class FileBrowser(QtGui.QTreeView): options = [ config.TextOption("persist", "current_browser_path", ""), config.BoolOption("persist", "show_hidden_files", False), ] def __init__(self, parent): QtGui.QTreeView.__init__(self, parent) self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.setDragEnabled(True) self.move_files_here_action = QtGui.QAction(_("&Move Tagged Files Here"), self) self.move_files_here_action.triggered.connect(self.move_files_here) self.addAction(self.move_files_here_action) self.toggle_hidden_action = QtGui.QAction(_("Show &Hidden Files"), self) self.toggle_hidden_action.setCheckable(True) self.toggle_hidden_action.setChecked(config.persist["show_hidden_files"]) self.toggle_hidden_action.toggled.connect(self.show_hidden) self.addAction(self.toggle_hidden_action) self.set_as_starting_directory_action = QtGui.QAction(_("&Set as starting directory"), self) self.set_as_starting_directory_action.triggered.connect(self.set_as_starting_directory) self.addAction(self.set_as_starting_directory_action) self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.focused = False self._set_model() def _set_model(self): self.model = QtGui.QFileSystemModel() self.model.layoutChanged.connect(self._layout_changed) self.model.setRootPath("") self._set_model_filter() filters = [] for exts, name in supported_formats(): filters.extend("*" + e for e in exts) self.model.setNameFilters(filters) # Hide unsupported files completely self.model.setNameFilterDisables(False) self.model.sort(0, QtCore.Qt.AscendingOrder) self.setModel(self.model) if sys.platform == "darwin": self.setRootIndex(self.model.index("/Volumes")) header = self.header() header.hideSection(1) header.hideSection(2) header.hideSection(3) header.setResizeMode(QtGui.QHeaderView.ResizeToContents) header.setStretchLastSection(False) header.setVisible(False) def _set_model_filter(self): filter = QtCore.QDir.AllDirs | QtCore.QDir.Files | QtCore.QDir.Drives | QtCore.QDir.NoDotAndDotDot if config.persist["show_hidden_files"]: filter |= QtCore.QDir.Hidden self.model.setFilter(filter) def _layout_changed(self): def scroll(): # XXX The currentIndex seems to change while QFileSystemModel is # populating itself (so setCurrentIndex in __init__ won't last). # The time it takes to load varies and there are no signals to find # out when it's done. As a workaround, keep restoring the state as # long as the layout is updating, and the user hasn't focused yet. if not self.focused: self._restore_state() self.scrollTo(self.currentIndex()) QtCore.QTimer.singleShot(0, scroll) def mousePressEvent(self, event): index = self.indexAt(event.pos()) if index.isValid(): self.selectionModel().setCurrentIndex(index, QtGui.QItemSelectionModel.NoUpdate) QtGui.QTreeView.mousePressEvent(self, event) def focusInEvent(self, event): self.focused = True QtGui.QTreeView.focusInEvent(self, event) def show_hidden(self, state): config.persist["show_hidden_files"] = state self._set_model_filter() def save_state(self): indexes = self.selectedIndexes() if indexes: path = self.model.filePath(indexes[0]) config.persist["current_browser_path"] = os.path.normpath(unicode(path)) def restore_state(self): pass def _restore_state(self): if config.setting["starting_directory"]: path = config.setting["starting_directory_path"] scrolltype = QtGui.QAbstractItemView.PositionAtTop else: path = config.persist["current_browser_path"] scrolltype = QtGui.QAbstractItemView.PositionAtCenter if path: index = self.model.index(find_existing_path(unicode(path))) self.setCurrentIndex(index) self.expand(index) self.scrollTo(index, scrolltype) def _get_destination_from_path(self, path): destination = os.path.normpath(unicode(path)) if not os.path.isdir(destination): destination = os.path.dirname(destination) return destination def move_files_here(self): indexes = self.selectedIndexes() if not indexes: return path = self.model.filePath(indexes[0]) config.setting["move_files_to"] = self._get_destination_from_path(path) def set_as_starting_directory(self): indexes = self.selectedIndexes() if indexes: path = self.model.filePath(indexes[0]) config.setting["starting_directory_path"] = self._get_destination_from_path(path) picard-release-1.4.2/picard/ui/infodialog.py000066400000000000000000000355151310410472100207440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os.path import cgi import traceback from PyQt4 import QtGui, QtCore from picard import log from picard.file import File from picard.track import Track from picard.album import Album from picard.coverart.image import CoverArtImageIOError from picard.util import format_time, encode_filename, bytes2human, webbrowser2, union_sorted_lists from picard.ui import PicardDialog from picard.ui.ui_infodialog import Ui_InfoDialog class ArtworkTable(QtGui.QTableWidget): def __init__(self, display_existing_art): QtGui.QTableWidget.__init__(self, 0, 2) self.display_existing_art = display_existing_art h_header = self.horizontalHeader() v_header = self.verticalHeader() h_header.setDefaultSectionSize(200) v_header.setDefaultSectionSize(230) if self.display_existing_art: self._existing_cover_col = 0 self._type_col = 1 self._new_cover_col = 2 self.insertColumn(2) self.setHorizontalHeaderLabels([_("Existing Cover"), _("Type"), _("New Cover")]) self.arrow_pixmap = QtGui.QPixmap(":/images/arrow.png") else: self._type_col = 0 self._new_cover_col = 1 self.setHorizontalHeaderLabels([_("Type"), _("Cover")]) self.setColumnWidth(self._type_col, 140) def get_coverart_widget(self, pixmap, text): """Return a QWidget that can be added to artwork column cell of ArtworkTable.""" coverart_widget = QtGui.QWidget() image_label = QtGui.QLabel() text_label = QtGui.QLabel() layout = QtGui.QVBoxLayout() image_label.setPixmap(pixmap.scaled(170, 170, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) image_label.setAlignment(QtCore.Qt.AlignCenter) text_label.setText(text) text_label.setAlignment(QtCore.Qt.AlignCenter) text_label.setWordWrap(True) layout.addWidget(image_label) layout.addWidget(text_label) coverart_widget.setLayout(layout) return coverart_widget def get_type_widget(self, type_text): """Return a QWidget that can be added to type column cell of ArtworkTable. If both existing and new artwork are to be displayed, insert an arrow icon to make comparison obvious. """ type_widget = QtGui.QWidget() type_label = QtGui.QLabel() layout = QtGui.QVBoxLayout() type_label.setText(type_text) type_label.setAlignment(QtCore.Qt.AlignCenter) type_label.setWordWrap(True) if self.display_existing_art: arrow_label = QtGui.QLabel() arrow_label.setPixmap(self.arrow_pixmap.scaled(170, 170, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) arrow_label.setAlignment(QtCore.Qt.AlignCenter) layout.addWidget(arrow_label) layout.addWidget(type_label) else: layout.addWidget(type_label) type_widget.setLayout(layout) return type_widget class InfoDialog(PicardDialog): def __init__(self, obj, parent=None): PicardDialog.__init__(self, parent) self.obj = obj self.images = [] self.existing_images = [] self.ui = Ui_InfoDialog() self.display_existing_artwork = False if (isinstance(obj, File) and isinstance(obj.parent, Track) or isinstance(obj, Track) or (isinstance(obj, Album) and obj.get_num_total_files() > 0)): # Display existing artwork only if selected object is track object # or linked to a track object or it's an album with files if (getattr(obj, 'orig_metadata', None) is not None and obj.orig_metadata.images and obj.orig_metadata.images != obj.metadata.images): self.display_existing_artwork = True self.existing_images = obj.orig_metadata.images if obj.metadata.images: self.images = obj.metadata.images if not self.images and self.existing_images: self.images = self.existing_images self.existing_images = [] self.display_existing_artwork = False self.ui.setupUi(self) self.ui.buttonBox.accepted.connect(self.accept) self.ui.buttonBox.rejected.connect(self.reject) # Add the ArtworkTable to the ui self.ui.artwork_table = ArtworkTable(self.display_existing_artwork) self.ui.artwork_table.setObjectName("artwork_table") self.ui.vboxlayout1.addWidget(self.ui.artwork_table) if self.display_existing_artwork: self.resize(665, 436) self.setTabOrder(self.ui.tabWidget, self.ui.artwork_table) self.setTabOrder(self.ui.artwork_table, self.ui.buttonBox) self.setWindowTitle(_("Info")) self.artwork_table = self.ui.artwork_table self._display_tabs() def _display_tabs(self): self._display_info_tab() self._display_artwork_tab() def _display_artwork(self, images, col): """Draw artwork in corresponding cell if image type matches type in Type column. Arguments: images -- The images to be drawn. col -- Column in which images are to be drawn. Can be _new_cover_col or _existing_cover_col. """ row = 0 row_count = self.artwork_table.rowCount() for image in images: while row != row_count: image_type = self.artwork_table.item(row, self.artwork_table._type_col) if image_type and image_type.data(QtCore.Qt.UserRole) == image.types_as_string(): break row += 1 if row == row_count: continue data = None try: if image.thumbnail: try: data = image.thumbnail.data except CoverArtImageIOError as e: log.warning(unicode(e)) pass else: data = image.data except CoverArtImageIOError: log.error(traceback.format_exc()) continue item = QtGui.QTableWidgetItem() item.setData(QtCore.Qt.UserRole, image) pixmap = QtGui.QPixmap() if data is not None: pixmap.loadFromData(data) item.setToolTip( _("Double-click to open in external viewer\n" "Temporary file: %s\n" "Source: %s") % (image.tempfile_filename, image.source)) infos = [] if image.comment: infos.append(image.comment) infos.append(u"%s (%s)" % (bytes2human.decimal(image.datalength), bytes2human.binary(image.datalength))) if image.width and image.height: infos.append(u"%d x %d" % (image.width, image.height)) infos.append(image.mimetype) img_wgt = self.artwork_table.get_coverart_widget(pixmap, "\n".join(infos)) self.artwork_table.setCellWidget(row, col, img_wgt) self.artwork_table.setItem(row, col, item) row += 1 def _display_artwork_type(self): """Display image type in Type column. If both existing covers and new covers are to be displayed, take union of both cover types list. """ types = [image.types_as_string() for image in self.images] if self.display_existing_artwork: existing_types = [image.types_as_string() for image in self.existing_images] # Merge both types and existing types list in sorted order. types = union_sorted_lists(types, existing_types) for row, type in enumerate(types): self.artwork_table.insertRow(row) type_wgt = self.artwork_table.get_type_widget(type) item = QtGui.QTableWidgetItem() item.setData(QtCore.Qt.UserRole, type) self.artwork_table.setCellWidget(row, self.artwork_table._type_col, type_wgt) self.artwork_table.setItem(row, self.artwork_table._type_col, item) def _display_artwork_tab(self): if not self.images: self.tab_hide(self.ui.artwork_tab) self._display_artwork_type() self._display_artwork(self.images, self.artwork_table._new_cover_col) if self.existing_images: self._display_artwork(self.existing_images, self.artwork_table._existing_cover_col) self.artwork_table.itemDoubleClicked.connect(self.show_item) def tab_hide(self, widget): tab = self.ui.tabWidget index = tab.indexOf(widget) tab.removeTab(index) def show_item(self, item): data = item.data(QtCore.Qt.UserRole) # Check if this function isn't triggered by cell in Type column if isinstance(data, unicode): return filename = data.tempfile_filename if filename: webbrowser2.open("file://" + filename) class FileInfoDialog(InfoDialog): def __init__(self, file, parent=None): InfoDialog.__init__(self, file, parent) self.setWindowTitle(_("Info") + " - " + file.base_filename) @staticmethod def format_file_info(file): info = [] info.append((_('Filename:'), file.filename)) if '~format' in file.orig_metadata: info.append((_('Format:'), file.orig_metadata['~format'])) try: size = os.path.getsize(encode_filename(file.filename)) sizestr = "%s (%s)" % (bytes2human.decimal(size), bytes2human.binary(size)) info.append((_('Size:'), sizestr)) except: pass if file.orig_metadata.length: info.append((_('Length:'), format_time(file.orig_metadata.length))) if '~bitrate' in file.orig_metadata: info.append((_('Bitrate:'), '%s kbps' % file.orig_metadata['~bitrate'])) if '~sample_rate' in file.orig_metadata: info.append((_('Sample rate:'), '%s Hz' % file.orig_metadata['~sample_rate'])) if '~bits_per_sample' in file.orig_metadata: info.append((_('Bits per sample:'), str(file.orig_metadata['~bits_per_sample']))) if '~channels' in file.orig_metadata: ch = file.orig_metadata['~channels'] if ch == 1: ch = _('Mono') elif ch == 2: ch = _('Stereo') else: ch = str(ch) info.append((_('Channels:'), ch)) return '
'.join(map(lambda i: '%s
%s' % (cgi.escape(i[0]), cgi.escape(i[1])), info)) def _display_info_tab(self): file = self.obj text = FileInfoDialog.format_file_info(file) self.ui.info.setText(text) class AlbumInfoDialog(InfoDialog): def __init__(self, album, parent=None): InfoDialog.__init__(self, album, parent) self.setWindowTitle(_("Album Info")) def _display_info_tab(self): tab = self.ui.info_tab album = self.obj tabWidget = self.ui.tabWidget tab_index = tabWidget.indexOf(tab) if album.errors: tabWidget.setTabText(tab_index, _("&Errors")) text = '
'.join(map(lambda s: '%s' % '
'.join(unicode(QtCore.Qt.escape(s)) .replace('\t', ' ') .replace(' ', ' ') .splitlines() ), album.errors) ) self.ui.info.setText(text + '


') else: tabWidget.setTabText(tab_index, _("&Info")) self.tab_hide(tab) class TrackInfoDialog(FileInfoDialog): def __init__(self, track, parent=None): InfoDialog.__init__(self, track, parent) self.setWindowTitle(_("Track Info")) def _display_info_tab(self): track = self.obj tab = self.ui.info_tab tabWidget = self.ui.tabWidget tab_index = tabWidget.indexOf(tab) if track.num_linked_files == 0: tabWidget.setTabText(tab_index, _("&Info")) self.tab_hide(tab) return tabWidget.setTabText(tab_index, _("&Info")) text = ungettext("%i file in this track", "%i files in this track", track.num_linked_files) % track.num_linked_files info_files = [FileInfoDialog.format_file_info(file) for file in track.linked_files] text += '
' + '
'.join(info_files) self.ui.info.setText(text) class ClusterInfoDialog(InfoDialog): def __init__(self, cluster, parent=None): InfoDialog.__init__(self, cluster, parent) self.setWindowTitle(_("Cluster Info")) def _display_info_tab(self): tab = self.ui.info_tab cluster = self.obj tabWidget = self.ui.tabWidget tab_index = tabWidget.indexOf(tab) tabWidget.setTabText(tab_index, _("&Info")) info = [] info.append("%s %s" % (_('Album:'), unicode(QtCore.Qt.escape(cluster.metadata["album"])))) info.append("%s %s" % (_('Artist:'), unicode(QtCore.Qt.escape(cluster.metadata["albumartist"])))) info.append("") lines = [] for file in cluster.iterfiles(False): m = file.metadata artist = m["artist"] or m["albumartist"] or cluster.metadata["albumartist"] lines.append(m["tracknumber"] + u" " + m["title"] + " - " + artist + " (" + m["~length"] + ")") info.append("%s
%s" % (_('Tracklist:'), '
'.join([unicode(QtCore.Qt.escape(s)).replace(' ', ' ') for s in lines]))) self.ui.info.setText('
'.join(info)) picard-release-1.4.2/picard/ui/infostatus.py000066400000000000000000000053221310410472100210210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtGui from PyQt4.QtGui import QIcon from picard.util import icontheme from picard.ui.ui_infostatus import Ui_InfoStatus class InfoStatus(QtGui.QWidget, Ui_InfoStatus): def __init__(self, parent): QtGui.QWidget.__init__(self, parent) Ui_InfoStatus.__init__(self) self.setupUi(self) self._size = QtCore.QSize(16, 16) self._create_icons() self._init_labels() def _init_labels(self): size = self._size self.label1.setPixmap(self.icon_file.pixmap(size)) self.label2.setPixmap(self.icon_cd.pixmap(size)) self.label3.setPixmap(self.icon_file_pending.pixmap(size)) self.label4.setPixmap(self.icon_download.pixmap(size, QIcon.Disabled)) self._init_tooltips() def _create_icons(self): self.icon_cd = icontheme.lookup('media-optical') self.icon_file = QtGui.QIcon(":/images/file.png") self.icon_file_pending = QtGui.QIcon(":/images/file-pending.png") self.icon_download = QtGui.QIcon(":/images/16x16/action-go-down-16.png") def _init_tooltips(self): t1 = _("Files") t2 = _("Albums") t3 = _("Pending files") t4 = _("Pending requests") self.val1.setToolTip(t1) self.label1.setToolTip(t1) self.val2.setToolTip(t2) self.label2.setToolTip(t2) self.val3.setToolTip(t3) self.label3.setToolTip(t3) self.val4.setToolTip(t4) self.label4.setToolTip(t4) def setFiles(self, num): self.val1.setText(unicode(num)) def setAlbums(self, num): self.val2.setText(unicode(num)) def setPendingFiles(self, num): self.val3.setText(unicode(num)) def setPendingRequests(self, num): if num <= 0: enabled = QIcon.Disabled else: enabled = QIcon.Normal self.label4.setPixmap(self.icon_download.pixmap(self._size, enabled)) self.val4.setText(unicode(num)) picard-release-1.4.2/picard/ui/item.py000066400000000000000000000032241310410472100175570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class Item(object): def can_save(self): """Return if this object can be saved.""" return False def can_remove(self): """Return if this object can be removed.""" return False def can_edit_tags(self): """Return if this object supports tag editing.""" return False def can_analyze(self): """Return if this object can be fingerprinted.""" return False def can_autotag(self): """Return if this object can be autotagged.""" return False def can_refresh(self): """Return if this object can be refreshed.""" return False def can_view_info(self): return False def can_browser_lookup(self): return True def is_album_like(self): return False def load(self, priority=False, refresh=False): pass picard-release-1.4.2/picard/ui/itemviews.py000066400000000000000000001027041310410472100206400ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import re import sys from functools import partial from PyQt4 import QtCore, QtGui from picard import config, log from picard.album import Album, NatAlbum from picard.cluster import Cluster, ClusterList, UnmatchedFiles from picard.file import File from picard.track import Track, NonAlbumTrack from picard.util import encode_filename, icontheme from picard.plugin import ExtensionPoint from picard.ui.ratingwidget import RatingWidget from picard.ui.collectionmenu import CollectionMenu if sys.platform == 'darwin': try: from Foundation import NSURL NSURL_IMPORTED = True except ImportError: NSURL_IMPORTED = False log.warning("Unable to import NSURL, file drag'n'drop might not work correctly") class BaseAction(QtGui.QAction): NAME = "Unknown" MENU = [] def __init__(self): QtGui.QAction.__init__(self, self.NAME, None) self.triggered.connect(self.__callback) def __callback(self): objs = self.tagger.window.selected_objects self.callback(objs) def callback(self, objs): raise NotImplementedError _album_actions = ExtensionPoint() _cluster_actions = ExtensionPoint() _clusterlist_actions = ExtensionPoint() _track_actions = ExtensionPoint() _file_actions = ExtensionPoint() def register_album_action(action): _album_actions.register(action.__module__, action) def register_cluster_action(action): _cluster_actions.register(action.__module__, action) def register_clusterlist_action(action): _clusterlist_actions.register(action.__module__, action) def register_track_action(action): _track_actions.register(action.__module__, action) def register_file_action(action): _file_actions.register(action.__module__, action) def get_match_color(similarity, basecolor): c1 = (basecolor.red(), basecolor.green(), basecolor.blue()) c2 = (223, 125, 125) return QtGui.QColor( c2[0] + (c1[0] - c2[0]) * similarity, c2[1] + (c1[1] - c2[1]) * similarity, c2[2] + (c1[2] - c2[2]) * similarity) class MainPanel(QtGui.QSplitter): options = [ config.Option("persist", "splitter_state", QtCore.QByteArray()), ] columns = [ (N_('Title'), 'title'), (N_('Length'), '~length'), (N_('Artist'), 'artist'), ] def __init__(self, window, parent=None): QtGui.QSplitter.__init__(self, parent) self.window = window self.create_icons() self.views = [FileTreeView(window, self), AlbumTreeView(window, self)] self.views[0].itemSelectionChanged.connect(self.update_selection_0) self.views[1].itemSelectionChanged.connect(self.update_selection_1) self._selected_view = 0 self._ignore_selection_changes = False TreeItem.window = window TreeItem.base_color = self.palette().base().color() TreeItem.text_color = self.palette().text().color() TreeItem.text_color_secondary = self.palette() \ .brush(QtGui.QPalette.Disabled, QtGui.QPalette.Text).color() TrackItem.track_colors = { File.NORMAL: config.setting["color_saved"], File.CHANGED: TreeItem.text_color, File.PENDING: config.setting["color_pending"], File.ERROR: config.setting["color_error"], } FileItem.file_colors = { File.NORMAL: TreeItem.text_color, File.CHANGED: config.setting["color_modified"], File.PENDING: config.setting["color_pending"], File.ERROR: config.setting["color_error"], } def save_state(self): config.persist["splitter_state"] = self.saveState() for view in self.views: view.save_state() def restore_state(self): self.restoreState(config.persist["splitter_state"]) def create_icons(self): if hasattr(QtGui.QStyle, 'SP_DirIcon'): ClusterItem.icon_dir = self.style().standardIcon(QtGui.QStyle.SP_DirIcon) else: ClusterItem.icon_dir = icontheme.lookup('folder', icontheme.ICON_SIZE_MENU) AlbumItem.icon_cd = icontheme.lookup('media-optical', icontheme.ICON_SIZE_MENU) AlbumItem.icon_cd_modified = icontheme.lookup('media-optical-modified', icontheme.ICON_SIZE_MENU) AlbumItem.icon_cd_saved = icontheme.lookup('media-optical-saved', icontheme.ICON_SIZE_MENU) AlbumItem.icon_cd_saved_modified = icontheme.lookup('media-optical-saved-modified', icontheme.ICON_SIZE_MENU) AlbumItem.icon_error = icontheme.lookup('media-optical-error', icontheme.ICON_SIZE_MENU) TrackItem.icon_audio = QtGui.QIcon(":/images/track-audio.png") TrackItem.icon_video = QtGui.QIcon(":/images/track-video.png") TrackItem.icon_data = QtGui.QIcon(":/images/track-data.png") FileItem.icon_file = QtGui.QIcon(":/images/file.png") FileItem.icon_file_pending = QtGui.QIcon(":/images/file-pending.png") FileItem.icon_error = icontheme.lookup('dialog-error', icontheme.ICON_SIZE_MENU) FileItem.icon_saved = QtGui.QIcon(":/images/track-saved.png") FileItem.match_icons = [ QtGui.QIcon(":/images/match-50.png"), QtGui.QIcon(":/images/match-60.png"), QtGui.QIcon(":/images/match-70.png"), QtGui.QIcon(":/images/match-80.png"), QtGui.QIcon(":/images/match-90.png"), QtGui.QIcon(":/images/match-100.png"), ] FileItem.match_icons_info = [ N_("Bad match"), N_("Poor match"), N_("Ok match"), N_("Good match"), N_("Great match"), N_("Excellent match"), ] FileItem.match_pending_icons = [ QtGui.QIcon(":/images/match-pending-50.png"), QtGui.QIcon(":/images/match-pending-60.png"), QtGui.QIcon(":/images/match-pending-70.png"), QtGui.QIcon(":/images/match-pending-80.png"), QtGui.QIcon(":/images/match-pending-90.png"), QtGui.QIcon(":/images/match-pending-100.png"), ] self.icon_plugins = icontheme.lookup('applications-system', icontheme.ICON_SIZE_MENU) def update_selection(self, i, j): self._selected_view = i self.views[j].clearSelection() self.window.update_selection( [item.obj for item in self.views[i].selectedItems()]) def update_selection_0(self): if not self._ignore_selection_changes: self._ignore_selection_changes = True self.update_selection(0, 1) self._ignore_selection_changes = False def update_selection_1(self): if not self._ignore_selection_changes: self._ignore_selection_changes = True self.update_selection(1, 0) self._ignore_selection_changes = False def update_current_view(self): self.update_selection(self._selected_view, abs(self._selected_view - 1)) def remove(self, objects): self._ignore_selection_changes = True self.tagger.remove(objects) self._ignore_selection_changes = False view = self.views[self._selected_view] index = view.currentIndex() if index.isValid(): # select the current index view.setCurrentIndex(index) else: self.update_current_view() class BaseTreeView(QtGui.QTreeWidget): options = [ config.Option("setting", "color_modified", QtGui.QColor(QtGui.QPalette.WindowText)), config.Option("setting", "color_saved", QtGui.QColor(0, 128, 0)), config.Option("setting", "color_error", QtGui.QColor(200, 0, 0)), config.Option("setting", "color_pending", QtGui.QColor(128, 128, 128)), ] def __init__(self, window, parent=None): QtGui.QTreeWidget.__init__(self, parent) self.window = window self.panel = parent self.numHeaderSections = len(MainPanel.columns) self.setHeaderLabels([_(h) for h, n in MainPanel.columns]) self.restore_state() self.setAcceptDrops(True) self.setDragEnabled(True) self.setDropIndicatorShown(True) self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) # enable sorting, but don't actually use it by default # XXX it would be nice to be able to go to the 'no sort' mode, but the # internal model that QTreeWidget uses doesn't support it self.header().setSortIndicator(-1, QtCore.Qt.AscendingOrder) self.setSortingEnabled(True) self.expand_all_action = QtGui.QAction(_("&Expand all"), self) self.expand_all_action.triggered.connect(self.expandAll) self.collapse_all_action = QtGui.QAction(_("&Collapse all"), self) self.collapse_all_action.triggered.connect(self.collapseAll) self.select_all_action = QtGui.QAction(_("Select &all"), self) self.select_all_action.triggered.connect(self.selectAll) self.select_all_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+A"))) self.doubleClicked.connect(self.activate_item) def contextMenuEvent(self, event): item = self.itemAt(event.pos()) if not item: return obj = item.obj plugin_actions = None can_view_info = self.window.view_info_action.isEnabled() menu = QtGui.QMenu(self) if isinstance(obj, Track): if can_view_info: menu.addAction(self.window.view_info_action) plugin_actions = list(_track_actions) if obj.num_linked_files == 1: menu.addAction(self.window.play_file_action) menu.addAction(self.window.open_folder_action) menu.addAction(self.window.track_search_action) plugin_actions.extend(_file_actions) menu.addAction(self.window.browser_lookup_action) menu.addSeparator() if isinstance(obj, NonAlbumTrack): menu.addAction(self.window.refresh_action) elif isinstance(obj, Cluster): if can_view_info: menu.addAction(self.window.view_info_action) menu.addAction(self.window.browser_lookup_action) menu.addSeparator() menu.addAction(self.window.autotag_action) menu.addAction(self.window.analyze_action) if isinstance(obj, UnmatchedFiles): menu.addAction(self.window.cluster_action) else: menu.addAction(self.window.album_search_action) plugin_actions = list(_cluster_actions) elif isinstance(obj, ClusterList): menu.addAction(self.window.autotag_action) menu.addAction(self.window.analyze_action) plugin_actions = list(_clusterlist_actions) elif isinstance(obj, File): if can_view_info: menu.addAction(self.window.view_info_action) menu.addAction(self.window.play_file_action) menu.addAction(self.window.open_folder_action) menu.addAction(self.window.browser_lookup_action) menu.addSeparator() menu.addAction(self.window.autotag_action) menu.addAction(self.window.analyze_action) menu.addAction(self.window.track_search_action) plugin_actions = list(_file_actions) elif isinstance(obj, Album): if can_view_info: menu.addAction(self.window.view_info_action) menu.addAction(self.window.browser_lookup_action) menu.addSeparator() menu.addAction(self.window.refresh_action) plugin_actions = list(_album_actions) menu.addAction(self.window.save_action) menu.addAction(self.window.remove_action) bottom_separator = False if isinstance(obj, Album) and not isinstance(obj, NatAlbum) and obj.loaded: releases_menu = QtGui.QMenu(_("&Other versions"), menu) menu.addSeparator() menu.addMenu(releases_menu) loading = releases_menu.addAction(_('Loading...')) loading.setDisabled(True) bottom_separator = True if len(self.selectedIndexes()) == len(MainPanel.columns): def _add_other_versions(): releases_menu.removeAction(loading) heading = releases_menu.addAction(obj.release_group.version_headings) heading.setDisabled(True) font = heading.font() font.setBold(True) heading.setFont(font) versions = obj.release_group.versions albumtracks = obj.get_num_total_files() if obj.get_num_total_files() else len(obj.tracks) preferred_countries = set(config.setting["preferred_release_countries"]) preferred_formats = set(config.setting["preferred_release_formats"]) matches = ("trackmatch", "countrymatch", "formatmatch") priorities = {} for version in versions: priority = { "trackmatch": "0" if version['totaltracks'] == albumtracks else "?", "countrymatch": "0" if len(preferred_countries) == 0 or preferred_countries & set(version['countries']) else "?", "formatmatch": "0" if len(preferred_formats) == 0 or preferred_formats & set(version['formats']) else "?", } priorities[version['id']] = "".join(priority[k] for k in matches) versions.sort(key=lambda version: priorities[version['id']] + version['name']) priority = normal = False for version in versions: if not normal and "?" in priorities[version['id']]: if priority: releases_menu.addSeparator() normal = True else: priority = True action = releases_menu.addAction(version["name"]) action.setCheckable(True) if obj.id == version["id"]: action.setChecked(True) action.triggered.connect(partial(obj.switch_release_version, version["id"])) if obj.release_group.loaded: _add_other_versions() else: obj.release_group.load_versions(_add_other_versions) releases_menu.setEnabled(True) else: releases_menu.setEnabled(False) if config.setting["enable_ratings"] and \ len(self.window.selected_objects) == 1 and isinstance(obj, Track): menu.addSeparator() action = QtGui.QWidgetAction(menu) action.setDefaultWidget(RatingWidget(menu, obj)) menu.addAction(action) menu.addSeparator() # Using type here is intentional. isinstance will return true for the # NatAlbum instance, which can't be part of a collection. selected_albums = [a for a in self.window.selected_objects if type(a) == Album] if selected_albums: if not bottom_separator: menu.addSeparator() menu.addMenu(CollectionMenu(selected_albums, _("Collections"), menu)) if plugin_actions: plugin_menu = QtGui.QMenu(_("P&lugins"), menu) plugin_menu.setIcon(self.panel.icon_plugins) menu.addSeparator() menu.addMenu(plugin_menu) plugin_menus = {} for action in plugin_actions: action_menu = plugin_menu for index in xrange(1, len(action.MENU) + 1): key = tuple(action.MENU[:index]) if key in plugin_menus: action_menu = plugin_menus[key] else: action_menu = plugin_menus[key] = action_menu.addMenu(key[-1]) action_menu.addAction(action) if isinstance(obj, Cluster) or isinstance(obj, ClusterList) or isinstance(obj, Album): menu.addSeparator() menu.addAction(self.expand_all_action) menu.addAction(self.collapse_all_action) menu.addAction(self.select_all_action) menu.exec_(event.globalPos()) event.accept() def restore_state(self): sizes = config.persist[self.view_sizes.name] header = self.header() sizes = sizes.split(" ") try: for i in range(self.numHeaderSections - 1): header.resizeSection(i, int(sizes[i])) except IndexError: pass def save_state(self): cols = range(self.numHeaderSections - 1) sizes = " ".join(str(self.header().sectionSize(i)) for i in cols) config.persist[self.view_sizes.name] = sizes def supportedDropActions(self): return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction def mimeTypes(self): """List of MIME types accepted by this view.""" return ["text/uri-list", "application/picard.album-list"] def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.setDropAction(QtCore.Qt.CopyAction) event.accept() else: event.acceptProposedAction() def startDrag(self, supportedActions): """Start drag, *without* using pixmap.""" items = self.selectedItems() if items: drag = QtGui.QDrag(self) drag.setMimeData(self.mimeData(items)) drag.start(supportedActions) def mimeData(self, items): """Return MIME data for specified items.""" album_ids = [] files = [] url = QtCore.QUrl.fromLocalFile for item in items: obj = item.obj if isinstance(obj, Album): album_ids.append(str(obj.id)) elif obj.iterfiles: files.extend([url(f.filename) for f in obj.iterfiles()]) mimeData = QtCore.QMimeData() mimeData.setData("application/picard.album-list", "\n".join(album_ids)) if files: mimeData.setUrls(files) return mimeData @staticmethod def drop_urls(urls, target): files = [] new_files = [] for url in urls: log.debug("Dropped the URL: %r", url.toString(QtCore.QUrl.RemoveUserInfo)) if url.scheme() == "file" or not url.scheme(): if sys.platform == 'darwin' and unicode(url.path()).startswith('/.file/id='): # Workaround for https://bugreports.qt.io/browse/QTBUG-40449 # OSX Urls follow the NSURL scheme and need to be converted if NSURL_IMPORTED: filename = os.path.normpath(os.path.realpath(unicode(NSURL.URLWithString_(str(url.toString())).filePathURL().path()).rstrip("\0"))) log.debug('OSX NSURL path detected. Dropped File is: %r', filename) else: log.error("Unable to get appropriate file path for %r", url.toString(QtCore.QUrl.RemoveUserInfo)) continue else: # Dropping a file from iTunes gives a filename with a NULL terminator filename = os.path.normpath(os.path.realpath(unicode(url.toLocalFile()).rstrip("\0"))) file = BaseTreeView.tagger.files.get(filename) if file: files.append(file) elif os.path.isdir(encode_filename(filename)): BaseTreeView.tagger.add_directory(filename) else: new_files.append(filename) elif url.scheme() in ("http", "https"): path = unicode(url.path()) match = re.search(r"/(release|recording)/([0-9a-z\-]{36})", path) if match: entity = match.group(1) mbid = match.group(2) if entity == "release": BaseTreeView.tagger.load_album(mbid) elif entity == "recording": BaseTreeView.tagger.load_nat(mbid) if files: BaseTreeView.tagger.move_files(files, target) if new_files: BaseTreeView.tagger.add_files(new_files, target=target) def dropEvent(self, event): return QtGui.QTreeView.dropEvent(self, event) def dropMimeData(self, parent, index, data, action): target = None if parent: if index == parent.childCount(): item = parent else: item = parent.child(index) if item is not None: target = item.obj log.debug("Drop target = %r", target) handled = False # text/uri-list urls = data.urls() if urls: self.drop_urls(urls, target) handled = True # application/picard.album-list albums = data.data("application/picard.album-list") if albums: if isinstance(self, FileTreeView) and target is None: target = self.tagger.unmatched_files albums = [self.tagger.load_album(id) for id in str(albums).split("\n")] self.tagger.move_files(self.tagger.get_files_from_objects(albums), target) handled = True return handled def activate_item(self, index): obj = self.itemFromIndex(index).obj # Double-clicking albums or clusters should expand them. The album info can be # viewed by using the toolbar button. if not isinstance(obj, (Album, Cluster)) and obj.can_view_info(): self.window.view_info() def add_cluster(self, cluster, parent_item=None): if parent_item is None: parent_item = self.clusters cluster_item = ClusterItem(cluster, not cluster.special, parent_item) if cluster.hide_if_empty and not cluster.files: cluster_item.update() cluster_item.setHidden(True) else: cluster_item.add_files(cluster.files) def moveCursor(self, action, modifiers): if action in (QtGui.QAbstractItemView.MoveUp, QtGui.QAbstractItemView.MoveDown): item = self.currentItem() if item and not item.isSelected(): self.setCurrentItem(item) return QtGui.QTreeWidget.moveCursor(self, action, modifiers) class FileTreeView(BaseTreeView): view_sizes = config.TextOption("persist", "file_view_sizes", "250 40 100") def __init__(self, window, parent=None): BaseTreeView.__init__(self, window, parent) self.setAccessibleName(_("file view")) self.setAccessibleDescription(_("Contains unmatched files and clusters")) self.unmatched_files = ClusterItem(self.tagger.unmatched_files, False, self) self.unmatched_files.update() self.setItemExpanded(self.unmatched_files, True) self.clusters = ClusterItem(self.tagger.clusters, False, self) self.set_clusters_text() self.setItemExpanded(self.clusters, True) self.tagger.cluster_added.connect(self.add_file_cluster) self.tagger.cluster_removed.connect(self.remove_file_cluster) def add_file_cluster(self, cluster, parent_item=None): self.add_cluster(cluster, parent_item) self.set_clusters_text() def remove_file_cluster(self, cluster): cluster.item.setSelected(False) self.clusters.removeChild(cluster.item) self.set_clusters_text() def set_clusters_text(self): self.clusters.setText(0, '%s (%d)' % (_(u"Clusters"), len(self.tagger.clusters))) class AlbumTreeView(BaseTreeView): view_sizes = config.TextOption("persist", "album_view_sizes", "250 40 100") def __init__(self, window, parent=None): BaseTreeView.__init__(self, window, parent) self.setAccessibleName(_("album view")) self.setAccessibleDescription(_("Contains albums and matched files")) self.tagger.album_added.connect(self.add_album) self.tagger.album_removed.connect(self.remove_album) def add_album(self, album): item = AlbumItem(album, True, self) item.setIcon(0, AlbumItem.icon_cd) for i, column in enumerate(MainPanel.columns): font = item.font(i) font.setBold(True) item.setFont(i, font) item.setText(i, album.column(column[1])) self.add_cluster(album.unmatched_files, item) def remove_album(self, album): album.item.setSelected(False) self.takeTopLevelItem(self.indexOfTopLevelItem(album.item)) class TreeItem(QtGui.QTreeWidgetItem): __lt__ = lambda self, other: False def __init__(self, obj, sortable, *args): QtGui.QTreeWidgetItem.__init__(self, *args) self.obj = obj if obj is not None: obj.item = self if sortable: self.__lt__ = self._lt self.setTextAlignment(1, QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) def _lt(self, other): column = self.treeWidget().sortColumn() if column == 1: return (self.obj.metadata.length or 0) < (other.obj.metadata.length or 0) return self.text(column).lower() < other.text(column).lower() class ClusterItem(TreeItem): def __init__(self, *args): TreeItem.__init__(self, *args) self.setIcon(0, ClusterItem.icon_dir) def update(self): for i, column in enumerate(MainPanel.columns): self.setText(i, self.obj.column(column[1])) album = self.obj.related_album if self.obj.special and album and album.loaded: album.item.update(update_tracks=False) if self.isSelected(): TreeItem.window.update_selection() def add_file(self, file): self.add_files([file]) def add_files(self, files): if self.obj.hide_if_empty and self.obj.files: self.setHidden(False) self.update() # addChild used (rather than building an items list and adding with addChildren) # to be certain about item order in the cluster (addChildren adds in reverse order). # Benchmarked performance was not noticeably different. for file in files: item = FileItem(file, True) item.update() self.addChild(item) def remove_file(self, file): file.item.setSelected(False) self.removeChild(file.item) self.update() if self.obj.hide_if_empty and not self.obj.files: self.setHidden(True) class AlbumItem(TreeItem): def update(self, update_tracks=True): album = self.obj if update_tracks: oldnum = self.childCount() - 1 newnum = len(album.tracks) if oldnum > newnum: # remove old items for i in xrange(oldnum - newnum): self.takeChild(newnum - 1) oldnum = newnum # update existing items for i in xrange(oldnum): item = self.child(i) track = album.tracks[i] item.obj = track track.item = item item.update(update_album=False) if newnum > oldnum: # add new items items = [] for i in xrange(newnum - 1, oldnum - 1, -1): # insertChildren is backwards item = TrackItem(album.tracks[i], False) item.setHidden(False) # Workaround to make sure the parent state gets updated items.append(item) self.insertChildren(oldnum, items) for item in items: # Update after insertChildren so that setExpanded works item.update(update_album=False) if album.errors: self.setIcon(0, AlbumItem.icon_error) self.setToolTip(0, _("Error")) elif album.is_complete(): if album.is_modified(): self.setIcon(0, AlbumItem.icon_cd_saved_modified) self.setToolTip(0, _("Album modified and complete")) else: self.setIcon(0, AlbumItem.icon_cd_saved) self.setToolTip(0, _("Album unchanged and complete")) else: if album.is_modified(): self.setIcon(0, AlbumItem.icon_cd_modified) self.setToolTip(0, _("Album modified")) else: self.setIcon(0, AlbumItem.icon_cd) self.setToolTip(0, _("Album unchanged")) for i, column in enumerate(MainPanel.columns): self.setText(i, album.column(column[1])) if self.isSelected(): TreeItem.window.update_selection() class TrackItem(TreeItem): def update(self, update_album=True, update_files=True): track = self.obj if track.num_linked_files == 1: file = track.linked_files[0] file.item = self color = TrackItem.track_colors[file.state] bgcolor = get_match_color(file.similarity, TreeItem.base_color) icon = FileItem.decide_file_icon(file) self.setToolTip(0, _(FileItem.decide_file_icon_info(file))) self.takeChildren() else: self.setToolTip(0, "") if track.ignored_for_completeness(): color = TreeItem.text_color_secondary else: color = TreeItem.text_color bgcolor = get_match_color(1, TreeItem.base_color) if track.is_video(): icon = TrackItem.icon_video elif track.is_data(): icon = TrackItem.icon_data else: icon = TrackItem.icon_audio if update_files: oldnum = self.childCount() newnum = track.num_linked_files if oldnum > newnum: # remove old items for i in xrange(oldnum - newnum): self.takeChild(newnum - 1).obj.item = None oldnum = newnum for i in xrange(oldnum): # update existing items item = self.child(i) file = track.linked_files[i] item.obj = file file.item = item item.update(update_track=False) if newnum > oldnum: # add new items items = [] for i in xrange(newnum - 1, oldnum - 1, -1): item = FileItem(track.linked_files[i], False) item.update(update_track=False) items.append(item) self.addChildren(items) self.setExpanded(True) self.setIcon(0, icon) for i, column in enumerate(MainPanel.columns): self.setText(i, track.column(column[1])) self.setForeground(i, color) self.setBackground(i, bgcolor) if self.isSelected(): TreeItem.window.update_selection() if update_album: self.parent().update(update_tracks=False) class FileItem(TreeItem): def update(self, update_track=True): file = self.obj self.setIcon(0, FileItem.decide_file_icon(file)) color = FileItem.file_colors[file.state] bgcolor = get_match_color(file.similarity, TreeItem.base_color) for i, column in enumerate(MainPanel.columns): self.setText(i, file.column(column[1])) self.setForeground(i, color) self.setBackground(i, bgcolor) if self.isSelected(): TreeItem.window.update_selection() parent = self.parent() if isinstance(parent, TrackItem) and update_track: parent.update(update_files=False) @staticmethod def decide_file_icon(file): if file.state == File.ERROR: return FileItem.icon_error elif isinstance(file.parent, Track): if file.state == File.NORMAL: return FileItem.icon_saved elif file.state == File.PENDING: return FileItem.match_pending_icons[int(file.similarity * 5 + 0.5)] else: return FileItem.match_icons[int(file.similarity * 5 + 0.5)] elif file.state == File.PENDING: return FileItem.icon_file_pending else: return FileItem.icon_file @staticmethod def decide_file_icon_info(file): # Note error state info is already handled if isinstance(file.parent, Track): if file.state == File.NORMAL: return N_("Track saved") elif file.state == File.PENDING: # unsure how to use int(file.similarity * 5 + 0.5) return N_("Pending") else: # returns description of the match ranging from bad to excellent return FileItem.match_icons_info[int(file.similarity * 5 + 0.5)] elif file.state == File.PENDING: return N_("Pending") picard-release-1.4.2/picard/ui/logview.py000066400000000000000000000123321310410472100202750ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtGui from picard import config, log from picard.ui import PicardDialog class LogViewCommon(PicardDialog): def __init__(self, title, logger, w=740, h=340, parent=None): PicardDialog.__init__(self, parent) self.logger = logger self.setWindowFlags(QtCore.Qt.Window) self.resize(w, h) self.setWindowTitle(title) self.doc = QtGui.QTextDocument(self) self.textCursor = QtGui.QTextCursor(self.doc) self.browser = QtGui.QTextBrowser(self) self.browser.setDocument(self.doc) self.vbox = QtGui.QVBoxLayout(self) self.vbox.addWidget(self.browser) self._display() def _setup_formats(self): font = QtGui.QFont() font.setFamily("Monospace") self.textFormatInfo = QtGui.QTextCharFormat() self.textFormatInfo.setFont(font) self.textFormatInfo.setForeground(QtGui.QColor('black')) self.textFormatDebug = QtGui.QTextCharFormat() self.textFormatDebug.setFont(font) self.textFormatDebug.setForeground(QtGui.QColor('purple')) self.textFormatWarning = QtGui.QTextCharFormat() self.textFormatWarning.setFont(font) self.textFormatWarning.setForeground(QtGui.QColor('darkorange')) self.textFormatError = QtGui.QTextCharFormat() self.textFormatError.setFont(font) self.textFormatError.setForeground(QtGui.QColor('red')) self.formats = { log.LOG_INFO: self.textFormatInfo, log.LOG_WARNING: self.textFormatWarning, log.LOG_ERROR: self.textFormatError, log.LOG_DEBUG: self.textFormatDebug, } def _format(self, level): return self.formats[level] def _display(self): self._setup_formats() for level, time, msg in self.logger.entries: self._add_entry(level, time, msg) self.logger.register_receiver(self._add_entry) def _add_entry(self, level, time, msg): self.textCursor.movePosition(QtGui.QTextCursor.End) self.textCursor.insertText(self._formatted_log_line(level, time, msg), self._format(level)) self.textCursor.insertBlock() sb = self.browser.verticalScrollBar() sb.setValue(sb.maximum()) def _formatted_log_line(self, level, time, msg): return log.formatted_log_line(level, time, msg) def closeEvent(self, event): self.logger.unregister_receiver(self._add_entry) return QtGui.QDialog.closeEvent(self, event) def saveWindowState(self, position, size): pos = self.pos() if not pos.isNull(): config.persist[position] = pos config.persist[size] = self.size() def restoreWindowState(self, position, size): pos = config.persist[position] if pos.x() > 0 and pos.y() > 0: self.move(pos) self.resize(config.persist[size]) class LogView(LogViewCommon): options = [ config.Option("persist", "logview_position", QtCore.QPoint()), config.Option("persist", "logview_size", QtCore.QSize(560, 400)), ] def __init__(self, parent=None): title = _("Log") logger = log.main_logger LogViewCommon.__init__(self, title, logger, parent=parent) self.restoreWindowState("logview_position", "logview_size") cb = QtGui.QCheckBox(_('Debug mode'), self) cb.setChecked(QtCore.QObject.tagger._debug) cb.stateChanged.connect(self.toggleDebug) self.vbox.addWidget(cb) def toggleDebug(self, state): QtCore.QObject.tagger.debug(state == QtCore.Qt.Checked) def closeEvent(self, event): self.saveWindowState("logview_position", "logview_size") event.accept() class HistoryView(LogViewCommon): options = [ config.Option("persist", "historyview_position", QtCore.QPoint()), config.Option("persist", "historyview_size", QtCore.QSize(560, 400)), ] def __init__(self, parent=None): title = _("Activity History") logger = log.history_logger LogViewCommon.__init__(self, title, logger, parent=parent) self.restoreWindowState("historyview_position", "historyview_size") def _formatted_log_line(self, level, time, msg): return log.formatted_log_line(level, time, msg, level_prefixes=False) def closeEvent(self, event): self.saveWindowState("historyview_position", "historyview_size") event.accept() picard-release-1.4.2/picard/ui/mainwindow.py000066400000000000000000001272161310410472100210050ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtGui import os.path from picard import config, log from picard.album import Album from picard.cluster import Cluster from picard.file import File from picard.track import Track from picard.formats import supported_formats from picard.ui.coverartbox import CoverArtBox from picard.ui.itemviews import MainPanel from picard.ui.metadatabox import MetadataBox from picard.ui.filebrowser import FileBrowser from picard.ui.tagsfromfilenames import TagsFromFileNamesDialog from picard.ui.options.dialog import OptionsDialog from picard.ui.infodialog import FileInfoDialog, AlbumInfoDialog, TrackInfoDialog, ClusterInfoDialog from picard.ui.infostatus import InfoStatus from picard.ui.passworddialog import PasswordDialog, ProxyDialog from picard.ui.logview import LogView, HistoryView from picard.ui.searchdialog import ( TrackSearchDialog, AlbumSearchDialog) from picard.ui.util import ( find_starting_directory, ButtonLineEdit, MultiDirsSelectDialog ) from picard.util import icontheme, webbrowser2, throttle, thread from picard.util.cdrom import discid, get_cdrom_drives from picard.plugin import ExtensionPoint ui_init = ExtensionPoint() def register_ui_init(function): ui_init.register(function.__module__, function) class MainWindow(QtGui.QMainWindow): selection_updated = QtCore.pyqtSignal(object) options = [ config.Option("persist", "window_state", QtCore.QByteArray()), config.Option("persist", "window_position", QtCore.QPoint()), config.Option("persist", "window_size", QtCore.QSize(780, 560)), config.Option("persist", "bottom_splitter_state", QtCore.QByteArray()), config.BoolOption("persist", "window_maximized", False), config.BoolOption("persist", "view_cover_art", True), config.BoolOption("persist", "view_toolbar", True), config.BoolOption("persist", "view_file_browser", False), config.TextOption("persist", "current_directory", ""), ] def __init__(self, parent=None): QtGui.QMainWindow.__init__(self, parent) self.selected_objects = [] self.ignore_selection_changes = False self.setupUi() def setupUi(self): self.setWindowTitle(_("MusicBrainz Picard")) icon = QtGui.QIcon() icon.addFile(":/images/16x16/picard.png", QtCore.QSize(16, 16)) icon.addFile(":/images/24x24/picard.png", QtCore.QSize(24, 24)) icon.addFile(":/images/32x32/picard.png", QtCore.QSize(32, 32)) icon.addFile(":/images/48x48/picard.png", QtCore.QSize(48, 48)) icon.addFile(":/images/128x128/picard.png", QtCore.QSize(128, 128)) icon.addFile(":/images/256x256/picard.png", QtCore.QSize(256, 256)) self.setWindowIcon(icon) self.create_actions() self.create_statusbar() self.create_toolbar() self.create_menus() mainLayout = QtGui.QSplitter(QtCore.Qt.Vertical) mainLayout.setContentsMargins(0, 0, 0, 0) mainLayout.setHandleWidth(1) self.panel = MainPanel(self, mainLayout) self.file_browser = FileBrowser(self.panel) if not self.show_file_browser_action.isChecked(): self.file_browser.hide() self.panel.insertWidget(0, self.file_browser) self.panel.restore_state() self.metadata_box = MetadataBox(self) self.cover_art_box = CoverArtBox(self) if not self.show_cover_art_action.isChecked(): self.cover_art_box.hide() self.logDialog = LogView(self) self.historyDialog = HistoryView(self) bottomLayout = QtGui.QHBoxLayout() bottomLayout.setContentsMargins(0, 0, 0, 0) bottomLayout.setSpacing(0) bottomLayout.addWidget(self.metadata_box, 1) bottomLayout.addWidget(self.cover_art_box, 0) bottom = QtGui.QWidget() bottom.setLayout(bottomLayout) mainLayout.addWidget(self.panel) mainLayout.addWidget(bottom) self.setCentralWidget(mainLayout) # accessibility self.set_tab_order() for function in ui_init: function(self) def keyPressEvent(self, event): if event.matches(QtGui.QKeySequence.Delete): if self.metadata_box.hasFocus(): self.metadata_box.remove_selected_tags() else: self.remove() else: QtGui.QMainWindow.keyPressEvent(self, event) def show(self): self.restoreWindowState() QtGui.QMainWindow.show(self) self.metadata_box.restore_state() def closeEvent(self, event): if config.setting["quit_confirmation"] and not self.show_quit_confirmation(): event.ignore() return self.saveWindowState() event.accept() def show_quit_confirmation(self): unsaved_files = sum(a.get_num_unsaved_files() for a in self.tagger.albums.itervalues()) QMessageBox = QtGui.QMessageBox if unsaved_files > 0: msg = QMessageBox(self) msg.setIcon(QMessageBox.Question) msg.setWindowModality(QtCore.Qt.WindowModal) msg.setWindowTitle(_(u"Unsaved Changes")) msg.setText(_(u"Are you sure you want to quit Picard?")) txt = ungettext( "There is %d unsaved file. Closing Picard will lose all unsaved changes.", "There are %d unsaved files. Closing Picard will lose all unsaved changes.", unsaved_files) % unsaved_files msg.setInformativeText(txt) cancel = msg.addButton(QMessageBox.Cancel) msg.setDefaultButton(cancel) msg.addButton(_(u"&Quit Picard"), QMessageBox.YesRole) ret = msg.exec_() if ret == QMessageBox.Cancel: return False return True def saveWindowState(self): config.persist["window_state"] = self.saveState() isMaximized = int(self.windowState()) & QtCore.Qt.WindowMaximized != 0 if isMaximized: # FIXME: this doesn't include the window frame geom = self.normalGeometry() config.persist["window_position"] = geom.topLeft() config.persist["window_size"] = geom.size() else: pos = self.pos() if not pos.isNull(): config.persist["window_position"] = pos config.persist["window_size"] = self.size() config.persist["window_maximized"] = isMaximized config.persist["view_cover_art"] = self.show_cover_art_action.isChecked() config.persist["view_toolbar"] = self.show_toolbar_action.isChecked() config.persist["view_file_browser"] = self.show_file_browser_action.isChecked() config.persist["bottom_splitter_state"] = self.centralWidget().saveState() self.file_browser.save_state() self.panel.save_state() self.metadata_box.save_state() def restoreWindowState(self): self.restoreState(config.persist["window_state"]) pos = config.persist["window_position"] size = config.persist["window_size"] self._desktopgeo = self.tagger.desktop().screenGeometry() if (pos.x() > 0 and pos.y() > 0 and pos.x() + size.width() < self._desktopgeo.width() and pos.y() + size.height() < self._desktopgeo.height()): self.move(pos) if size.width() <= 0 or size.height() <= 0: size = QtCore.QSize(780, 560) self.resize(size) if config.persist["window_maximized"]: self.setWindowState(QtCore.Qt.WindowMaximized) bottom_splitter_state = config.persist["bottom_splitter_state"] if bottom_splitter_state.isEmpty(): self.centralWidget().setSizes([366, 194]) else: self.centralWidget().restoreState(bottom_splitter_state) self.file_browser.restore_state() def create_statusbar(self): """Creates a new status bar.""" self.statusBar().showMessage(_("Ready")) self.infostatus = InfoStatus(self) self.listening_label = QtGui.QLabel() self.listening_label.setVisible(False) self.listening_label.setToolTip("" + _( "Picard listens on this port to integrate with your browser. When " "you \"Search\" or \"Open in Browser\" from Picard, clicking the " "\"Tagger\" button on the web page loads the release into Picard." )) self.statusBar().addPermanentWidget(self.infostatus) self.statusBar().addPermanentWidget(self.listening_label) self.tagger.tagger_stats_changed.connect(self.update_statusbar_stats) self.tagger.listen_port_changed.connect(self.update_statusbar_listen_port) self.update_statusbar_stats() @throttle(100) def update_statusbar_stats(self): """Updates the status bar information.""" self.infostatus.setFiles(len(self.tagger.files)) self.infostatus.setAlbums(len(self.tagger.albums)) self.infostatus.setPendingFiles(File.num_pending_files) ws = self.tagger.xmlws self.infostatus.setPendingRequests(ws.num_pending_web_requests) def update_statusbar_listen_port(self, listen_port): if listen_port: self.listening_label.setVisible(True) self.listening_label.setText(_(" Listening on port %(port)d ") % {"port": listen_port}) else: self.listening_label.setVisible(False) def set_statusbar_message(self, message, *args, **kwargs): """Set the status bar message. *args are passed to % operator, if args[0] is a mapping it is used for named place holders values >>> w.set_statusbar_message("File %(filename)s", {'filename': 'x.txt'}) Keyword arguments: `echo` parameter defaults to `log.debug`, called before message is translated, it can be disabled passing None or replaced by ie. `log.error`. If None, skipped. `translate` is a method called on message before it is sent to history log and status bar, it defaults to `_()`. If None, skipped. `timeout` defines duration of the display in milliseconds `history` is a method called with translated message as argument, it defaults to `log.history_info`. If None, skipped. Empty messages are never passed to echo and history functions but they are sent to status bar (ie. to clear it). """ def isdict(obj): return hasattr(obj, 'keys') and hasattr(obj, '__getitem__') echo = kwargs.get('echo', log.debug) # _ is defined using __builtin__.__dict__, so setting it as default named argument # value doesn't work as expected translate = kwargs.get('translate', _) timeout = kwargs.get('timeout', 0) history = kwargs.get('history', log.history_info) if len(args) == 1 and isdict(args[0]): # named place holders mparms = args[0] else: # simple place holders, ensure compatibility mparms = args if message: if echo: echo(message % mparms) if translate: message = translate(message) message = message % mparms if history: history(message) thread.to_main(self.statusBar().showMessage, message, timeout) def _on_submit_acoustid(self): if self.tagger.use_acoustid: if not config.setting["acoustid_apikey"]: QtGui.QMessageBox.warning(self, _(u"Submission Error"), _(u"You need to configure your AcoustID API key before you can submit fingerprints.")) else: self.tagger.acoustidmanager.submit() def create_actions(self): self.options_action = QtGui.QAction(icontheme.lookup('preferences-desktop'), _("&Options..."), self) self.options_action.setMenuRole(QtGui.QAction.PreferencesRole) self.options_action.triggered.connect(self.show_options) self.cut_action = QtGui.QAction(icontheme.lookup('edit-cut', icontheme.ICON_SIZE_MENU), _(u"&Cut"), self) self.cut_action.setShortcut(QtGui.QKeySequence.Cut) self.cut_action.setEnabled(False) self.cut_action.triggered.connect(self.cut) self.paste_action = QtGui.QAction(icontheme.lookup('edit-paste', icontheme.ICON_SIZE_MENU), _(u"&Paste"), self) self.paste_action.setShortcut(QtGui.QKeySequence.Paste) self.paste_action.setEnabled(False) self.paste_action.triggered.connect(self.paste) self.help_action = QtGui.QAction(_("&Help..."), self) self.help_action.setShortcut(QtGui.QKeySequence.HelpContents) self.help_action.triggered.connect(self.show_help) self.about_action = QtGui.QAction(_("&About..."), self) self.about_action.setMenuRole(QtGui.QAction.AboutRole) self.about_action.triggered.connect(self.show_about) self.donate_action = QtGui.QAction(_("&Donate..."), self) self.donate_action.triggered.connect(self.open_donation_page) self.report_bug_action = QtGui.QAction(_("&Report a Bug..."), self) self.report_bug_action.triggered.connect(self.open_bug_report) self.support_forum_action = QtGui.QAction(_("&Support Forum..."), self) self.support_forum_action.triggered.connect(self.open_support_forum) self.add_files_action = QtGui.QAction(icontheme.lookup('document-open'), _(u"&Add Files..."), self) self.add_files_action.setStatusTip(_(u"Add files to the tagger")) # TR: Keyboard shortcut for "Add Files..." self.add_files_action.setShortcut(QtGui.QKeySequence.Open) self.add_files_action.triggered.connect(self.add_files) self.add_directory_action = QtGui.QAction(icontheme.lookup('folder'), _(u"A&dd Folder..."), self) self.add_directory_action.setStatusTip(_(u"Add a folder to the tagger")) # TR: Keyboard shortcut for "Add Directory..." self.add_directory_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+D"))) self.add_directory_action.triggered.connect(self.add_directory) self.save_action = QtGui.QAction(icontheme.lookup('document-save'), _(u"&Save"), self) self.save_action.setStatusTip(_(u"Save selected files")) # TR: Keyboard shortcut for "Save" self.save_action.setShortcut(QtGui.QKeySequence.Save) self.save_action.setEnabled(False) self.save_action.triggered.connect(self.save) self.submit_acoustid_action = QtGui.QAction(icontheme.lookup('acoustid-fingerprinter'), _(u"S&ubmit AcoustIDs"), self) self.submit_acoustid_action.setStatusTip(_(u"Submit acoustic fingerprints")) self.submit_acoustid_action.setEnabled(False) self.submit_acoustid_action.triggered.connect(self._on_submit_acoustid) self.exit_action = QtGui.QAction(_(u"E&xit"), self) self.exit_action.setMenuRole(QtGui.QAction.QuitRole) # TR: Keyboard shortcut for "Exit" self.exit_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+Q"))) self.exit_action.triggered.connect(self.close) self.remove_action = QtGui.QAction(icontheme.lookup('list-remove'), _(u"&Remove"), self) self.remove_action.setStatusTip(_(u"Remove selected files/albums")) self.remove_action.setEnabled(False) self.remove_action.triggered.connect(self.remove) self.browser_lookup_action = QtGui.QAction(icontheme.lookup('lookup-musicbrainz'), _(u"Lookup in &Browser"), self) self.browser_lookup_action.setStatusTip(_(u"Lookup selected item on MusicBrainz website")) self.browser_lookup_action.setEnabled(False) # TR: Keyboard shortcut for "Lookup in Browser" self.browser_lookup_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+Shift+L"))) self.browser_lookup_action.triggered.connect(self.browser_lookup) self.album_search_action = QtGui.QAction(icontheme.lookup('system-search'), _(u"Search for similar albums..."), self) self.album_search_action.setStatusTip(_(u"View similar releases and optionally choose a different release")) self.album_search_action.triggered.connect(self.show_more_albums) self.track_search_action = QtGui.QAction(icontheme.lookup('system-search'), _(u"Search for similar tracks..."), self) self.track_search_action.setStatusTip(_(u"View similar tracks and optionally choose a different release")) self.track_search_action.triggered.connect(self.show_more_tracks) self.show_file_browser_action = QtGui.QAction(_(u"File &Browser"), self) self.show_file_browser_action.setCheckable(True) if config.persist["view_file_browser"]: self.show_file_browser_action.setChecked(True) self.show_file_browser_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+B"))) self.show_file_browser_action.triggered.connect(self.show_file_browser) self.show_cover_art_action = QtGui.QAction(_(u"&Cover Art"), self) self.show_cover_art_action.setCheckable(True) if config.persist["view_cover_art"]: self.show_cover_art_action.setChecked(True) self.show_cover_art_action.triggered.connect(self.show_cover_art) self.show_toolbar_action = QtGui.QAction(_(u"&Actions"), self) self.show_toolbar_action.setCheckable(True) if config.persist["view_toolbar"]: self.show_toolbar_action.setChecked(True) self.show_toolbar_action.triggered.connect(self.show_toolbar) self.search_action = QtGui.QAction(icontheme.lookup('system-search'), _(u"Search"), self) self.search_action.setEnabled(False) self.search_action.triggered.connect(self.search) self.cd_lookup_action = QtGui.QAction(icontheme.lookup('media-optical'), _(u"Lookup &CD..."), self) self.cd_lookup_action.setStatusTip(_(u"Lookup the details of the CD in your drive")) # TR: Keyboard shortcut for "Lookup CD" self.cd_lookup_action.setShortcut(QtGui.QKeySequence(_("Ctrl+K"))) self.cd_lookup_action.triggered.connect(self.tagger.lookup_cd) self.analyze_action = QtGui.QAction(icontheme.lookup('picard-analyze'), _(u"&Scan"), self) self.analyze_action.setStatusTip(_(u"Use AcoustID audio fingerprint to identify the files by the actual music, even if they have no metadata")) self.analyze_action.setEnabled(False) self.analyze_action.setToolTip(_(u'Identify the file using its AcoustID audio fingerprint')) # TR: Keyboard shortcut for "Analyze" self.analyze_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+Y"))) self.analyze_action.triggered.connect(self.analyze) self.cluster_action = QtGui.QAction(icontheme.lookup('picard-cluster'), _(u"Cl&uster"), self) self.cluster_action.setStatusTip(_(u"Cluster files into album clusters")) self.cluster_action.setEnabled(False) # TR: Keyboard shortcut for "Cluster" self.cluster_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+U"))) self.cluster_action.triggered.connect(self.cluster) self.autotag_action = QtGui.QAction(icontheme.lookup('picard-auto-tag'), _(u"&Lookup"), self) tip = _(u"Lookup selected items in MusicBrainz") self.autotag_action.setToolTip(tip) self.autotag_action.setStatusTip(tip) self.autotag_action.setEnabled(False) # TR: Keyboard shortcut for "Lookup" self.autotag_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+L"))) self.autotag_action.triggered.connect(self.autotag) self.view_info_action = QtGui.QAction(icontheme.lookup('picard-edit-tags'), _(u"&Info..."), self) self.view_info_action.setEnabled(False) # TR: Keyboard shortcut for "Info" self.view_info_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+I"))) self.view_info_action.triggered.connect(self.view_info) self.refresh_action = QtGui.QAction(icontheme.lookup('view-refresh', icontheme.ICON_SIZE_MENU), _("&Refresh"), self) self.refresh_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+R"))) self.refresh_action.triggered.connect(self.refresh) self.enable_renaming_action = QtGui.QAction(_(u"&Rename Files"), self) self.enable_renaming_action.setCheckable(True) self.enable_renaming_action.setChecked(config.setting["rename_files"]) self.enable_renaming_action.triggered.connect(self.toggle_rename_files) self.enable_moving_action = QtGui.QAction(_(u"&Move Files"), self) self.enable_moving_action.setCheckable(True) self.enable_moving_action.setChecked(config.setting["move_files"]) self.enable_moving_action.triggered.connect(self.toggle_move_files) self.enable_tag_saving_action = QtGui.QAction(_(u"Save &Tags"), self) self.enable_tag_saving_action.setCheckable(True) self.enable_tag_saving_action.setChecked(not config.setting["dont_write_tags"]) self.enable_tag_saving_action.triggered.connect(self.toggle_tag_saving) self.tags_from_filenames_action = QtGui.QAction(_(u"Tags From &File Names..."), self) self.tags_from_filenames_action.triggered.connect(self.open_tags_from_filenames) self.tags_from_filenames_action.setEnabled(False) self.open_collection_in_browser_action = QtGui.QAction(_(u"&Open My Collections in Browser"), self) self.open_collection_in_browser_action.triggered.connect(self.open_collection_in_browser) self.open_collection_in_browser_action.setEnabled(config.setting["username"] != u'') self.view_log_action = QtGui.QAction(_(u"View Error/Debug &Log"), self) self.view_log_action.triggered.connect(self.show_log) self.view_history_action = QtGui.QAction(_(u"View Activity &History"), self) self.view_history_action.triggered.connect(self.show_history) xmlws_manager = self.tagger.xmlws.manager xmlws_manager.authenticationRequired.connect(self.show_password_dialog) xmlws_manager.proxyAuthenticationRequired.connect(self.show_proxy_dialog) self.play_file_action = QtGui.QAction(icontheme.lookup('play-music'), _(u"Open in &Player"), self) self.play_file_action.setStatusTip(_(u"Play the file in your default media player")) self.play_file_action.setEnabled(False) self.play_file_action.triggered.connect(self.play_file) self.open_folder_action = QtGui.QAction(icontheme.lookup('folder', icontheme.ICON_SIZE_MENU), _(u"Open Containing &Folder"), self) self.open_folder_action.setStatusTip(_(u"Open the containing folder in your file explorer")) self.open_folder_action.setEnabled(False) self.open_folder_action.triggered.connect(self.open_folder) def toggle_rename_files(self, checked): config.setting["rename_files"] = checked def toggle_move_files(self, checked): config.setting["move_files"] = checked def toggle_tag_saving(self, checked): config.setting["dont_write_tags"] = not checked def get_selected_or_unmatched_files(self): files = self.tagger.get_files_from_objects(self.selected_objects) if not files: files = self.tagger.unmatched_files.files return files def open_tags_from_filenames(self): files = self.get_selected_or_unmatched_files() if files: dialog = TagsFromFileNamesDialog(files, self) dialog.exec_() def open_collection_in_browser(self): self.tagger.collection_lookup() def create_menus(self): menu = self.menuBar().addMenu(_(u"&File")) menu.addAction(self.add_directory_action) menu.addAction(self.add_files_action) menu.addSeparator() menu.addAction(self.play_file_action) menu.addAction(self.open_folder_action) menu.addSeparator() menu.addAction(self.save_action) menu.addAction(self.submit_acoustid_action) menu.addSeparator() menu.addAction(self.exit_action) menu = self.menuBar().addMenu(_(u"&Edit")) menu.addAction(self.cut_action) menu.addAction(self.paste_action) menu.addSeparator() menu.addAction(self.view_info_action) menu.addAction(self.remove_action) menu = self.menuBar().addMenu(_(u"&View")) menu.addAction(self.show_file_browser_action) menu.addAction(self.show_cover_art_action) menu.addSeparator() menu.addAction(self.show_toolbar_action) menu.addAction(self.search_toolbar_toggle_action) menu = self.menuBar().addMenu(_(u"&Options")) menu.addAction(self.enable_renaming_action) menu.addAction(self.enable_moving_action) menu.addAction(self.enable_tag_saving_action) menu.addSeparator() menu.addAction(self.options_action) menu = self.menuBar().addMenu(_(u"&Tools")) menu.addAction(self.refresh_action) menu.addAction(self.cd_lookup_action) menu.addAction(self.autotag_action) menu.addAction(self.analyze_action) menu.addAction(self.cluster_action) menu.addAction(self.browser_lookup_action) menu.addSeparator() menu.addAction(self.tags_from_filenames_action) menu.addAction(self.open_collection_in_browser_action) self.menuBar().addSeparator() menu = self.menuBar().addMenu(_(u"&Help")) menu.addAction(self.help_action) menu.addSeparator() menu.addAction(self.view_history_action) menu.addSeparator() menu.addAction(self.support_forum_action) menu.addAction(self.report_bug_action) menu.addAction(self.view_log_action) menu.addSeparator() menu.addAction(self.donate_action) menu.addAction(self.about_action) def update_toolbar_style(self): if config.setting["toolbar_show_labels"]: self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) else: self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) self.cd_lookup_action.setEnabled(len(get_cdrom_drives()) > 0 and discid is not None) def create_toolbar(self): self.create_search_toolbar() self.create_action_toolbar() def create_action_toolbar(self): if getattr(self, 'toolbar', None): self.toolbar.clear() self.removeToolBar(self.toolbar) self.toolbar = toolbar = QtGui.QToolBar(_(u"Actions")) self.insertToolBar(self.search_toolbar, self.toolbar) self.update_toolbar_style() toolbar.setObjectName("main_toolbar") def add_toolbar_action(action): toolbar.addAction(action) widget = toolbar.widgetForAction(action) widget.setFocusPolicy(QtCore.Qt.TabFocus) widget.setAttribute(QtCore.Qt.WA_MacShowFocusRect) for action in config.setting['toolbar_layout']: if action not in ('cd_lookup_action', 'separator'): try: add_toolbar_action(getattr(self, action)) except AttributeError: log.warning('Warning: Unknown action name "%r" found in config. Ignored.', action) elif action == 'cd_lookup_action': add_toolbar_action(self.cd_lookup_action) drives = get_cdrom_drives() if len(drives) > 1: self.cd_lookup_menu = QtGui.QMenu() for drive in drives: self.cd_lookup_menu.addAction(drive) self.cd_lookup_menu.triggered.connect(self.tagger.lookup_cd) button = toolbar.widgetForAction(self.cd_lookup_action) button.setPopupMode(QtGui.QToolButton.MenuButtonPopup) button.setMenu(self.cd_lookup_menu) elif action == 'separator': toolbar.addSeparator() self.show_toolbar() def create_search_toolbar(self): self.search_toolbar = toolbar = self.addToolBar(_(u"Search")) self.search_toolbar_toggle_action = self.search_toolbar.toggleViewAction() toolbar.setObjectName("search_toolbar") search_panel = QtGui.QWidget(toolbar) hbox = QtGui.QHBoxLayout(search_panel) self.search_combo = QtGui.QComboBox(search_panel) self.search_combo.addItem(_(u"Album"), "album") self.search_combo.addItem(_(u"Artist"), "artist") self.search_combo.addItem(_(u"Track"), "track") hbox.addWidget(self.search_combo, 0) self.search_edit = ButtonLineEdit(search_panel) self.search_edit.returnPressed.connect(self.trigger_search_action) self.search_edit.textChanged.connect(self.enable_search) hbox.addWidget(self.search_edit, 0) self.search_button = QtGui.QToolButton(search_panel) self.search_button.setAutoRaise(True) self.search_button.setDefaultAction(self.search_action) self.search_button.setIconSize(QtCore.QSize(22, 22)) self.search_button.setAttribute(QtCore.Qt.WA_MacShowFocusRect) hbox.addWidget(self.search_button) toolbar.addWidget(search_panel) def set_tab_order(self): tab_order = self.setTabOrder tw = self.toolbar.widgetForAction prev_action = None current_action = None # Setting toolbar widget tab-orders for accessibility for action in config.setting['toolbar_layout']: if action != 'separator': try: current_action = tw(getattr(self, action)) except AttributeError: # No need to log warnings since we have already # done it once in create_toolbar pass if prev_action is not None and prev_action != current_action: tab_order(prev_action, current_action) prev_action = current_action tab_order(prev_action, self.search_combo) tab_order(self.search_combo, self.search_edit) tab_order(self.search_edit, self.search_button) # Panels tab_order(self.search_button, self.file_browser) tab_order(self.file_browser, self.panel.views[0]) tab_order(self.panel.views[0], self.panel.views[1]) tab_order(self.panel.views[1], self.metadata_box) def enable_submit(self, enabled): """Enable/disable the 'Submit fingerprints' action.""" self.submit_acoustid_action.setEnabled(enabled) def enable_cluster(self, enabled): """Enable/disable the 'Cluster' action.""" self.cluster_action.setEnabled(enabled) def enable_search(self): """Enable/disable the 'Search' action.""" if self.search_edit.text(): self.search_action.setEnabled(True) else: self.search_action.setEnabled(False) def trigger_search_action(self): if self.search_action.isEnabled(): self.search_action.trigger() def search(self): """Search for album, artist or track on the MusicBrainz website.""" text = self.search_edit.text() type = self.search_combo.itemData(self.search_combo.currentIndex()) self.tagger.search(text, type, config.setting["use_adv_search_syntax"]) def add_files(self): """Add files to the tagger.""" current_directory = find_starting_directory() formats = [] extensions = [] for exts, name in supported_formats(): exts = ["*" + e for e in exts] formats.append("%s (%s)" % (name, " ".join(exts))) extensions.extend(exts) formats.sort() extensions.sort() formats.insert(0, _("All Supported Formats") + " (%s)" % " ".join(extensions)) files = QtGui.QFileDialog.getOpenFileNames(self, "", current_directory, u";;".join(formats)) if files: files = map(unicode, files) config.persist["current_directory"] = os.path.dirname(files[0]) self.tagger.add_files(files) def add_directory(self): """Add directory to the tagger.""" current_directory = find_starting_directory() dir_list = [] if not config.setting["toolbar_multiselect"]: directory = QtGui.QFileDialog.getExistingDirectory(self, "", current_directory) if directory: dir_list.append(directory) else: file_dialog = MultiDirsSelectDialog(self, "", current_directory) if file_dialog.exec_() == QtGui.QDialog.Accepted: dir_list = file_dialog.selectedFiles() dir_count = len(dir_list) if dir_count: parent = os.path.dirname(dir_list[0]) if dir_count > 1 else dir_list[0] config.persist["current_directory"] = parent if dir_count > 1: self.set_statusbar_message( N_("Adding multiple directories from '%(directory)s' ..."), {'directory': parent} ) else: self.set_statusbar_message( N_("Adding directory: '%(directory)s' ..."), {'directory': dir_list[0]} ) for directory in dir_list: self.tagger.add_directory(directory) def show_about(self): self.show_options("about") def show_options(self, page=None): dialog = OptionsDialog(page, self) dialog.exec_() def show_help(self): webbrowser2.goto('documentation') def show_log(self): self.logDialog.show() self.logDialog.raise_() self.logDialog.activateWindow() def show_history(self): self.historyDialog.show() self.historyDialog.raise_() self.historyDialog.activateWindow() def open_bug_report(self): webbrowser2.goto('troubleshooting') def open_support_forum(self): webbrowser2.goto('forum') def open_donation_page(self): webbrowser2.goto('donate') def save(self): """Tell the tagger to save the selected objects.""" self.tagger.save(self.selected_objects) def remove(self): """Tell the tagger to remove the selected objects.""" self.panel.remove(self.selected_objects) def analyze(self): if not config.setting['fingerprinting_system']: if self.show_analyze_settings_info(): self.show_options("fingerprinting") if not config.setting['fingerprinting_system']: return return self.tagger.analyze(self.selected_objects) def _openUrl(self,url): # Resolves a bug in Qt opening remote URLs - QTBUG-13359 # See https://bugreports.qt.io/browse/QTBUG-13359 if url.startswith("\\\\") or url.startswith("//"): return QtCore.QUrl(QtCore.QDir.toNativeSeparators(url)) else: return QtCore.QUrl.fromLocalFile(url) def play_file(self): files = self.tagger.get_files_from_objects(self.selected_objects) for file in files: QtGui.QDesktopServices.openUrl(self._openUrl(file.filename)) def open_folder(self): files = self.tagger.get_files_from_objects(self.selected_objects) folders = set([os.path.dirname(f.filename) for f in files]) for folder in folders: QtGui.QDesktopServices.openUrl(self._openUrl(folder)) def show_analyze_settings_info(self): ret = QtGui.QMessageBox.question(self, _(u"Configuration Required"), _(u"Audio fingerprinting is not yet configured. Would you like to configure it now?"), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) return ret == QtGui.QMessageBox.Yes def show_more_tracks(self): obj = self.selected_objects[0] if isinstance(obj, Track): obj = obj.linked_files[0] dialog = TrackSearchDialog(self) dialog.load_similar_tracks(obj) dialog.exec_() def show_more_albums(self): obj = self.selected_objects[0] dialog = AlbumSearchDialog(self) dialog.show_similar_albums(obj) dialog.exec_() def view_info(self, default_tab=0): if isinstance(self.selected_objects[0], Album): album = self.selected_objects[0] dialog = AlbumInfoDialog(album, self) elif isinstance(self.selected_objects[0], Cluster): cluster = self.selected_objects[0] dialog = ClusterInfoDialog(cluster, self) elif isinstance(self.selected_objects[0], Track): track = self.selected_objects[0] dialog = TrackInfoDialog(track, self) else: file = self.tagger.get_files_from_objects(self.selected_objects)[0] dialog = FileInfoDialog(file, self) dialog.ui.tabWidget.setCurrentIndex(default_tab) dialog.exec_() def cluster(self): self.tagger.cluster(self.selected_objects) self.update_actions() def refresh(self): self.tagger.refresh(self.selected_objects) def browser_lookup(self): self.tagger.browser_lookup(self.selected_objects[0]) @throttle(100) def update_actions(self): can_remove = False can_save = False can_analyze = False can_refresh = False can_autotag = False single = self.selected_objects[0] if len(self.selected_objects) == 1 else None can_view_info = bool(single and single.can_view_info()) can_browser_lookup = bool(single and single.can_browser_lookup()) have_files = len(self.tagger.get_files_from_objects(self.selected_objects)) > 0 for obj in self.selected_objects: if obj is None: continue if obj.can_analyze(): can_analyze = True if obj.can_save(): can_save = True if obj.can_remove(): can_remove = True if obj.can_refresh(): can_refresh = True if obj.can_autotag(): can_autotag = True # Skip further loops if all values now True. if can_analyze and can_save and can_remove and can_refresh and can_autotag: break self.remove_action.setEnabled(can_remove) self.save_action.setEnabled(can_save) self.view_info_action.setEnabled(can_view_info) self.analyze_action.setEnabled(can_analyze) self.refresh_action.setEnabled(can_refresh) self.autotag_action.setEnabled(can_autotag) self.browser_lookup_action.setEnabled(can_browser_lookup) self.play_file_action.setEnabled(have_files) self.open_folder_action.setEnabled(have_files) self.cut_action.setEnabled(bool(self.selected_objects)) files = self.get_selected_or_unmatched_files() self.tags_from_filenames_action.setEnabled(bool(files)) def update_selection(self, objects=None): if self.ignore_selection_changes: return if objects is not None: self.selected_objects = objects else: objects = self.selected_objects self.update_actions() metadata = None orig_metadata = None obj = None # Clear any existing status bar messages self.set_statusbar_message("") if len(objects) == 1: obj = list(objects)[0] if isinstance(obj, File): metadata = obj.metadata orig_metadata = obj.orig_metadata if obj.state == obj.ERROR: msg = N_("%(filename)s (error: %(error)s)") mparms = { 'filename': obj.filename, 'error': obj.error } else: msg = N_("%(filename)s") mparms = { 'filename': obj.filename, } self.set_statusbar_message(msg, mparms, echo=None, history=None) elif isinstance(obj, Track): metadata = obj.metadata if obj.num_linked_files == 1: file = obj.linked_files[0] orig_metadata = file.orig_metadata if file.state == File.ERROR: msg = N_("%(filename)s (%(similarity)d%%) (error: %(error)s)") mparms = { 'filename': file.filename, 'similarity': file.similarity * 100, 'error': file.error } else: msg = N_("%(filename)s (%(similarity)d%%)") mparms = { 'filename': file.filename, 'similarity': file.similarity * 100, } self.set_statusbar_message(msg, mparms, echo=None, history=None) elif isinstance(obj, Album): metadata = obj.metadata orig_metadata = obj.orig_metadata elif obj.can_edit_tags(): metadata = obj.metadata self.metadata_box.selection_dirty = True self.metadata_box.update() self.cover_art_box.set_metadata(metadata, orig_metadata, obj) self.selection_updated.emit(objects) def show_cover_art(self): """Show/hide the cover art box.""" if self.show_cover_art_action.isChecked(): self.cover_art_box.show() else: self.cover_art_box.hide() def show_toolbar(self): """Show/hide the Action toolbar.""" if self.show_toolbar_action.isChecked(): self.toolbar.show() else: self.toolbar.hide() def show_file_browser(self): """Show/hide the file browser.""" if self.show_file_browser_action.isChecked(): sizes = self.panel.sizes() if sizes[0] == 0: sizes[0] = sum(sizes) / 4 self.panel.setSizes(sizes) self.file_browser.show() else: self.file_browser.hide() def show_password_dialog(self, reply, authenticator): if reply.url().host() == config.setting['server_host']: ret = QtGui.QMessageBox.question(self, _(u"Authentication Required"), _(u"Picard needs authorization to access your personal data on the MusicBrainz server. Would you like to log in now?"), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) if ret == QtGui.QMessageBox.Yes: pass else: dialog = PasswordDialog(authenticator, reply, parent=self) dialog.exec_() def show_proxy_dialog(self, proxy, authenticator): dialog = ProxyDialog(authenticator, proxy, parent=self) dialog.exec_() def autotag(self): self.tagger.autotag(self.selected_objects) def cut(self): self.tagger.copy_files(self.selected_objects) self.paste_action.setEnabled(bool(self.selected_objects)) def paste(self): selected_objects = self.selected_objects if not selected_objects: target = self.tagger.unmatched_files else: target = selected_objects[0] self.tagger.paste_files(target) self.paste_action.setEnabled(False) picard-release-1.4.2/picard/ui/metadatabox.py000066400000000000000000000533611310410472100211210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2004 Robert Kaye # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtGui from collections import defaultdict from functools import partial from picard import config from picard.album import Album from picard.cluster import Cluster from picard.track import Track from picard.file import File from picard.util import format_time, throttle, thread, uniqify from picard.util.tags import display_tag_name from picard.ui.edittagdialog import EditTagDialog from picard.metadata import MULTI_VALUED_JOINER from picard.browser.filelookup import FileLookup from picard.browser.browser import BrowserIntegration COMMON_TAGS = [ "title", "artist", "album", "tracknumber", "~length", "date", ] class TagStatus: NoChange = 1 Added = 2 Removed = 4 # Added | Removed = Changed Changed = 6 Empty = 8 NotRemovable = 16 class TagCounter(dict): __slots__ = ("parent", "counts", "different") def __init__(self, parent): self.parent = parent self.counts = defaultdict(lambda: 0) self.different = set() def __getitem__(self, tag): return dict.get(self, tag, [""]) def add(self, tag, values): if tag not in self.different: if tag not in self: self[tag] = values elif self[tag] != values: self.different.add(tag) self[tag] = [""] self.counts[tag] += 1 def display_value(self, tag): count = self.counts[tag] missing = self.parent.objects - count if tag in self.different: return (ungettext("(different across %d item)", "(different across %d items)", count) % count, True) else: if tag == "~length": msg = format_time(self.get(tag, 0)) else: msg = MULTI_VALUED_JOINER.join(self[tag]) if count > 0 and missing > 0: return (msg + " " + (ungettext("(missing from %d item)", "(missing from %d items)", missing) % missing), True) else: return (msg, False) class TagDiff(object): __slots__ = ("tag_names", "new", "orig", "status", "objects") def __init__(self): self.tag_names = [] self.new = TagCounter(self) self.orig = TagCounter(self) self.status = defaultdict(lambda: 0) self.objects = 0 def __tag_ne(self, tag, orig, new): if tag == "~length": return abs(float(orig) - float(new)) > 2000 else: return orig != new def add(self, tag, orig_values, new_values, removable, removed=False): if orig_values: self.orig.add(tag, orig_values) if new_values: self.new.add(tag, new_values) if (orig_values and not new_values) or removed: self.status[tag] |= TagStatus.Removed elif new_values and not orig_values: self.status[tag] |= TagStatus.Added removable = True elif orig_values and new_values and self.__tag_ne(tag, orig_values, new_values): self.status[tag] |= TagStatus.Changed elif not (orig_values or new_values or tag in COMMON_TAGS): self.status[tag] |= TagStatus.Empty else: self.status[tag] |= TagStatus.NoChange if not removable: self.status[tag] |= TagStatus.NotRemovable def tag_status(self, tag): status = self.status[tag] for s in (TagStatus.Changed, TagStatus.Added, TagStatus.Removed, TagStatus.Empty): if status & s == s: return s return TagStatus.NoChange class MetadataBox(QtGui.QTableWidget): options = ( config.Option("persist", "metadatabox_header_state", QtCore.QByteArray()), config.BoolOption("persist", "show_changes_first", False) ) def __init__(self, parent): QtGui.QTableWidget.__init__(self, parent) self.parent = parent self.setAccessibleName(_("metadata view")) self.setAccessibleDescription(_("Displays original and new tags for the selected files")) self.setColumnCount(3) self.setHorizontalHeaderLabels((_("Tag"), _("Original Value"), _("New Value"))) self.horizontalHeader().setStretchLastSection(True) self.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch) self.horizontalHeader().setClickable(False) self.verticalHeader().setDefaultSectionSize(21) self.verticalHeader().setVisible(False) self.setHorizontalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.setTabKeyNavigation(False) self.setStyleSheet("QTableWidget {border: none;}") self.setAttribute(QtCore.Qt.WA_MacShowFocusRect, 1) self.colors = { TagStatus.NoChange: self.palette().color(QtGui.QPalette.Text), TagStatus.Removed: QtGui.QBrush(QtGui.QColor("red")), TagStatus.Added: QtGui.QBrush(QtGui.QColor("green")), TagStatus.Changed: QtGui.QBrush(QtGui.QColor("darkgoldenrod")) } self.files = set() self.tracks = set() self.objects = set() self.selection_mutex = QtCore.QMutex() self.selection_dirty = False self.editing = None # the QTableWidgetItem being edited self.clipboard = [""] self.add_tag_action = QtGui.QAction(_(u"Add New Tag..."), parent) self.add_tag_action.triggered.connect(partial(self.edit_tag, "")) self.changes_first_action = QtGui.QAction(_(u"Show Changes First"), parent) self.changes_first_action.setCheckable(True) self.changes_first_action.setChecked(config.persist["show_changes_first"]) self.changes_first_action.toggled.connect(self.toggle_changes_first) self.browser_integration = BrowserIntegration() # TR: Keyboard shortcut for "Add New Tag..." self.add_tag_shortcut = QtGui.QShortcut(QtGui.QKeySequence(_("Alt+Shift+A")), self, partial(self.edit_tag, "")) self.add_tag_action.setShortcut(self.add_tag_shortcut.key()) # TR: Keyboard shortcut for "Edit..." (tag) self.edit_tag_shortcut = QtGui.QShortcut(QtGui.QKeySequence(_("Alt+Shift+E")), self, partial(self.edit_selected_tag)) # TR: Keyboard shortcut for "Remove" (tag) self.remove_tag_shortcut = QtGui.QShortcut(QtGui.QKeySequence(_("Alt+Shift+R")), self, self.remove_selected_tags) def get_file_lookup(self): """Return a FileLookup object.""" return FileLookup(self, config.setting["server_host"], config.setting["server_port"], self.browser_integration.port) def lookup_tags(self): lookup = self.get_file_lookup() LOOKUP_TAGS = { "musicbrainz_recordingid": lookup.recordingLookup, "musicbrainz_trackid": lookup.trackLookup, "musicbrainz_albumid": lookup.albumLookup, "musicbrainz_workid": lookup.workLookup, "musicbrainz_artistid": lookup.artistLookup, "musicbrainz_albumartistid": lookup.artistLookup, "musicbrainz_releasegroupid": lookup.releaseGroupLookup, "acoustid_id": lookup.acoustLookup } return LOOKUP_TAGS def open_link(self, values, tag): lookup = self.lookup_tags() lookup_func = lookup[tag] for v in values: lookup_func(v) def edit(self, index, trigger, event): if index.column() != 2: return False item = self.itemFromIndex(index) if item.flags() & QtCore.Qt.ItemIsEditable and \ trigger in (QtGui.QAbstractItemView.DoubleClicked, QtGui.QAbstractItemView.EditKeyPressed, QtGui.QAbstractItemView.AnyKeyPressed): tag = self.tag_diff.tag_names[item.row()] values = self.tag_diff.new[tag] if len(values) > 1: self.edit_tag(tag) return False else: self.editing = item item.setText(values[0]) return QtGui.QTableWidget.edit(self, index, trigger, event) return False def event(self, e): item = self.currentItem() if (item and e.type() == QtCore.QEvent.KeyPress and e.modifiers() == QtCore.Qt.ControlModifier): column = item.column() tag = self.tag_diff.tag_names[item.row()] if e.key() == QtCore.Qt.Key_C: if column == 1: self.clipboard = list(self.tag_diff.orig[tag]) elif column == 2: self.clipboard = list(self.tag_diff.new[tag]) elif e.key() == QtCore.Qt.Key_V and column == 2 and tag != "~length": self.set_tag_values(tag, list(self.clipboard)) return QtGui.QTableWidget.event(self, e) def closeEditor(self, editor, hint): QtGui.QTableWidget.closeEditor(self, editor, hint) tag = self.tag_diff.tag_names[self.editing.row()] old = self.tag_diff.new[tag] new = [unicode(editor.text())] if old == new: self.editing.setText(old[0]) else: self.set_tag_values(tag, new) self.editing = None self.update() def contextMenuEvent(self, event): menu = QtGui.QMenu(self) if self.objects: tags = self.selected_tags(discard=('~length')) if len(tags) == 1: selected_tag = tags[0] edit_tag_action = QtGui.QAction(_(u"Edit..."), self.parent) edit_tag_action.triggered.connect(partial(self.edit_tag, selected_tag)) edit_tag_action.setShortcut(self.edit_tag_shortcut.key()) menu.addAction(edit_tag_action) preserved_tags = [tag for tag in map(lambda x: x.strip(), config.setting['preserved_tags'].split(',')) if tag != ""] if selected_tag not in preserved_tags: add_to_preserved_tags_action = QtGui.QAction(_(u"Add to 'Preserve Tags' List"), self.parent) add_to_preserved_tags_action.triggered.connect(partial(self.add_to_preserved_tags, selected_tag, preserved_tags)) menu.addAction(add_to_preserved_tags_action) else: remove_from_preserved_tags_action = QtGui.QAction(_(u"Remove from 'Preserve Tags' List"), self.parent) remove_from_preserved_tags_action.triggered.connect(partial(self.remove_from_preserved_tags, selected_tag, preserved_tags)) menu.addAction(remove_from_preserved_tags_action) removals = [] useorigs = [] item = self.currentItem() column = item.column() for tag in tags: if tag in self.lookup_tags().keys(): if (column == 1 or column == 2) and len(tags) == 1 and item.text(): if column == 1: values = self.tag_diff.orig[tag] else: values = self.tag_diff.new[tag] lookup_action = QtGui.QAction(_(u"Lookup in &Browser"), self.parent) lookup_action.triggered.connect(partial(self.open_link, values, tag)) menu.addAction(lookup_action) if self.tag_is_removable(tag): removals.append(partial(self.remove_tag, tag)) status = self.tag_diff.status[tag] & TagStatus.Changed if status == TagStatus.Changed or status == TagStatus.Removed: for file in self.files: objects = [file] if file.parent in self.tracks and len(self.files & set(file.parent.linked_files)) == 1: objects.append(file.parent) orig_values = list(file.orig_metadata.getall(tag)) or [""] useorigs.append(partial(self.set_tag_values, tag, orig_values, objects)) if removals: remove_tag_action = QtGui.QAction(_(u"Remove"), self.parent) remove_tag_action.triggered.connect(lambda: [f() for f in removals]) remove_tag_action.setShortcut(self.remove_tag_shortcut.key()) menu.addAction(remove_tag_action) if useorigs: name = ungettext("Use Original Value", "Use Original Values", len(useorigs)) use_orig_value_action = QtGui.QAction(name, self.parent) use_orig_value_action.triggered.connect(lambda: [f() for f in useorigs]) menu.addAction(use_orig_value_action) menu.addSeparator() if len(tags) == 1 or removals or useorigs: menu.addSeparator() menu.addAction(self.add_tag_action) menu.addSeparator() menu.addAction(self.changes_first_action) menu.exec_(event.globalPos()) event.accept() @staticmethod def _save_preserved_tags(preserved_tags): config.setting['preserved_tags'] = ", ".join(uniqify(preserved_tags)) def add_to_preserved_tags(self, name, preserved_tags): preserved_tags.append(name) self._save_preserved_tags(preserved_tags) def remove_from_preserved_tags(self, name, preserved_tags): preserved_tags = filter(lambda x: x != name, preserved_tags) self._save_preserved_tags(preserved_tags) def edit_tag(self, tag): EditTagDialog(self.parent, tag).exec_() def edit_selected_tag(self): tags = self.selected_tags(discard=('~length')) if len(tags) == 1: self.edit_tag(tags[0]) def toggle_changes_first(self, checked): config.persist["show_changes_first"] = checked self.update() def set_tag_values(self, tag, values, objects=None): if objects is None: objects = self.objects self.parent.ignore_selection_changes = True if values != [""] or self.tag_is_removable(tag): for obj in objects: obj.metadata[tag] = values obj.update() self.update() self.parent.ignore_selection_changes = False def remove_tag(self, tag): self.set_tag_values(tag, [""]) def remove_selected_tags(self): for tag in self.selected_tags(discard=('~length')): if self.tag_is_removable(tag): self.remove_tag(tag) def tag_is_removable(self, tag): return self.tag_diff.status[tag] & TagStatus.NotRemovable == 0 def selected_tags(self, discard=None): if discard is None: discard = set() tags = set(self.tag_diff.tag_names[item.row()] for item in self.selectedItems()) return list(tags.difference(discard)) def _update_selection(self): files = set() tracks = set() objects = set() for obj in self.parent.selected_objects: if isinstance(obj, File): files.add(obj) elif isinstance(obj, Track): tracks.add(obj) files.update(obj.linked_files) elif isinstance(obj, Cluster) and obj.can_edit_tags(): objects.add(obj) files.update(obj.files) elif isinstance(obj, Album): objects.add(obj) tracks.update(obj.tracks) for track in obj.tracks: files.update(track.linked_files) objects.update(files) objects.update(tracks) self.selection_dirty = False self.selection_mutex.lock() self.files = files self.tracks = tracks self.objects = objects self.selection_mutex.unlock() @throttle(100) def update(self): if self.editing: return if self.selection_dirty: self._update_selection() thread.run_task(self._update_tags, self._update_items) def _update_tags(self): self.selection_mutex.lock() files = self.files tracks = self.tracks self.selection_mutex.unlock() if not (files or tracks): return None tag_diff = TagDiff() orig_tags = tag_diff.orig new_tags = tag_diff.new # existing_tags are orig_tags that would not be overwritten by # any new_tags, assuming clear_existing_tags is disabled. existing_tags = set() tag_diff.objects = len(files) clear_existing_tags = config.setting["clear_existing_tags"] for file in files: new_metadata = file.new_metadata orig_metadata = file.orig_metadata tags = set(new_metadata.keys() + orig_metadata.keys()) for name in filter(lambda x: not x.startswith("~") and file.supports_tag(x), tags): new_values = new_metadata.getall(name) orig_values = orig_metadata.getall(name) if not ((new_values and name not in existing_tags) or clear_existing_tags): new_values = list(orig_values or [""]) existing_tags.add(name) removed = name in new_metadata.deleted_tags tag_diff.add(name, orig_values, new_values, True, removed) tag_diff.add("~length", str(orig_metadata.length), str(new_metadata.length), False) for track in tracks: if track.num_linked_files == 0: for name, values in dict.iteritems(track.metadata): if not name.startswith("~"): tag_diff.add(name, values, values, True) length = str(track.metadata.length) tag_diff.add("~length", length, length, False) tag_diff.objects += 1 all_tags = set(orig_tags.keys() + new_tags.keys()) tag_names = COMMON_TAGS + \ sorted(all_tags.difference(COMMON_TAGS), key=lambda x: display_tag_name(x).lower()) if config.persist["show_changes_first"]: tags_by_status = {} for tag in tag_names: tags_by_status.setdefault(tag_diff.tag_status(tag), []).append(tag) for status in (TagStatus.Changed, TagStatus.Added, TagStatus.Removed, TagStatus.NoChange): tag_diff.tag_names += tags_by_status.pop(status, []) else: tag_diff.tag_names = [ tag for tag in tag_names if tag_diff.status[tag] != TagStatus.Empty] return tag_diff def _update_items(self, result=None, error=None): if self.editing: return if not (self.files or self.tracks): result = None self.tag_diff = result if result is None: self.setRowCount(0) return self.setRowCount(len(result.tag_names)) orig_flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled new_flags = orig_flags | QtCore.Qt.ItemIsEditable for i, name in enumerate(result.tag_names): length = name == "~length" tag_item = self.item(i, 0) orig_item = self.item(i, 1) new_item = self.item(i, 2) if not tag_item: tag_item = QtGui.QTableWidgetItem() tag_item.setFlags(orig_flags) font = tag_item.font() font.setBold(True) tag_item.setFont(font) self.setItem(i, 0, tag_item) if not orig_item: orig_item = QtGui.QTableWidgetItem() orig_item.setFlags(orig_flags) self.setItem(i, 1, orig_item) if not new_item: new_item = QtGui.QTableWidgetItem() self.setItem(i, 2, new_item) tag_item.setText(display_tag_name(name)) self.set_item_value(orig_item, self.tag_diff.orig, name) new_item.setFlags(orig_flags if length else new_flags) self.set_item_value(new_item, self.tag_diff.new, name) font = new_item.font() if result.tag_status(name) == TagStatus.Removed: font.setStrikeOut(True) else: font.setStrikeOut(False) new_item.setFont(font) color = self.colors.get(result.tag_status(name), self.colors[TagStatus.NoChange]) orig_item.setForeground(color) new_item.setForeground(color) def set_item_value(self, item, tags, name): text, italic = tags.display_value(name) item.setText(text) font = item.font() font.setItalic(italic) item.setFont(font) def restore_state(self): state = config.persist["metadatabox_header_state"] header = self.horizontalHeader() header.restoreState(state) header.setResizeMode(QtGui.QHeaderView.Interactive) def save_state(self): header = self.horizontalHeader() state = header.saveState() config.persist["metadatabox_header_state"] = state picard-release-1.4.2/picard/ui/options/000077500000000000000000000000001310410472100177415ustar00rootroot00000000000000picard-release-1.4.2/picard/ui/options/__init__.py000066400000000000000000000060131310410472100220520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import re from PyQt4 import QtGui from picard import config from picard.plugin import ExtensionPoint class OptionsCheckError(Exception): def __init__(self, title, info): self.title = title self.info = info class OptionsPage(QtGui.QWidget): PARENT = None SORT_ORDER = 1000 ACTIVE = True STYLESHEET_ERROR = "QWidget { background-color: #f55; color: white; font-weight:bold }" def info(self): raise NotImplementedError def check(self): pass def load(self): pass def save(self): pass def restore_defaults(self): try: options = self.options except AttributeError: return old_options = {} for option in options: if option.section == 'setting': old_options[option.name] = config.setting[option.name] config.setting[option.name] = option.default self.load() # Restore the config values incase the user doesn't save after restoring defaults for key in old_options: config.setting[key] = old_options[key] def display_error(self, error): dialog = QtGui.QMessageBox(QtGui.QMessageBox.Warning, error.title, error.info, QtGui.QMessageBox.Ok, self) dialog.exec_() def init_regex_checker(self, regex_edit, regex_error): """ regex_edit : a widget supporting text() and textChanged() methods, ie QLineEdit regex_error : a widget supporting setStyleSheet() and setText() methods, ie. QLabel """ def check(): try: re.compile(unicode(regex_edit.text())) except re.error as e: raise OptionsCheckError(_("Regex Error"), str(e)) def live_checker(text): regex_error.setStyleSheet("") regex_error.setText("") try: check() except OptionsCheckError as e: regex_error.setStyleSheet(self.STYLESHEET_ERROR) regex_error.setText(e.info) regex_edit.textChanged.connect(live_checker) _pages = ExtensionPoint() def register_options_page(page_class): _pages.register(page_class.__module__, page_class) picard-release-1.4.2/picard/ui/options/about.py000066400000000000000000000067641310410472100214420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006-2014 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from picard.const import PICARD_URLS from picard.formats import supported_extensions from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_about import Ui_AboutOptionsPage from picard.util import versions class AboutOptionsPage(OptionsPage): NAME = "about" TITLE = N_("About") PARENT = None SORT_ORDER = 100 ACTIVE = True def __init__(self, parent=None): super(AboutOptionsPage, self).__init__(parent) self.ui = Ui_AboutOptionsPage() self.ui.setupUi(self) def load(self): args = { "picard-doc-url": PICARD_URLS['home'], "picard-donate-url": PICARD_URLS['donate'], } args.update(versions.as_dict(i18n=True)) args["formats"] = ", ".join(map(lambda x: x[1:], supported_extensions())) # TR: Replace this with your name to have it appear in the "About" dialog. args["translator-credits"] = _("translator-credits") if args["translator-credits"] != "translator-credits": # TR: Replace LANG with language you are translatig to. args["translator-credits"] = _("
Translated to LANG by %s") % args["translator-credits"].replace("\n", "
") else: args["translator-credits"] = "" args['third_parties_versions'] = '
'.join([u"%s %s" % (versions.version_name(name), value) for name, value in versions.as_dict(i18n=True).items() if name != 'version']) text = _(u"""

MusicBrainz Picard
Version %(version)s

%(third_parties_versions)s

Supported formats
%(formats)s

Please donate
Thank you for using Picard. Picard relies on the MusicBrainz database, which is operated by the MetaBrainz Foundation with the help of thousands of volunteers. If you like this application please consider donating to the MetaBrainz Foundation to keep the service running.

Donate now!

Credits
Copyright © 2004-2017 Robert Kaye, Lukáš Lalinský, Laurent Monin and others%(translator-credits)s

Official website
%(picard-doc-url)s

""") % args self.ui.label.setOpenExternalLinks(True) self.ui.label.setText(text) register_options_page(AboutOptionsPage) picard-release-1.4.2/picard/ui/options/advanced.py000066400000000000000000000063731310410472100220710ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4.QtGui import QPalette, QColor from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_advanced import Ui_AdvancedOptionsPage class AdvancedOptionsPage(OptionsPage): NAME = "advanced" TITLE = N_("Advanced") PARENT = None SORT_ORDER = 90 ACTIVE = True options = [ config.TextOption("setting", "ignore_regex", ""), config.BoolOption("setting", "ignore_hidden_files", False), config.BoolOption("setting", "recursively_add_files", True), config.BoolOption("setting", "completeness_ignore_videos", False), config.BoolOption("setting", "completeness_ignore_pregap", False), config.BoolOption("setting", "completeness_ignore_data", False), config.BoolOption("setting", "completeness_ignore_silence", False), ] def __init__(self, parent=None): super(AdvancedOptionsPage, self).__init__(parent) self.ui = Ui_AdvancedOptionsPage() self.ui.setupUi(self) self.init_regex_checker(self.ui.ignore_regex, self.ui.regex_error) def load(self): self.ui.ignore_regex.setText(config.setting["ignore_regex"]) self.ui.ignore_hidden_files.setChecked(config.setting["ignore_hidden_files"]) self.ui.recursively_add_files.setChecked(config.setting["recursively_add_files"]) self.ui.completeness_ignore_videos.setChecked(config.setting["completeness_ignore_videos"]) self.ui.completeness_ignore_pregap.setChecked(config.setting["completeness_ignore_pregap"]) self.ui.completeness_ignore_data.setChecked(config.setting["completeness_ignore_data"]) self.ui.completeness_ignore_silence.setChecked(config.setting["completeness_ignore_silence"]) def save(self): config.setting["ignore_regex"] = unicode(self.ui.ignore_regex.text()) config.setting["ignore_hidden_files"] = self.ui.ignore_hidden_files.isChecked() config.setting["recursively_add_files"] = self.ui.recursively_add_files.isChecked() config.setting["completeness_ignore_videos"] = self.ui.completeness_ignore_videos.isChecked() config.setting["completeness_ignore_pregap"] = self.ui.completeness_ignore_pregap.isChecked() config.setting["completeness_ignore_data"] = self.ui.completeness_ignore_data.isChecked() config.setting["completeness_ignore_silence"] = self.ui.completeness_ignore_silence.isChecked() register_options_page(AdvancedOptionsPage) picard-release-1.4.2/picard/ui/options/cdlookup.py000066400000000000000000000046001310410472100221330ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (c) 2004 Robert Kaye # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.util.cdrom import ( get_cdrom_drives, AUTO_DETECT_DRIVES, DEFAULT_DRIVES ) if AUTO_DETECT_DRIVES: from picard.ui.ui_options_cdlookup_select import Ui_CDLookupOptionsPage else: from picard.ui.ui_options_cdlookup import Ui_CDLookupOptionsPage class CDLookupOptionsPage(OptionsPage): NAME = "cdlookup" TITLE = N_("CD Lookup") PARENT = None SORT_ORDER = 50 ACTIVE = True options = [ config.TextOption("setting", "cd_lookup_device", ",".join(DEFAULT_DRIVES)), ] def __init__(self, parent=None): super(CDLookupOptionsPage, self).__init__(parent) self.ui = Ui_CDLookupOptionsPage() self.ui.setupUi(self) if AUTO_DETECT_DRIVES: self.drives = get_cdrom_drives() self.ui.cd_lookup_device.addItems(self.drives) def load(self): if AUTO_DETECT_DRIVES: try: self.ui.cd_lookup_device.setCurrentIndex(self.drives.index(config.setting["cd_lookup_device"])) except ValueError: pass else: self.ui.cd_lookup_device.setText(config.setting["cd_lookup_device"]) def save(self): if AUTO_DETECT_DRIVES: config.setting["cd_lookup_device"] = unicode(self.ui.cd_lookup_device.currentText()) else: config.setting["cd_lookup_device"] = unicode(self.ui.cd_lookup_device.text()) register_options_page(CDLookupOptionsPage) picard-release-1.4.2/picard/ui/options/cover.py000066400000000000000000000115561310410472100214410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtGui from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_cover import Ui_CoverOptionsPage from picard.coverart.providers import cover_art_providers, is_provider_enabled from picard.ui.sortablecheckboxlist import ( SortableCheckboxListWidget, SortableCheckboxListItem ) class CoverOptionsPage(OptionsPage): NAME = "cover" TITLE = N_("Cover Art") PARENT = None SORT_ORDER = 35 ACTIVE = True options = [ config.BoolOption("setting", "save_images_to_tags", True), config.BoolOption("setting", "embed_only_one_front_image", True), config.BoolOption("setting", "save_images_to_files", False), config.TextOption("setting", "cover_image_filename", "cover"), config.BoolOption("setting", "save_images_overwrite", False), config.ListOption("setting", "ca_providers", [ ('Cover Art Archive', True), ('Amazon', True), ('Whitelist', True), ('CaaReleaseGroup', False), ('Local', False), ]), ] def __init__(self, parent=None): super(CoverOptionsPage, self).__init__(parent) self.ui = Ui_CoverOptionsPage() self.ui.setupUi(self) self.ui.save_images_to_files.clicked.connect(self.update_filename) self.ui.save_images_to_tags.clicked.connect(self.update_save_images_to_tags) self.provider_list_widget = SortableCheckboxListWidget() self.ui.ca_providers_list.insertWidget(0, self.provider_list_widget) self.ca_providers = [] def load_cover_art_providers(self): """Load available providers, initialize provider-specific options, restore state of each """ providers = cover_art_providers() for provider in providers: try: title = _(provider.TITLE) except AttributeError: title = provider.NAME checked = is_provider_enabled(provider.NAME) self.provider_list_widget.addItem(SortableCheckboxListItem(title, checked=checked, data=provider.NAME)) def update_providers_options(items): self.ca_providers = [(item.data, item.checked) for item in items] self.provider_list_widget.changed.connect(update_providers_options) def restore_defaults(self): # Remove previous entries self.provider_list_widget.clear() super(CoverOptionsPage, self).restore_defaults() def load(self): self.ui.save_images_to_tags.setChecked(config.setting["save_images_to_tags"]) self.ui.cb_embed_front_only.setChecked(config.setting["embed_only_one_front_image"]) self.ui.save_images_to_files.setChecked(config.setting["save_images_to_files"]) self.ui.cover_image_filename.setText(config.setting["cover_image_filename"]) self.ui.save_images_overwrite.setChecked(config.setting["save_images_overwrite"]) self.ca_providers = config.setting["ca_providers"] self.load_cover_art_providers() self.update_all() def save(self): config.setting["save_images_to_tags"] = self.ui.save_images_to_tags.isChecked() config.setting["embed_only_one_front_image"] = self.ui.cb_embed_front_only.isChecked() config.setting["save_images_to_files"] = self.ui.save_images_to_files.isChecked() config.setting["cover_image_filename"] = unicode(self.ui.cover_image_filename.text()) config.setting["save_images_overwrite"] = self.ui.save_images_overwrite.isChecked() config.setting["ca_providers"] = self.ca_providers def update_all(self): self.update_filename() self.update_save_images_to_tags() def update_filename(self): enabled = self.ui.save_images_to_files.isChecked() self.ui.cover_image_filename.setEnabled(enabled) self.ui.save_images_overwrite.setEnabled(enabled) def update_save_images_to_tags(self): enabled = self.ui.save_images_to_tags.isChecked() self.ui.cb_embed_front_only.setEnabled(enabled) register_options_page(CoverOptionsPage) picard-release-1.4.2/picard/ui/options/dialog.py000066400000000000000000000156121310410472100215570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006-2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtGui from picard import config from picard.util import webbrowser2 from picard.ui import PicardDialog from picard.ui.util import StandardButton from picard.ui.options import ( about, advanced, cdlookup, cover, general, interface, folksonomy, ratings, matching, metadata, releases, renaming, plugins, network, scripting, tags, fingerprinting, OptionsCheckError, _pages as page_classes ) class OptionsDialog(PicardDialog): options = [ config.Option("persist", "options_position", QtCore.QPoint()), config.Option("persist", "options_size", QtCore.QSize(560, 400)), config.Option("persist", "options_splitter", QtCore.QByteArray()), ] def add_pages(self, parent, default_page, parent_item): pages = [(p.SORT_ORDER, p.NAME, p) for p in self.pages if p.PARENT == parent] items = [] for foo, bar, page in sorted(pages): item = QtGui.QTreeWidgetItem(parent_item) item.setText(0, _(page.TITLE)) if page.ACTIVE: self.item_to_page[item] = page self.page_to_item[page.NAME] = item self.ui.pages_stack.addWidget(page) else: item.setFlags(QtCore.Qt.ItemIsEnabled) self.add_pages(page.NAME, default_page, item) if page.NAME == default_page: self.default_item = item items.append(item) if not self.default_item and not parent: self.default_item = items[0] def __init__(self, default_page=None, parent=None): PicardDialog.__init__(self, parent) from picard.ui.ui_options import Ui_Dialog self.ui = Ui_Dialog() self.ui.setupUi(self) self.ui.reset_all_button = QtGui.QPushButton(_("&Restore all Defaults")) self.ui.reset_all_button.setToolTip(_("Reset all of Picard's settings")) self.ui.reset_button = QtGui.QPushButton(_("Restore &Defaults")) self.ui.reset_button.setToolTip(_("Reset all settings for current option page")) ok = StandardButton(StandardButton.OK) ok.setText(_("Make It So!")) self.ui.buttonbox.addButton(ok, QtGui.QDialogButtonBox.AcceptRole) self.ui.buttonbox.addButton(StandardButton(StandardButton.CANCEL), QtGui.QDialogButtonBox.RejectRole) self.ui.buttonbox.addButton(StandardButton(StandardButton.HELP), QtGui.QDialogButtonBox.HelpRole) self.ui.buttonbox.addButton(self.ui.reset_all_button, QtGui.QDialogButtonBox.ActionRole) self.ui.buttonbox.addButton(self.ui.reset_button, QtGui.QDialogButtonBox.ActionRole) self.ui.buttonbox.accepted.connect(self.accept) self.ui.buttonbox.rejected.connect(self.reject) self.ui.reset_all_button.clicked.connect(self.confirm_reset_all) self.ui.reset_button.clicked.connect(self.confirm_reset) self.ui.buttonbox.helpRequested.connect(self.help) self.pages = [] for Page in page_classes: page = Page(self.ui.pages_stack) self.pages.append(page) self.item_to_page = {} self.page_to_item = {} self.default_item = None self.add_pages(None, default_page, self.ui.pages_tree) # work-around to set optimal option pane width self.ui.pages_tree.expandAll() max_page_name = self.ui.pages_tree.sizeHintForColumn(0) + 2*self.ui.pages_tree.frameWidth() self.ui.pages_tree.collapseAll() self.ui.pages_tree.setMinimumWidth(max_page_name) self.ui.pages_tree.setHeaderLabels([""]) self.ui.pages_tree.header().hide() self.ui.pages_tree.itemSelectionChanged.connect(self.switch_page) self.restoreWindowState() for page in self.pages: page.load() self.ui.pages_tree.setCurrentItem(self.default_item) def switch_page(self): items = self.ui.pages_tree.selectedItems() if items: page = self.item_to_page[items[0]] self.ui.pages_stack.setCurrentWidget(page) def help(self): webbrowser2.goto('doc_options') def accept(self): for page in self.pages: try: page.check() except OptionsCheckError as e: self.ui.pages_tree.setCurrentItem(self.page_to_item[page.NAME]) page.display_error(e) return for page in self.pages: page.save() self.saveWindowState() QtGui.QDialog.accept(self) def closeEvent(self, event): self.saveWindowState() event.accept() def saveWindowState(self): pos = self.pos() if not pos.isNull(): config.persist["options_position"] = pos config.persist["options_size"] = self.size() config.persist["options_splitter"] = self.ui.splitter.saveState() def restoreWindowState(self): pos = config.persist["options_position"] if pos.x() > 0 and pos.y() > 0: self.move(pos) self.resize(config.persist["options_size"]) self.ui.splitter.restoreState(config.persist["options_splitter"]) def restore_all_defaults(self): for page in self.pages: page.restore_defaults() def restore_page_defaults(self): self.ui.pages_stack.currentWidget().restore_defaults() def confirm_reset(self): msg = _("You are about to reset your options for this page.") self._show_dialog(msg, self.restore_page_defaults) def confirm_reset_all(self): msg = _("Warning! This will reset all of your settings.") self._show_dialog(msg, self.restore_all_defaults) def _show_dialog(self, msg, function): message_box = QtGui.QMessageBox() message_box.setIcon(QtGui.QMessageBox.Warning) message_box.setWindowModality(QtCore.Qt.WindowModal) message_box.setWindowTitle(_("Confirm Reset")) message_box.setText(_("Are you sure?") + "\n\n" + msg) message_box.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) if message_box.exec_() == QtGui.QMessageBox.Yes: function() picard-release-1.4.2/picard/ui/options/fingerprinting.py000066400000000000000000000135711310410472100233470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2011 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os from PyQt4 import QtGui, QtCore from picard import config from picard.util import webbrowser2, find_executable from picard.const import FPCALC_NAMES from picard.ui.options import OptionsPage, OptionsCheckError, register_options_page from picard.ui.ui_options_fingerprinting import Ui_FingerprintingOptionsPage class FingerprintingOptionsPage(OptionsPage): NAME = "fingerprinting" TITLE = N_("Fingerprinting") PARENT = None SORT_ORDER = 45 ACTIVE = True options = [ config.BoolOption("setting", "ignore_existing_acoustid_fingerprints", False), config.TextOption("setting", "fingerprinting_system", "acoustid"), config.TextOption("setting", "acoustid_fpcalc", ""), config.TextOption("setting", "acoustid_apikey", ""), ] def __init__(self, parent=None): super(FingerprintingOptionsPage, self).__init__(parent) self._fpcalc_valid = True self.ui = Ui_FingerprintingOptionsPage() self.ui.setupUi(self) self.ui.disable_fingerprinting.clicked.connect(self.update_groupboxes) self.ui.use_acoustid.clicked.connect(self.update_groupboxes) self.ui.acoustid_fpcalc.textChanged.connect(self._acoustid_fpcalc_check) self.ui.acoustid_fpcalc_browse.clicked.connect(self.acoustid_fpcalc_browse) self.ui.acoustid_fpcalc_download.clicked.connect(self.acoustid_fpcalc_download) self.ui.acoustid_apikey_get.clicked.connect(self.acoustid_apikey_get) def load(self): if config.setting["fingerprinting_system"] == "acoustid": self.ui.use_acoustid.setChecked(True) else: self.ui.disable_fingerprinting.setChecked(True) self.ui.acoustid_fpcalc.setText(config.setting["acoustid_fpcalc"]) self.ui.acoustid_apikey.setText(config.setting["acoustid_apikey"]) self.ui.ignore_existing_acoustid_fingerprints.setChecked(config.setting["ignore_existing_acoustid_fingerprints"]) self.update_groupboxes() def save(self): if self.ui.use_acoustid.isChecked(): config.setting["fingerprinting_system"] = "acoustid" else: config.setting["fingerprinting_system"] = "" config.setting["acoustid_fpcalc"] = unicode(self.ui.acoustid_fpcalc.text()) config.setting["acoustid_apikey"] = unicode(self.ui.acoustid_apikey.text()) config.setting["ignore_existing_acoustid_fingerprints"] = self.ui.ignore_existing_acoustid_fingerprints.isChecked() def update_groupboxes(self): if self.ui.use_acoustid.isChecked(): self.ui.acoustid_settings.setEnabled(True) if not self.ui.acoustid_fpcalc.text(): fpcalc_path = find_executable(*FPCALC_NAMES) if fpcalc_path: self.ui.acoustid_fpcalc.setText(fpcalc_path) else: self.ui.acoustid_settings.setEnabled(False) self._acoustid_fpcalc_check() def acoustid_fpcalc_browse(self): path = QtGui.QFileDialog.getOpenFileName(self, "", self.ui.acoustid_fpcalc.text()) if path: path = os.path.normpath(unicode(path)) self.ui.acoustid_fpcalc.setText(path) def acoustid_fpcalc_download(self): webbrowser2.goto('chromaprint') def acoustid_apikey_get(self): webbrowser2.goto('acoustid_apikey') def _acoustid_fpcalc_check(self): if not self.ui.use_acoustid.isChecked(): self._acoustid_fpcalc_set_success("") return fpcalc = unicode(self.ui.acoustid_fpcalc.text()) if not fpcalc: self._acoustid_fpcalc_set_success("") return self._fpcalc_valid = False process = QtCore.QProcess(self) process.finished.connect(self._on_acoustid_fpcalc_check_finished) process.error.connect(self._on_acoustid_fpcalc_check_error) process.start(fpcalc, ["-v"]) def _on_acoustid_fpcalc_check_finished(self, exit_code, exit_status): process = self.sender() if exit_code == 0 and exit_status == 0: output = str(process.readAllStandardOutput()) if output.startswith("fpcalc version"): self._acoustid_fpcalc_set_success(output.strip()) else: self._acoustid_fpcalc_set_error() else: self._acoustid_fpcalc_set_error() def _on_acoustid_fpcalc_check_error(self, error): self._acoustid_fpcalc_set_error() def _acoustid_fpcalc_set_success(self, version): self._fpcalc_valid = True self.ui.acoustid_fpcalc_info.setStyleSheet("") self.ui.acoustid_fpcalc_info.setText(version) def _acoustid_fpcalc_set_error(self): self._fpcalc_valid = False self.ui.acoustid_fpcalc_info.setStyleSheet(self.STYLESHEET_ERROR) self.ui.acoustid_fpcalc_info.setText(_("Please select a valid fpcalc executable.")) def check(self): if not self._fpcalc_valid: raise OptionsCheckError(_("Invalid fpcalc executable"), _("Please select a valid fpcalc executable.")) def display_error(self, error): pass register_options_page(FingerprintingOptionsPage) picard-release-1.4.2/picard/ui/options/folksonomy.py000066400000000000000000000051151310410472100225150ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2008 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_folksonomy import Ui_FolksonomyOptionsPage class FolksonomyOptionsPage(OptionsPage): NAME = "folsonomy" TITLE = N_("Folksonomy Tags") PARENT = "metadata" SORT_ORDER = 20 ACTIVE = True options = [ config.IntOption("setting", "max_tags", 5), config.IntOption("setting", "min_tag_usage", 90), config.TextOption("setting", "ignore_tags", "seen live,favorites,fixme,owned"), config.TextOption("setting", "join_tags", ""), config.BoolOption("setting", "only_my_tags", False), config.BoolOption("setting", "artists_tags", False), ] def __init__(self, parent=None): super(FolksonomyOptionsPage, self).__init__(parent) self.ui = Ui_FolksonomyOptionsPage() self.ui.setupUi(self) def load(self): self.ui.max_tags.setValue(config.setting["max_tags"]) self.ui.min_tag_usage.setValue(config.setting["min_tag_usage"]) self.ui.join_tags.setEditText(config.setting["join_tags"]) self.ui.ignore_tags.setText(config.setting["ignore_tags"]) self.ui.only_my_tags.setChecked(config.setting["only_my_tags"]) self.ui.artists_tags.setChecked(config.setting["artists_tags"]) def save(self): config.setting["max_tags"] = self.ui.max_tags.value() config.setting["min_tag_usage"] = self.ui.min_tag_usage.value() config.setting["join_tags"] = self.ui.join_tags.currentText() config.setting["ignore_tags"] = self.ui.ignore_tags.text() config.setting["only_my_tags"] = self.ui.only_my_tags.isChecked() config.setting["artists_tags"] = self.ui.artists_tags.isChecked() register_options_page(FolksonomyOptionsPage) picard-release-1.4.2/picard/ui/options/general.py000066400000000000000000000107751310410472100217420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4.QtGui import QInputDialog from PyQt4.QtCore import QUrl from picard import config, log from picard.util import webbrowser2 from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_general import Ui_GeneralOptionsPage from picard.const import MUSICBRAINZ_SERVERS from picard.collection import load_user_collections class GeneralOptionsPage(OptionsPage): NAME = "general" TITLE = N_("General") PARENT = None SORT_ORDER = 1 ACTIVE = True options = [ config.TextOption("setting", "server_host", MUSICBRAINZ_SERVERS[0]), config.IntOption("setting", "server_port", 443), config.TextOption("persist", "oauth_refresh_token", ""), config.BoolOption("setting", "analyze_new_files", False), config.BoolOption("setting", "ignore_file_mbids", False), config.TextOption("persist", "oauth_refresh_token", ""), config.TextOption("persist", "oauth_refresh_token_scopes", ""), config.TextOption("persist", "oauth_access_token", ""), config.IntOption("persist", "oauth_access_token_expires", 0), config.TextOption("persist", "oauth_username", ""), ] def __init__(self, parent=None): super(GeneralOptionsPage, self).__init__(parent) self.ui = Ui_GeneralOptionsPage() self.ui.setupUi(self) self.ui.server_host.addItems(MUSICBRAINZ_SERVERS) self.ui.login.clicked.connect(self.login) self.ui.logout.clicked.connect(self.logout) self.update_login_logout() def load(self): self.ui.server_host.setEditText(config.setting["server_host"]) self.ui.server_port.setValue(config.setting["server_port"]) self.ui.analyze_new_files.setChecked(config.setting["analyze_new_files"]) self.ui.ignore_file_mbids.setChecked(config.setting["ignore_file_mbids"]) def save(self): config.setting["server_host"] = unicode(self.ui.server_host.currentText()).strip() config.setting["server_port"] = self.ui.server_port.value() config.setting["analyze_new_files"] = self.ui.analyze_new_files.isChecked() config.setting["ignore_file_mbids"] = self.ui.ignore_file_mbids.isChecked() def update_login_logout(self): if self.tagger.xmlws.oauth_manager.is_logged_in(): self.ui.logged_in.setText(_("Logged in as %s.") % config.persist["oauth_username"]) self.ui.logged_in.show() self.ui.login.hide() self.ui.logout.show() else: self.ui.logged_in.hide() self.ui.login.show() self.ui.logout.hide() def login(self): scopes = "profile tag rating collection submit_isrc submit_barcode" authorization_url = self.tagger.xmlws.oauth_manager.get_authorization_url(scopes) webbrowser2.open(authorization_url) authorization_code, ok = QInputDialog.getText(self, _("MusicBrainz Account"), _("Authorization code:")) if ok: self.tagger.xmlws.oauth_manager.exchange_authorization_code( authorization_code, scopes, self.on_authorization_finished) def restore_defaults(self): super(GeneralOptionsPage, self).restore_defaults() self.logout() def on_authorization_finished(self, successful): if successful: self.tagger.xmlws.oauth_manager.fetch_username( self.on_login_finished) def on_login_finished(self, successful): self.update_login_logout() if successful: load_user_collections() def logout(self): self.tagger.xmlws.oauth_manager.revoke_tokens() self.update_login_logout() load_user_collections() register_options_page(GeneralOptionsPage) picard-release-1.4.2/picard/ui/options/interface.py000066400000000000000000000314551310410472100222630ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os.path from functools import partial from PyQt4 import QtCore, QtGui from picard import config, log from picard.util import icontheme from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_interface import Ui_InterfaceOptionsPage from picard.ui.util import enabledSlot from picard.const import UI_LANGUAGES import operator import locale class InterfaceOptionsPage(OptionsPage): NAME = "interface" TITLE = N_("User Interface") PARENT = None SORT_ORDER = 80 ACTIVE = True SEPARATOR = u'—'*5 TOOLBAR_BUTTONS = { 'add_directory_action': { 'label': N_(u'Add Folder'), 'icon': 'folder' }, 'add_files_action': { 'label': N_(u'Add Files'), 'icon': 'document-open' }, 'cluster_action': { 'label': N_(u'Cluster'), 'icon': 'picard-cluster' }, 'autotag_action': { 'label': N_(u'Lookup'), 'icon': 'picard-auto-tag' }, 'analyze_action': { 'label': N_(u'Scan'), 'icon': 'picard-analyze' }, 'browser_lookup_action': { 'label': N_(u'Lookup in Browser'), 'icon': 'lookup-musicbrainz' }, 'save_action': { 'label': N_(u'Save'), 'icon': 'document-save' }, 'view_info_action': { 'label': N_(u'Info'), 'icon': 'picard-edit-tags' }, 'remove_action': { 'label': N_(u'Remove'), 'icon': 'list-remove' }, 'submit_acoustid_action': { 'label': N_(u'Submit AcoustIDs'), 'icon': 'acoustid-fingerprinter' }, 'play_file_action': { 'label': N_(u'Open in Player'), 'icon': 'play-music' }, 'cd_lookup_action': { 'label': N_(u'Lookup CD...'), 'icon': 'media-optical' }, } ACTION_NAMES = set(TOOLBAR_BUTTONS.keys()) options = [ config.BoolOption("setting", "toolbar_show_labels", True), config.BoolOption("setting", "toolbar_multiselect", False), config.BoolOption("setting", "builtin_search", False), config.BoolOption("setting", "use_adv_search_syntax", False), config.BoolOption("setting", "quit_confirmation", True), config.TextOption("setting", "ui_language", u""), config.BoolOption("setting", "starting_directory", False), config.TextOption("setting", "starting_directory_path", ""), config.TextOption("setting", "load_image_behavior", "append"), config.ListOption("setting", "toolbar_layout", [ 'add_directory_action', 'add_files_action', 'separator', 'cluster_action', 'separator', 'autotag_action', 'analyze_action', 'browser_lookup_action', 'separator', 'save_action', 'view_info_action', 'remove_action', 'separator', 'cd_lookup_action', 'separator', 'submit_acoustid_action', ]), ] def __init__(self, parent=None): super(InterfaceOptionsPage, self).__init__(parent) self.ui = Ui_InterfaceOptionsPage() self.ui.setupUi(self) self.ui.ui_language.addItem(_('System default'), '') language_list = [(l[0], l[1], _(l[2])) for l in UI_LANGUAGES] for lang_code, native, translation in sorted(language_list, key=operator.itemgetter(2), cmp=locale.strcoll): if native and native != translation: name = u'%s (%s)' % (translation, native) else: name = translation self.ui.ui_language.addItem(name, lang_code) self.ui.starting_directory.stateChanged.connect( partial( enabledSlot, self.ui.starting_directory_path.setEnabled ) ) self.ui.starting_directory.stateChanged.connect( partial( enabledSlot, self.ui.starting_directory_browse.setEnabled ) ) self.ui.starting_directory_browse.clicked.connect(self.starting_directory_browse) self.ui.add_button.clicked.connect(self.add_to_toolbar) self.ui.insert_separator_button.clicked.connect(self.insert_separator) self.ui.remove_button.clicked.connect(self.remove_action) self.ui.up_button.clicked.connect(partial(self.move_item, 1)) self.ui.down_button.clicked.connect(partial(self.move_item, -1)) self.ui.toolbar_layout_list.currentRowChanged.connect(self.update_buttons) self.ui.toolbar_layout_list.setDragDropMode(QtGui.QAbstractItemView.DragDrop) self.ui.toolbar_layout_list.setDefaultDropAction(QtCore.Qt.MoveAction) def load(self): self.ui.toolbar_show_labels.setChecked(config.setting["toolbar_show_labels"]) self.ui.toolbar_multiselect.setChecked(config.setting["toolbar_multiselect"]) self.ui.builtin_search.setChecked(config.setting["builtin_search"]) self.ui.use_adv_search_syntax.setChecked(config.setting["use_adv_search_syntax"]) self.ui.quit_confirmation.setChecked(config.setting["quit_confirmation"]) current_ui_language = config.setting["ui_language"] self.ui.ui_language.setCurrentIndex(self.ui.ui_language.findData(current_ui_language)) self.ui.starting_directory.setChecked(config.setting["starting_directory"]) self.ui.starting_directory_path.setText(config.setting["starting_directory_path"]) self.populate_action_list() self.ui.toolbar_layout_list.setCurrentRow(0) self.update_buttons() def save(self): config.setting["toolbar_show_labels"] = self.ui.toolbar_show_labels.isChecked() config.setting["toolbar_multiselect"] = self.ui.toolbar_multiselect.isChecked() config.setting["builtin_search"] = self.ui.builtin_search.isChecked() config.setting["use_adv_search_syntax"] = self.ui.use_adv_search_syntax.isChecked() config.setting["quit_confirmation"] = self.ui.quit_confirmation.isChecked() self.tagger.window.update_toolbar_style() new_language = self.ui.ui_language.itemData(self.ui.ui_language.currentIndex()) if new_language != config.setting["ui_language"]: config.setting["ui_language"] = self.ui.ui_language.itemData(self.ui.ui_language.currentIndex()) dialog = QtGui.QMessageBox( QtGui.QMessageBox.Information, _('Language changed'), _('You have changed the interface language. You have to restart Picard in order for the change to take effect.'), QtGui.QMessageBox.Ok, self) dialog.exec_() config.setting["starting_directory"] = self.ui.starting_directory.isChecked() config.setting["starting_directory_path"] = os.path.normpath(unicode(self.ui.starting_directory_path.text())) self.update_layout_config() def restore_defaults(self): super(InterfaceOptionsPage, self).restore_defaults() self.update_buttons() def starting_directory_browse(self): item = self.ui.starting_directory_path path = QtGui.QFileDialog.getExistingDirectory(self, "", item.text()) if path: path = os.path.normpath(unicode(path)) item.setText(path) def _get_icon_from_name(self, name): return self.TOOLBAR_BUTTONS[name]['icon'] def _insert_item(self, action, index=None): list_item = ToolbarListItem(action) list_item.setToolTip(_(u'Drag and Drop to re-order')) if action in self.TOOLBAR_BUTTONS: list_item.setText(_(self.TOOLBAR_BUTTONS[action]['label'])) list_item.setIcon(icontheme.lookup(self._get_icon_from_name(action), icontheme.ICON_SIZE_MENU)) else: list_item.setText(self.SEPARATOR) if index is not None: self.ui.toolbar_layout_list.insertItem(index, list_item) else: self.ui.toolbar_layout_list.addItem(list_item) return list_item def _all_list_items(self): return [self.ui.toolbar_layout_list.item(i).action_name for i in range(self.ui.toolbar_layout_list.count())] def _added_actions(self): actions = self._all_list_items() actions = filter(lambda x: x != 'separator', actions) return set(actions) def populate_action_list(self): self.ui.toolbar_layout_list.clear() for name in config.setting['toolbar_layout']: if name in self.ACTION_NAMES or name == 'separator': self._insert_item(name) def update_buttons(self): self.ui.add_button.setEnabled(self._added_actions() != self.ACTION_NAMES) current_row = self.ui.toolbar_layout_list.currentRow() self.ui.up_button.setEnabled(current_row > 0) self.ui.down_button.setEnabled(current_row < self.ui.toolbar_layout_list.count() - 1) def add_to_toolbar(self): display_list = set.difference(self.ACTION_NAMES, self._added_actions()) selected_action, ok = AddActionDialog.get_selected_action(display_list, self) if ok: list_item = self._insert_item(selected_action, self.ui.toolbar_layout_list.currentRow() + 1) self.ui.toolbar_layout_list.setCurrentItem(list_item) self.update_buttons() def insert_separator(self): insert_index = self.ui.toolbar_layout_list.currentRow() + 1 self._insert_item('separator', insert_index) def move_item(self, offset): current_index = self.ui.toolbar_layout_list.currentRow() offset_index = current_index - offset offset_item = self.ui.toolbar_layout_list.item(offset_index) if offset_item: current_item = self.ui.toolbar_layout_list.takeItem(current_index) self.ui.toolbar_layout_list.insertItem(offset_index, current_item) self.ui.toolbar_layout_list.setCurrentItem(current_item) self.update_buttons() def remove_action(self): item = self.ui.toolbar_layout_list.takeItem(self.ui.toolbar_layout_list.currentRow()) del item self.update_buttons() def update_layout_config(self): config.setting['toolbar_layout'] = self._all_list_items() self._update_toolbar() def _update_toolbar(self): widget = self.parent() while not isinstance(widget, QtGui.QMainWindow): widget = widget.parent() # Call the main window's create toolbar method widget.create_action_toolbar() widget.set_tab_order() class ToolbarListItem(QtGui.QListWidgetItem): def __init__(self, action_name, *args, **kwargs): super(ToolbarListItem, self).__init__(*args, **kwargs) self.action_name = action_name class AddActionDialog(QtGui.QDialog): def __init__(self, action_list, *args, **kwargs): super(AddActionDialog, self).__init__(*args, **kwargs) layout = QtGui.QVBoxLayout(self) self.action_list = sorted([[_(self.parent().TOOLBAR_BUTTONS[action]['label']), action] for action in action_list]) self.combo_box = QtGui.QComboBox(self) self.combo_box.addItems([label for label, action in self.action_list]) layout.addWidget(self.combo_box) buttons = QtGui.QDialogButtonBox( QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel, QtCore.Qt.Horizontal, self) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) def selected_action(self): return self.action_list[self.combo_box.currentIndex()][1] @staticmethod def get_selected_action(action_list, parent=None): dialog = AddActionDialog(action_list, parent) result = dialog.exec_() selected_action = dialog.selected_action() return (selected_action, result == QtGui.QDialog.Accepted) register_options_page(InterfaceOptionsPage) picard-release-1.4.2/picard/ui/options/matching.py000066400000000000000000000043561310410472100221150ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_matching import Ui_MatchingOptionsPage class MatchingOptionsPage(OptionsPage): NAME = "matching" TITLE = N_("Matching") PARENT = "advanced" SORT_ORDER = 30 ACTIVE = True options = [ config.FloatOption("setting", "file_lookup_threshold", 0.7), config.FloatOption("setting", "cluster_lookup_threshold", 0.8), config.FloatOption("setting", "track_matching_threshold", 0.4), ] _release_type_sliders = {} def __init__(self, parent=None): super(MatchingOptionsPage, self).__init__(parent) self.ui = Ui_MatchingOptionsPage() self.ui.setupUi(self) def load(self): self.ui.file_lookup_threshold.setValue(int(config.setting["file_lookup_threshold"] * 100)) self.ui.cluster_lookup_threshold.setValue(int(config.setting["cluster_lookup_threshold"] * 100)) self.ui.track_matching_threshold.setValue(int(config.setting["track_matching_threshold"] * 100)) def save(self): config.setting["file_lookup_threshold"] = float(self.ui.file_lookup_threshold.value()) / 100.0 config.setting["cluster_lookup_threshold"] = float(self.ui.cluster_lookup_threshold.value()) / 100.0 config.setting["track_matching_threshold"] = float(self.ui.track_matching_threshold.value()) / 100.0 register_options_page(MatchingOptionsPage) picard-release-1.4.2/picard/ui/options/metadata.py000066400000000000000000000103621310410472100220750ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_metadata import Ui_MetadataOptionsPage from picard.const import ALIAS_LOCALES class MetadataOptionsPage(OptionsPage): NAME = "metadata" TITLE = N_("Metadata") PARENT = None SORT_ORDER = 20 ACTIVE = True options = [ config.TextOption("setting", "va_name", u"Various Artists"), config.TextOption("setting", "nat_name", u"[non-album tracks]"), config.TextOption("setting", "artist_locale", u"en"), config.BoolOption("setting", "translate_artist_names", False), config.BoolOption("setting", "release_ars", True), config.BoolOption("setting", "track_ars", False), config.BoolOption("setting", "folksonomy_tags", False), config.BoolOption("setting", "convert_punctuation", True), config.BoolOption("setting", "standardize_artists", False), ] def __init__(self, parent=None): super(MetadataOptionsPage, self).__init__(parent) self.ui = Ui_MetadataOptionsPage() self.ui.setupUi(self) self.ui.va_name_default.clicked.connect(self.set_va_name_default) self.ui.nat_name_default.clicked.connect(self.set_nat_name_default) def load(self): self.ui.translate_artist_names.setChecked(config.setting["translate_artist_names"]) combo_box = self.ui.artist_locale locales = sorted(ALIAS_LOCALES.keys()) for i, loc in enumerate(locales): name = ALIAS_LOCALES[loc] if "_" in loc: name = " " + name combo_box.addItem(name, loc) if loc == config.setting["artist_locale"]: combo_box.setCurrentIndex(i) self.ui.convert_punctuation.setChecked(config.setting["convert_punctuation"]) self.ui.release_ars.setChecked(config.setting["release_ars"]) self.ui.track_ars.setChecked(config.setting["track_ars"]) self.ui.folksonomy_tags.setChecked(config.setting["folksonomy_tags"]) self.ui.va_name.setText(config.setting["va_name"]) self.ui.nat_name.setText(config.setting["nat_name"]) self.ui.standardize_artists.setChecked(config.setting["standardize_artists"]) def save(self): config.setting["translate_artist_names"] = self.ui.translate_artist_names.isChecked() config.setting["artist_locale"] = self.ui.artist_locale.itemData(self.ui.artist_locale.currentIndex()) config.setting["convert_punctuation"] = self.ui.convert_punctuation.isChecked() config.setting["release_ars"] = self.ui.release_ars.isChecked() config.setting["track_ars"] = self.ui.track_ars.isChecked() config.setting["folksonomy_tags"] = self.ui.folksonomy_tags.isChecked() config.setting["va_name"] = self.ui.va_name.text() nat_name = unicode(self.ui.nat_name.text()) if nat_name != config.setting["nat_name"]: config.setting["nat_name"] = nat_name if self.tagger.nats is not None: self.tagger.nats.update() config.setting["standardize_artists"] = self.ui.standardize_artists.isChecked() def set_va_name_default(self): self.ui.va_name.setText(self.options[0].default) self.ui.va_name.setCursorPosition(0) def set_nat_name_default(self): self.ui.nat_name.setText(self.options[1].default) self.ui.nat_name.setCursorPosition(0) register_options_page(MetadataOptionsPage) picard-release-1.4.2/picard/ui/options/network.py000066400000000000000000000075431310410472100220150ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_network import Ui_NetworkOptionsPage class NetworkOptionsPage(OptionsPage): NAME = "network" TITLE = N_("Network") PARENT = "advanced" SORT_ORDER = 10 ACTIVE = True options = [ config.BoolOption("setting", "use_proxy", False), config.TextOption("setting", "proxy_server_host", ""), config.IntOption("setting", "proxy_server_port", 80), config.TextOption("setting", "proxy_username", ""), config.TextOption("setting", "proxy_password", ""), config.BoolOption("setting", "browser_integration", True), config.IntOption("setting", "browser_integration_port", 8000), config.BoolOption("setting", "browser_integration_localhost_only", True) ] def __init__(self, parent=None): super(NetworkOptionsPage, self).__init__(parent) self.ui = Ui_NetworkOptionsPage() self.ui.setupUi(self) def load(self): self.ui.web_proxy.setChecked(config.setting["use_proxy"]) self.ui.server_host.setText(config.setting["proxy_server_host"]) self.ui.server_port.setValue(config.setting["proxy_server_port"]) self.ui.username.setText(config.setting["proxy_username"]) self.ui.password.setText(config.setting["proxy_password"]) self.ui.browser_integration.setChecked(config.setting["browser_integration"]) self.ui.browser_integration_port.setValue(config.setting["browser_integration_port"]) self.ui.browser_integration_localhost_only.setChecked( config.setting["browser_integration_localhost_only"]) QtCore.QObject.connect(self.ui.browser_integration_port, QtCore.SIGNAL('valueChanged(int)'), self.change_browser_integration_port) def save(self): config.setting["use_proxy"] = self.ui.web_proxy.isChecked() config.setting["proxy_server_host"] = unicode(self.ui.server_host.text()) config.setting["proxy_server_port"] = self.ui.server_port.value() config.setting["proxy_username"] = unicode(self.ui.username.text()) config.setting["proxy_password"] = unicode(self.ui.password.text()) self.tagger.xmlws.setup_proxy() config.setting["browser_integration"] = self.ui.browser_integration.isChecked() config.setting["browser_integration_port"] = self.ui.browser_integration_port.value() config.setting["browser_integration_localhost_only"] = \ self.ui.browser_integration_localhost_only.isChecked() self.update_browser_integration() def update_browser_integration(self): if self.ui.browser_integration.isChecked(): self.tagger.browser_integration.start() else: self.tagger.browser_integration.stop() def change_browser_integration_port(self, port): config.setting["browser_integration_port"] = self.ui.browser_integration_port.value() register_options_page(NetworkOptionsPage) picard-release-1.4.2/picard/ui/options/plugins.py000066400000000000000000000337761310410472100220140ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # Copyright (C) 2009 Carlin Mangar # Copyright (C) 2014 Shadab Zafar # Copyright (C) 2015 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os.path import sys from functools import partial from PyQt4 import QtCore, QtGui from picard import config, log from picard.const import ( USER_PLUGIN_DIR, PLUGINS_API, ) from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_plugins import Ui_PluginsOptionsPage def cmp_plugins(a, b): return cmp(a.name, b.name) class PluginTreeWidgetItem(QtGui.QTreeWidgetItem): def __lt__(self, other): if (not isinstance(other, PluginTreeWidgetItem)): return super(PluginTreeWidgetItem, self).__lt__(other) tree = self.treeWidget() if not tree: column = 0 else: column = tree.sortColumn() return self.sortData(column) < other.sortData(column) def __init__(self, *args): super(PluginTreeWidgetItem, self).__init__(*args) self._sortData = {} def sortData(self, column): return self._sortData.get(column, self.text(column)) def setSortData(self, column, data): self._sortData[column] = data class PluginsOptionsPage(OptionsPage): NAME = "plugins" TITLE = N_("Plugins") PARENT = None SORT_ORDER = 70 ACTIVE = True options = [ config.ListOption("setting", "enabled_plugins", []), config.Option("persist", "plugins_list_state", QtCore.QByteArray()), config.Option("persist", "plugins_list_sort_section", 0), config.Option("persist", "plugins_list_sort_order", QtCore.Qt.AscendingOrder), config.Option("persist", "plugins_list_selected", ""), ] def __init__(self, parent=None): super(PluginsOptionsPage, self).__init__(parent) self.ui = Ui_PluginsOptionsPage() self.ui.setupUi(self) self.items = {} self.ui.plugins.itemSelectionChanged.connect(self.change_details) self.ui.plugins.mimeTypes = self.mimeTypes self.ui.plugins.dropEvent = self.dropEvent self.ui.plugins.dragEnterEvent = self.dragEnterEvent if sys.platform == "win32": self.loader = "file:///%s" else: self.loader = "file://%s" self.ui.install_plugin.clicked.connect(self.open_plugins) self.ui.folder_open.clicked.connect(self.open_plugin_dir) self.ui.reload_list_of_plugins.clicked.connect(self.reload_list_of_plugins) self.tagger.pluginmanager.plugin_installed.connect(self.plugin_installed) self.tagger.pluginmanager.plugin_updated.connect(self.plugin_updated) self.ui.plugins.header().setStretchLastSection(False) self.ui.plugins.header().setResizeMode(0, QtGui.QHeaderView.Stretch) self.ui.plugins.header().setResizeMode(1, QtGui.QHeaderView.Stretch) self.ui.plugins.header().resizeSection(2, 100) self.ui.plugins.setSortingEnabled(True) def save_state(self): header = self.ui.plugins.header() config.persist["plugins_list_state"] = header.saveState() config.persist["plugins_list_sort_section"] = header.sortIndicatorSection() config.persist["plugins_list_sort_order"] = header.sortIndicatorOrder() try: selected = self.items[self.ui.plugins.selectedItems()[0]].module_name except IndexError: selected = "" config.persist["plugins_list_selected"] = selected def restore_state(self, restore_selection=False): header = self.ui.plugins.header() header.restoreState(config.persist["plugins_list_state"]) idx = config.persist["plugins_list_sort_section"] order = config.persist["plugins_list_sort_order"] header.setSortIndicator(idx, order) self.ui.plugins.sortByColumn(idx, order) selected = restore_selection and config.persist["plugins_list_selected"] if selected: for i, p in self.items.items(): if selected == p.module_name: self.ui.plugins.setCurrentItem(i) self.ui.plugins.scrollToItem(i) break else: self.ui.plugins.setCurrentItem(self.ui.plugins.topLevelItem(0)) def _populate(self): self.ui.details.setText("" + _("No plugins installed.") + "") self._user_interaction(False) plugins = sorted(self.tagger.pluginmanager.plugins, cmp=cmp_plugins) enabled_plugins = config.setting["enabled_plugins"] available_plugins = dict([(p.module_name, p.version) for p in self.tagger.pluginmanager.available_plugins]) installed = [] for plugin in plugins: if plugin.module_name in enabled_plugins: plugin.enabled = True if plugin.module_name in available_plugins.keys(): latest = available_plugins[plugin.module_name] if latest.split('.') > plugin.version.split('.'): plugin.new_version = latest plugin.can_be_updated = True item = self.add_plugin_item(plugin) installed.append(plugin.module_name) for plugin in sorted(self.tagger.pluginmanager.available_plugins, cmp=cmp_plugins): if plugin.module_name not in installed: plugin.can_be_downloaded = True item = self.add_plugin_item(plugin) self._user_interaction(True) def _remove_all(self): for i, p in self.items.items(): idx = self.ui.plugins.indexOfTopLevelItem(i) item = self.ui.plugins.takeTopLevelItem(idx) del item self.items = {} def restore_defaults(self): # Plugin manager has to be updated for plugin in self.tagger.pluginmanager.plugins: plugin.enabled = False # Remove previous entries self._user_interaction(False) self._remove_all() super(PluginsOptionsPage, self).restore_defaults() def load(self): self._populate() self.restore_state() def _reload(self): self._populate() self.restore_state(restore_selection=True) def _user_interaction(self, enabled): self.ui.plugins.blockSignals(not enabled) self.ui.plugins_container.setEnabled(enabled) def reload_list_of_plugins(self): self.ui.details.setText("") self._user_interaction(False) self.save_state() self._remove_all() self.tagger.pluginmanager.query_available_plugins(callback=self._reload) def plugin_installed(self, plugin): if not plugin.compatible: msgbox = QtGui.QMessageBox(self) msgbox.setText(_(u"The plugin '%s' is not compatible with this version of Picard.") % plugin.name) msgbox.setStandardButtons(QtGui.QMessageBox.Ok) msgbox.setDefaultButton(QtGui.QMessageBox.Ok) msgbox.exec_() return plugin.new_version = "" plugin.enabled = False plugin.can_be_updated = False plugin.can_be_downloaded = False for i, p in self.items.items(): if plugin.module_name == p.module_name: if i.checkState(0) == QtCore.Qt.Checked: plugin.enabled = True self.add_plugin_item(plugin, item=i) self.ui.plugins.setCurrentItem(i) self.change_details() break else: self.add_plugin_item(plugin) def plugin_updated(self, plugin_name): for i, p in self.items.items(): if plugin_name == p.module_name: p.can_be_updated = False p.can_be_downloaded = False p.marked_for_update = True msgbox = QtGui.QMessageBox(self) msgbox.setText( _(u"The plugin '%s' will be upgraded to version %s on next run of Picard.") % (p.name, p.new_version)) msgbox.setStandardButtons(QtGui.QMessageBox.Ok) msgbox.setDefaultButton(QtGui.QMessageBox.Ok) msgbox.exec_() self.add_plugin_item(p, item=i) self.ui.plugins.setCurrentItem(i) self.change_details() break def add_plugin_item(self, plugin, item=None): if item is None: item = PluginTreeWidgetItem(self.ui.plugins) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) item.setText(0, plugin.name) item.setSortData(0, plugin.name.lower()) if plugin.enabled: item.setCheckState(0, QtCore.Qt.Checked) else: item.setCheckState(0, QtCore.Qt.Unchecked) if plugin.marked_for_update: item.setText(1, plugin.new_version) else: item.setText(1, plugin.version) label = None if plugin.can_be_updated: label = _("Update") elif plugin.can_be_downloaded: label = _("Install") item.setFlags(item.flags() ^ QtCore.Qt.ItemIsUserCheckable) if label is not None: button = QtGui.QPushButton(label) button.setMaximumHeight(button.fontMetrics().boundingRect(label).height() + 7) self.ui.plugins.setItemWidget(item, 2, button) def download_button_process(): self.ui.plugins.setCurrentItem(item) self.download_plugin() button.released.connect(download_button_process) else: # Note: setText() don't work after it was set to a button if plugin.marked_for_update: label = _("Updated") else: label = _("Installed") self.ui.plugins.setItemWidget(item, 2, QtGui.QLabel(label)) item.setSortData(2, label) self.ui.plugins.header().resizeSections(QtGui.QHeaderView.ResizeToContents) self.items[item] = plugin return item def save(self): enabled_plugins = [] for item, plugin in self.items.iteritems(): if item.checkState(0) == QtCore.Qt.Checked: enabled_plugins.append(plugin.module_name) config.setting["enabled_plugins"] = enabled_plugins self.save_state() def change_details(self): try: plugin = self.items[self.ui.plugins.selectedItems()[0]] except IndexError: return text = [] if plugin.new_version: if plugin.marked_for_update: text.append("" + _("Restart Picard to upgrade to new version") + ": " + plugin.new_version + "") else: text.append("" + _("New version available") + ": " + plugin.new_version + "") if plugin.description: text.append(plugin.description + "
") if plugin.name: text.append("" + _("Name") + ": " + plugin.name) if plugin.author: text.append("" + _("Authors") + ": " + plugin.author) if plugin.license: text.append("" + _("License") + ": " + plugin.license) text.append("" + _("Files") + ": " + plugin.files_list) self.ui.details.setText("

%s

" % "
\n".join(text)) def open_plugins(self): files = QtGui.QFileDialog.getOpenFileNames(self, "", QtCore.QDir.homePath(), "Picard plugin (*.py *.pyc *.zip)") if files: files = map(unicode, files) for path in files: self.tagger.pluginmanager.install_plugin(path) def download_plugin(self): selected = self.ui.plugins.selectedItems()[0] plugin = self.items[selected] self.tagger.xmlws.get( PLUGINS_API['host'], PLUGINS_API['port'], PLUGINS_API['endpoint']['download'], partial(self.download_handler, plugin=plugin), xml=False, priority=True, important=True, queryargs={"id": plugin.module_name} ) def download_handler(self, response, reply, error, plugin): if error: msgbox = QtGui.QMessageBox(self) msgbox.setText(_(u"The plugin '%s' could not be downloaded.") % plugin.module_name) msgbox.setInformativeText(_("Please try again later.")) msgbox.setStandardButtons(QtGui.QMessageBox.Ok) msgbox.setDefaultButton(QtGui.QMessageBox.Ok) msgbox.exec_() log.error("Error occurred while trying to download the plugin: '%s'" % plugin.module_name) return self.tagger.pluginmanager.install_plugin( None, plugin_name=plugin.module_name, plugin_data=response ) def open_plugin_dir(self): QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.loader % USER_PLUGIN_DIR, QtCore.QUrl.TolerantMode)) def mimeTypes(self): return ["text/uri-list"] def dragEnterEvent(self, event): event.setDropAction(QtCore.Qt.CopyAction) event.accept() def dropEvent(self, event): for path in [os.path.normpath(unicode(u.toLocalFile())) for u in event.mimeData().urls()]: self.tagger.pluginmanager.install_plugin(path) register_options_page(PluginsOptionsPage) picard-release-1.4.2/picard/ui/options/ratings.py000066400000000000000000000041231310410472100217620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2008 Philipp Wolfer # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_ratings import Ui_RatingsOptionsPage class RatingsOptionsPage(OptionsPage): NAME = "ratings" TITLE = N_("Ratings") PARENT = "metadata" SORT_ORDER = 20 ACTIVE = True options = [ config.BoolOption("setting", "enable_ratings", False), config.TextOption("setting", "rating_user_email", "users@musicbrainz.org"), config.BoolOption("setting", "submit_ratings", True), config.IntOption("setting", "rating_steps", 6), ] def __init__(self, parent=None): super(RatingsOptionsPage, self).__init__(parent) self.ui = Ui_RatingsOptionsPage() self.ui.setupUi(self) def load(self): self.ui.enable_ratings.setChecked(config.setting["enable_ratings"]) self.ui.rating_user_email.setText(config.setting["rating_user_email"]) self.ui.submit_ratings.setChecked(config.setting["submit_ratings"]) def save(self): config.setting["enable_ratings"] = self.ui.enable_ratings.isChecked() config.setting["rating_user_email"] = self.ui.rating_user_email.text() config.setting["submit_ratings"] = self.ui.submit_ratings.isChecked() register_options_page(RatingsOptionsPage) picard-release-1.4.2/picard/ui/options/releases.py000066400000000000000000000215251310410472100221230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from operator import itemgetter from locale import strcoll from PyQt4 import QtCore, QtGui from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_releases import Ui_ReleasesOptionsPage from picard.const import (RELEASE_COUNTRIES, RELEASE_FORMATS, RELEASE_PRIMARY_GROUPS, RELEASE_SECONDARY_GROUPS) from picard.i18n import ugettext_attr _DEFAULT_SCORE = 0.5 _release_type_scores = [(g, _DEFAULT_SCORE) for g in RELEASE_PRIMARY_GROUPS.keys() + RELEASE_SECONDARY_GROUPS.keys()] class ReleaseTypeScore: def __init__(self, group, layout, label, cell): row, column = cell # it uses 2 cells (r,c and r,c+1) self.group = group self.layout = layout self.label = QtGui.QLabel(self.group) self.label.setText(label) self.layout.addWidget(self.label, row, column, 1, 1) self.slider = QtGui.QSlider(self.group) self.slider.setMaximum(100) self.slider.setOrientation(QtCore.Qt.Horizontal) self.layout.addWidget(self.slider, row, column + 1, 1, 1) self.reset() def setValue(self, value): self.slider.setValue(int(value * 100)) def value(self): return float(self.slider.value()) / 100.0 def reset(self): self.setValue(_DEFAULT_SCORE) class RowColIter: def __init__(self, max_cells, max_cols=6, step=2): assert(max_cols % step == 0) self.step = step self.cols = max_cols self.rows = int((max_cells - 1) / (self.cols / step)) + 1 self.current = (-1, 0) def __iter__(self): return self def next(self): row, col = self.current row += 1 if row == self.rows: col += self.step row = 0 self.current = (row, col) return self.current class ReleasesOptionsPage(OptionsPage): NAME = "releases" TITLE = N_("Preferred Releases") PARENT = "metadata" SORT_ORDER = 10 ACTIVE = True options = [ config.ListOption("setting", "release_type_scores", _release_type_scores), config.ListOption("setting", "preferred_release_countries", []), config.ListOption("setting", "preferred_release_formats", []), ] def __init__(self, parent=None): super(ReleasesOptionsPage, self).__init__(parent) self.ui = Ui_ReleasesOptionsPage() self.ui.setupUi(self) self._release_type_sliders = {} def add_slider(name, griditer, context): label = ugettext_attr(name, context) self._release_type_sliders[name] = \ ReleaseTypeScore(self.ui.type_group, self.ui.gridLayout, label, next(griditer)) griditer = RowColIter(len(RELEASE_PRIMARY_GROUPS) + len(RELEASE_SECONDARY_GROUPS) + 1) # +1 for Reset button for name in RELEASE_PRIMARY_GROUPS: add_slider(name, griditer, context=u'release_group_primary_type') for name in RELEASE_SECONDARY_GROUPS: add_slider(name, griditer, context=u'release_group_secondary_type') self.reset_preferred_types_btn = QtGui.QPushButton(self.ui.type_group) self.reset_preferred_types_btn.setText(_("Reset all")) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.reset_preferred_types_btn.sizePolicy().hasHeightForWidth()) self.reset_preferred_types_btn.setSizePolicy(sizePolicy) r, c = next(griditer) self.ui.gridLayout.addWidget(self.reset_preferred_types_btn, r, c, 1, 2) self.reset_preferred_types_btn.clicked.connect(self.reset_preferred_types) self.ui.add_countries.clicked.connect(self.add_preferred_countries) self.ui.remove_countries.clicked.connect(self.remove_preferred_countries) self.ui.add_formats.clicked.connect(self.add_preferred_formats) self.ui.remove_formats.clicked.connect(self.remove_preferred_formats) self.ui.country_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.ui.preferred_country_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.ui.format_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.ui.preferred_format_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) def restore_defaults(self): # Clear lists self.ui.preferred_country_list.clear() self.ui.preferred_format_list.clear() self.ui.country_list.clear() self.ui.format_list.clear() super(ReleasesOptionsPage, self).restore_defaults() def load(self): scores = dict(config.setting["release_type_scores"]) for (release_type, release_type_slider) in self._release_type_sliders.iteritems(): release_type_slider.setValue(scores.get(release_type, _DEFAULT_SCORE)) self._load_list_items("preferred_release_countries", RELEASE_COUNTRIES, self.ui.country_list, self.ui.preferred_country_list) self._load_list_items("preferred_release_formats", RELEASE_FORMATS, self.ui.format_list, self.ui.preferred_format_list) def save(self): scores = [] for (release_type, release_type_slider) in self._release_type_sliders.iteritems(): scores.append((release_type, release_type_slider.value())) config.setting["release_type_scores"] = scores self._save_list_items("preferred_release_countries", self.ui.preferred_country_list) self._save_list_items("preferred_release_formats", self.ui.preferred_format_list) def reset_preferred_types(self): for release_type_slider in self._release_type_sliders.values(): release_type_slider.reset() def add_preferred_countries(self): self._move_selected_items(self.ui.country_list, self.ui.preferred_country_list) def remove_preferred_countries(self): self._move_selected_items(self.ui.preferred_country_list, self.ui.country_list) self.ui.country_list.sortItems() def add_preferred_formats(self): self._move_selected_items(self.ui.format_list, self.ui.preferred_format_list) def remove_preferred_formats(self): self._move_selected_items(self.ui.preferred_format_list, self.ui.format_list) self.ui.format_list.sortItems() def _move_selected_items(self, list1, list2): for item in list1.selectedItems(): clone = item.clone() list2.addItem(clone) list1.takeItem(list1.row(item)) def _load_list_items(self, setting, source, list1, list2): if setting == "preferred_release_countries": source_list = [(c[0], ugettext_countries(c[1])) for c in source.items()] elif setting == "preferred_release_formats": source_list = [(c[0], ugettext_attr(c[1], u"medium_format")) for c in source.items()] else: source_list = [(c[0], _(c[1])) for c in source.items()] source_list.sort(key=itemgetter(1), cmp=strcoll) saved_data = config.setting[setting] move = [] for data, name in source_list: item = QtGui.QListWidgetItem(name) item.setData(QtCore.Qt.UserRole, data) try: i = saved_data.index(data) move.append((i, item)) except: list1.addItem(item) move.sort(key=itemgetter(0)) for i, item in move: list2.addItem(item) def _save_list_items(self, setting, list1): data = [] for i in range(list1.count()): item = list1.item(i) data.append(unicode(item.data(QtCore.Qt.UserRole))) config.setting[setting] = data register_options_page(ReleasesOptionsPage) picard-release-1.4.2/picard/ui/options/renaming.py000066400000000000000000000326171310410472100221240ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006-2008 Lukáš Lalinský # Copyright (C) 2009 Nikolai Prokoschenko # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os.path import sys from functools import partial from PyQt4 import QtGui from PyQt4.QtGui import QPalette, QColor from picard import config from picard.const import PICARD_URLS from picard.file import File from picard.script import ScriptParser, SyntaxError, ScriptError, UnknownFunction from picard.ui.options import OptionsPage, OptionsCheckError, register_options_page from picard.ui.ui_options_renaming import Ui_RenamingOptionsPage from picard.ui.util import enabledSlot from picard.ui.options.scripting import TaggerScriptSyntaxHighlighter _DEFAULT_FILE_NAMING_FORMAT = "$if2(%albumartist%,%artist%)/" \ "$if($ne(%albumartist%,),%album%/,)" \ "$if($gt(%totaldiscs%,1),%discnumber%-,)" \ "$if($ne(%albumartist%,),$num(%tracknumber%,2) ,)" \ "$if(%_multiartist%,%artist% - ,)" \ "%title%" class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File Naming") PARENT = None SORT_ORDER = 40 ACTIVE = True options = [ config.BoolOption("setting", "windows_compatibility", True), config.BoolOption("setting", "ascii_filenames", False), config.BoolOption("setting", "rename_files", False), config.TextOption( "setting", "file_naming_format", _DEFAULT_FILE_NAMING_FORMAT, ), config.BoolOption("setting", "move_files", False), config.TextOption("setting", "move_files_to", ""), config.BoolOption("setting", "move_additional_files", False), config.TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), config.BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatibility.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) self.ui.move_files.toggled.connect( partial( enabledSlot, self.toggle_file_moving ) ) self.ui.rename_files.toggled.connect( partial( enabledSlot, self.toggle_file_renaming ) ) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect(self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) textEdit = self.ui.file_naming_format self.textEditPaletteNormal = textEdit.palette() self.textEditPaletteReadOnly = QPalette(self.textEditPaletteNormal) disabled_color = self.textEditPaletteNormal.color(QPalette.Inactive, QPalette.Window) self.textEditPaletteReadOnly.setColor(QPalette.Disabled, QPalette.Base, disabled_color) def toggle_file_moving(self, state): self.ui.delete_empty_dirs.setEnabled(state) self.ui.move_files_to.setEnabled(state) self.ui.move_files_to_browse.setEnabled(state) self.ui.move_additional_files.setEnabled(state) self.ui.move_additional_files_pattern.setEnabled(state) def toggle_file_renaming(self, state): self.ui.file_naming_format.setEnabled(state) self.ui.file_naming_format_default.setEnabled(state) self.ui.ascii_filenames.setEnabled(state) self.ui.file_naming_format_group.setEnabled(state) if not sys.platform == "win32": self.ui.windows_compatibility.setEnabled(state) if self.ui.file_naming_format.isEnabled(): self.ui.file_naming_format.setPalette(self.textEditPaletteNormal) else: self.ui.file_naming_format.setPalette(self.textEditPaletteReadOnly) def check_formats(self): self.test() self.update_examples() def _example_to_filename(self, file): settings = { 'windows_compatibility': self.ui.windows_compatibility.isChecked(), 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'rename_files': self.ui.rename_files.isChecked(), 'move_files': self.ui.move_files.isChecked(), 'use_va_format': False, # TODO remove 'file_naming_format': unicode(self.ui.file_naming_format.toPlainText()), 'move_files_to': os.path.normpath(unicode(self.ui.move_files_to.text())) } try: if config.setting["enable_tagger_scripts"]: for s_pos, s_name, s_enabled, s_text in config.setting["list_of_scripts"]: if s_enabled and s_text: parser = ScriptParser() parser.eval(s_text, file.metadata) filename = file._make_filename(file.filename, file.metadata, settings) if not settings["move_files"]: return os.path.basename(filename) return filename except SyntaxError: return "" except ScriptError: return "" except TypeError: return "" def update_examples(self): # TODO: Here should be more examples etc. # TODO: Would be nice to show diffs too.... example1 = self._example_to_filename(self.example_1()) example2 = self._example_to_filename(self.example_2()) self.ui.example_filename.setText(example1) self.ui.example_filename_va.setText(example2) def load(self): if sys.platform == "win32": self.ui.windows_compatibility.setChecked(True) self.ui.windows_compatibility.setEnabled(False) else: self.ui.windows_compatibility.setChecked(config.setting["windows_compatibility"]) self.ui.rename_files.setChecked(config.setting["rename_files"]) self.ui.move_files.setChecked(config.setting["move_files"]) self.ui.ascii_filenames.setChecked(config.setting["ascii_filenames"]) self.ui.file_naming_format.setPlainText(config.setting["file_naming_format"]) args = { "picard-doc-scripting-url": PICARD_URLS['doc_scripting'], } text = _(u'Open Scripting' ' Documentation in your browser') % args self.ui.file_naming_format_documentation.setText(text) self.ui.move_files_to.setText(config.setting["move_files_to"]) self.ui.move_files_to.setCursorPosition(0) self.ui.move_additional_files.setChecked(config.setting["move_additional_files"]) self.ui.move_additional_files_pattern.setText(config.setting["move_additional_files_pattern"]) self.ui.delete_empty_dirs.setChecked(config.setting["delete_empty_dirs"]) self.update_examples() def check(self): self.check_format() if self.ui.move_files.isChecked() and not unicode(self.ui.move_files_to.text()).strip(): raise OptionsCheckError(_("Error"), _("The location to move files to must not be empty.")) def check_format(self): parser = ScriptParser() try: parser.eval(unicode(self.ui.file_naming_format.toPlainText())) except Exception as e: raise OptionsCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not unicode(self.ui.file_naming_format.toPlainText()).strip(): raise OptionsCheckError("", _("The file naming format must not be empty.")) def save(self): config.setting["windows_compatibility"] = self.ui.windows_compatibility.isChecked() config.setting["ascii_filenames"] = self.ui.ascii_filenames.isChecked() config.setting["rename_files"] = self.ui.rename_files.isChecked() config.setting["file_naming_format"] = unicode(self.ui.file_naming_format.toPlainText()) self.tagger.window.enable_renaming_action.setChecked(config.setting["rename_files"]) config.setting["move_files"] = self.ui.move_files.isChecked() config.setting["move_files_to"] = os.path.normpath(unicode(self.ui.move_files_to.text())) config.setting["move_additional_files"] = self.ui.move_additional_files.isChecked() config.setting["move_additional_files_pattern"] = unicode(self.ui.move_additional_files_pattern.text()) config.setting["delete_empty_dirs"] = self.ui.delete_empty_dirs.isChecked() self.tagger.window.enable_moving_action.setChecked(config.setting["move_files"]) def display_error(self, error): pass def set_file_naming_format_default(self): self.ui.file_naming_format.setText(self.options[3].default) # self.ui.file_naming_format.setCursorPosition(0) def example_1(self): file = File("ticket_to_ride.mp3") file.state = File.NORMAL file.metadata['album'] = 'Help!' file.metadata['title'] = 'Ticket to Ride' file.metadata['artist'] = 'The Beatles' file.metadata['artistsort'] = 'Beatles, The' file.metadata['albumartist'] = 'The Beatles' file.metadata['albumartistsort'] = 'Beatles, The' file.metadata['tracknumber'] = '7' file.metadata['totaltracks'] = '14' file.metadata['discnumber'] = '1' file.metadata['totaldiscs'] = '1' file.metadata['date'] = '1965-08-06' file.metadata['releasetype'] = ['album', 'soundtrack'] file.metadata['~primaryreleasetype'] = ['album'] file.metadata['~secondaryreleasetype'] = ['soundtrack'] file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'US' file.metadata['~extension'] = 'mp3' file.metadata['musicbrainz_albumid'] = '2c053984-4645-4699-9474-d2c35c227028' file.metadata['musicbrainz_albumartistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata['musicbrainz_artistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata['musicbrainz_recordingid'] = 'ed052ae1-c950-47f2-8d2b-46e1b58ab76c' file.metadata['musicbrainz_releasetrackid'] = '7668a62a-2fac-3151-a744-5707ac8c883c' return file def example_2(self): file = File("track05.mp3") file.state = File.NORMAL file.metadata['album'] = u"Coup d'État, Volume 1: Ku De Ta / Prologue" file.metadata['title'] = u"I've Got to Learn the Mambo" file.metadata['artist'] = u"Snowboy feat. James Hunter" file.metadata['artistsort'] = u"Snowboy feat. Hunter, James" file.metadata['albumartist'] = config.setting['va_name'] file.metadata['albumartistsort'] = config.setting['va_name'] file.metadata['tracknumber'] = '5' file.metadata['totaltracks'] = '13' file.metadata['discnumber'] = '2' file.metadata['totaldiscs'] = '2' file.metadata['discsubtitle'] = u"Beat Up" file.metadata['date'] = u'2005-07-04' file.metadata['releasetype'] = [u'album', u'compilation'] file.metadata['~primaryreleasetype'] = u'album' file.metadata['~secondaryreleasetype'] = u'compilation' file.metadata['releasestatus'] = u'official' file.metadata['releasecountry'] = u'AU' file.metadata['compilation'] = '1' file.metadata['~multiartist'] = '1' file.metadata['~extension'] = 'mp3' file.metadata['musicbrainz_albumid'] = u'4b50c71e-0a07-46ac-82e4-cb85dc0c9bdd' file.metadata['musicbrainz_recordingid'] = u'b3c487cb-0e55-477d-8df3-01ec6590f099' file.metadata['musicbrainz_releasetrackid'] = u'f8649a05-da39-39ba-957c-7abf8f9012be' file.metadata['musicbrainz_albumartistid'] = u'89ad4ac3-39f7-470e-963a-56509c546377' file.metadata['musicbrainz_artistid'] = [u'7b593455-d207-482c-8c6f-19ce22c94679', u'9e082466-2390-40d1-891e-4803531f43fd'] return file def move_files_to_browse(self): path = QtGui.QFileDialog.getExistingDirectory(self, "", self.ui.move_files_to.text()) if path: path = os.path.normpath(unicode(path)) self.ui.move_files_to.setText(path) def test(self): self.ui.renaming_error.setStyleSheet("") self.ui.renaming_error.setText("") try: self.check_format() except OptionsCheckError as e: self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.renaming_error.setText(e.info) return register_options_page(RenamingOptionsPage) picard-release-1.4.2/picard/ui/options/scripting.py000066400000000000000000000435071310410472100223260ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtGui from picard import config from picard.const import PICARD_URLS from picard.script import ScriptParser from picard.ui.options import OptionsPage, OptionsCheckError, register_options_page from picard.ui.ui_options_script import Ui_ScriptingOptionsPage DEFAULT_NUMBERED_SCRIPT_NAME = N_("My script %d") DEFAULT_SCRIPT_NAME = N_("My script") class TaggerScriptSyntaxHighlighter(QtGui.QSyntaxHighlighter): def __init__(self, document): QtGui.QSyntaxHighlighter.__init__(self, document) self.func_re = QtCore.QRegExp(r"\$(?!noop)[a-zA-Z][_a-zA-Z0-9]*\(") self.func_fmt = QtGui.QTextCharFormat() self.func_fmt.setFontWeight(QtGui.QFont.Bold) self.func_fmt.setForeground(QtCore.Qt.blue) self.var_re = QtCore.QRegExp(r"%[_a-zA-Z0-9:]*%") self.var_fmt = QtGui.QTextCharFormat() self.var_fmt.setForeground(QtCore.Qt.darkCyan) self.escape_re = QtCore.QRegExp(r"\\.") self.escape_fmt = QtGui.QTextCharFormat() self.escape_fmt.setForeground(QtCore.Qt.darkRed) self.special_re = QtCore.QRegExp(r"[^\\][(),]") self.special_fmt = QtGui.QTextCharFormat() self.special_fmt.setForeground(QtCore.Qt.blue) self.bracket_re = QtCore.QRegExp(r"[()]") self.noop_re = QtCore.QRegExp(r"\$noop\(") self.noop_fmt = QtGui.QTextCharFormat() self.noop_fmt.setFontWeight(QtGui.QFont.Bold) self.noop_fmt.setFontItalic(True) self.noop_fmt.setForeground(QtCore.Qt.darkGray) self.rules = [ (self.func_re, self.func_fmt, 0, -1), (self.var_re, self.var_fmt, 0, 0), (self.escape_re, self.escape_fmt, 0, 0), (self.special_re, self.special_fmt, 1, -1), ] def highlightBlock(self, text): self.setCurrentBlockState(0) for expr, fmt, a, b in self.rules: index = expr.indexIn(text) while index >= 0: length = expr.matchedLength() self.setFormat(index + a, length + b, fmt) index = expr.indexIn(text, index + length + b) # Ignore everything if we're already in a noop function index = self.noop_re.indexIn(text) if self.previousBlockState() <= 0 else 0 open_brackets = self.previousBlockState() if self.previousBlockState() > 0 else 0 while index >= 0: next_index = self.bracket_re.indexIn(text, index) # Skip escaped brackets if (next_index > 0) and text[next_index - 1] == '\\': next_index += 1 if (next_index > -1) and text[next_index] == '(': open_brackets += 1 elif (next_index > -1) and text[next_index] == ')': open_brackets -= 1 if (next_index > -1): self.setFormat(index, next_index - index + 1, self.noop_fmt) elif (next_index == -1) and (open_brackets > 0): self.setFormat(index, len(text) - index, self.noop_fmt) # Check for next noop operation, necessary for multiple noops in one line if open_brackets == 0: next_index = self.noop_re.indexIn(text, next_index) index = next_index + 1 if (next_index > -1) and (next_index < len(text)) else -1 self.setCurrentBlockState(open_brackets) class AdvancedScriptItem(QtGui.QWidget): """Custom widget for script list items""" _CHECKBOX_POS = 0 _NAME_POS = 1 _BUTTON_UP = 2 _BUTTON_DOWN = 3 _BUTTON_OTHER = 4 def __init__(self, name=None, state=True, parent=None): super(AdvancedScriptItem, self).__init__(parent) layout = QtGui.QGridLayout() layout.setHorizontalSpacing(5) layout.setVerticalSpacing(2) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) checkbox = QtGui.QCheckBox() checkbox.setChecked(state) checkbox.setMaximumSize(QtCore.QSize(22, 22)) layout.addWidget(checkbox, 0, self._CHECKBOX_POS) layout.addWidget(QtGui.QLabel(name), 0, self._NAME_POS) up_button = QtGui.QToolButton() up_button.setArrowType(QtCore.Qt.UpArrow) up_button.setMaximumSize(QtCore.QSize(16, 16)) up_button.setToolTip(_("Move script up")) down_button = QtGui.QToolButton() down_button.setArrowType(QtCore.Qt.DownArrow) down_button.setMaximumSize(QtCore.QSize(16, 16)) down_button.setToolTip(_("Move script down")) layout.addWidget(up_button, 0, self._BUTTON_UP) layout.addWidget(down_button, 0, self._BUTTON_DOWN) other_button = QtGui.QToolButton() other_button.setText("...") other_button.setAutoRaise(True) other_button.setMaximumSize(QtCore.QSize(16, 16)) other_button.setToolTip(_("Other options")) menu = QtGui.QMenu() menu.addAction(_("Rename script")) menu.addAction(_("Remove script")) self.menu = menu other_button.setMenu(menu) # remove menu indicator other_button.setStyleSheet('QToolButton::menu-indicator { image: none; }') other_button.clicked.connect(other_button.showMenu) layout.addWidget(other_button, 0, self._BUTTON_OTHER) def set_up_connection(self, move_up): layout = self.layout() up = layout.itemAtPosition(0, self._BUTTON_UP).widget() up.clicked.connect(move_up) def set_down_connection(self, move_down): layout = self.layout() down = layout.itemAtPosition(0, self._BUTTON_DOWN).widget() down.clicked.connect(move_down) def set_remove_connection(self, remove): menu_options = self.menu.actions() menu_options[1].triggered.connect(remove) def set_rename_connection(self, rename): menu_options = self.menu.actions() menu_options[0].triggered.connect(rename) def set_checkbox_connection(self, check_state): layout = self.layout() checkbox = layout.itemAtPosition(0, self._CHECKBOX_POS).widget() checkbox.stateChanged.connect(check_state) def update_name(self, name): layout = self.layout() name_label = layout.itemAtPosition(0, self._NAME_POS).widget() name_label.setText(name) def checkbox_state(self): layout = self.layout() checkbox = layout.itemAtPosition(0, self._CHECKBOX_POS).widget() return checkbox.isChecked() class ScriptItem: """Holds a script's list and text widget properties and improves readability""" def __init__(self, pos, name=None, enabled=True, text=""): self.pos = pos if name is None: self.name = _(DEFAULT_SCRIPT_NAME) else: self.name = name self.enabled = enabled self.text = text def get_all(self): # tuples used to get pickle dump of settings to work return (self.pos, self.name, self.enabled, self.text) class ScriptingOptionsPage(OptionsPage): NAME = "scripting" TITLE = N_("Scripting") PARENT = None SORT_ORDER = 85 ACTIVE = True options = [ config.BoolOption("setting", "enable_tagger_scripts", False), config.ListOption("setting", "list_of_scripts", []), config.IntOption("persist", "last_selected_script_pos", 0), config.Option("persist", "scripting_splitter", QtCore.QByteArray()), ] def __init__(self, parent=None): super(ScriptingOptionsPage, self).__init__(parent) self.ui = Ui_ScriptingOptionsPage() self.ui.setupUi(self) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.tagger_script.document()) self.ui.tagger_script.textChanged.connect(self.live_update_and_check) self.ui.script_name.textChanged.connect(self.script_name_changed) self.ui.add_script.clicked.connect(self.add_to_list_of_scripts) self.ui.script_list.itemSelectionChanged.connect(self.script_selected) self.ui.tagger_script.setEnabled(False) self.ui.script_name.setEnabled(False) self.listitem_to_scriptitem = {} self.list_of_scripts = [] self.last_selected_script_pos = 0 self.ui.splitter.setStretchFactor(0, 1) self.ui.splitter.setStretchFactor(1, 2) def script_name_changed(self): items = self.ui.script_list.selectedItems() if items: script = self.listitem_to_scriptitem[items[0]] script.name = self.ui.script_name.text() list_widget = self.ui.script_list.itemWidget(items[0]) list_widget.update_name(script.name) self.list_of_scripts[script.pos] = script.get_all() def script_selected(self): items = self.ui.script_list.selectedItems() if items: self.ui.tagger_script.setEnabled(True) self.ui.script_name.setEnabled(True) script = self.listitem_to_scriptitem[items[0]] self.ui.tagger_script.setText(script.text) self.ui.script_name.setText(script.name) self.last_selected_script_pos = script.pos def setSignals(self, list_widget, item): list_widget.set_up_connection(lambda: self.move_script(self.ui.script_list.row(item), 1)) list_widget.set_down_connection(lambda: self.move_script(self.ui.script_list.row(item), -1)) list_widget.set_remove_connection(lambda: self.remove_from_list_of_scripts(self.ui.script_list.row(item))) list_widget.set_checkbox_connection(lambda: self.update_check_state(item, list_widget.checkbox_state())) list_widget.set_rename_connection(lambda: self.rename_script(item)) def rename_script(self, item): self.ui.script_list.setItemSelected(item, True) self.ui.script_name.setFocus() self.ui.script_name.selectAll() def update_check_state(self, item, checkbox_state): script = self.listitem_to_scriptitem[item] script.enabled = checkbox_state self.list_of_scripts[script.pos] = script.get_all() def add_to_list_of_scripts(self): count = self.ui.script_list.count() numbered_name = _(DEFAULT_NUMBERED_SCRIPT_NAME) % (count + 1) script = ScriptItem(pos=count, name=numbered_name) list_item = QtGui.QListWidgetItem() list_widget = AdvancedScriptItem(numbered_name) self.setSignals(list_widget, list_item) self.ui.script_list.addItem(list_item) self.ui.script_list.setItemWidget(list_item, list_widget) self.listitem_to_scriptitem[list_item] = script self.list_of_scripts.append(script.get_all()) self.ui.script_list.setItemSelected(list_item, True) def update_script_positions(self): for i, script in enumerate(self.list_of_scripts): self.list_of_scripts[i] = (i, script[1], script[2], script[3]) item = self.ui.script_list.item(i) self.listitem_to_scriptitem[item].pos = i def remove_from_list_of_scripts(self, row): item = self.ui.script_list.item(row) confirm_remove = QtGui.QMessageBox() msg = _("Are you sure you want to remove this script?") reply = confirm_remove.question(confirm_remove, _('Confirm Remove'), msg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if item and reply == QtGui.QMessageBox.Yes: item = self.ui.script_list.takeItem(row) script = self.listitem_to_scriptitem[item] del self.listitem_to_scriptitem[item] del self.list_of_scripts[script.pos] del script item = None # update positions of other items self.update_script_positions() if not self.ui.script_list: self.ui.tagger_script.setText("") self.ui.tagger_script.setEnabled(False) self.ui.script_name.setText("") self.ui.script_name.setEnabled(False) # update position of last_selected_script if row == self.last_selected_script_pos: self.last_selected_script_pos = 0 # workaround to remove residue on UI if not self.ui.script_list.selectedItems(): current_item = self.ui.script_list.currentItem() if current_item: self.ui.script_list.setItemSelected(current_item, True) else: item = self.ui.script_list.item(0) self.ui.script_list.setItemSelected(item, True) elif row < self.last_selected_script_pos: self.last_selected_script_pos -= 1 def move_script(self, row, step): item1 = self.ui.script_list.item(row) item2 = self.ui.script_list.item(row - step) if item1 and item2: # make changes in the ui list_item = self.ui.script_list.takeItem(row) script = self.listitem_to_scriptitem[list_item] # list_widget has to be set again list_widget = AdvancedScriptItem(name=script.name, state=script.enabled) self.setSignals(list_widget, list_item) self.ui.script_list.insertItem(row - step, list_item) self.ui.script_list.setItemWidget(list_item, list_widget) # make changes in the picklable list script1 = self.listitem_to_scriptitem[item1] script2 = self.listitem_to_scriptitem[item2] # workaround since tuples are immutable indices = script1.pos, script2.pos self.list_of_scripts = [i for j, i in enumerate(self.list_of_scripts) if j not in indices] new_script1 = (script1.pos - step, script1.name, script1.enabled, script1.text) new_script2 = (script2.pos + step, script2.name, script2.enabled, script2.text) self.list_of_scripts.append(new_script1) self.list_of_scripts.append(new_script2) self.list_of_scripts = sorted(self.list_of_scripts, key=lambda x: x[0]) # corresponding mapping support also has to be updated self.listitem_to_scriptitem[item1] = ScriptItem(script1.pos - step, script1.name, script1.enabled, script1.text) self.listitem_to_scriptitem[item2] = ScriptItem(script2.pos + step, script2.name, script2.enabled, script2.text) def live_update_and_check(self): items = self.ui.script_list.selectedItems() if items: script = self.listitem_to_scriptitem[items[0]] script.text = self.ui.tagger_script.toPlainText() self.list_of_scripts[script.pos] = script.get_all() self.ui.script_error.setStyleSheet("") self.ui.script_error.setText("") try: self.check() except OptionsCheckError as e: self.ui.script_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.script_error.setText(e.info) return def check(self): parser = ScriptParser() try: parser.eval(unicode(self.ui.tagger_script.toPlainText())) except Exception as e: raise OptionsCheckError(_("Script Error"), str(e)) def restore_defaults(self): # Remove existing scripts self.ui.script_list.clear() self.ui.script_name.setText("") self.ui.tagger_script.setText("") super(ScriptingOptionsPage, self).restore_defaults() def load(self): self.ui.enable_tagger_scripts.setChecked(config.setting["enable_tagger_scripts"]) self.list_of_scripts = config.setting["list_of_scripts"] for s_pos, s_name, s_enabled, s_text in self.list_of_scripts: script = ScriptItem(s_pos, s_name, s_enabled, s_text) list_item = QtGui.QListWidgetItem() list_widget = AdvancedScriptItem(name=s_name, state=s_enabled) self.setSignals(list_widget, list_item) self.ui.script_list.addItem(list_item) self.ui.script_list.setItemWidget(list_item, list_widget) self.listitem_to_scriptitem[list_item] = script # Select the last selected script item self.last_selected_script_pos = config.persist["last_selected_script_pos"] last_selected_script = self.ui.script_list.item(self.last_selected_script_pos) if last_selected_script: self.ui.script_list.setItemSelected(last_selected_script, True) # Preserve previous splitter position self.ui.splitter.restoreState(config.persist["scripting_splitter"]) args = { "picard-doc-scripting-url": PICARD_URLS['doc_scripting'], } text = _(u'Open Scripting' ' Documentation in your browser') % args self.ui.scripting_doc_link.setText(text) def save(self): config.setting["enable_tagger_scripts"] = self.ui.enable_tagger_scripts.isChecked() config.setting["list_of_scripts"] = self.list_of_scripts config.persist["last_selected_script_pos"] = self.last_selected_script_pos config.persist["scripting_splitter"] = self.ui.splitter.saveState() def display_error(self, error): pass register_options_page(ScriptingOptionsPage) picard-release-1.4.2/picard/ui/options/tags.py000066400000000000000000000143161310410472100212560ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from functools import partial from PyQt4 import QtCore, QtGui from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_tags import Ui_TagsOptionsPage from picard.util.tags import TAG_NAMES class TagsOptionsPage(OptionsPage): NAME = "tags" TITLE = N_("Tags") PARENT = None SORT_ORDER = 30 ACTIVE = True options = [ config.BoolOption("setting", "clear_existing_tags", False), config.TextOption("setting", "preserved_tags", ""), config.BoolOption("setting", "write_id3v1", True), config.BoolOption("setting", "write_id3v23", True), config.TextOption("setting", "id3v2_encoding", "utf-16"), config.TextOption("setting", "id3v23_join_with", "/"), config.BoolOption("setting", "remove_id3_from_flac", False), config.BoolOption("setting", "remove_ape_from_mp3", False), config.BoolOption("setting", "tpe2_albumartist", False), config.BoolOption("setting", "dont_write_tags", False), config.BoolOption("setting", "preserve_timestamps", False), ] def __init__(self, parent=None): super(TagsOptionsPage, self).__init__(parent) self.ui = Ui_TagsOptionsPage() self.ui.setupUi(self) self.ui.write_id3v23.clicked.connect(self.update_encodings) self.ui.write_id3v24.clicked.connect(partial(self.update_encodings, force_utf8=True)) self.completer = QtGui.QCompleter(sorted(TAG_NAMES.keys()), self) self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) self.completer.setWidget(self.ui.preserved_tags) self.ui.preserved_tags.textEdited.connect(self.preserved_tags_edited) self.completer.activated.connect(self.completer_activated) def load(self): self.ui.write_tags.setChecked(not config.setting["dont_write_tags"]) self.ui.preserve_timestamps.setChecked(config.setting["preserve_timestamps"]) self.ui.clear_existing_tags.setChecked(config.setting["clear_existing_tags"]) self.ui.write_id3v1.setChecked(config.setting["write_id3v1"]) if config.setting["write_id3v23"]: self.ui.write_id3v23.setChecked(True) else: self.ui.write_id3v24.setChecked(True) if config.setting["id3v2_encoding"] == "iso-8859-1": self.ui.enc_iso88591.setChecked(True) elif config.setting["id3v2_encoding"] == "utf-16": self.ui.enc_utf16.setChecked(True) else: self.ui.enc_utf8.setChecked(True) self.ui.id3v23_join_with.setEditText(config.setting["id3v23_join_with"]) self.ui.remove_ape_from_mp3.setChecked(config.setting["remove_ape_from_mp3"]) self.ui.remove_id3_from_flac.setChecked(config.setting["remove_id3_from_flac"]) self.ui.preserved_tags.setText(config.setting["preserved_tags"]) self.update_encodings() def save(self): config.setting["dont_write_tags"] = not self.ui.write_tags.isChecked() config.setting["preserve_timestamps"] = self.ui.preserve_timestamps.isChecked() clear_existing_tags = self.ui.clear_existing_tags.isChecked() if clear_existing_tags != config.setting["clear_existing_tags"]: config.setting["clear_existing_tags"] = clear_existing_tags self.tagger.window.metadata_box.update() config.setting["write_id3v1"] = self.ui.write_id3v1.isChecked() config.setting["write_id3v23"] = self.ui.write_id3v23.isChecked() config.setting["id3v23_join_with"] = unicode(self.ui.id3v23_join_with.currentText()) if self.ui.enc_iso88591.isChecked(): config.setting["id3v2_encoding"] = "iso-8859-1" elif self.ui.enc_utf16.isChecked(): config.setting["id3v2_encoding"] = "utf-16" else: config.setting["id3v2_encoding"] = "utf-8" config.setting["remove_ape_from_mp3"] = self.ui.remove_ape_from_mp3.isChecked() config.setting["remove_id3_from_flac"] = self.ui.remove_id3_from_flac.isChecked() config.setting["preserved_tags"] = unicode(self.ui.preserved_tags.text()) self.tagger.window.enable_tag_saving_action.setChecked(not config.setting["dont_write_tags"]) def update_encodings(self, force_utf8=False): if self.ui.write_id3v23.isChecked(): if self.ui.enc_utf8.isChecked(): self.ui.enc_utf16.setChecked(True) self.ui.enc_utf8.setEnabled(False) self.ui.label_id3v23_join_with.setEnabled(True) self.ui.id3v23_join_with.setEnabled(True) else: self.ui.enc_utf8.setEnabled(True) if force_utf8: self.ui.enc_utf8.setChecked(True) self.ui.label_id3v23_join_with.setEnabled(False) self.ui.id3v23_join_with.setEnabled(False) def preserved_tags_edited(self, text): prefix = unicode(text)[:self.ui.preserved_tags.cursorPosition()].split(",")[-1] self.completer.setCompletionPrefix(prefix) if prefix: self.completer.complete() else: self.completer.popup().hide() def completer_activated(self, text): input = self.ui.preserved_tags current = unicode(input.text()) i = input.cursorPosition() p = len(self.completer.completionPrefix()) input.setText("%s%s %s" % (current[:i - p], text, current[i:])) input.setCursorPosition(i - p + len(text) + 1) register_options_page(TagsOptionsPage) picard-release-1.4.2/picard/ui/passworddialog.py000066400000000000000000000053401310410472100216440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2008 Philipp Wolfer # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore from picard import config from picard.ui import PicardDialog from picard.ui.ui_passworddialog import Ui_PasswordDialog class PasswordDialog(PicardDialog): def __init__(self, authenticator, reply, parent=None): PicardDialog.__init__(self, parent) self._authenticator = authenticator self.ui = Ui_PasswordDialog() self.ui.setupUi(self) self.ui.info_text.setText( _("The server %s requires you to login. Please enter your username and password.") % reply.url().host()) self.ui.username.setText(reply.url().userName()) self.ui.password.setText(reply.url().password()) self.ui.buttonbox.accepted.connect(self.set_new_password) def set_new_password(self): self._authenticator.setUser(unicode(self.ui.username.text())) self._authenticator.setPassword(unicode(self.ui.password.text())) self.accept() class ProxyDialog(PicardDialog): def __init__(self, authenticator, proxy, parent=None): PicardDialog.__init__(self, parent) self._authenticator = authenticator self._proxy = proxy self.ui = Ui_PasswordDialog() self.ui.setupUi(self) self.ui.info_text.setText(_("The proxy %s requires you to login. Please enter your username and password.") % config.setting["proxy_server_host"]) self.ui.username.setText(config.setting["proxy_username"]) self.ui.password.setText(config.setting["proxy_password"]) self.ui.buttonbox.accepted.connect(self.set_proxy_password) def set_proxy_password(self): config.setting["proxy_username"] = unicode(self.ui.username.text()) config.setting["proxy_password"] = unicode(self.ui.password.text()) self._authenticator.setUser(unicode(self.ui.username.text())) self._authenticator.setPassword(unicode(self.ui.password.text())) self.accept() picard-release-1.4.2/picard/ui/ratingwidget.py000066400000000000000000000070431310410472100213140ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2008 Philipp Wolfer # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from PyQt4 import QtCore, QtGui from picard import config class RatingWidget(QtGui.QWidget): def __init__(self, parent, track): QtGui.QWidget.__init__(self, parent) self._track = track self._maximum = config.setting["rating_steps"] - 1 self._rating = int(track.metadata["~rating"] or 0) self._highlight = 0 self._star_pixmap = QtGui.QPixmap(":/images/star.png") self._star_gray_pixmap = QtGui.QPixmap(":/images/star-gray.png") self._star_size = 16 self._star_spacing = 2 self._offset = 16 self._width = self._maximum * (self._star_size + self._star_spacing) + self._offset self._height = self._star_size + 6 self.setMaximumSize(self._width, self._height) self.setMinimumSize(self._width, self._height) self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)) self.setMouseTracking(True) def sizeHint(self): return QtCore.QSize(self._width, self._height) def _setHighlight(self, highlight): assert 0 <= highlight <= self._maximum if highlight != self._highlight: self._highlight = highlight self.update() def mousePressEvent(self, event): if event.button() == QtCore.Qt.LeftButton: x = event.x() if x < self._offset: return rating = self._getRatingFromPosition(x) if self._rating == rating: rating = 0 self._rating = rating self._update_track() self.update() event.accept() def mouseMoveEvent(self, event): self._setHighlight(self._getRatingFromPosition(event.x())) event.accept() def leaveEvent(self, event): self._setHighlight(0) event.accept() def _getRatingFromPosition(self, position): rating = int((position - self._offset) / (self._star_size + self._star_spacing)) + 1 if rating > self._maximum: rating = self._maximum return rating def _update_track(self): track = self._track track.metadata["~rating"] = unicode(self._rating) if config.setting["submit_ratings"]: ratings = {("recording", track.id): self._rating} self.tagger.xmlws.submit_ratings(ratings, None) def paintEvent(self, event=None): painter = QtGui.QPainter(self) offset = self._offset for i in range(1, self._maximum + 1): if i <= self._rating or i <= self._highlight: pixmap = self._star_pixmap else: pixmap = self._star_gray_pixmap painter.drawPixmap(offset, 3, pixmap) offset += self._star_size + self._star_spacing picard-release-1.4.2/picard/ui/searchdialog.py000066400000000000000000001002751310410472100212520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2016 Rahul Raturi # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import json from PyQt4 import QtGui, QtCore, QtNetwork from operator import itemgetter from functools import partial from collections import namedtuple from picard import config from picard.file import File from picard.ui import PicardDialog from picard.ui.util import StandardButton, ButtonLineEdit from picard.util import format_time, icontheme from picard.mbxml import ( artist_to_metadata, recording_to_metadata, release_to_metadata, release_group_to_metadata, media_formats_from_node, country_list_from_node ) from picard.i18n import ugettext_attr from picard.metadata import Metadata from picard.webservice import escape_lucene_query from picard.track import Track from picard.const import CAA_HOST, CAA_PORT, QUERY_LIMIT from picard.coverart.image import CaaThumbnailCoverArtImage class ResultTable(QtGui.QTableWidget): def __init__(self, parent, column_titles): QtGui.QTableWidget.__init__(self, 0, len(column_titles)) self.parent = parent self.setHorizontalHeaderLabels(column_titles) self.setSelectionMode( QtGui.QAbstractItemView.SingleSelection) self.setSelectionBehavior( QtGui.QAbstractItemView.SelectRows) self.setEditTriggers( QtGui.QAbstractItemView.NoEditTriggers) self.horizontalHeader().setStretchLastSection(True) self.horizontalHeader().setResizeMode( QtGui.QHeaderView.Stretch) self.horizontalHeader().setResizeMode( QtGui.QHeaderView.Interactive) class SearchBox(QtGui.QWidget): def __init__(self, parent): self.parent = parent QtGui.QWidget.__init__(self, parent) self.search_action = QtGui.QAction(icontheme.lookup('system-search'), _(u"Search"), self) self.search_action.setEnabled(False) self.search_action.triggered.connect(self.search) self.setupUi() def focus_in_event(self, event): # When focus is on search edit box (ButtonLineEdit), need to disable # dialog's accept button. This would avoid closing of dialog when user # hits enter. if self.parent.table: self.parent.table.clearSelection() self.parent.accept_button.setEnabled(False) def setupUi(self): self.layout = QtGui.QVBoxLayout(self) self.search_row_widget = QtGui.QWidget(self) self.search_row_layout = QtGui.QHBoxLayout(self.search_row_widget) self.search_row_layout.setContentsMargins(1, 1, 1, 1) self.search_row_layout.setSpacing(1) self.search_edit = ButtonLineEdit(self.search_row_widget) self.search_edit.returnPressed.connect(self.trigger_search_action) self.search_edit.textChanged.connect(self.enable_search) self.search_edit.setFocusPolicy(QtCore.Qt.StrongFocus) self.search_edit.focusInEvent = self.focus_in_event self.search_row_layout.addWidget(self.search_edit) self.search_button = QtGui.QToolButton(self.search_row_widget) self.search_button.setAutoRaise(True) self.search_button.setDefaultAction(self.search_action) self.search_button.setIconSize(QtCore.QSize(22, 22)) self.search_row_layout.addWidget(self.search_button) self.search_row_widget.setLayout(self.search_row_layout) self.layout.addWidget(self.search_row_widget) self.adv_opt_row_widget = QtGui.QWidget(self) self.adv_opt_row_layout = QtGui.QHBoxLayout(self.adv_opt_row_widget) self.adv_opt_row_layout.setAlignment(QtCore.Qt.AlignLeft) self.adv_opt_row_layout.setContentsMargins(1, 1, 1, 1) self.adv_opt_row_layout.setSpacing(1) self.use_adv_search_syntax = QtGui.QCheckBox(self.adv_opt_row_widget) self.use_adv_search_syntax.setText(_("Use advanced query syntax")) self.use_adv_search_syntax.stateChanged.connect(self.update_advanced_syntax_setting) self.adv_opt_row_layout.addWidget(self.use_adv_search_syntax) self.adv_syntax_help = QtGui.QLabel(self.adv_opt_row_widget) self.adv_syntax_help.setOpenExternalLinks(True) self.adv_syntax_help.setText(_( " (" "Syntax Help)")) self.adv_opt_row_layout.addWidget(self.adv_syntax_help) self.adv_opt_row_widget.setLayout(self.adv_opt_row_layout) self.layout.addWidget(self.adv_opt_row_widget) self.layout.setContentsMargins(1, 1, 1, 1) self.layout.setSpacing(1) self.setMaximumHeight(60) def search(self): self.parent.search(self.search_edit.text()) def restore_checkbox_state(self): self.use_adv_search_syntax.setChecked(config.setting["use_adv_search_syntax"]) def update_advanced_syntax_setting(self): config.setting["use_adv_search_syntax"] = self.use_adv_search_syntax.isChecked() def enable_search(self): if self.search_edit.text(): self.search_action.setEnabled(True) else: self.search_action.setEnabled(False) def trigger_search_action(self): if self.search_action.isEnabled(): self.search_action.trigger() class CoverArt(QtGui.QWidget): def __init__(self, parent): QtGui.QWidget.__init__(self, parent) self.layout = QtGui.QVBoxLayout(self) self.loading_gif_label = QtGui.QLabel(self) self.loading_gif_label.setAlignment(QtCore.Qt.AlignCenter) loading_gif = QtGui.QMovie(":/images/loader.gif") self.loading_gif_label.setMovie(loading_gif) loading_gif.start() self.layout.addWidget(self.loading_gif_label) def update(self, pixmap): wid = self.layout.takeAt(0) if wid: wid.widget().deleteLater() cover_label = QtGui.QLabel(self) cover_label.setPixmap(pixmap.scaled(100, 100, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) ) self.layout.addWidget(cover_label) def not_found(self): """Update the widget with a blank image.""" shadow = QtGui.QPixmap(":/images/CoverArtShadow.png") self.update(shadow) Retry = namedtuple("Retry", ["function", "query"]) class SearchDialog(PicardDialog): def __init__(self, parent, accept_button_title): PicardDialog.__init__(self, parent) self.search_results = [] self.table = None self.setupUi(accept_button_title) self.restore_state() def setupUi(self, accept_button_title): self.verticalLayout = QtGui.QVBoxLayout(self) self.verticalLayout.setObjectName("vertical_layout") self.search_box = SearchBox(self) self.search_box.setObjectName("search_box") self.verticalLayout.addWidget(self.search_box) self.center_widget = QtGui.QWidget(self) self.center_widget.setObjectName("center_widget") self.center_layout = QtGui.QVBoxLayout(self.center_widget) self.center_layout.setObjectName("center_layout") self.center_layout.setContentsMargins(1, 1, 1, 1) self.center_widget.setLayout(self.center_layout) self.verticalLayout.addWidget(self.center_widget) self.buttonBox = QtGui.QDialogButtonBox(self) self.accept_button = QtGui.QPushButton( accept_button_title, self.buttonBox) self.accept_button.setEnabled(False) self.buttonBox.addButton( self.accept_button, QtGui.QDialogButtonBox.AcceptRole) self.buttonBox.addButton( StandardButton(StandardButton.CANCEL), QtGui.QDialogButtonBox.RejectRole) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.verticalLayout.addWidget(self.buttonBox) def add_widget_to_center_layout(self, widget): """Update center widget with new child. If child widget exists, schedule it for deletion.""" wid = self.center_layout.takeAt(0) if wid: if wid.widget().objectName() == "results_table": self.table = None wid.widget().deleteLater() self.center_layout.addWidget(widget) def show_progress(self): self.progress_widget = QtGui.QWidget(self) self.progress_widget.setObjectName("progress_widget") layout = QtGui.QVBoxLayout(self.progress_widget) text_label = QtGui.QLabel(_('Loading...'), self.progress_widget) text_label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom) gif_label = QtGui.QLabel(self.progress_widget) movie = QtGui.QMovie(":/images/loader.gif") gif_label.setMovie(movie) movie.start() gif_label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop) layout.addWidget(text_label) layout.addWidget(gif_label) layout.setContentsMargins(1, 1, 1, 1) self.progress_widget.setLayout(layout) self.add_widget_to_center_layout(self.progress_widget) def show_error(self, error, show_retry_button=False): """Display the error string. Args: error -- Error string show_retry_button -- Whether to display retry button or not """ self.error_widget = QtGui.QWidget(self) self.error_widget.setObjectName("error_widget") layout = QtGui.QVBoxLayout(self.error_widget) error_label = QtGui.QLabel(error, self.error_widget) error_label.setWordWrap(True) error_label.setAlignment(QtCore.Qt.AlignCenter) error_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) layout.addWidget(error_label) if show_retry_button: retry_widget = QtGui.QWidget(self.error_widget) retry_layout = QtGui.QHBoxLayout(retry_widget) retry_button = QtGui.QPushButton(_("Retry"), self.error_widget) retry_button.clicked.connect(self.retry) retry_button.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Fixed)) retry_layout.addWidget(retry_button) retry_layout.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop) retry_widget.setLayout(retry_layout) layout.addWidget(retry_widget) self.error_widget.setLayout(layout) self.add_widget_to_center_layout(self.error_widget) def show_table(self, column_headers): self.table = ResultTable(self, self.table_headers) self.table.setObjectName("results_table") self.table.cellDoubleClicked.connect(self.accept) self.table.horizontalHeader().sectionResized.connect( self.save_table_header_state) self.restore_table_header_state() self.add_widget_to_center_layout(self.table) def enable_accept_button(): self.accept_button.setEnabled(True) self.table.itemSelectionChanged.connect( enable_accept_button) def network_error(self, reply, error): error_msg = _("Following error occurred while fetching results:

" "Network request error for %s:
%s (QT code %d, HTTP code %s)
") % ( reply.request().url().toString(QtCore.QUrl.RemoveUserInfo), reply.errorString(), error, repr(reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)) ) self.show_error(error_msg, show_retry_button=True) def no_results_found(self): error_msg = _("No results found. Please try a different search query.") self.show_error(error_msg) def accept(self): if self.table: row = self.table.selectionModel().selectedRows()[0].row() self.accept_event(row) self.save_state() QtGui.QDialog.accept(self) def reject(self): self.save_state() QtGui.QDialog.reject(self) class TrackSearchDialog(SearchDialog): options = [ config.Option("persist", "tracksearchdialog_window_size", QtCore.QSize(720, 360)), config.Option("persist", "tracksearchdialog_header_state", QtCore.QByteArray()) ] def __init__(self, parent): super(TrackSearchDialog, self).__init__( parent, accept_button_title=_("Load into Picard")) self.file_ = None self.setWindowTitle(_("Track Search Results")) self.table_headers = [ _("Name"), _("Length"), _("Artist"), _("Release"), _("Date"), _("Country"), _("Type") ] def search(self, text): """Perform search using query provided by the user.""" self.retry_params = Retry(self.search, text) self.search_box.search_edit.setText(text) self.show_progress() self.tagger.xmlws.find_tracks(self.handle_reply, query=text, search=True, limit=QUERY_LIMIT) def load_similar_tracks(self, file_): """Perform search using existing metadata information from the file as query.""" self.retry_params = Retry(self.load_similar_tracks, file_) self.file_ = file_ metadata = file_.orig_metadata query = { 'track': metadata['title'], 'artist': metadata['artist'], 'release': metadata['album'], 'tnum': metadata['tracknumber'], 'tracks': metadata['totaltracks'], 'qdur': str(metadata.length / 2000), 'isrc': metadata['isrc'], } # Generate query to be displayed to the user (in search box). # If advanced query syntax setting is enabled by user, display query in # advanced syntax style. Otherwise display only track title. if config.setting["use_adv_search_syntax"]: query_str = ' '.join(['%s:(%s)' % (item, escape_lucene_query(value)) for item, value in query.iteritems() if value]) else: query_str = query["track"] query["limit"] = QUERY_LIMIT self.search_box.search_edit.setText(query_str) self.show_progress() self.tagger.xmlws.find_tracks( self.handle_reply, **query) def retry(self): self.retry_params.function(self.retry_params.query) def handle_reply(self, document, http, error): if error: self.network_error(http, error) return try: tracks = document.metadata[0].recording_list[0].recording except (AttributeError, IndexError): self.no_results_found() return if self.file_: sorted_results = sorted( (self.file_.orig_metadata.compare_to_track( track, File.comparison_weights) for track in tracks), reverse=True, key=itemgetter(0)) tracks = [item[3] for item in sorted_results] del self.search_results[:] # Clear existing data self.parse_tracks_from_xml(tracks) self.display_results() def display_results(self): self.show_table(self.table_headers) for row, obj in enumerate(self.search_results): track = obj[0] table_item = QtGui.QTableWidgetItem self.table.insertRow(row) self.table.setItem(row, 0, table_item(track.get("title", ""))) self.table.setItem(row, 1, table_item(track.get("~length", ""))) self.table.setItem(row, 2, table_item(track.get("artist", ""))) self.table.setItem(row, 3, table_item(track.get("album", ""))) self.table.setItem(row, 4, table_item(track.get("date", ""))) self.table.setItem(row, 5, table_item(track.get("country", ""))) self.table.setItem(row, 6, table_item(track.get("releasetype", ""))) def parse_tracks_from_xml(self, tracks_xml): for node in tracks_xml: if "release_list" in node.children and "release" in node.release_list[0].children: for rel_node in node.release_list[0].release: track = Metadata() recording_to_metadata(node, track) release_to_metadata(rel_node, track) rg_node = rel_node.release_group[0] release_group_to_metadata(rg_node, track) countries = country_list_from_node(rel_node) if countries: track["country"] = ", ".join(countries) self.search_results.append((track, node)) else: # This handles the case when no release is associated with a track # i.e. the track is an NAT track = Metadata() recording_to_metadata(node, track) track["album"] = _("Standalone Recording") self.search_results.append((track, node)) def accept_event(self, arg): self.load_selection(arg) def load_selection(self, row): """Load the album corresponding to the selected track. If the search is performed for a file, also associate the file to corresponding track in the album. """ track, node = self.search_results[row] if track.get("musicbrainz_albumid"): # The track is not an NAT self.tagger.get_release_group_by_id(track["musicbrainz_releasegroupid"]).loaded_albums.add( track["musicbrainz_albumid"]) if self.file_: # Search is performed for a file. # Have to move that file from its existing album to the new one. if isinstance(self.file_.parent, Track): album = self.file_.parent.album self.tagger.move_file_to_track(self.file_, track["musicbrainz_albumid"], track["musicbrainz_recordingid"]) if album._files == 0: # Remove album if it has no more files associated self.tagger.remove_album(album) else: self.tagger.move_file_to_track(self.file_, track["musicbrainz_albumid"], track["musicbrainz_recordingid"]) else: # No files associated. Just a normal search. self.tagger.load_album(track["musicbrainz_albumid"]) else: if self.file_: album = self.file_.parent.album self.tagger.move_file_to_nat(track["musicbrainz_recordingid"]) if album._files == 0: self.tagger.remove_album(album) else: self.tagger.load_nat(track["musicbrainz_recordingid"], node) def restore_state(self): size = config.persist["tracksearchdialog_window_size"] if size: self.resize(size) self.search_box.restore_checkbox_state() def restore_table_header_state(self): header = self.table.horizontalHeader() state = config.persist["tracksearchdialog_header_state"] if state: header.restoreState(state) header.setResizeMode(QtGui.QHeaderView.Interactive) def save_state(self): if self.table: self.save_table_header_state() config.persist["tracksearchdialog_window_size"] = self.size() def save_table_header_state(self): state = self.table.horizontalHeader().saveState() config.persist["tracksearchdialog_header_state"] = state class AlbumSearchDialog(SearchDialog): options = [ config.Option("persist", "albumsearchdialog_window_size", QtCore.QSize(720, 360)), config.Option("persist", "albumsearchdialog_header_state", QtCore.QByteArray()) ] def __init__(self, parent): super(AlbumSearchDialog, self).__init__( parent, accept_button_title=_("Load into Picard")) self.cluster = None self.setWindowTitle(_("Album Search Results")) self.table_headers = [ _("Name"), _("Artist"), _("Format"), _("Tracks"), _("Date"), _("Country"), _("Labels"), _("Catalog #s"), _("Barcode"), _("Language"), _("Type"), _("Status"), _("Cover") ] def search(self, text): """Perform search using query provided by the user.""" self.retry_params = Retry(self.search, text) self.search_box.search_edit.setText(text) self.show_progress() self.tagger.xmlws.find_releases(self.handle_reply, query=text, search=True, limit=QUERY_LIMIT) def show_similar_albums(self, cluster): """Perform search by using existing metadata information from the cluster as query.""" self.retry_params = Retry(self.show_similar_albums, cluster) self.cluster = cluster metadata = cluster.metadata query = { "artist": metadata["albumartist"], "release": metadata["album"], "tracks": str(len(cluster.files)) } # Generate query to be displayed to the user (in search box). # If advanced query syntax setting is enabled by user, display query in # advanced syntax style. Otherwise display only album title. if config.setting["use_adv_search_syntax"]: query_str = ' '.join(['%s:(%s)' % (item, escape_lucene_query(value)) for item, value in query.iteritems() if value]) else: query_str = query["release"] query["limit"] = QUERY_LIMIT self.search_box.search_edit.setText(query_str) self.show_progress() self.tagger.xmlws.find_releases( self.handle_reply, **query) def retry(self): self.retry_params.function(self.retry_params.query) def handle_reply(self, document, http, error): if error: self.network_error(http, error) return try: releases = document.metadata[0].release_list[0].release except (AttributeError, IndexError): self.no_results_found() return del self.search_results[:] self.parse_releases_from_xml(releases) self.display_results() self.fetch_coverarts() def fetch_coverarts(self): """Queue cover art jsons from CAA server for each album in search results. """ for row, release in enumerate(self.search_results): caa_path = "/release/%s" % release["musicbrainz_albumid"] self.tagger.xmlws.download( CAA_HOST, CAA_PORT, caa_path, partial(self._caa_json_downloaded, row) ) def _caa_json_downloaded(self, row, data, http, error): """Handle json reply from CAA server. If server replies without error, try to get small thumbnail of front coverart of the release. """ if not self.table: return cover_cell = self.table.cellWidget(row, len(self.table_headers)-1) if error: cover_cell.not_found() return try: caa_data = json.loads(data) except ValueError: cover_cell.not_found() return front = None for image in caa_data["images"]: if image["front"]: front = image break if front: url = front["thumbnails"]["small"] coverartimage = CaaThumbnailCoverArtImage(url=url) self.tagger.xmlws.download( coverartimage.host, coverartimage.port, coverartimage.path, partial(self._cover_downloaded, row), ) else: cover_cell.not_found() def _cover_downloaded(self, row, data, http, error): """Handle cover art query reply from CAA server. If server returns the cover image succesfully, update the cover art cell of particular release. Args: row -- Album's row in results table """ cover_cell = self.table.cellWidget(row, len(self.table_headers)-1) if error: cover_cell.not_found() else: pixmap = QtGui.QPixmap() try: pixmap.loadFromData(data) cover_cell.update(pixmap) except: cover_cell.not_found() def parse_releases_from_xml(self, release_xml): for node in release_xml: release = Metadata() release_to_metadata(node, release) rg_node = node.release_group[0] release_group_to_metadata(rg_node, release) if "medium_list" in node.children: medium_list = node.medium_list[0] release["format"] = media_formats_from_node(medium_list) release["tracks"] = medium_list.track_count[0].text countries = country_list_from_node(node) if countries: release["country"] = ", ".join(countries) self.search_results.append(release) def display_results(self): self.show_table(self.table_headers) self.table.verticalHeader().setDefaultSectionSize(100) for row, release in enumerate(self.search_results): table_item = QtGui.QTableWidgetItem self.table.insertRow(row) self.table.setItem(row, 0, table_item(release.get("album", ""))) self.table.setItem(row, 1, table_item(release.get("albumartist", ""))) self.table.setItem(row, 2, table_item(release.get("format", ""))) self.table.setItem(row, 3, table_item(release.get("tracks", ""))) self.table.setItem(row, 4, table_item(release.get("date", ""))) self.table.setItem(row, 5, table_item(release.get("country", ""))) self.table.setItem(row, 6, table_item(release.get("label", ""))) self.table.setItem(row, 7, table_item(release.get("catalognumber", ""))) self.table.setItem(row, 8, table_item(release.get("barcode", ""))) self.table.setItem(row, 9, table_item(release.get("~releaselanguage", ""))) self.table.setItem(row, 10, table_item(release.get("releasetype", ""))) self.table.setItem(row, 11, table_item(release.get("releasestatus", ""))) self.table.setCellWidget(row, 12, CoverArt(self.table)) def accept_event(self, arg): self.load_selection(arg) def load_selection(self, row): release = self.search_results[row] self.tagger.get_release_group_by_id( release["musicbrainz_releasegroupid"]).loaded_albums.add( release["musicbrainz_albumid"]) album = self.tagger.load_album(release["musicbrainz_albumid"]) if self.cluster: files = self.tagger.get_files_from_objects([self.cluster]) self.tagger.move_files_to_album(files, release["musicbrainz_albumid"], album) def restore_state(self): size = config.persist["albumsearchdialog_window_size"] if size: self.resize(size) self.search_box.restore_checkbox_state() def restore_table_header_state(self): header = self.table.horizontalHeader() state = config.persist["albumsearchdialog_header_state"] if state: header.restoreState(state) header.setResizeMode(QtGui.QHeaderView.Interactive) def save_state(self): if self.table: self.save_table_header_state() config.persist["albumsearchdialog_window_size"] = self.size() def save_table_header_state(self): state = self.table.horizontalHeader().saveState() config.persist["albumsearchdialog_header_state"] = state class ArtistSearchDialog(SearchDialog): options = [ config.Option("persist", "artistsearchdialog_window_size", QtCore.QSize(720, 360)), config.Option("persist", "artistsearchdialog_header_state", QtCore.QByteArray()) ] def __init__(self, parent): super(ArtistSearchDialog, self).__init__( parent, accept_button_title=_("Show in browser")) self.setWindowTitle(_("Artist Search Dialog")) self.table_headers = [ _("Name"), _("Type"), _("Gender"), _("Area"), _("Begin"), _("Begin Area"), _("End"), _("End Area"), ] def search(self, text): self.retry_params = (self.search, text) self.search_box.search_edit.setText(text) self.show_progress() self.tagger.xmlws.find_artists(self.handle_reply, query=text, search=True, limit=QUERY_LIMIT) def retry(self): self.retry_params[0](self.retry_params[1]) def handle_reply(self, document, http, error): if error: self.network_error(http, error) return try: artists = document.metadata[0].artist_list[0].artist except (AttributeError, IndexError): self.no_results() return del self.search_results[:] self.parse_artists_from_xml(artists) self.display_results() def parse_artists_from_xml(self, artist_xml): for node in artist_xml: artist = Metadata() artist_to_metadata(node, artist) self.search_results.append(artist) def display_results(self): self.show_table(self.table_headers) for row, artist in enumerate(self.search_results): table_item = QtGui.QTableWidgetItem self.table.insertRow(row) self.table.setItem(row, 0, table_item(artist.get("name", ""))) self.table.setItem(row, 1, table_item(artist.get("type", ""))) self.table.setItem(row, 2, table_item(artist.get("gender", ""))) self.table.setItem(row, 3, table_item(artist.get("area", ""))) self.table.setItem(row, 4, table_item(artist.get("begindate", ""))) self.table.setItem(row, 5, table_item(artist.get("beginarea", ""))) self.table.setItem(row, 6, table_item(artist.get("enddate", ""))) self.table.setItem(row, 7, table_item(artist.get("endarea", ""))) def accept_event(self, row): self.load_in_browser(row) def load_in_browser(self, row): self.tagger.search(self.search_results[row]["musicbrainz_artistid"], "artist") def restore_state(self): size = config.persist["artistsearchdialog_window_size"] if size: self.resize(size) self.search_box.restore_checkbox_state() def restore_table_header_state(self): header = self.table.horizontalHeader() state = config.persist["artistsearchdialog_header_state"] if state: header.restoreState(state) header.setResizeMode(QtGui.QHeaderView.Interactive) def save_state(self): if self.table: self.save_table_header_state() config.persist["artistsearchdialog_window_size"] = self.size() def save_table_header_state(self): state = self.table.horizontalHeader().saveState() config.persist["artistsearchdialog_header_state"] = state picard-release-1.4.2/picard/ui/sortablecheckboxlist.py000066400000000000000000000115361310410472100230440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2015 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import sys from functools import partial from PyQt4 import QtGui, QtCore from PyQt4.QtCore import pyqtSignal class SortableCheckboxListWidget(QtGui.QWidget): _CHECKBOX_POS = 0 _BUTTON_UP = 1 _BUTTON_DOWN = 2 __no_emit = False changed = pyqtSignal(list) def __init__(self, parent=None): super(SortableCheckboxListWidget, self).__init__(parent) layout = QtGui.QGridLayout() layout.setHorizontalSpacing(5) layout.setVerticalSpacing(2) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.__items = [] def addItems(self, items): for item in items: self.addItem(item) def setSignals(self, row): layout = self.layout() checkbox = layout.itemAtPosition(row, self._CHECKBOX_POS).widget() up = layout.itemAtPosition(row, self._BUTTON_UP).widget() down = layout.itemAtPosition(row, self._BUTTON_DOWN).widget() checkbox.stateChanged.connect(partial(self.checkbox_toggled, row)) up.clicked.connect(partial(self.move_button_clicked, row, up=True)) down.clicked.connect(partial(self.move_button_clicked, row, up=False)) def moveItem(self, from_row, to_row): to_row = to_row % len(self.__items) self.__items[to_row], self.__items[from_row] = \ self.__items[from_row], self.__items[to_row] self.updateRow(to_row) self.updateRow(from_row) self._emit_changed() def checkbox_toggled(self, row, state): self.__items[row].setChecked(state == QtCore.Qt.Checked) self._emit_changed() def move_button_clicked(self, row, up): if up: to = row - 1 else: to = row + 1 self.moveItem(row, to) def updateRow(self, row): self.__no_emit = True item = self.__items[row] layout = self.layout() checkbox = layout.itemAtPosition(row, self._CHECKBOX_POS).widget() checkbox.setText(item.text) checkbox.setChecked(item.checked) self.__no_emit = False def addItem(self, item): self.__items.append(item) row = len(self.__items) - 1 layout = self.layout() layout.addWidget(QtGui.QCheckBox(), row, self._CHECKBOX_POS) self.updateRow(row) up_button = QtGui.QToolButton() up_button.setArrowType(QtCore.Qt.UpArrow) up_button.setMaximumSize(QtCore.QSize(16, 16)) down_button = QtGui.QToolButton() down_button.setArrowType(QtCore.Qt.DownArrow) down_button.setMaximumSize(QtCore.QSize(16, 16)) layout.addWidget(up_button, row, self._BUTTON_UP) layout.addWidget(down_button, row, self._BUTTON_DOWN) self.setSignals(row) def _emit_changed(self): if not self.__no_emit: self.changed.emit(self.__items) def clear(self): for i in reversed(range(len(self.__items))): self._remove(i) self.__items = [] def _remove(self, row): self.layout().itemAtPosition(row, self._CHECKBOX_POS).widget().setParent(None) self.layout().itemAtPosition(row, self._BUTTON_UP).widget().setParent(None) self.layout().itemAtPosition(row, self._BUTTON_DOWN).widget().setParent(None) class SortableCheckboxListItem(object): def __init__(self, text=u'', checked=False, data=None): self._checked = checked self._text = text self._data = data @property def text(self): return self._text def setText(self, text): self._text = text @property def checked(self): return self._checked def setChecked(self, state): self._checked = state @property def data(self): return self._data def setData(self, data): self._data = data def __repr__(self): params = [] params.append('text=' + repr(self.text)) params.append('checked=' + repr(self.checked)) if self.data is not None: params.append('data=' + repr(self.data)) return "%s(%s)" % (self.__class__.__name__, ", ".join(params)) picard-release-1.4.2/picard/ui/tagsfromfilenames.py000066400000000000000000000137371310410472100223410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import re import os.path from PyQt4 import QtCore, QtGui from picard import config from picard.ui.util import StandardButton from picard.ui import PicardDialog from picard.ui.ui_tagsfromfilenames import Ui_TagsFromFileNamesDialog from picard.util.tags import display_tag_name class TagsFromFileNamesDialog(PicardDialog): options = [ config.TextOption("persist", "tags_from_filenames_format", ""), config.Option("persist", "tags_from_filenames_position", QtCore.QPoint()), config.Option("persist", "tags_from_filenames_size", QtCore.QSize(560, 400)), ] def __init__(self, files, parent=None): PicardDialog.__init__(self, parent) self.ui = Ui_TagsFromFileNamesDialog() self.ui.setupUi(self) items = [ "%artist%/%album%/%title%", "%artist%/%album%/%tracknumber% %title%", "%artist%/%album%/%tracknumber% - %title%", "%artist%/%album% - %tracknumber% - %title%", "%artist% - %album%/%title%", "%artist% - %album%/%tracknumber% %title%", "%artist% - %album%/%tracknumber% - %title%", ] format = config.persist["tags_from_filenames_format"] if format not in items: selected_index = 0 if format: items.insert(0, format) else: selected_index = items.index(format) self.ui.format.addItems(items) self.ui.format.setCurrentIndex(selected_index) self.ui.buttonbox.addButton(StandardButton(StandardButton.OK), QtGui.QDialogButtonBox.AcceptRole) self.ui.buttonbox.addButton(StandardButton(StandardButton.CANCEL), QtGui.QDialogButtonBox.RejectRole) self.ui.buttonbox.accepted.connect(self.accept) self.ui.buttonbox.rejected.connect(self.reject) self.ui.preview.clicked.connect(self.preview) self.ui.files.setHeaderLabels([_("File Name")]) self.restoreWindowState() self.files = files self.items = [] for file in files: item = QtGui.QTreeWidgetItem(self.ui.files) item.setText(0, os.path.basename(file.filename)) self.items.append(item) self._tag_re = re.compile(r"(%\w+%)") self.numeric_tags = ('tracknumber', 'totaltracks', 'discnumber', 'totaldiscs') def parse_format(self): format = unicode(self.ui.format.currentText()) columns = [] format_re = ['(?:^|/)'] for part in self._tag_re.split(format): if part.startswith('%') and part.endswith('%'): name = part[1:-1] columns.append(name) if name in self.numeric_tags: format_re.append('(?P<' + name + r'>\d+)') elif name in ('date'): format_re.append('(?P<' + name + r'>\d+(?:-\d+(?:-\d+)?)?)') else: format_re.append('(?P<' + name + '>[^/]*?)') else: format_re.append(re.escape(part)) format_re.append(r'\.(\w+)$') format_re = re.compile("".join(format_re)) return format_re, columns def match_file(self, file, format): match = format.search(file.filename.replace('\\','/')) if match: result = {} for name, value in match.groupdict().iteritems(): value = value.strip() if name in self.numeric_tags: value = value.lstrip("0") if self.ui.replace_underscores.isChecked(): value = value.replace('_', ' ') result[name] = value return result else: return {} def preview(self): format, columns = self.parse_format() self.ui.files.setHeaderLabels([_("File Name")] + map(display_tag_name, columns)) for item, file in zip(self.items, self.files): matches = self.match_file(file, format) for i in range(len(columns)): value = matches.get(columns[i], '') item.setText(i + 1, value) self.ui.files.header().resizeSections(QtGui.QHeaderView.ResizeToContents) self.ui.files.header().setStretchLastSection(True) def accept(self): format, columns = self.parse_format() for file in self.files: metadata = self.match_file(file, format) for name, value in metadata.iteritems(): file.metadata[name] = value file.update() config.persist["tags_from_filenames_format"] = self.ui.format.currentText() self.saveWindowState() QtGui.QDialog.accept(self) def reject(self): self.saveWindowState() QtGui.QDialog.reject(self) def closeEvent(self, event): self.saveWindowState() event.accept() def saveWindowState(self): pos = self.pos() if not pos.isNull(): config.persist["tags_from_filenames_position"] = pos config.persist["tags_from_filenames_size"] = self.size() def restoreWindowState(self): pos = config.persist["tags_from_filenames_position"] if pos.x() > 0 and pos.y() > 0: self.move(pos) self.resize(config.persist["tags_from_filenames_size"]) picard-release-1.4.2/picard/ui/ui_cdlookup.py000066400000000000000000000047721310410472100211470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(640, 240) self.vboxlayout = QtGui.QVBoxLayout(Dialog) self.vboxlayout.setSpacing(6) self.vboxlayout.setMargin(9) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.label = QtGui.QLabel(Dialog) self.label.setObjectName(_fromUtf8("label")) self.vboxlayout.addWidget(self.label) self.release_list = QtGui.QTreeWidget(Dialog) self.release_list.setRootIsDecorated(False) self.release_list.setObjectName(_fromUtf8("release_list")) self.vboxlayout.addWidget(self.release_list) self.hboxlayout = QtGui.QHBoxLayout() self.hboxlayout.setSpacing(6) self.hboxlayout.setMargin(0) self.hboxlayout.setObjectName(_fromUtf8("hboxlayout")) spacerItem = QtGui.QSpacerItem(111, 31, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.hboxlayout.addItem(spacerItem) self.ok_button = QtGui.QPushButton(Dialog) self.ok_button.setEnabled(False) self.ok_button.setObjectName(_fromUtf8("ok_button")) self.hboxlayout.addWidget(self.ok_button) self.lookup_button = QtGui.QPushButton(Dialog) self.lookup_button.setObjectName(_fromUtf8("lookup_button")) self.hboxlayout.addWidget(self.lookup_button) self.cancel_button = QtGui.QPushButton(Dialog) self.cancel_button.setObjectName(_fromUtf8("cancel_button")) self.hboxlayout.addWidget(self.cancel_button) self.vboxlayout.addLayout(self.hboxlayout) self.retranslateUi(Dialog) QtCore.QObject.connect(self.ok_button, QtCore.SIGNAL(_fromUtf8("clicked()")), Dialog.accept) QtCore.QObject.connect(self.cancel_button, QtCore.SIGNAL(_fromUtf8("clicked()")), Dialog.reject) QtCore.QMetaObject.connectSlotsByName(Dialog) Dialog.setTabOrder(self.ok_button, self.cancel_button) def retranslateUi(self, Dialog): Dialog.setWindowTitle(_("CD Lookup")) self.label.setText(_("The following releases on MusicBrainz match the CD:")) self.ok_button.setText(_("OK")) self.lookup_button.setText(_("Lookup manually")) self.cancel_button.setText(_("Cancel")) picard-release-1.4.2/picard/ui/ui_edittagdialog.py000066400000000000000000000114421310410472100221200ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_EditTagDialog(object): def setupUi(self, EditTagDialog): EditTagDialog.setObjectName(_fromUtf8("EditTagDialog")) EditTagDialog.setWindowModality(QtCore.Qt.ApplicationModal) EditTagDialog.resize(400, 250) EditTagDialog.setFocusPolicy(QtCore.Qt.StrongFocus) EditTagDialog.setModal(True) self.verticalLayout_2 = QtGui.QVBoxLayout(EditTagDialog) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.tag_names = QtGui.QComboBox(EditTagDialog) self.tag_names.setEditable(True) self.tag_names.setObjectName(_fromUtf8("tag_names")) self.verticalLayout_2.addWidget(self.tag_names) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.value_list = QtGui.QListWidget(EditTagDialog) self.value_list.setFocusPolicy(QtCore.Qt.StrongFocus) self.value_list.setTabKeyNavigation(False) self.value_list.setProperty("showDropIndicator", False) self.value_list.setObjectName(_fromUtf8("value_list")) self.horizontalLayout.addWidget(self.value_list) self.verticalLayout = QtGui.QVBoxLayout() self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.edit_value = QtGui.QPushButton(EditTagDialog) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(100) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.edit_value.sizePolicy().hasHeightForWidth()) self.edit_value.setSizePolicy(sizePolicy) self.edit_value.setMinimumSize(QtCore.QSize(100, 0)) self.edit_value.setAutoDefault(False) self.edit_value.setObjectName(_fromUtf8("edit_value")) self.verticalLayout.addWidget(self.edit_value) self.add_value = QtGui.QPushButton(EditTagDialog) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(100) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.add_value.sizePolicy().hasHeightForWidth()) self.add_value.setSizePolicy(sizePolicy) self.add_value.setMinimumSize(QtCore.QSize(100, 0)) self.add_value.setAutoDefault(False) self.add_value.setObjectName(_fromUtf8("add_value")) self.verticalLayout.addWidget(self.add_value) self.remove_value = QtGui.QPushButton(EditTagDialog) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(120) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.remove_value.sizePolicy().hasHeightForWidth()) self.remove_value.setSizePolicy(sizePolicy) self.remove_value.setMinimumSize(QtCore.QSize(120, 0)) self.remove_value.setAutoDefault(False) self.remove_value.setObjectName(_fromUtf8("remove_value")) self.verticalLayout.addWidget(self.remove_value) spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem) self.horizontalLayout.addLayout(self.verticalLayout) self.verticalLayout_2.addLayout(self.horizontalLayout) self.buttonbox = QtGui.QDialogButtonBox(EditTagDialog) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(150) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.buttonbox.sizePolicy().hasHeightForWidth()) self.buttonbox.setSizePolicy(sizePolicy) self.buttonbox.setMinimumSize(QtCore.QSize(150, 0)) self.buttonbox.setOrientation(QtCore.Qt.Horizontal) self.buttonbox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Save) self.buttonbox.setObjectName(_fromUtf8("buttonbox")) self.verticalLayout_2.addWidget(self.buttonbox) self.retranslateUi(EditTagDialog) QtCore.QObject.connect(self.buttonbox, QtCore.SIGNAL(_fromUtf8("accepted()")), EditTagDialog.accept) QtCore.QObject.connect(self.buttonbox, QtCore.SIGNAL(_fromUtf8("rejected()")), EditTagDialog.reject) QtCore.QMetaObject.connectSlotsByName(EditTagDialog) def retranslateUi(self, EditTagDialog): EditTagDialog.setWindowTitle(_("Edit Tag")) self.edit_value.setText(_("Edit value")) self.add_value.setText(_("Add value")) self.remove_value.setText(_("Remove value")) picard-release-1.4.2/picard/ui/ui_infodialog.py000066400000000000000000000067061310410472100214410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_InfoDialog(object): def setupUi(self, InfoDialog): InfoDialog.setObjectName(_fromUtf8("InfoDialog")) InfoDialog.resize(535, 436) self.verticalLayout = QtGui.QVBoxLayout(InfoDialog) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.tabWidget = QtGui.QTabWidget(InfoDialog) self.tabWidget.setObjectName(_fromUtf8("tabWidget")) self.info_tab = QtGui.QWidget() self.info_tab.setObjectName(_fromUtf8("info_tab")) self.vboxlayout = QtGui.QVBoxLayout(self.info_tab) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.info_scroll = QtGui.QScrollArea(self.info_tab) self.info_scroll.setWidgetResizable(True) self.info_scroll.setObjectName(_fromUtf8("info_scroll")) self.scrollAreaWidgetContents = QtGui.QWidget() self.scrollAreaWidgetContents.setEnabled(True) self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 493, 334)) self.scrollAreaWidgetContents.setObjectName(_fromUtf8("scrollAreaWidgetContents")) self.verticalLayoutLabel = QtGui.QVBoxLayout(self.scrollAreaWidgetContents) self.verticalLayoutLabel.setObjectName(_fromUtf8("verticalLayoutLabel")) self.info = QtGui.QLabel(self.scrollAreaWidgetContents) self.info.setText(_fromUtf8("")) self.info.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.info.setWordWrap(True) self.info.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByKeyboard|QtCore.Qt.TextSelectableByMouse) self.info.setObjectName(_fromUtf8("info")) self.verticalLayoutLabel.addWidget(self.info) self.info_scroll.setWidget(self.scrollAreaWidgetContents) self.vboxlayout.addWidget(self.info_scroll) self.tabWidget.addTab(self.info_tab, _fromUtf8("")) self.artwork_tab = QtGui.QWidget() self.artwork_tab.setObjectName(_fromUtf8("artwork_tab")) self.vboxlayout1 = QtGui.QVBoxLayout(self.artwork_tab) self.vboxlayout1.setObjectName(_fromUtf8("vboxlayout1")) self.tabWidget.addTab(self.artwork_tab, _fromUtf8("")) self.verticalLayout.addWidget(self.tabWidget) self.buttonBox = QtGui.QDialogButtonBox(InfoDialog) self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) self.buttonBox.setObjectName(_fromUtf8("buttonBox")) self.verticalLayout.addWidget(self.buttonBox) self.retranslateUi(InfoDialog) self.tabWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(InfoDialog) InfoDialog.setTabOrder(self.tabWidget, self.buttonBox) def retranslateUi(self, InfoDialog): self.tabWidget.setTabText(self.tabWidget.indexOf(self.info_tab), _("&Info")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.artwork_tab), _("A&rtwork")) picard-release-1.4.2/picard/ui/ui_infostatus.py000066400000000000000000000112221310410472100215120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_InfoStatus(object): def setupUi(self, InfoStatus): InfoStatus.setObjectName(_fromUtf8("InfoStatus")) InfoStatus.resize(350, 24) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(InfoStatus.sizePolicy().hasHeightForWidth()) InfoStatus.setSizePolicy(sizePolicy) InfoStatus.setMinimumSize(QtCore.QSize(0, 0)) self.horizontalLayout = QtGui.QHBoxLayout(InfoStatus) self.horizontalLayout.setSpacing(2) self.horizontalLayout.setMargin(0) self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.val1 = QtGui.QLabel(InfoStatus) self.val1.setMinimumSize(QtCore.QSize(40, 0)) self.val1.setText(_fromUtf8("")) self.val1.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.val1.setObjectName(_fromUtf8("val1")) self.horizontalLayout.addWidget(self.val1) self.label1 = QtGui.QLabel(InfoStatus) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label1.sizePolicy().hasHeightForWidth()) self.label1.setSizePolicy(sizePolicy) self.label1.setFrameShape(QtGui.QFrame.NoFrame) self.label1.setTextFormat(QtCore.Qt.AutoText) self.label1.setScaledContents(False) self.label1.setMargin(1) self.label1.setObjectName(_fromUtf8("label1")) self.horizontalLayout.addWidget(self.label1) self.val2 = QtGui.QLabel(InfoStatus) self.val2.setMinimumSize(QtCore.QSize(40, 0)) self.val2.setText(_fromUtf8("")) self.val2.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.val2.setObjectName(_fromUtf8("val2")) self.horizontalLayout.addWidget(self.val2) self.label2 = QtGui.QLabel(InfoStatus) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label2.sizePolicy().hasHeightForWidth()) self.label2.setSizePolicy(sizePolicy) self.label2.setText(_fromUtf8("")) self.label2.setObjectName(_fromUtf8("label2")) self.horizontalLayout.addWidget(self.label2) self.val3 = QtGui.QLabel(InfoStatus) self.val3.setMinimumSize(QtCore.QSize(40, 0)) self.val3.setText(_fromUtf8("")) self.val3.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.val3.setObjectName(_fromUtf8("val3")) self.horizontalLayout.addWidget(self.val3) self.label3 = QtGui.QLabel(InfoStatus) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label3.sizePolicy().hasHeightForWidth()) self.label3.setSizePolicy(sizePolicy) self.label3.setText(_fromUtf8("")) self.label3.setObjectName(_fromUtf8("label3")) self.horizontalLayout.addWidget(self.label3) self.val4 = QtGui.QLabel(InfoStatus) self.val4.setMinimumSize(QtCore.QSize(40, 0)) self.val4.setText(_fromUtf8("")) self.val4.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.val4.setObjectName(_fromUtf8("val4")) self.horizontalLayout.addWidget(self.val4) self.label4 = QtGui.QLabel(InfoStatus) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label4.sizePolicy().hasHeightForWidth()) self.label4.setSizePolicy(sizePolicy) self.label4.setText(_fromUtf8("")) self.label4.setScaledContents(False) self.label4.setObjectName(_fromUtf8("label4")) self.horizontalLayout.addWidget(self.label4) self.retranslateUi(InfoStatus) QtCore.QMetaObject.connectSlotsByName(InfoStatus) def retranslateUi(self, InfoStatus): InfoStatus.setWindowTitle(_("Form")) picard-release-1.4.2/picard/ui/ui_options.py000066400000000000000000000026341310410472100210150ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(485, 398) self.vboxlayout = QtGui.QVBoxLayout(Dialog) self.vboxlayout.setMargin(9) self.vboxlayout.setSpacing(6) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.splitter = QtGui.QSplitter(Dialog) self.splitter.setOrientation(QtCore.Qt.Horizontal) self.splitter.setObjectName(_fromUtf8("splitter")) self.pages_tree = QtGui.QTreeWidget(self.splitter) self.pages_tree.setObjectName(_fromUtf8("pages_tree")) self.pages_stack = QtGui.QStackedWidget(self.splitter) self.pages_stack.setObjectName(_fromUtf8("pages_stack")) self.vboxlayout.addWidget(self.splitter) self.buttonbox = QtGui.QDialogButtonBox(Dialog) self.buttonbox.setOrientation(QtCore.Qt.Horizontal) self.buttonbox.setObjectName(_fromUtf8("buttonbox")) self.vboxlayout.addWidget(self.buttonbox) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(_("Options")) picard-release-1.4.2/picard/ui/ui_options_about.py000066400000000000000000000042511310410472100222040ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_AboutOptionsPage(object): def setupUi(self, AboutOptionsPage): AboutOptionsPage.setObjectName(_fromUtf8("AboutOptionsPage")) AboutOptionsPage.resize(171, 137) self.vboxlayout = QtGui.QVBoxLayout(AboutOptionsPage) self.vboxlayout.setSpacing(6) self.vboxlayout.setMargin(0) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.scrollArea = QtGui.QScrollArea(AboutOptionsPage) self.scrollArea.setFrameShape(QtGui.QFrame.NoFrame) self.scrollArea.setFrameShadow(QtGui.QFrame.Plain) self.scrollArea.setLineWidth(0) self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.scrollArea.setWidgetResizable(True) self.scrollArea.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.scrollArea.setObjectName(_fromUtf8("scrollArea")) self.scrollAreaWidgetContents = QtGui.QWidget() self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 171, 137)) self.scrollAreaWidgetContents.setObjectName(_fromUtf8("scrollAreaWidgetContents")) self.verticalLayout = QtGui.QVBoxLayout(self.scrollAreaWidgetContents) self.verticalLayout.setSpacing(6) self.verticalLayout.setMargin(9) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.label = QtGui.QLabel(self.scrollAreaWidgetContents) self.label.setText(_fromUtf8("")) self.label.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop) self.label.setWordWrap(True) self.label.setObjectName(_fromUtf8("label")) self.verticalLayout.addWidget(self.label) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.vboxlayout.addWidget(self.scrollArea) self.retranslateUi(AboutOptionsPage) QtCore.QMetaObject.connectSlotsByName(AboutOptionsPage) def retranslateUi(self, AboutOptionsPage): pass picard-release-1.4.2/picard/ui/ui_options_advanced.py000066400000000000000000000112641310410472100226410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_AdvancedOptionsPage(object): def setupUi(self, AdvancedOptionsPage): AdvancedOptionsPage.setObjectName(_fromUtf8("AdvancedOptionsPage")) AdvancedOptionsPage.resize(570, 435) self.vboxlayout = QtGui.QVBoxLayout(AdvancedOptionsPage) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.groupBox = QtGui.QGroupBox(AdvancedOptionsPage) self.groupBox.setObjectName(_fromUtf8("groupBox")) self.gridlayout = QtGui.QGridLayout(self.groupBox) self.gridlayout.setSpacing(2) self.gridlayout.setObjectName(_fromUtf8("gridlayout")) self.regex_error = QtGui.QLabel(self.groupBox) self.regex_error.setText(_fromUtf8("")) self.regex_error.setObjectName(_fromUtf8("regex_error")) self.gridlayout.addWidget(self.regex_error, 3, 0, 1, 1) self.label_ignore_regex = QtGui.QLabel(self.groupBox) self.label_ignore_regex.setObjectName(_fromUtf8("label_ignore_regex")) self.gridlayout.addWidget(self.label_ignore_regex, 1, 0, 1, 1) self.ignore_hidden_files = QtGui.QCheckBox(self.groupBox) self.ignore_hidden_files.setObjectName(_fromUtf8("ignore_hidden_files")) self.gridlayout.addWidget(self.ignore_hidden_files, 4, 0, 1, 1) self.ignore_regex = QtGui.QLineEdit(self.groupBox) self.ignore_regex.setObjectName(_fromUtf8("ignore_regex")) self.gridlayout.addWidget(self.ignore_regex, 2, 0, 1, 1) self.recursively_add_files = QtGui.QCheckBox(self.groupBox) self.recursively_add_files.setObjectName(_fromUtf8("recursively_add_files")) self.gridlayout.addWidget(self.recursively_add_files, 5, 0, 1, 1) self.vboxlayout.addWidget(self.groupBox) self.groupBox_completeness = QtGui.QGroupBox(AdvancedOptionsPage) self.groupBox_completeness.setObjectName(_fromUtf8("groupBox_completeness")) self.verticalLayout_2 = QtGui.QVBoxLayout(self.groupBox_completeness) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.completeness_ignore_videos = QtGui.QCheckBox(self.groupBox_completeness) self.completeness_ignore_videos.setObjectName(_fromUtf8("completeness_ignore_videos")) self.verticalLayout_2.addWidget(self.completeness_ignore_videos) self.completeness_ignore_pregap = QtGui.QCheckBox(self.groupBox_completeness) self.completeness_ignore_pregap.setObjectName(_fromUtf8("completeness_ignore_pregap")) self.verticalLayout_2.addWidget(self.completeness_ignore_pregap) self.completeness_ignore_data = QtGui.QCheckBox(self.groupBox_completeness) self.completeness_ignore_data.setCheckable(True) self.completeness_ignore_data.setObjectName(_fromUtf8("completeness_ignore_data")) self.verticalLayout_2.addWidget(self.completeness_ignore_data) self.completeness_ignore_silence = QtGui.QCheckBox(self.groupBox_completeness) self.completeness_ignore_silence.setObjectName(_fromUtf8("completeness_ignore_silence")) self.verticalLayout_2.addWidget(self.completeness_ignore_silence) self.vboxlayout.addWidget(self.groupBox_completeness) spacerItem = QtGui.QSpacerItem(181, 21, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.vboxlayout.addItem(spacerItem) self.retranslateUi(AdvancedOptionsPage) QtCore.QMetaObject.connectSlotsByName(AdvancedOptionsPage) def retranslateUi(self, AdvancedOptionsPage): self.groupBox.setTitle(_("Advanced options")) self.label_ignore_regex.setText(_("Ignore file paths matching the following regular expression:")) self.ignore_hidden_files.setText(_("Ignore hidden files")) self.recursively_add_files.setText(_("Recursively add files and folders from directory")) self.groupBox_completeness.setTitle(_("Ignore the following tracks when determining whether a release is complete")) self.completeness_ignore_videos.setText(_("Video tracks")) self.completeness_ignore_pregap.setText(_("Pregap tracks")) self.completeness_ignore_data.setText(_("Data tracks")) self.completeness_ignore_silence.setText(_("Silent tracks")) picard-release-1.4.2/picard/ui/ui_options_cdlookup.py000066400000000000000000000035631310410472100227170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_CDLookupOptionsPage(object): def setupUi(self, CDLookupOptionsPage): CDLookupOptionsPage.setObjectName(_fromUtf8("CDLookupOptionsPage")) CDLookupOptionsPage.resize(224, 176) self.vboxlayout = QtGui.QVBoxLayout(CDLookupOptionsPage) self.vboxlayout.setMargin(9) self.vboxlayout.setSpacing(6) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.rename_files = QtGui.QGroupBox(CDLookupOptionsPage) self.rename_files.setObjectName(_fromUtf8("rename_files")) self.gridlayout = QtGui.QGridLayout(self.rename_files) self.gridlayout.setMargin(9) self.gridlayout.setSpacing(2) self.gridlayout.setObjectName(_fromUtf8("gridlayout")) self.cd_lookup_device = QtGui.QLineEdit(self.rename_files) self.cd_lookup_device.setObjectName(_fromUtf8("cd_lookup_device")) self.gridlayout.addWidget(self.cd_lookup_device, 1, 0, 1, 1) self.label_3 = QtGui.QLabel(self.rename_files) self.label_3.setObjectName(_fromUtf8("label_3")) self.gridlayout.addWidget(self.label_3, 0, 0, 1, 1) self.vboxlayout.addWidget(self.rename_files) spacerItem = QtGui.QSpacerItem(161, 81, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.vboxlayout.addItem(spacerItem) self.label_3.setBuddy(self.cd_lookup_device) self.retranslateUi(CDLookupOptionsPage) QtCore.QMetaObject.connectSlotsByName(CDLookupOptionsPage) def retranslateUi(self, CDLookupOptionsPage): self.rename_files.setTitle(_("CD Lookup")) self.label_3.setText(_("CD-ROM device to use for lookups:")) picard-release-1.4.2/picard/ui/ui_options_cdlookup_select.py000066400000000000000000000044141310410472100242520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_CDLookupOptionsPage(object): def setupUi(self, CDLookupOptionsPage): CDLookupOptionsPage.setObjectName(_fromUtf8("CDLookupOptionsPage")) CDLookupOptionsPage.resize(255, 155) self.vboxlayout = QtGui.QVBoxLayout(CDLookupOptionsPage) self.vboxlayout.setMargin(9) self.vboxlayout.setSpacing(6) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.rename_files = QtGui.QGroupBox(CDLookupOptionsPage) self.rename_files.setObjectName(_fromUtf8("rename_files")) self.gridlayout = QtGui.QGridLayout(self.rename_files) self.gridlayout.setMargin(9) self.gridlayout.setSpacing(2) self.gridlayout.setObjectName(_fromUtf8("gridlayout")) self.cd_lookup_ = QtGui.QLabel(self.rename_files) self.cd_lookup_.setObjectName(_fromUtf8("cd_lookup_")) self.gridlayout.addWidget(self.cd_lookup_, 0, 0, 1, 1) self.hboxlayout = QtGui.QHBoxLayout() self.hboxlayout.setMargin(0) self.hboxlayout.setSpacing(6) self.hboxlayout.setObjectName(_fromUtf8("hboxlayout")) self.cd_lookup_device = QtGui.QComboBox(self.rename_files) self.cd_lookup_device.setObjectName(_fromUtf8("cd_lookup_device")) self.hboxlayout.addWidget(self.cd_lookup_device) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.hboxlayout.addItem(spacerItem) self.gridlayout.addLayout(self.hboxlayout, 1, 0, 1, 1) self.vboxlayout.addWidget(self.rename_files) spacerItem1 = QtGui.QSpacerItem(161, 81, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.vboxlayout.addItem(spacerItem1) self.cd_lookup_.setBuddy(self.cd_lookup_device) self.retranslateUi(CDLookupOptionsPage) QtCore.QMetaObject.connectSlotsByName(CDLookupOptionsPage) def retranslateUi(self, CDLookupOptionsPage): self.rename_files.setTitle(_("CD Lookup")) self.cd_lookup_.setText(_("Default CD-ROM drive to use for lookups:")) picard-release-1.4.2/picard/ui/ui_options_cover.py000066400000000000000000000112601310410472100222060ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_CoverOptionsPage(object): def setupUi(self, CoverOptionsPage): CoverOptionsPage.setObjectName(_fromUtf8("CoverOptionsPage")) CoverOptionsPage.resize(632, 560) self.verticalLayout = QtGui.QVBoxLayout(CoverOptionsPage) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.location = QtGui.QGroupBox(CoverOptionsPage) self.location.setObjectName(_fromUtf8("location")) self.vboxlayout = QtGui.QVBoxLayout(self.location) self.vboxlayout.setSpacing(2) self.vboxlayout.setMargin(9) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.save_images_to_tags = QtGui.QCheckBox(self.location) self.save_images_to_tags.setObjectName(_fromUtf8("save_images_to_tags")) self.vboxlayout.addWidget(self.save_images_to_tags) self.cb_embed_front_only = QtGui.QCheckBox(self.location) self.cb_embed_front_only.setObjectName(_fromUtf8("cb_embed_front_only")) self.vboxlayout.addWidget(self.cb_embed_front_only) self.save_images_to_files = QtGui.QCheckBox(self.location) self.save_images_to_files.setObjectName(_fromUtf8("save_images_to_files")) self.vboxlayout.addWidget(self.save_images_to_files) self.label_use_filename = QtGui.QLabel(self.location) self.label_use_filename.setObjectName(_fromUtf8("label_use_filename")) self.vboxlayout.addWidget(self.label_use_filename) self.cover_image_filename = QtGui.QLineEdit(self.location) self.cover_image_filename.setObjectName(_fromUtf8("cover_image_filename")) self.vboxlayout.addWidget(self.cover_image_filename) self.save_images_overwrite = QtGui.QCheckBox(self.location) self.save_images_overwrite.setObjectName(_fromUtf8("save_images_overwrite")) self.vboxlayout.addWidget(self.save_images_overwrite) self.verticalLayout.addWidget(self.location) self.ca_providers_groupbox = QtGui.QGroupBox(CoverOptionsPage) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.ca_providers_groupbox.sizePolicy().hasHeightForWidth()) self.ca_providers_groupbox.setSizePolicy(sizePolicy) self.ca_providers_groupbox.setObjectName(_fromUtf8("ca_providers_groupbox")) self.ca_providers_layout = QtGui.QVBoxLayout(self.ca_providers_groupbox) self.ca_providers_layout.setObjectName(_fromUtf8("ca_providers_layout")) self.ca_providers_list = QtGui.QHBoxLayout() self.ca_providers_list.setObjectName(_fromUtf8("ca_providers_list")) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.ca_providers_list.addItem(spacerItem) self.ca_providers_layout.addLayout(self.ca_providers_list) self.verticalLayout.addWidget(self.ca_providers_groupbox) spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem1) self.retranslateUi(CoverOptionsPage) QtCore.QMetaObject.connectSlotsByName(CoverOptionsPage) CoverOptionsPage.setTabOrder(self.save_images_to_tags, self.cb_embed_front_only) CoverOptionsPage.setTabOrder(self.cb_embed_front_only, self.save_images_to_files) CoverOptionsPage.setTabOrder(self.save_images_to_files, self.cover_image_filename) CoverOptionsPage.setTabOrder(self.cover_image_filename, self.save_images_overwrite) def retranslateUi(self, CoverOptionsPage): self.location.setTitle(_("Location")) self.save_images_to_tags.setText(_("Embed cover images into tags")) self.cb_embed_front_only.setText(_("Only embed a front image")) self.save_images_to_files.setText(_("Save cover images as separate files")) self.label_use_filename.setText(_("Use the following file name for images:")) self.save_images_overwrite.setText(_("Overwrite the file if it already exists")) self.ca_providers_groupbox.setTitle(_("Cover Art Providers")) picard-release-1.4.2/picard/ui/ui_options_fingerprinting.py000066400000000000000000000126521310410472100241230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_FingerprintingOptionsPage(object): def setupUi(self, FingerprintingOptionsPage): FingerprintingOptionsPage.setObjectName(_fromUtf8("FingerprintingOptionsPage")) FingerprintingOptionsPage.resize(371, 408) self.verticalLayout = QtGui.QVBoxLayout(FingerprintingOptionsPage) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.fingerprinting = QtGui.QGroupBox(FingerprintingOptionsPage) self.fingerprinting.setCheckable(False) self.fingerprinting.setObjectName(_fromUtf8("fingerprinting")) self.verticalLayout_3 = QtGui.QVBoxLayout(self.fingerprinting) self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) self.disable_fingerprinting = QtGui.QRadioButton(self.fingerprinting) self.disable_fingerprinting.setObjectName(_fromUtf8("disable_fingerprinting")) self.verticalLayout_3.addWidget(self.disable_fingerprinting) self.use_acoustid = QtGui.QRadioButton(self.fingerprinting) self.use_acoustid.setObjectName(_fromUtf8("use_acoustid")) self.verticalLayout_3.addWidget(self.use_acoustid) self.verticalLayout.addWidget(self.fingerprinting) self.acoustid_settings = QtGui.QGroupBox(FingerprintingOptionsPage) self.acoustid_settings.setObjectName(_fromUtf8("acoustid_settings")) self.verticalLayout_2 = QtGui.QVBoxLayout(self.acoustid_settings) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.ignore_existing_acoustid_fingerprints = QtGui.QCheckBox(self.acoustid_settings) self.ignore_existing_acoustid_fingerprints.setObjectName(_fromUtf8("ignore_existing_acoustid_fingerprints")) self.verticalLayout_2.addWidget(self.ignore_existing_acoustid_fingerprints) self.label = QtGui.QLabel(self.acoustid_settings) self.label.setObjectName(_fromUtf8("label")) self.verticalLayout_2.addWidget(self.label) self.horizontalLayout_2 = QtGui.QHBoxLayout() self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) self.acoustid_fpcalc = QtGui.QLineEdit(self.acoustid_settings) self.acoustid_fpcalc.setObjectName(_fromUtf8("acoustid_fpcalc")) self.horizontalLayout_2.addWidget(self.acoustid_fpcalc) self.acoustid_fpcalc_browse = QtGui.QPushButton(self.acoustid_settings) self.acoustid_fpcalc_browse.setObjectName(_fromUtf8("acoustid_fpcalc_browse")) self.horizontalLayout_2.addWidget(self.acoustid_fpcalc_browse) self.acoustid_fpcalc_download = QtGui.QPushButton(self.acoustid_settings) self.acoustid_fpcalc_download.setObjectName(_fromUtf8("acoustid_fpcalc_download")) self.horizontalLayout_2.addWidget(self.acoustid_fpcalc_download) self.verticalLayout_2.addLayout(self.horizontalLayout_2) self.acoustid_fpcalc_info = QtGui.QLabel(self.acoustid_settings) self.acoustid_fpcalc_info.setText(_fromUtf8("")) self.acoustid_fpcalc_info.setObjectName(_fromUtf8("acoustid_fpcalc_info")) self.verticalLayout_2.addWidget(self.acoustid_fpcalc_info) self.label_2 = QtGui.QLabel(self.acoustid_settings) self.label_2.setObjectName(_fromUtf8("label_2")) self.verticalLayout_2.addWidget(self.label_2) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.acoustid_apikey = QtGui.QLineEdit(self.acoustid_settings) self.acoustid_apikey.setObjectName(_fromUtf8("acoustid_apikey")) self.horizontalLayout.addWidget(self.acoustid_apikey) self.acoustid_apikey_get = QtGui.QPushButton(self.acoustid_settings) self.acoustid_apikey_get.setObjectName(_fromUtf8("acoustid_apikey_get")) self.horizontalLayout.addWidget(self.acoustid_apikey_get) self.verticalLayout_2.addLayout(self.horizontalLayout) self.verticalLayout.addWidget(self.acoustid_settings) spacerItem = QtGui.QSpacerItem(181, 21, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem) self.retranslateUi(FingerprintingOptionsPage) QtCore.QMetaObject.connectSlotsByName(FingerprintingOptionsPage) def retranslateUi(self, FingerprintingOptionsPage): self.fingerprinting.setTitle(_("Audio Fingerprinting")) self.disable_fingerprinting.setText(_("Do not use audio fingerprinting")) self.use_acoustid.setText(_("Use AcoustID")) self.acoustid_settings.setTitle(_("AcoustID Settings")) self.ignore_existing_acoustid_fingerprints.setText(_("Ignore existing AcoustID fingerprints")) self.label.setText(_("Fingerprint calculator:")) self.acoustid_fpcalc_browse.setText(_("Browse...")) self.acoustid_fpcalc_download.setText(_("Download...")) self.label_2.setText(_("API key:")) self.acoustid_apikey_get.setText(_("Get API key...")) picard-release-1.4.2/picard/ui/ui_options_folksonomy.py000066400000000000000000000144731310410472100233010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_FolksonomyOptionsPage(object): def setupUi(self, FolksonomyOptionsPage): FolksonomyOptionsPage.setObjectName(_fromUtf8("FolksonomyOptionsPage")) FolksonomyOptionsPage.resize(590, 304) self.verticalLayout_2 = QtGui.QVBoxLayout(FolksonomyOptionsPage) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.rename_files_3 = QtGui.QGroupBox(FolksonomyOptionsPage) self.rename_files_3.setObjectName(_fromUtf8("rename_files_3")) self.verticalLayout = QtGui.QVBoxLayout(self.rename_files_3) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.ignore_tags_2 = QtGui.QLabel(self.rename_files_3) self.ignore_tags_2.setObjectName(_fromUtf8("ignore_tags_2")) self.verticalLayout.addWidget(self.ignore_tags_2) self.ignore_tags = QtGui.QLineEdit(self.rename_files_3) self.ignore_tags.setObjectName(_fromUtf8("ignore_tags")) self.verticalLayout.addWidget(self.ignore_tags) self.only_my_tags = QtGui.QCheckBox(self.rename_files_3) self.only_my_tags.setObjectName(_fromUtf8("only_my_tags")) self.verticalLayout.addWidget(self.only_my_tags) self.artists_tags = QtGui.QCheckBox(self.rename_files_3) self.artists_tags.setEnabled(True) self.artists_tags.setObjectName(_fromUtf8("artists_tags")) self.verticalLayout.addWidget(self.artists_tags) self.hboxlayout = QtGui.QHBoxLayout() self.hboxlayout.setSpacing(6) self.hboxlayout.setMargin(0) self.hboxlayout.setObjectName(_fromUtf8("hboxlayout")) self.label_5 = QtGui.QLabel(self.rename_files_3) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth()) self.label_5.setSizePolicy(sizePolicy) self.label_5.setObjectName(_fromUtf8("label_5")) self.hboxlayout.addWidget(self.label_5) self.min_tag_usage = QtGui.QSpinBox(self.rename_files_3) self.min_tag_usage.setMaximum(100) self.min_tag_usage.setObjectName(_fromUtf8("min_tag_usage")) self.hboxlayout.addWidget(self.min_tag_usage) self.verticalLayout.addLayout(self.hboxlayout) self.hboxlayout1 = QtGui.QHBoxLayout() self.hboxlayout1.setSpacing(6) self.hboxlayout1.setMargin(0) self.hboxlayout1.setObjectName(_fromUtf8("hboxlayout1")) self.label_6 = QtGui.QLabel(self.rename_files_3) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_6.sizePolicy().hasHeightForWidth()) self.label_6.setSizePolicy(sizePolicy) self.label_6.setObjectName(_fromUtf8("label_6")) self.hboxlayout1.addWidget(self.label_6) self.max_tags = QtGui.QSpinBox(self.rename_files_3) self.max_tags.setMaximum(100) self.max_tags.setObjectName(_fromUtf8("max_tags")) self.hboxlayout1.addWidget(self.max_tags) self.verticalLayout.addLayout(self.hboxlayout1) self.hboxlayout2 = QtGui.QHBoxLayout() self.hboxlayout2.setSpacing(6) self.hboxlayout2.setMargin(0) self.hboxlayout2.setObjectName(_fromUtf8("hboxlayout2")) self.ignore_tags_4 = QtGui.QLabel(self.rename_files_3) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(4) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.ignore_tags_4.sizePolicy().hasHeightForWidth()) self.ignore_tags_4.setSizePolicy(sizePolicy) self.ignore_tags_4.setObjectName(_fromUtf8("ignore_tags_4")) self.hboxlayout2.addWidget(self.ignore_tags_4) self.join_tags = QtGui.QComboBox(self.rename_files_3) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.join_tags.sizePolicy().hasHeightForWidth()) self.join_tags.setSizePolicy(sizePolicy) self.join_tags.setEditable(True) self.join_tags.setObjectName(_fromUtf8("join_tags")) self.join_tags.addItem(_fromUtf8("")) self.join_tags.setItemText(0, _fromUtf8("")) self.join_tags.addItem(_fromUtf8("")) self.join_tags.addItem(_fromUtf8("")) self.hboxlayout2.addWidget(self.join_tags) self.verticalLayout.addLayout(self.hboxlayout2) self.verticalLayout_2.addWidget(self.rename_files_3) spacerItem = QtGui.QSpacerItem(181, 31, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout_2.addItem(spacerItem) self.label_5.setBuddy(self.min_tag_usage) self.label_6.setBuddy(self.min_tag_usage) self.retranslateUi(FolksonomyOptionsPage) QtCore.QMetaObject.connectSlotsByName(FolksonomyOptionsPage) def retranslateUi(self, FolksonomyOptionsPage): self.rename_files_3.setTitle(_("Folksonomy Tags")) self.ignore_tags_2.setText(_("Ignore tags:")) self.only_my_tags.setText(_("Only use my tags")) self.artists_tags.setText(_("Fall back on album\'s artists tags if no tags are found for the release or release group")) self.label_5.setText(_("Minimal tag usage:")) self.min_tag_usage.setSuffix(_(" %")) self.label_6.setText(_("Maximum number of tags:")) self.ignore_tags_4.setText(_("Join multiple tags with:")) self.join_tags.setItemText(1, _(" / ")) self.join_tags.setItemText(2, _(", ")) picard-release-1.4.2/picard/ui/ui_options_general.py000066400000000000000000000120021310410472100225000ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_GeneralOptionsPage(object): def setupUi(self, GeneralOptionsPage): GeneralOptionsPage.setObjectName(_fromUtf8("GeneralOptionsPage")) GeneralOptionsPage.resize(283, 435) self.vboxlayout = QtGui.QVBoxLayout(GeneralOptionsPage) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.groupBox = QtGui.QGroupBox(GeneralOptionsPage) self.groupBox.setObjectName(_fromUtf8("groupBox")) self.gridlayout = QtGui.QGridLayout(self.groupBox) self.gridlayout.setSpacing(2) self.gridlayout.setObjectName(_fromUtf8("gridlayout")) self.server_host = QtGui.QComboBox(self.groupBox) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.server_host.sizePolicy().hasHeightForWidth()) self.server_host.setSizePolicy(sizePolicy) self.server_host.setEditable(True) self.server_host.setObjectName(_fromUtf8("server_host")) self.gridlayout.addWidget(self.server_host, 1, 0, 1, 1) self.label_7 = QtGui.QLabel(self.groupBox) self.label_7.setObjectName(_fromUtf8("label_7")) self.gridlayout.addWidget(self.label_7, 0, 1, 1, 1) self.server_port = QtGui.QSpinBox(self.groupBox) self.server_port.setMinimum(1) self.server_port.setMaximum(65535) self.server_port.setProperty("value", 80) self.server_port.setObjectName(_fromUtf8("server_port")) self.gridlayout.addWidget(self.server_port, 1, 1, 1, 1) self.label = QtGui.QLabel(self.groupBox) self.label.setObjectName(_fromUtf8("label")) self.gridlayout.addWidget(self.label, 0, 0, 1, 1) self.vboxlayout.addWidget(self.groupBox) self.rename_files_2 = QtGui.QGroupBox(GeneralOptionsPage) self.rename_files_2.setObjectName(_fromUtf8("rename_files_2")) self.gridlayout1 = QtGui.QGridLayout(self.rename_files_2) self.gridlayout1.setSpacing(2) self.gridlayout1.setObjectName(_fromUtf8("gridlayout1")) self.login = QtGui.QPushButton(self.rename_files_2) self.login.setObjectName(_fromUtf8("login")) self.gridlayout1.addWidget(self.login, 1, 0, 1, 1) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.gridlayout1.addItem(spacerItem, 1, 2, 1, 1) self.logout = QtGui.QPushButton(self.rename_files_2) self.logout.setObjectName(_fromUtf8("logout")) self.gridlayout1.addWidget(self.logout, 1, 1, 1, 1) self.logged_in = QtGui.QLabel(self.rename_files_2) self.logged_in.setText(_fromUtf8("")) self.logged_in.setObjectName(_fromUtf8("logged_in")) self.gridlayout1.addWidget(self.logged_in, 0, 0, 1, 3) self.vboxlayout.addWidget(self.rename_files_2) self.groupBox_2 = QtGui.QGroupBox(GeneralOptionsPage) self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) self.verticalLayout = QtGui.QVBoxLayout(self.groupBox_2) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.analyze_new_files = QtGui.QCheckBox(self.groupBox_2) self.analyze_new_files.setObjectName(_fromUtf8("analyze_new_files")) self.verticalLayout.addWidget(self.analyze_new_files) self.ignore_file_mbids = QtGui.QCheckBox(self.groupBox_2) self.ignore_file_mbids.setObjectName(_fromUtf8("ignore_file_mbids")) self.verticalLayout.addWidget(self.ignore_file_mbids) self.vboxlayout.addWidget(self.groupBox_2) spacerItem1 = QtGui.QSpacerItem(181, 21, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.vboxlayout.addItem(spacerItem1) self.retranslateUi(GeneralOptionsPage) QtCore.QMetaObject.connectSlotsByName(GeneralOptionsPage) GeneralOptionsPage.setTabOrder(self.server_host, self.server_port) def retranslateUi(self, GeneralOptionsPage): self.groupBox.setTitle(_("MusicBrainz Server")) self.label_7.setText(_("Port:")) self.label.setText(_("Server address:")) self.rename_files_2.setTitle(_("MusicBrainz Account")) self.login.setText(_("Log in")) self.logout.setText(_("Log out")) self.groupBox_2.setTitle(_("General")) self.analyze_new_files.setText(_("Automatically scan all new files")) self.ignore_file_mbids.setText(_("Ignore MBIDs when loading new files")) picard-release-1.4.2/picard/ui/ui_options_interface.py000066400000000000000000000203231310410472100230300ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_InterfaceOptionsPage(object): def setupUi(self, InterfaceOptionsPage): InterfaceOptionsPage.setObjectName(_fromUtf8("InterfaceOptionsPage")) InterfaceOptionsPage.resize(466, 500) self.vboxlayout = QtGui.QVBoxLayout(InterfaceOptionsPage) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.groupBox_2 = QtGui.QGroupBox(InterfaceOptionsPage) self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) self.vboxlayout1 = QtGui.QVBoxLayout(self.groupBox_2) self.vboxlayout1.setObjectName(_fromUtf8("vboxlayout1")) self.toolbar_show_labels = QtGui.QCheckBox(self.groupBox_2) self.toolbar_show_labels.setObjectName(_fromUtf8("toolbar_show_labels")) self.vboxlayout1.addWidget(self.toolbar_show_labels) self.toolbar_multiselect = QtGui.QCheckBox(self.groupBox_2) self.toolbar_multiselect.setObjectName(_fromUtf8("toolbar_multiselect")) self.vboxlayout1.addWidget(self.toolbar_multiselect) self.builtin_search = QtGui.QCheckBox(self.groupBox_2) self.builtin_search.setObjectName(_fromUtf8("builtin_search")) self.vboxlayout1.addWidget(self.builtin_search) self.use_adv_search_syntax = QtGui.QCheckBox(self.groupBox_2) self.use_adv_search_syntax.setObjectName(_fromUtf8("use_adv_search_syntax")) self.vboxlayout1.addWidget(self.use_adv_search_syntax) self.quit_confirmation = QtGui.QCheckBox(self.groupBox_2) self.quit_confirmation.setObjectName(_fromUtf8("quit_confirmation")) self.vboxlayout1.addWidget(self.quit_confirmation) self.starting_directory = QtGui.QCheckBox(self.groupBox_2) self.starting_directory.setObjectName(_fromUtf8("starting_directory")) self.vboxlayout1.addWidget(self.starting_directory) self.horizontalLayout_4 = QtGui.QHBoxLayout() self.horizontalLayout_4.setSpacing(2) self.horizontalLayout_4.setObjectName(_fromUtf8("horizontalLayout_4")) self.starting_directory_path = QtGui.QLineEdit(self.groupBox_2) self.starting_directory_path.setEnabled(False) self.starting_directory_path.setObjectName(_fromUtf8("starting_directory_path")) self.horizontalLayout_4.addWidget(self.starting_directory_path) self.starting_directory_browse = QtGui.QPushButton(self.groupBox_2) self.starting_directory_browse.setEnabled(False) self.starting_directory_browse.setObjectName(_fromUtf8("starting_directory_browse")) self.horizontalLayout_4.addWidget(self.starting_directory_browse) self.vboxlayout1.addLayout(self.horizontalLayout_4) self.label = QtGui.QLabel(self.groupBox_2) self.label.setObjectName(_fromUtf8("label")) self.vboxlayout1.addWidget(self.label) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.ui_language = QtGui.QComboBox(self.groupBox_2) self.ui_language.setObjectName(_fromUtf8("ui_language")) self.horizontalLayout.addWidget(self.ui_language) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.vboxlayout1.addLayout(self.horizontalLayout) self.vboxlayout.addWidget(self.groupBox_2) self.customize_toolbar_box = QtGui.QGroupBox(InterfaceOptionsPage) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.customize_toolbar_box.sizePolicy().hasHeightForWidth()) self.customize_toolbar_box.setSizePolicy(sizePolicy) self.customize_toolbar_box.setObjectName(_fromUtf8("customize_toolbar_box")) self.verticalLayout = QtGui.QVBoxLayout(self.customize_toolbar_box) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.toolbar_layout_list = QtGui.QListWidget(self.customize_toolbar_box) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.toolbar_layout_list.sizePolicy().hasHeightForWidth()) self.toolbar_layout_list.setSizePolicy(sizePolicy) self.toolbar_layout_list.setObjectName(_fromUtf8("toolbar_layout_list")) self.verticalLayout.addWidget(self.toolbar_layout_list) self.edit_button_box = QtGui.QWidget(self.customize_toolbar_box) self.edit_button_box.setObjectName(_fromUtf8("edit_button_box")) self.edit_box_layout = QtGui.QHBoxLayout(self.edit_button_box) self.edit_box_layout.setMargin(0) self.edit_box_layout.setObjectName(_fromUtf8("edit_box_layout")) self.add_button = QtGui.QPushButton(self.edit_button_box) self.add_button.setObjectName(_fromUtf8("add_button")) self.edit_box_layout.addWidget(self.add_button) self.insert_separator_button = QtGui.QPushButton(self.edit_button_box) self.insert_separator_button.setObjectName(_fromUtf8("insert_separator_button")) self.edit_box_layout.addWidget(self.insert_separator_button) spacerItem1 = QtGui.QSpacerItem(50, 20, QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Minimum) self.edit_box_layout.addItem(spacerItem1) self.up_button = QtGui.QToolButton(self.edit_button_box) self.up_button.setArrowType(QtCore.Qt.UpArrow) self.up_button.setObjectName(_fromUtf8("up_button")) self.edit_box_layout.addWidget(self.up_button) self.down_button = QtGui.QToolButton(self.edit_button_box) self.down_button.setArrowType(QtCore.Qt.DownArrow) self.down_button.setObjectName(_fromUtf8("down_button")) self.edit_box_layout.addWidget(self.down_button) self.remove_button = QtGui.QPushButton(self.edit_button_box) self.remove_button.setObjectName(_fromUtf8("remove_button")) self.edit_box_layout.addWidget(self.remove_button) self.verticalLayout.addWidget(self.edit_button_box) self.vboxlayout.addWidget(self.customize_toolbar_box) self.retranslateUi(InterfaceOptionsPage) QtCore.QMetaObject.connectSlotsByName(InterfaceOptionsPage) def retranslateUi(self, InterfaceOptionsPage): self.groupBox_2.setTitle(_("Miscellaneous")) self.toolbar_show_labels.setText(_("Show text labels under icons")) self.toolbar_multiselect.setText(_("Allow selection of multiple directories")) self.builtin_search.setText(_("Use builtin search rather than looking in browser")) self.use_adv_search_syntax.setText(_("Use advanced query syntax")) self.quit_confirmation.setText(_("Show a quit confirmation dialog for unsaved changes")) self.starting_directory.setText(_("Begin browsing in the following directory:")) self.starting_directory_browse.setText(_("Browse...")) self.label.setText(_("User interface language:")) self.customize_toolbar_box.setTitle(_("Customize Action Toolbar")) self.add_button.setToolTip(_("Add a new button to Toolbar")) self.add_button.setText(_("Add Action")) self.insert_separator_button.setToolTip(_("Insert a separator")) self.insert_separator_button.setText(_("Add Separator")) self.up_button.setToolTip(_("Move selected item up")) self.up_button.setText(_("...")) self.down_button.setToolTip(_("Move selected item down")) self.down_button.setText(_("...")) self.remove_button.setToolTip(_("Remove button from toolbar")) self.remove_button.setText(_("Remove")) picard-release-1.4.2/picard/ui/ui_options_matching.py000066400000000000000000000104451310410472100226660ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_MatchingOptionsPage(object): def setupUi(self, MatchingOptionsPage): MatchingOptionsPage.setObjectName(_fromUtf8("MatchingOptionsPage")) MatchingOptionsPage.resize(413, 612) self.vboxlayout = QtGui.QVBoxLayout(MatchingOptionsPage) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.rename_files = QtGui.QGroupBox(MatchingOptionsPage) self.rename_files.setObjectName(_fromUtf8("rename_files")) self.gridlayout = QtGui.QGridLayout(self.rename_files) self.gridlayout.setSpacing(2) self.gridlayout.setObjectName(_fromUtf8("gridlayout")) self.label_6 = QtGui.QLabel(self.rename_files) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_6.sizePolicy().hasHeightForWidth()) self.label_6.setSizePolicy(sizePolicy) self.label_6.setObjectName(_fromUtf8("label_6")) self.gridlayout.addWidget(self.label_6, 2, 0, 1, 1) self.track_matching_threshold = QtGui.QSpinBox(self.rename_files) self.track_matching_threshold.setMaximum(100) self.track_matching_threshold.setObjectName(_fromUtf8("track_matching_threshold")) self.gridlayout.addWidget(self.track_matching_threshold, 2, 1, 1, 1) self.cluster_lookup_threshold = QtGui.QSpinBox(self.rename_files) self.cluster_lookup_threshold.setMaximum(100) self.cluster_lookup_threshold.setObjectName(_fromUtf8("cluster_lookup_threshold")) self.gridlayout.addWidget(self.cluster_lookup_threshold, 1, 1, 1, 1) self.file_lookup_threshold = QtGui.QSpinBox(self.rename_files) self.file_lookup_threshold.setMaximum(100) self.file_lookup_threshold.setObjectName(_fromUtf8("file_lookup_threshold")) self.gridlayout.addWidget(self.file_lookup_threshold, 0, 1, 1, 1) self.label_4 = QtGui.QLabel(self.rename_files) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) self.label_4.setSizePolicy(sizePolicy) self.label_4.setObjectName(_fromUtf8("label_4")) self.gridlayout.addWidget(self.label_4, 0, 0, 1, 1) self.label_5 = QtGui.QLabel(self.rename_files) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth()) self.label_5.setSizePolicy(sizePolicy) self.label_5.setObjectName(_fromUtf8("label_5")) self.gridlayout.addWidget(self.label_5, 1, 0, 1, 1) self.vboxlayout.addWidget(self.rename_files) spacerItem = QtGui.QSpacerItem(20, 41, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.vboxlayout.addItem(spacerItem) self.label_6.setBuddy(self.file_lookup_threshold) self.label_4.setBuddy(self.file_lookup_threshold) self.label_5.setBuddy(self.file_lookup_threshold) self.retranslateUi(MatchingOptionsPage) QtCore.QMetaObject.connectSlotsByName(MatchingOptionsPage) MatchingOptionsPage.setTabOrder(self.file_lookup_threshold, self.cluster_lookup_threshold) MatchingOptionsPage.setTabOrder(self.cluster_lookup_threshold, self.track_matching_threshold) def retranslateUi(self, MatchingOptionsPage): self.rename_files.setTitle(_("Thresholds")) self.label_6.setText(_("Minimal similarity for matching files to tracks:")) self.track_matching_threshold.setSuffix(_(" %")) self.cluster_lookup_threshold.setSuffix(_(" %")) self.file_lookup_threshold.setSuffix(_(" %")) self.label_4.setText(_("Minimal similarity for file lookups:")) self.label_5.setText(_("Minimal similarity for cluster lookups:")) picard-release-1.4.2/picard/ui/ui_options_metadata.py000066400000000000000000000154401310410472100226540ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_MetadataOptionsPage(object): def setupUi(self, MetadataOptionsPage): MetadataOptionsPage.setObjectName(_fromUtf8("MetadataOptionsPage")) MetadataOptionsPage.resize(423, 553) self.verticalLayout = QtGui.QVBoxLayout(MetadataOptionsPage) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.metadata_groupbox = QtGui.QGroupBox(MetadataOptionsPage) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.metadata_groupbox.sizePolicy().hasHeightForWidth()) self.metadata_groupbox.setSizePolicy(sizePolicy) self.metadata_groupbox.setMinimumSize(QtCore.QSize(397, 135)) self.metadata_groupbox.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.metadata_groupbox.setObjectName(_fromUtf8("metadata_groupbox")) self.verticalLayout_3 = QtGui.QVBoxLayout(self.metadata_groupbox) self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) self.translate_artist_names = QtGui.QCheckBox(self.metadata_groupbox) self.translate_artist_names.setObjectName(_fromUtf8("translate_artist_names")) self.verticalLayout_3.addWidget(self.translate_artist_names) self.artist_locale = QtGui.QComboBox(self.metadata_groupbox) self.artist_locale.setObjectName(_fromUtf8("artist_locale")) self.verticalLayout_3.addWidget(self.artist_locale) self.standardize_artists = QtGui.QCheckBox(self.metadata_groupbox) self.standardize_artists.setObjectName(_fromUtf8("standardize_artists")) self.verticalLayout_3.addWidget(self.standardize_artists) self.convert_punctuation = QtGui.QCheckBox(self.metadata_groupbox) self.convert_punctuation.setObjectName(_fromUtf8("convert_punctuation")) self.verticalLayout_3.addWidget(self.convert_punctuation) self.release_ars = QtGui.QCheckBox(self.metadata_groupbox) self.release_ars.setObjectName(_fromUtf8("release_ars")) self.verticalLayout_3.addWidget(self.release_ars) self.track_ars = QtGui.QCheckBox(self.metadata_groupbox) self.track_ars.setObjectName(_fromUtf8("track_ars")) self.verticalLayout_3.addWidget(self.track_ars) self.folksonomy_tags = QtGui.QCheckBox(self.metadata_groupbox) self.folksonomy_tags.setObjectName(_fromUtf8("folksonomy_tags")) self.verticalLayout_3.addWidget(self.folksonomy_tags) self.verticalLayout.addWidget(self.metadata_groupbox) self.custom_fields_groupbox = QtGui.QGroupBox(MetadataOptionsPage) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.custom_fields_groupbox.sizePolicy().hasHeightForWidth()) self.custom_fields_groupbox.setSizePolicy(sizePolicy) self.custom_fields_groupbox.setMinimumSize(QtCore.QSize(397, 0)) self.custom_fields_groupbox.setObjectName(_fromUtf8("custom_fields_groupbox")) self.gridlayout = QtGui.QGridLayout(self.custom_fields_groupbox) self.gridlayout.setSpacing(2) self.gridlayout.setObjectName(_fromUtf8("gridlayout")) self.label_6 = QtGui.QLabel(self.custom_fields_groupbox) self.label_6.setObjectName(_fromUtf8("label_6")) self.gridlayout.addWidget(self.label_6, 0, 0, 1, 2) self.label_7 = QtGui.QLabel(self.custom_fields_groupbox) self.label_7.setObjectName(_fromUtf8("label_7")) self.gridlayout.addWidget(self.label_7, 2, 0, 1, 2) self.nat_name = QtGui.QLineEdit(self.custom_fields_groupbox) self.nat_name.setObjectName(_fromUtf8("nat_name")) self.gridlayout.addWidget(self.nat_name, 3, 0, 1, 1) self.nat_name_default = QtGui.QPushButton(self.custom_fields_groupbox) self.nat_name_default.setObjectName(_fromUtf8("nat_name_default")) self.gridlayout.addWidget(self.nat_name_default, 3, 1, 1, 1) self.va_name_default = QtGui.QPushButton(self.custom_fields_groupbox) self.va_name_default.setObjectName(_fromUtf8("va_name_default")) self.gridlayout.addWidget(self.va_name_default, 1, 1, 1, 1) self.va_name = QtGui.QLineEdit(self.custom_fields_groupbox) self.va_name.setObjectName(_fromUtf8("va_name")) self.gridlayout.addWidget(self.va_name, 1, 0, 1, 1) self.verticalLayout.addWidget(self.custom_fields_groupbox) spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.MinimumExpanding) self.verticalLayout.addItem(spacerItem) self.label_6.setBuddy(self.va_name_default) self.label_7.setBuddy(self.nat_name_default) self.retranslateUi(MetadataOptionsPage) QtCore.QMetaObject.connectSlotsByName(MetadataOptionsPage) MetadataOptionsPage.setTabOrder(self.translate_artist_names, self.artist_locale) MetadataOptionsPage.setTabOrder(self.artist_locale, self.standardize_artists) MetadataOptionsPage.setTabOrder(self.standardize_artists, self.convert_punctuation) MetadataOptionsPage.setTabOrder(self.convert_punctuation, self.release_ars) MetadataOptionsPage.setTabOrder(self.release_ars, self.track_ars) MetadataOptionsPage.setTabOrder(self.track_ars, self.folksonomy_tags) MetadataOptionsPage.setTabOrder(self.folksonomy_tags, self.va_name) MetadataOptionsPage.setTabOrder(self.va_name, self.va_name_default) MetadataOptionsPage.setTabOrder(self.va_name_default, self.nat_name) MetadataOptionsPage.setTabOrder(self.nat_name, self.nat_name_default) def retranslateUi(self, MetadataOptionsPage): self.metadata_groupbox.setTitle(_("Metadata")) self.translate_artist_names.setText(_("Translate artist names to this locale where possible:")) self.standardize_artists.setText(_("Use standardized artist names")) self.convert_punctuation.setText(_("Convert Unicode punctuation characters to ASCII")) self.release_ars.setText(_("Use release relationships")) self.track_ars.setText(_("Use track relationships")) self.folksonomy_tags.setText(_("Use folksonomy tags as genre")) self.custom_fields_groupbox.setTitle(_("Custom Fields")) self.label_6.setText(_("Various artists:")) self.label_7.setText(_("Non-album tracks:")) self.nat_name_default.setText(_("Default")) self.va_name_default.setText(_("Default")) picard-release-1.4.2/picard/ui/ui_options_network.py000066400000000000000000000141131310410472100225610ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_NetworkOptionsPage(object): def setupUi(self, NetworkOptionsPage): NetworkOptionsPage.setObjectName(_fromUtf8("NetworkOptionsPage")) NetworkOptionsPage.resize(316, 337) self.vboxlayout = QtGui.QVBoxLayout(NetworkOptionsPage) self.vboxlayout.setSpacing(6) self.vboxlayout.setMargin(9) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.web_proxy = QtGui.QGroupBox(NetworkOptionsPage) self.web_proxy.setCheckable(True) self.web_proxy.setObjectName(_fromUtf8("web_proxy")) self.gridlayout = QtGui.QGridLayout(self.web_proxy) self.gridlayout.setSpacing(2) self.gridlayout.setMargin(9) self.gridlayout.setObjectName(_fromUtf8("gridlayout")) self.password = QtGui.QLineEdit(self.web_proxy) self.password.setEchoMode(QtGui.QLineEdit.Password) self.password.setObjectName(_fromUtf8("password")) self.gridlayout.addWidget(self.password, 5, 0, 1, 2) self.username = QtGui.QLineEdit(self.web_proxy) self.username.setObjectName(_fromUtf8("username")) self.gridlayout.addWidget(self.username, 3, 0, 1, 2) self.label_5 = QtGui.QLabel(self.web_proxy) self.label_5.setObjectName(_fromUtf8("label_5")) self.gridlayout.addWidget(self.label_5, 4, 0, 1, 2) self.label_6 = QtGui.QLabel(self.web_proxy) self.label_6.setObjectName(_fromUtf8("label_6")) self.gridlayout.addWidget(self.label_6, 2, 0, 1, 2) self.server_host = QtGui.QLineEdit(self.web_proxy) self.server_host.setObjectName(_fromUtf8("server_host")) self.gridlayout.addWidget(self.server_host, 1, 0, 1, 1) self.label_7 = QtGui.QLabel(self.web_proxy) self.label_7.setObjectName(_fromUtf8("label_7")) self.gridlayout.addWidget(self.label_7, 0, 1, 1, 1) self.server_port = QtGui.QSpinBox(self.web_proxy) self.server_port.setMinimum(1) self.server_port.setMaximum(65535) self.server_port.setProperty("value", 80) self.server_port.setObjectName(_fromUtf8("server_port")) self.gridlayout.addWidget(self.server_port, 1, 1, 1, 1) self.label = QtGui.QLabel(self.web_proxy) self.label.setObjectName(_fromUtf8("label")) self.gridlayout.addWidget(self.label, 0, 0, 1, 1) self.vboxlayout.addWidget(self.web_proxy) self.browser_integration = QtGui.QGroupBox(NetworkOptionsPage) self.browser_integration.setCheckable(True) self.browser_integration.setChecked(True) self.browser_integration.setObjectName(_fromUtf8("browser_integration")) self.verticalLayout_2 = QtGui.QVBoxLayout(self.browser_integration) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.widget = QtGui.QWidget(self.browser_integration) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) self.widget.setSizePolicy(sizePolicy) self.widget.setObjectName(_fromUtf8("widget")) self.horizontalLayout = QtGui.QHBoxLayout(self.widget) self.horizontalLayout.setContentsMargins(6, 0, 0, 0) self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.label_2 = QtGui.QLabel(self.widget) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) self.label_2.setSizePolicy(sizePolicy) self.label_2.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_2.setObjectName(_fromUtf8("label_2")) self.horizontalLayout.addWidget(self.label_2) self.browser_integration_port = QtGui.QSpinBox(self.widget) self.browser_integration_port.setMinimum(1) self.browser_integration_port.setMaximum(65535) self.browser_integration_port.setProperty("value", 8000) self.browser_integration_port.setObjectName(_fromUtf8("browser_integration_port")) self.horizontalLayout.addWidget(self.browser_integration_port) self.verticalLayout_2.addWidget(self.widget) self.browser_integration_localhost_only = QtGui.QCheckBox(self.browser_integration) self.browser_integration_localhost_only.setChecked(False) self.browser_integration_localhost_only.setObjectName(_fromUtf8("browser_integration_localhost_only")) self.verticalLayout_2.addWidget(self.browser_integration_localhost_only) self.vboxlayout.addWidget(self.browser_integration) spacerItem = QtGui.QSpacerItem(101, 31, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.vboxlayout.addItem(spacerItem) self.label_5.setBuddy(self.password) self.label_6.setBuddy(self.username) self.label.setBuddy(self.server_host) self.retranslateUi(NetworkOptionsPage) QtCore.QMetaObject.connectSlotsByName(NetworkOptionsPage) NetworkOptionsPage.setTabOrder(self.server_host, self.server_port) NetworkOptionsPage.setTabOrder(self.server_port, self.username) NetworkOptionsPage.setTabOrder(self.username, self.password) def retranslateUi(self, NetworkOptionsPage): self.web_proxy.setTitle(_("Web Proxy")) self.label_5.setText(_("Password:")) self.label_6.setText(_("Username:")) self.label_7.setText(_("Port:")) self.label.setText(_("Server address:")) self.browser_integration.setTitle(_("Browser Integration")) self.label_2.setText(_("Default listening port:")) self.browser_integration_localhost_only.setText(_("Listen only on localhost")) picard-release-1.4.2/picard/ui/ui_options_plugins.py000066400000000000000000000175561310410472100225670ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_PluginsOptionsPage(object): def setupUi(self, PluginsOptionsPage): PluginsOptionsPage.setObjectName(_fromUtf8("PluginsOptionsPage")) PluginsOptionsPage.resize(513, 312) self.vboxlayout = QtGui.QVBoxLayout(PluginsOptionsPage) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.plugins_container = QtGui.QSplitter(PluginsOptionsPage) self.plugins_container.setEnabled(True) self.plugins_container.setOrientation(QtCore.Qt.Vertical) self.plugins_container.setHandleWidth(2) self.plugins_container.setObjectName(_fromUtf8("plugins_container")) self.groupBox_2 = QtGui.QGroupBox(self.plugins_container) self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) self.vboxlayout1 = QtGui.QVBoxLayout(self.groupBox_2) self.vboxlayout1.setSpacing(2) self.vboxlayout1.setObjectName(_fromUtf8("vboxlayout1")) self.plugins = QtGui.QTreeWidget(self.groupBox_2) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.plugins.sizePolicy().hasHeightForWidth()) self.plugins.setSizePolicy(sizePolicy) self.plugins.setAcceptDrops(True) self.plugins.setDragDropMode(QtGui.QAbstractItemView.DropOnly) self.plugins.setRootIsDecorated(False) self.plugins.setObjectName(_fromUtf8("plugins")) self.vboxlayout1.addWidget(self.plugins) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.install_plugin = QtGui.QPushButton(self.groupBox_2) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.install_plugin.sizePolicy().hasHeightForWidth()) self.install_plugin.setSizePolicy(sizePolicy) self.install_plugin.setObjectName(_fromUtf8("install_plugin")) self.horizontalLayout.addWidget(self.install_plugin) self.folder_open = QtGui.QPushButton(self.groupBox_2) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.folder_open.sizePolicy().hasHeightForWidth()) self.folder_open.setSizePolicy(sizePolicy) self.folder_open.setObjectName(_fromUtf8("folder_open")) self.horizontalLayout.addWidget(self.folder_open) self.reload_list_of_plugins = QtGui.QPushButton(self.groupBox_2) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.reload_list_of_plugins.sizePolicy().hasHeightForWidth()) self.reload_list_of_plugins.setSizePolicy(sizePolicy) self.reload_list_of_plugins.setObjectName(_fromUtf8("reload_list_of_plugins")) self.horizontalLayout.addWidget(self.reload_list_of_plugins) self.vboxlayout1.addLayout(self.horizontalLayout) self.groupBox = QtGui.QGroupBox(self.plugins_container) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) self.groupBox.setSizePolicy(sizePolicy) self.groupBox.setObjectName(_fromUtf8("groupBox")) self.vboxlayout2 = QtGui.QVBoxLayout(self.groupBox) self.vboxlayout2.setSpacing(0) self.vboxlayout2.setObjectName(_fromUtf8("vboxlayout2")) self.scrollArea = QtGui.QScrollArea(self.groupBox) self.scrollArea.setEnabled(True) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth()) self.scrollArea.setSizePolicy(sizePolicy) self.scrollArea.setFrameShape(QtGui.QFrame.HLine) self.scrollArea.setFrameShadow(QtGui.QFrame.Plain) self.scrollArea.setLineWidth(0) self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self.scrollArea.setWidgetResizable(True) self.scrollArea.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.scrollArea.setObjectName(_fromUtf8("scrollArea")) self.scrollAreaWidgetContents = QtGui.QWidget() self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 469, 76)) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.scrollAreaWidgetContents.sizePolicy().hasHeightForWidth()) self.scrollAreaWidgetContents.setSizePolicy(sizePolicy) self.scrollAreaWidgetContents.setObjectName(_fromUtf8("scrollAreaWidgetContents")) self.verticalLayout = QtGui.QVBoxLayout(self.scrollAreaWidgetContents) self.verticalLayout.setSizeConstraint(QtGui.QLayout.SetNoConstraint) self.verticalLayout.setContentsMargins(0, 0, 6, 0) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.details = QtGui.QLabel(self.scrollAreaWidgetContents) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.details.sizePolicy().hasHeightForWidth()) self.details.setSizePolicy(sizePolicy) self.details.setMinimumSize(QtCore.QSize(0, 0)) self.details.setFrameShape(QtGui.QFrame.Box) self.details.setLineWidth(0) self.details.setText(_fromUtf8("")) self.details.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.details.setWordWrap(True) self.details.setIndent(0) self.details.setOpenExternalLinks(True) self.details.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.details.setObjectName(_fromUtf8("details")) self.verticalLayout.addWidget(self.details) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.vboxlayout2.addWidget(self.scrollArea) self.vboxlayout.addWidget(self.plugins_container) self.retranslateUi(PluginsOptionsPage) QtCore.QMetaObject.connectSlotsByName(PluginsOptionsPage) def retranslateUi(self, PluginsOptionsPage): self.groupBox_2.setTitle(_("Plugins")) self.plugins.headerItem().setText(0, _("Name")) self.plugins.headerItem().setText(1, _("Version")) self.plugins.headerItem().setText(2, _("Status")) self.install_plugin.setText(_("Install plugin...")) self.folder_open.setText(_("Open plugin folder")) self.reload_list_of_plugins.setText(_("Reload List of Plugins")) self.groupBox.setTitle(_("Details")) picard-release-1.4.2/picard/ui/ui_options_ratings.py000066400000000000000000000051551310410472100225450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_RatingsOptionsPage(object): def setupUi(self, RatingsOptionsPage): RatingsOptionsPage.setObjectName(_fromUtf8("RatingsOptionsPage")) RatingsOptionsPage.resize(397, 267) self.vboxlayout = QtGui.QVBoxLayout(RatingsOptionsPage) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.enable_ratings = QtGui.QGroupBox(RatingsOptionsPage) self.enable_ratings.setCheckable(True) self.enable_ratings.setChecked(True) self.enable_ratings.setObjectName(_fromUtf8("enable_ratings")) self.vboxlayout1 = QtGui.QVBoxLayout(self.enable_ratings) self.vboxlayout1.setObjectName(_fromUtf8("vboxlayout1")) self.label = QtGui.QLabel(self.enable_ratings) self.label.setWordWrap(True) self.label.setObjectName(_fromUtf8("label")) self.vboxlayout1.addWidget(self.label) self.ignore_tags_2 = QtGui.QLabel(self.enable_ratings) self.ignore_tags_2.setEnabled(True) self.ignore_tags_2.setWordWrap(True) self.ignore_tags_2.setObjectName(_fromUtf8("ignore_tags_2")) self.vboxlayout1.addWidget(self.ignore_tags_2) self.rating_user_email = QtGui.QLineEdit(self.enable_ratings) self.rating_user_email.setReadOnly(False) self.rating_user_email.setObjectName(_fromUtf8("rating_user_email")) self.vboxlayout1.addWidget(self.rating_user_email) self.submit_ratings = QtGui.QCheckBox(self.enable_ratings) self.submit_ratings.setObjectName(_fromUtf8("submit_ratings")) self.vboxlayout1.addWidget(self.submit_ratings) self.vboxlayout.addWidget(self.enable_ratings) spacerItem = QtGui.QSpacerItem(181, 31, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.vboxlayout.addItem(spacerItem) self.retranslateUi(RatingsOptionsPage) QtCore.QMetaObject.connectSlotsByName(RatingsOptionsPage) def retranslateUi(self, RatingsOptionsPage): self.enable_ratings.setTitle(_("Enable track ratings")) self.label.setText(_("Picard saves the ratings together with an e-mail address identifying the user who did the rating. That way different ratings for different users can be stored in the files. Please specify the e-mail you want to use to save your ratings.")) self.ignore_tags_2.setText(_("E-mail:")) self.submit_ratings.setText(_("Submit ratings to MusicBrainz")) picard-release-1.4.2/picard/ui/ui_options_releases.py000066400000000000000000000145321310410472100227000ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_ReleasesOptionsPage(object): def setupUi(self, ReleasesOptionsPage): ReleasesOptionsPage.setObjectName(_fromUtf8("ReleasesOptionsPage")) ReleasesOptionsPage.resize(551, 497) self.verticalLayout_3 = QtGui.QVBoxLayout(ReleasesOptionsPage) self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) self.type_group = QtGui.QGroupBox(ReleasesOptionsPage) self.type_group.setObjectName(_fromUtf8("type_group")) self.gridLayout = QtGui.QGridLayout(self.type_group) self.gridLayout.setVerticalSpacing(6) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.verticalLayout_3.addWidget(self.type_group) self.country_group = QtGui.QGroupBox(ReleasesOptionsPage) self.country_group.setObjectName(_fromUtf8("country_group")) self.horizontalLayout = QtGui.QHBoxLayout(self.country_group) self.horizontalLayout.setSpacing(4) self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.country_list = QtGui.QListWidget(self.country_group) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.country_list.sizePolicy().hasHeightForWidth()) self.country_list.setSizePolicy(sizePolicy) self.country_list.setObjectName(_fromUtf8("country_list")) self.horizontalLayout.addWidget(self.country_list) self.verticalLayout = QtGui.QVBoxLayout() self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem) self.add_countries = QtGui.QPushButton(self.country_group) self.add_countries.setObjectName(_fromUtf8("add_countries")) self.verticalLayout.addWidget(self.add_countries) self.remove_countries = QtGui.QPushButton(self.country_group) self.remove_countries.setObjectName(_fromUtf8("remove_countries")) self.verticalLayout.addWidget(self.remove_countries) spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem1) self.horizontalLayout.addLayout(self.verticalLayout) self.preferred_country_list = QtGui.QListWidget(self.country_group) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.preferred_country_list.sizePolicy().hasHeightForWidth()) self.preferred_country_list.setSizePolicy(sizePolicy) self.preferred_country_list.setDragEnabled(True) self.preferred_country_list.setDragDropMode(QtGui.QAbstractItemView.InternalMove) self.preferred_country_list.setObjectName(_fromUtf8("preferred_country_list")) self.horizontalLayout.addWidget(self.preferred_country_list) self.verticalLayout_3.addWidget(self.country_group) self.format_group = QtGui.QGroupBox(ReleasesOptionsPage) self.format_group.setObjectName(_fromUtf8("format_group")) self.horizontalLayout_2 = QtGui.QHBoxLayout(self.format_group) self.horizontalLayout_2.setSpacing(4) self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) self.format_list = QtGui.QListWidget(self.format_group) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.format_list.sizePolicy().hasHeightForWidth()) self.format_list.setSizePolicy(sizePolicy) self.format_list.setObjectName(_fromUtf8("format_list")) self.horizontalLayout_2.addWidget(self.format_list) self.verticalLayout_2 = QtGui.QVBoxLayout() self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout_2.addItem(spacerItem2) self.add_formats = QtGui.QPushButton(self.format_group) self.add_formats.setObjectName(_fromUtf8("add_formats")) self.verticalLayout_2.addWidget(self.add_formats) self.remove_formats = QtGui.QPushButton(self.format_group) self.remove_formats.setObjectName(_fromUtf8("remove_formats")) self.verticalLayout_2.addWidget(self.remove_formats) spacerItem3 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout_2.addItem(spacerItem3) self.horizontalLayout_2.addLayout(self.verticalLayout_2) self.preferred_format_list = QtGui.QListWidget(self.format_group) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.preferred_format_list.sizePolicy().hasHeightForWidth()) self.preferred_format_list.setSizePolicy(sizePolicy) self.preferred_format_list.setDragEnabled(True) self.preferred_format_list.setDragDropMode(QtGui.QAbstractItemView.InternalMove) self.preferred_format_list.setObjectName(_fromUtf8("preferred_format_list")) self.horizontalLayout_2.addWidget(self.preferred_format_list) self.verticalLayout_3.addWidget(self.format_group) self.retranslateUi(ReleasesOptionsPage) QtCore.QMetaObject.connectSlotsByName(ReleasesOptionsPage) def retranslateUi(self, ReleasesOptionsPage): self.type_group.setTitle(_("Preferred release types")) self.country_group.setTitle(_("Preferred release countries")) self.add_countries.setText(_(">")) self.remove_countries.setText(_("<")) self.format_group.setTitle(_("Preferred release formats")) self.add_formats.setText(_(">")) self.remove_formats.setText(_("<")) picard-release-1.4.2/picard/ui/ui_options_renaming.py000066400000000000000000000253201310410472100226720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_RenamingOptionsPage(object): def setupUi(self, RenamingOptionsPage): RenamingOptionsPage.setObjectName(_fromUtf8("RenamingOptionsPage")) RenamingOptionsPage.setEnabled(True) RenamingOptionsPage.resize(453, 465) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(RenamingOptionsPage.sizePolicy().hasHeightForWidth()) RenamingOptionsPage.setSizePolicy(sizePolicy) self.verticalLayout_5 = QtGui.QVBoxLayout(RenamingOptionsPage) self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5")) self.move_files = QtGui.QGroupBox(RenamingOptionsPage) self.move_files.setFlat(False) self.move_files.setCheckable(True) self.move_files.setChecked(False) self.move_files.setObjectName(_fromUtf8("move_files")) self.verticalLayout_4 = QtGui.QVBoxLayout(self.move_files) self.verticalLayout_4.setObjectName(_fromUtf8("verticalLayout_4")) self.label = QtGui.QLabel(self.move_files) self.label.setObjectName(_fromUtf8("label")) self.verticalLayout_4.addWidget(self.label) self.horizontalLayout_4 = QtGui.QHBoxLayout() self.horizontalLayout_4.setSpacing(2) self.horizontalLayout_4.setObjectName(_fromUtf8("horizontalLayout_4")) self.move_files_to = QtGui.QLineEdit(self.move_files) self.move_files_to.setEnabled(False) self.move_files_to.setObjectName(_fromUtf8("move_files_to")) self.horizontalLayout_4.addWidget(self.move_files_to) self.move_files_to_browse = QtGui.QPushButton(self.move_files) self.move_files_to_browse.setEnabled(False) self.move_files_to_browse.setObjectName(_fromUtf8("move_files_to_browse")) self.horizontalLayout_4.addWidget(self.move_files_to_browse) self.verticalLayout_4.addLayout(self.horizontalLayout_4) self.move_additional_files = QtGui.QCheckBox(self.move_files) self.move_additional_files.setEnabled(False) self.move_additional_files.setObjectName(_fromUtf8("move_additional_files")) self.verticalLayout_4.addWidget(self.move_additional_files) self.move_additional_files_pattern = QtGui.QLineEdit(self.move_files) self.move_additional_files_pattern.setEnabled(False) self.move_additional_files_pattern.setObjectName(_fromUtf8("move_additional_files_pattern")) self.verticalLayout_4.addWidget(self.move_additional_files_pattern) self.delete_empty_dirs = QtGui.QCheckBox(self.move_files) self.delete_empty_dirs.setEnabled(False) self.delete_empty_dirs.setObjectName(_fromUtf8("delete_empty_dirs")) self.verticalLayout_4.addWidget(self.delete_empty_dirs) self.verticalLayout_5.addWidget(self.move_files) self.rename_files = QtGui.QGroupBox(RenamingOptionsPage) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.rename_files.sizePolicy().hasHeightForWidth()) self.rename_files.setSizePolicy(sizePolicy) self.rename_files.setCheckable(True) self.rename_files.setChecked(False) self.rename_files.setObjectName(_fromUtf8("rename_files")) self.verticalLayout_3 = QtGui.QVBoxLayout(self.rename_files) self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) self.ascii_filenames = QtGui.QCheckBox(self.rename_files) self.ascii_filenames.setObjectName(_fromUtf8("ascii_filenames")) self.verticalLayout_3.addWidget(self.ascii_filenames) self.windows_compatibility = QtGui.QCheckBox(self.rename_files) self.windows_compatibility.setObjectName(_fromUtf8("windows_compatibility")) self.verticalLayout_3.addWidget(self.windows_compatibility) self.file_naming_format_group = QtGui.QGroupBox(self.rename_files) self.file_naming_format_group.setObjectName(_fromUtf8("file_naming_format_group")) self.verticalLayout_2 = QtGui.QVBoxLayout(self.file_naming_format_group) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.file_naming_format = QtGui.QTextEdit(self.file_naming_format_group) self.file_naming_format.setEnabled(False) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.file_naming_format.sizePolicy().hasHeightForWidth()) self.file_naming_format.setSizePolicy(sizePolicy) self.file_naming_format.setMinimumSize(QtCore.QSize(0, 0)) font = QtGui.QFont() font.setFamily(_fromUtf8("Monospace")) self.file_naming_format.setFont(font) self.file_naming_format.viewport().setProperty("cursor", QtGui.QCursor(QtCore.Qt.IBeamCursor)) self.file_naming_format.setTabChangesFocus(False) self.file_naming_format.setLineWrapMode(QtGui.QTextEdit.NoWrap) self.file_naming_format.setTabStopWidth(20) self.file_naming_format.setAcceptRichText(True) self.file_naming_format.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) self.file_naming_format.setObjectName(_fromUtf8("file_naming_format")) self.verticalLayout_2.addWidget(self.file_naming_format) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setSpacing(2) self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.renaming_error = QtGui.QLabel(self.file_naming_format_group) self.renaming_error.setText(_fromUtf8("")) self.renaming_error.setAlignment(QtCore.Qt.AlignCenter) self.renaming_error.setObjectName(_fromUtf8("renaming_error")) self.horizontalLayout.addWidget(self.renaming_error) self.file_naming_format_default = QtGui.QPushButton(self.file_naming_format_group) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.file_naming_format_default.sizePolicy().hasHeightForWidth()) self.file_naming_format_default.setSizePolicy(sizePolicy) self.file_naming_format_default.setMinimumSize(QtCore.QSize(0, 0)) self.file_naming_format_default.setObjectName(_fromUtf8("file_naming_format_default")) self.horizontalLayout.addWidget(self.file_naming_format_default) self.verticalLayout_2.addLayout(self.horizontalLayout) self.verticalLayout_3.addWidget(self.file_naming_format_group) self.file_naming_format_documentation = QtGui.QLabel(self.rename_files) self.file_naming_format_documentation.setText(_fromUtf8("")) self.file_naming_format_documentation.setOpenExternalLinks(True) self.file_naming_format_documentation.setTextFormat(QtCore.Qt.RichText) self.file_naming_format_documentation.setWordWrap(True) self.file_naming_format_documentation.setObjectName(_fromUtf8("file_naming_format_documentation")) self.verticalLayout_3.addWidget(self.file_naming_format_documentation) self.verticalLayout_5.addWidget(self.rename_files) self.groupBox = QtGui.QGroupBox(RenamingOptionsPage) self.groupBox.setObjectName(_fromUtf8("groupBox")) self.verticalLayout = QtGui.QVBoxLayout(self.groupBox) self.verticalLayout.setContentsMargins(2, 0, 2, 0) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.example_filename = QtGui.QLabel(self.groupBox) self.example_filename.setText(_fromUtf8("")) self.example_filename.setTextFormat(QtCore.Qt.RichText) self.example_filename.setWordWrap(True) self.example_filename.setObjectName(_fromUtf8("example_filename")) self.verticalLayout.addWidget(self.example_filename) self.example_filename_va = QtGui.QLabel(self.groupBox) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.example_filename_va.sizePolicy().hasHeightForWidth()) self.example_filename_va.setSizePolicy(sizePolicy) self.example_filename_va.setText(_fromUtf8("")) self.example_filename_va.setWordWrap(True) self.example_filename_va.setObjectName(_fromUtf8("example_filename_va")) self.verticalLayout.addWidget(self.example_filename_va) self.verticalLayout_5.addWidget(self.groupBox) self.retranslateUi(RenamingOptionsPage) QtCore.QMetaObject.connectSlotsByName(RenamingOptionsPage) def retranslateUi(self, RenamingOptionsPage): self.move_files.setTitle(_("Move files when saving")) self.label.setText(_("Destination directory:")) self.move_files_to_browse.setText(_("Browse...")) self.move_additional_files.setText(_("Move additional files (case insensitive):")) self.delete_empty_dirs.setText(_("Delete empty directories")) self.rename_files.setTitle(_("Rename files when saving")) self.ascii_filenames.setText(_("Replace non-ASCII characters")) self.windows_compatibility.setText(_("Windows compatibility")) self.file_naming_format_group.setTitle(_("Name files like this")) self.file_naming_format.setHtml(_translate("RenamingOptionsPage", "\n" "\n" "


", None)) self.file_naming_format_default.setText(_("Default")) self.groupBox.setTitle(_("Examples")) picard-release-1.4.2/picard/ui/ui_options_script.py000066400000000000000000000126001310410472100223730ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_ScriptingOptionsPage(object): def setupUi(self, ScriptingOptionsPage): ScriptingOptionsPage.setObjectName(_fromUtf8("ScriptingOptionsPage")) ScriptingOptionsPage.resize(605, 377) self.vboxlayout = QtGui.QVBoxLayout(ScriptingOptionsPage) self.vboxlayout.setMargin(9) self.vboxlayout.setSpacing(6) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.enable_tagger_scripts = QtGui.QGroupBox(ScriptingOptionsPage) self.enable_tagger_scripts.setCheckable(True) self.enable_tagger_scripts.setObjectName(_fromUtf8("enable_tagger_scripts")) self.verticalLayout = QtGui.QVBoxLayout(self.enable_tagger_scripts) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.groupBox = QtGui.QGroupBox(self.enable_tagger_scripts) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) self.groupBox.setSizePolicy(sizePolicy) self.groupBox.setTitle(_fromUtf8("")) self.groupBox.setObjectName(_fromUtf8("groupBox")) self.verticalLayout_3 = QtGui.QVBoxLayout(self.groupBox) self.verticalLayout_3.setMargin(0) self.verticalLayout_3.setSpacing(6) self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.add_script = QtGui.QPushButton(self.groupBox) self.add_script.setObjectName(_fromUtf8("add_script")) self.horizontalLayout.addWidget(self.add_script) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.verticalLayout_3.addLayout(self.horizontalLayout) self.splitter = QtGui.QSplitter(self.groupBox) self.splitter.setOrientation(QtCore.Qt.Horizontal) self.splitter.setChildrenCollapsible(False) self.splitter.setObjectName(_fromUtf8("splitter")) self.script_list = QtGui.QListWidget(self.splitter) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.script_list.sizePolicy().hasHeightForWidth()) self.script_list.setSizePolicy(sizePolicy) self.script_list.setObjectName(_fromUtf8("script_list")) self.formWidget = QtGui.QWidget(self.splitter) self.formWidget.setObjectName(_fromUtf8("formWidget")) self.verticalLayout_2 = QtGui.QVBoxLayout(self.formWidget) self.verticalLayout_2.setMargin(0) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.script_name = QtGui.QLineEdit(self.formWidget) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.script_name.sizePolicy().hasHeightForWidth()) self.script_name.setSizePolicy(sizePolicy) self.script_name.setObjectName(_fromUtf8("script_name")) self.verticalLayout_2.addWidget(self.script_name) self.tagger_script = QtGui.QTextEdit(self.formWidget) self.tagger_script.setObjectName(_fromUtf8("tagger_script")) self.verticalLayout_2.addWidget(self.tagger_script) self.verticalLayout_3.addWidget(self.splitter) self.verticalLayout.addWidget(self.groupBox) self.script_error = QtGui.QLabel(self.enable_tagger_scripts) self.script_error.setText(_fromUtf8("")) self.script_error.setAlignment(QtCore.Qt.AlignCenter) self.script_error.setObjectName(_fromUtf8("script_error")) self.verticalLayout.addWidget(self.script_error) self.scripting_doc_link = QtGui.QLabel(self.enable_tagger_scripts) self.scripting_doc_link.setText(_fromUtf8("")) self.scripting_doc_link.setWordWrap(True) self.scripting_doc_link.setOpenExternalLinks(True) self.scripting_doc_link.setObjectName(_fromUtf8("scripting_doc_link")) self.verticalLayout.addWidget(self.scripting_doc_link) self.vboxlayout.addWidget(self.enable_tagger_scripts) self.retranslateUi(ScriptingOptionsPage) QtCore.QMetaObject.connectSlotsByName(ScriptingOptionsPage) def retranslateUi(self, ScriptingOptionsPage): self.enable_tagger_scripts.setTitle(_("Tagger Script(s)")) self.add_script.setToolTip(_("Add new script")) self.add_script.setText(_("Add new script")) self.script_name.setPlaceholderText(_("Display Name")) picard-release-1.4.2/picard/ui/ui_options_tags.py000066400000000000000000000245151310410472100220350ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_TagsOptionsPage(object): def setupUi(self, TagsOptionsPage): TagsOptionsPage.setObjectName(_fromUtf8("TagsOptionsPage")) TagsOptionsPage.resize(539, 425) self.vboxlayout = QtGui.QVBoxLayout(TagsOptionsPage) self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) self.write_tags = QtGui.QCheckBox(TagsOptionsPage) self.write_tags.setObjectName(_fromUtf8("write_tags")) self.vboxlayout.addWidget(self.write_tags) self.preserve_timestamps = QtGui.QCheckBox(TagsOptionsPage) self.preserve_timestamps.setObjectName(_fromUtf8("preserve_timestamps")) self.vboxlayout.addWidget(self.preserve_timestamps) self.before_tagging = QtGui.QGroupBox(TagsOptionsPage) self.before_tagging.setObjectName(_fromUtf8("before_tagging")) self.vboxlayout1 = QtGui.QVBoxLayout(self.before_tagging) self.vboxlayout1.setSpacing(2) self.vboxlayout1.setContentsMargins(-1, 6, -1, 7) self.vboxlayout1.setObjectName(_fromUtf8("vboxlayout1")) self.clear_existing_tags = QtGui.QCheckBox(self.before_tagging) self.clear_existing_tags.setObjectName(_fromUtf8("clear_existing_tags")) self.vboxlayout1.addWidget(self.clear_existing_tags) self.remove_id3_from_flac = QtGui.QCheckBox(self.before_tagging) self.remove_id3_from_flac.setObjectName(_fromUtf8("remove_id3_from_flac")) self.vboxlayout1.addWidget(self.remove_id3_from_flac) self.remove_ape_from_mp3 = QtGui.QCheckBox(self.before_tagging) self.remove_ape_from_mp3.setObjectName(_fromUtf8("remove_ape_from_mp3")) self.vboxlayout1.addWidget(self.remove_ape_from_mp3) spacerItem = QtGui.QSpacerItem(20, 6, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) self.vboxlayout1.addItem(spacerItem) self.preserved_tags_label = QtGui.QLabel(self.before_tagging) self.preserved_tags_label.setObjectName(_fromUtf8("preserved_tags_label")) self.vboxlayout1.addWidget(self.preserved_tags_label) self.preserved_tags = QtGui.QLineEdit(self.before_tagging) self.preserved_tags.setObjectName(_fromUtf8("preserved_tags")) self.vboxlayout1.addWidget(self.preserved_tags) self.preserved_tags_help = QtGui.QLabel(self.before_tagging) self.preserved_tags_help.setObjectName(_fromUtf8("preserved_tags_help")) self.vboxlayout1.addWidget(self.preserved_tags_help) self.vboxlayout.addWidget(self.before_tagging) self.tag_compatibility = QtGui.QGroupBox(TagsOptionsPage) self.tag_compatibility.setObjectName(_fromUtf8("tag_compatibility")) self.vboxlayout2 = QtGui.QVBoxLayout(self.tag_compatibility) self.vboxlayout2.setSpacing(2) self.vboxlayout2.setContentsMargins(-1, 6, -1, 7) self.vboxlayout2.setObjectName(_fromUtf8("vboxlayout2")) self.id3v2_version = QtGui.QGroupBox(self.tag_compatibility) self.id3v2_version.setFlat(False) self.id3v2_version.setCheckable(False) self.id3v2_version.setObjectName(_fromUtf8("id3v2_version")) self.horizontalLayout = QtGui.QHBoxLayout(self.id3v2_version) self.horizontalLayout.setContentsMargins(-1, 6, -1, 7) self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.write_id3v24 = QtGui.QRadioButton(self.id3v2_version) self.write_id3v24.setChecked(True) self.write_id3v24.setObjectName(_fromUtf8("write_id3v24")) self.horizontalLayout.addWidget(self.write_id3v24) self.write_id3v23 = QtGui.QRadioButton(self.id3v2_version) self.write_id3v23.setChecked(False) self.write_id3v23.setObjectName(_fromUtf8("write_id3v23")) self.horizontalLayout.addWidget(self.write_id3v23) self.label = QtGui.QLabel(self.id3v2_version) self.label.setText(_fromUtf8("")) self.label.setWordWrap(True) self.label.setObjectName(_fromUtf8("label")) self.horizontalLayout.addWidget(self.label) spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) self.vboxlayout2.addWidget(self.id3v2_version) self.id3v2_text_encoding = QtGui.QGroupBox(self.tag_compatibility) self.id3v2_text_encoding.setObjectName(_fromUtf8("id3v2_text_encoding")) self.horizontalLayout_2 = QtGui.QHBoxLayout(self.id3v2_text_encoding) self.horizontalLayout_2.setContentsMargins(-1, 6, -1, 7) self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) self.enc_utf8 = QtGui.QRadioButton(self.id3v2_text_encoding) self.enc_utf8.setObjectName(_fromUtf8("enc_utf8")) self.horizontalLayout_2.addWidget(self.enc_utf8) self.enc_utf16 = QtGui.QRadioButton(self.id3v2_text_encoding) self.enc_utf16.setObjectName(_fromUtf8("enc_utf16")) self.horizontalLayout_2.addWidget(self.enc_utf16) self.enc_iso88591 = QtGui.QRadioButton(self.id3v2_text_encoding) self.enc_iso88591.setObjectName(_fromUtf8("enc_iso88591")) self.horizontalLayout_2.addWidget(self.enc_iso88591) spacerItem2 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.label_2 = QtGui.QLabel(self.id3v2_text_encoding) self.label_2.setText(_fromUtf8("")) self.label_2.setWordWrap(True) self.label_2.setObjectName(_fromUtf8("label_2")) self.horizontalLayout_2.addWidget(self.label_2) self.vboxlayout2.addWidget(self.id3v2_text_encoding) self.hbox_id3v23_join_with = QtGui.QHBoxLayout() self.hbox_id3v23_join_with.setObjectName(_fromUtf8("hbox_id3v23_join_with")) self.label_id3v23_join_with = QtGui.QLabel(self.tag_compatibility) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(4) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_id3v23_join_with.sizePolicy().hasHeightForWidth()) self.label_id3v23_join_with.setSizePolicy(sizePolicy) self.label_id3v23_join_with.setObjectName(_fromUtf8("label_id3v23_join_with")) self.hbox_id3v23_join_with.addWidget(self.label_id3v23_join_with) self.id3v23_join_with = QtGui.QComboBox(self.tag_compatibility) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.id3v23_join_with.sizePolicy().hasHeightForWidth()) self.id3v23_join_with.setSizePolicy(sizePolicy) self.id3v23_join_with.setEditable(True) self.id3v23_join_with.setObjectName(_fromUtf8("id3v23_join_with")) self.id3v23_join_with.addItem(_fromUtf8("")) self.id3v23_join_with.setItemText(0, _fromUtf8("/")) self.id3v23_join_with.addItem(_fromUtf8("")) self.id3v23_join_with.setItemText(1, _fromUtf8("; ")) self.id3v23_join_with.addItem(_fromUtf8("")) self.id3v23_join_with.setItemText(2, _fromUtf8(" / ")) self.hbox_id3v23_join_with.addWidget(self.id3v23_join_with) self.vboxlayout2.addLayout(self.hbox_id3v23_join_with) self.write_id3v1 = QtGui.QCheckBox(self.tag_compatibility) self.write_id3v1.setObjectName(_fromUtf8("write_id3v1")) self.vboxlayout2.addWidget(self.write_id3v1) self.vboxlayout.addWidget(self.tag_compatibility) spacerItem3 = QtGui.QSpacerItem(274, 41, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.vboxlayout.addItem(spacerItem3) self.retranslateUi(TagsOptionsPage) QtCore.QMetaObject.connectSlotsByName(TagsOptionsPage) TagsOptionsPage.setTabOrder(self.write_tags, self.preserve_timestamps) TagsOptionsPage.setTabOrder(self.preserve_timestamps, self.clear_existing_tags) TagsOptionsPage.setTabOrder(self.clear_existing_tags, self.remove_id3_from_flac) TagsOptionsPage.setTabOrder(self.remove_id3_from_flac, self.remove_ape_from_mp3) TagsOptionsPage.setTabOrder(self.remove_ape_from_mp3, self.preserved_tags) TagsOptionsPage.setTabOrder(self.preserved_tags, self.write_id3v24) TagsOptionsPage.setTabOrder(self.write_id3v24, self.write_id3v23) TagsOptionsPage.setTabOrder(self.write_id3v23, self.enc_utf8) TagsOptionsPage.setTabOrder(self.enc_utf8, self.enc_utf16) TagsOptionsPage.setTabOrder(self.enc_utf16, self.enc_iso88591) TagsOptionsPage.setTabOrder(self.enc_iso88591, self.id3v23_join_with) TagsOptionsPage.setTabOrder(self.id3v23_join_with, self.write_id3v1) def retranslateUi(self, TagsOptionsPage): self.write_tags.setText(_("Write tags to files")) self.preserve_timestamps.setText(_("Preserve timestamps of tagged files")) self.before_tagging.setTitle(_("Before Tagging")) self.clear_existing_tags.setText(_("Clear existing tags")) self.remove_id3_from_flac.setText(_("Remove ID3 tags from FLAC files")) self.remove_ape_from_mp3.setText(_("Remove APEv2 tags from MP3 files")) self.preserved_tags_label.setText(_("Preserve these tags from being cleared or overwritten with MusicBrainz data:")) self.preserved_tags_help.setText(_("Tags are separated by commas, and are case-sensitive.")) self.tag_compatibility.setTitle(_("Tag Compatibility")) self.id3v2_version.setTitle(_("ID3v2 Version")) self.write_id3v24.setText(_("2.4")) self.write_id3v23.setText(_("2.3")) self.id3v2_text_encoding.setTitle(_("ID3v2 Text Encoding")) self.enc_utf8.setText(_("UTF-8")) self.enc_utf16.setText(_("UTF-16")) self.enc_iso88591.setText(_("ISO-8859-1")) self.label_id3v23_join_with.setText(_("Join multiple ID3v2.3 tags with:")) self.id3v23_join_with.setToolTip(_("

Default is \'/\' to maintain compatibility with previous Picard releases.

New alternatives are \';_\' or \'_/_\' or type your own.

")) self.write_id3v1.setText(_("Also include ID3v1 tags in the files")) picard-release-1.4.2/picard/ui/ui_passworddialog.py000066400000000000000000000064051310410472100223440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_PasswordDialog(object): def setupUi(self, PasswordDialog): PasswordDialog.setObjectName(_fromUtf8("PasswordDialog")) PasswordDialog.setWindowModality(QtCore.Qt.WindowModal) PasswordDialog.resize(378, 246) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(PasswordDialog.sizePolicy().hasHeightForWidth()) PasswordDialog.setSizePolicy(sizePolicy) self.verticalLayout = QtGui.QVBoxLayout(PasswordDialog) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.info_text = QtGui.QLabel(PasswordDialog) self.info_text.setText(_fromUtf8("")) self.info_text.setWordWrap(True) self.info_text.setObjectName(_fromUtf8("info_text")) self.verticalLayout.addWidget(self.info_text) spacerItem = QtGui.QSpacerItem(20, 60, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem) self.label = QtGui.QLabel(PasswordDialog) self.label.setObjectName(_fromUtf8("label")) self.verticalLayout.addWidget(self.label) self.username = QtGui.QLineEdit(PasswordDialog) self.username.setWindowModality(QtCore.Qt.NonModal) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.username.sizePolicy().hasHeightForWidth()) self.username.setSizePolicy(sizePolicy) self.username.setObjectName(_fromUtf8("username")) self.verticalLayout.addWidget(self.username) self.label_2 = QtGui.QLabel(PasswordDialog) self.label_2.setObjectName(_fromUtf8("label_2")) self.verticalLayout.addWidget(self.label_2) self.password = QtGui.QLineEdit(PasswordDialog) self.password.setEchoMode(QtGui.QLineEdit.Password) self.password.setObjectName(_fromUtf8("password")) self.verticalLayout.addWidget(self.password) spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem1) self.buttonbox = QtGui.QDialogButtonBox(PasswordDialog) self.buttonbox.setOrientation(QtCore.Qt.Horizontal) self.buttonbox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) self.buttonbox.setObjectName(_fromUtf8("buttonbox")) self.verticalLayout.addWidget(self.buttonbox) self.retranslateUi(PasswordDialog) QtCore.QObject.connect(self.buttonbox, QtCore.SIGNAL(_fromUtf8("rejected()")), PasswordDialog.reject) QtCore.QMetaObject.connectSlotsByName(PasswordDialog) def retranslateUi(self, PasswordDialog): PasswordDialog.setWindowTitle(_("Authentication required")) self.label.setText(_("Username:")) self.label_2.setText(_("Password:")) picard-release-1.4.2/picard/ui/ui_provider_options_caa.py000066400000000000000000000121251310410472100235270ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_CaaOptions(object): def setupUi(self, CaaOptions): CaaOptions.setObjectName(_fromUtf8("CaaOptions")) CaaOptions.resize(660, 194) self.verticalLayout = QtGui.QVBoxLayout(CaaOptions) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.select_caa_types_group = QtGui.QHBoxLayout() self.select_caa_types_group.setObjectName(_fromUtf8("select_caa_types_group")) self.restrict_images_types = QtGui.QCheckBox(CaaOptions) self.restrict_images_types.setObjectName(_fromUtf8("restrict_images_types")) self.select_caa_types_group.addWidget(self.restrict_images_types) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.select_caa_types_group.addItem(spacerItem) self.select_caa_types = QtGui.QPushButton(CaaOptions) self.select_caa_types.setEnabled(False) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(100) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.select_caa_types.sizePolicy().hasHeightForWidth()) self.select_caa_types.setSizePolicy(sizePolicy) self.select_caa_types.setObjectName(_fromUtf8("select_caa_types")) self.select_caa_types_group.addWidget(self.select_caa_types) self.verticalLayout.addLayout(self.select_caa_types_group) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.label = QtGui.QLabel(CaaOptions) self.label.setObjectName(_fromUtf8("label")) self.horizontalLayout.addWidget(self.label) spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) self.cb_image_size = QtGui.QComboBox(CaaOptions) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.cb_image_size.sizePolicy().hasHeightForWidth()) self.cb_image_size.setSizePolicy(sizePolicy) self.cb_image_size.setObjectName(_fromUtf8("cb_image_size")) self.cb_image_size.addItem(_fromUtf8("")) self.cb_image_size.addItem(_fromUtf8("")) self.cb_image_size.addItem(_fromUtf8("")) self.horizontalLayout.addWidget(self.cb_image_size) self.verticalLayout.addLayout(self.horizontalLayout) self.cb_save_single_front_image = QtGui.QCheckBox(CaaOptions) self.cb_save_single_front_image.setObjectName(_fromUtf8("cb_save_single_front_image")) self.verticalLayout.addWidget(self.cb_save_single_front_image) self.cb_approved_only = QtGui.QCheckBox(CaaOptions) self.cb_approved_only.setObjectName(_fromUtf8("cb_approved_only")) self.verticalLayout.addWidget(self.cb_approved_only) self.cb_type_as_filename = QtGui.QCheckBox(CaaOptions) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.cb_type_as_filename.sizePolicy().hasHeightForWidth()) self.cb_type_as_filename.setSizePolicy(sizePolicy) self.cb_type_as_filename.setObjectName(_fromUtf8("cb_type_as_filename")) self.verticalLayout.addWidget(self.cb_type_as_filename) spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem2) self.retranslateUi(CaaOptions) QtCore.QMetaObject.connectSlotsByName(CaaOptions) def retranslateUi(self, CaaOptions): CaaOptions.setWindowTitle(_("Form")) self.restrict_images_types.setText(_("Download only cover art images matching selected types")) self.select_caa_types.setText(_("Select types...")) self.label.setText(_("Only use images of the following size:")) self.cb_image_size.setItemText(0, _("250 px")) self.cb_image_size.setItemText(1, _("500 px")) self.cb_image_size.setItemText(2, _("Full size")) self.cb_save_single_front_image.setText(_("Save only one front image as separate file")) self.cb_approved_only.setText(_("Download only approved images")) self.cb_type_as_filename.setText(_("Use the first image type as the filename. This will not change the filename of front images.")) picard-release-1.4.2/picard/ui/ui_provider_options_local.py000066400000000000000000000064671310410472100241110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_LocalOptions(object): def setupUi(self, LocalOptions): LocalOptions.setObjectName(_fromUtf8("LocalOptions")) LocalOptions.resize(472, 215) self.verticalLayout = QtGui.QVBoxLayout(LocalOptions) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.local_cover_regex_label = QtGui.QLabel(LocalOptions) self.local_cover_regex_label.setObjectName(_fromUtf8("local_cover_regex_label")) self.verticalLayout.addWidget(self.local_cover_regex_label) self.local_cover_regex_edit = QtGui.QLineEdit(LocalOptions) self.local_cover_regex_edit.setObjectName(_fromUtf8("local_cover_regex_edit")) self.verticalLayout.addWidget(self.local_cover_regex_edit) self.horizontalLayout_2 = QtGui.QHBoxLayout() self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) self.local_cover_regex_error = QtGui.QLabel(LocalOptions) self.local_cover_regex_error.setText(_fromUtf8("")) self.local_cover_regex_error.setObjectName(_fromUtf8("local_cover_regex_error")) self.horizontalLayout_2.addWidget(self.local_cover_regex_error) self.local_cover_regex_default = QtGui.QPushButton(LocalOptions) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.local_cover_regex_default.sizePolicy().hasHeightForWidth()) self.local_cover_regex_default.setSizePolicy(sizePolicy) self.local_cover_regex_default.setObjectName(_fromUtf8("local_cover_regex_default")) self.horizontalLayout_2.addWidget(self.local_cover_regex_default) self.verticalLayout.addLayout(self.horizontalLayout_2) self.note = QtGui.QLabel(LocalOptions) font = QtGui.QFont() font.setItalic(True) self.note.setFont(font) self.note.setWordWrap(True) self.note.setObjectName(_fromUtf8("note")) self.verticalLayout.addWidget(self.note) spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem) self.retranslateUi(LocalOptions) QtCore.QMetaObject.connectSlotsByName(LocalOptions) def retranslateUi(self, LocalOptions): LocalOptions.setWindowTitle(_("Form")) self.local_cover_regex_label.setText(_("Local cover art files match the following regular expression:")) self.local_cover_regex_default.setText(_("Default")) self.note.setText(_("First group in the regular expression, if any, will be used as type, ie. cover-back-spine.jpg will be set as types Back + Spine. If no type is found, it will default to Front type.")) picard-release-1.4.2/picard/ui/ui_tagsfromfilenames.py000066400000000000000000000047261310410472100230340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Automatically generated - don't edit. # Use `python setup.py build_ui` to update it. from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_TagsFromFileNamesDialog(object): def setupUi(self, TagsFromFileNamesDialog): TagsFromFileNamesDialog.setObjectName(_fromUtf8("TagsFromFileNamesDialog")) TagsFromFileNamesDialog.resize(487, 368) self.gridlayout = QtGui.QGridLayout(TagsFromFileNamesDialog) self.gridlayout.setMargin(9) self.gridlayout.setSpacing(6) self.gridlayout.setObjectName(_fromUtf8("gridlayout")) self.files = QtGui.QTreeWidget(TagsFromFileNamesDialog) self.files.setAlternatingRowColors(True) self.files.setRootIsDecorated(False) self.files.setObjectName(_fromUtf8("files")) self.gridlayout.addWidget(self.files, 1, 0, 1, 2) self.replace_underscores = QtGui.QCheckBox(TagsFromFileNamesDialog) self.replace_underscores.setObjectName(_fromUtf8("replace_underscores")) self.gridlayout.addWidget(self.replace_underscores, 2, 0, 1, 2) self.buttonbox = QtGui.QDialogButtonBox(TagsFromFileNamesDialog) self.buttonbox.setOrientation(QtCore.Qt.Horizontal) self.buttonbox.setObjectName(_fromUtf8("buttonbox")) self.gridlayout.addWidget(self.buttonbox, 3, 0, 1, 2) self.preview = QtGui.QPushButton(TagsFromFileNamesDialog) self.preview.setObjectName(_fromUtf8("preview")) self.gridlayout.addWidget(self.preview, 0, 1, 1, 1) self.format = QtGui.QComboBox(TagsFromFileNamesDialog) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Policy(7), QtGui.QSizePolicy.Policy(0)) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.format.sizePolicy().hasHeightForWidth()) self.format.setSizePolicy(sizePolicy) self.format.setEditable(True) self.format.setObjectName(_fromUtf8("format")) self.gridlayout.addWidget(self.format, 0, 0, 1, 1) self.retranslateUi(TagsFromFileNamesDialog) QtCore.QMetaObject.connectSlotsByName(TagsFromFileNamesDialog) def retranslateUi(self, TagsFromFileNamesDialog): TagsFromFileNamesDialog.setWindowTitle(_("Convert File Names to Tags")) self.replace_underscores.setText(_("Replace underscores with spaces")) self.preview.setText(_("&Preview")) picard-release-1.4.2/picard/ui/util.py000066400000000000000000000104701310410472100175770ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import sys from PyQt4 import QtCore, QtGui from picard import config from picard.util import find_existing_path, icontheme class StandardButton(QtGui.QPushButton): OK = 0 CANCEL = 1 HELP = 2 __types = { OK: (N_('&Ok'), 'SP_DialogOkButton'), CANCEL: (N_('&Cancel'), 'SP_DialogCancelButton'), HELP: (N_('&Help'), 'SP_DialogHelpButton'), } def __init__(self, btntype): label = _(self.__types[btntype][0]) args = [label] if sys.platform != 'win32' and sys.platform != 'darwin': iconname = self.__types[btntype][1] if hasattr(QtGui.QStyle, iconname): icon = self.tagger.style().standardIcon(getattr(QtGui.QStyle, iconname)) args = [icon, label] QtGui.QPushButton.__init__(self, *args) # The following code is there to fix # https://tickets.metabrainz.org/browse/PICARD-417 # In some older version of PyQt/sip it's impossible to connect a signal # emitting an `int` to a slot expecting a `bool`. # By using `enabledSlot` instead we can force python to do the # conversion from int (`state`) to bool. def enabledSlot(func, state): """Calls `func` with `state`.""" func(state) def find_starting_directory(): if config.setting["starting_directory"]: path = config.setting["starting_directory_path"] else: path = config.persist["current_directory"] or QtCore.QDir.homePath() return find_existing_path(unicode(path)) class ButtonLineEdit(QtGui.QLineEdit): def __init__(self, parent=None): QtGui.QLineEdit.__init__(self, parent) self.clear_button = QtGui.QToolButton(self) self.clear_button.setVisible(False) self.clear_button.setCursor(QtCore.Qt.PointingHandCursor) self.clear_button.setFocusPolicy(QtCore.Qt.NoFocus) fallback_icon = icontheme.lookup('edit-clear', icontheme.ICON_SIZE_TOOLBAR) self.clear_button.setIcon(QtGui.QIcon.fromTheme("edit-clear", fallback_icon)) self.clear_button.setStyleSheet( "QToolButton { background: transparent; border: none;} QToolButton QWidget { color: black;}") layout = QtGui.QHBoxLayout(self) layout.addWidget(self.clear_button, 0, QtCore.Qt.AlignRight) layout.setSpacing(0) layout.setMargin(5) self.clear_button.setToolTip(_("Clear entry")) self.clear_button.clicked.connect(self.clear) self.textChanged.connect(self._update_clear_button) self._margins = self.getTextMargins() def _update_clear_button(self, text): self.clear_button.setVisible(text != "") left, top, right, bottom = self._margins self.setTextMargins(left, top, right + self.clear_button.width(), bottom) class MultiDirsSelectDialog(QtGui.QFileDialog): """Custom file selection dialog which allows the selection of multiple directories. Depending on the platform, dialog may fallback on non-native. """ def __init__(self, *args): super(MultiDirsSelectDialog, self).__init__(*args) self.setFileMode(self.Directory) self.setOption(self.ShowDirsOnly) if sys.platform == "darwin": # The native dialog doesn't allow selecting >1 directory self.setOption(self.DontUseNativeDialog) for view in self.findChildren((QtGui.QListView, QtGui.QTreeView)): if isinstance(view.model(), QtGui.QFileSystemModel): view.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) picard-release-1.4.2/picard/util/000077500000000000000000000000001310410472100166065ustar00rootroot00000000000000picard-release-1.4.2/picard/util/__init__.py000066400000000000000000000326471310410472100207330ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2004 Robert Kaye # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import ntpath import re import sys import unicodedata if sys.platform == 'win32': from ctypes import windll from time import time from PyQt4 import QtCore from string import Template # Required for compatibility with lastfmplus which imports this from here rather than loading it direct. from functools import partial from picard.const import MUSICBRAINZ_SERVERS class LockableObject(QtCore.QObject): """Read/write lockable object.""" def __init__(self): QtCore.QObject.__init__(self) self.__lock = QtCore.QReadWriteLock() def lock_for_read(self): """Lock the object for read operations.""" self.__lock.lockForRead() def lock_for_write(self): """Lock the object for write operations.""" self.__lock.lockForWrite() def unlock(self): """Unlock the object.""" self.__lock.unlock() _io_encoding = sys.getfilesystemencoding() # The following was adapted from k3b's source code: #// On a glibc system the system locale defaults to ANSI_X3.4-1968 #// It is very unlikely that one would set the locale to ANSI_X3.4-1968 #// intentionally def check_io_encoding(): if _io_encoding == "ANSI_X3.4-1968": from picard import log log.warning(""" System locale charset is ANSI_X3.4-1968 Your system's locale charset (i.e. the charset used to encode filenames) is set to ANSI_X3.4-1968. It is highly unlikely that this has been done intentionally. Most likely the locale is not set at all. An invalid setting will result in problems when creating data projects. To properly set the locale charset make sure the LC_* environment variables are set. Normally the distribution setup tools take care of this. Translation: Picard will have problems with non-english characters in filenames until you change your charset. """) def encode_filename(filename): """Encode unicode strings to filesystem encoding.""" if isinstance(filename, unicode): if os.path.supports_unicode_filenames and sys.platform != "darwin": return filename else: return filename.encode(_io_encoding, 'replace') else: return filename def decode_filename(filename): """Decode strings from filesystem encoding to unicode.""" if isinstance(filename, unicode): return filename else: return filename.decode(_io_encoding) def pathcmp(a, b): return os.path.normcase(a) == os.path.normcase(b) def format_time(ms): """Formats time in milliseconds to a string representation.""" ms = float(ms) if ms == 0: return "?:??" else: return "%d:%02d" % (round(ms / 1000.0) / 60, round(ms / 1000.0) % 60) def sanitize_date(datestr): """Sanitize date format. e.g.: "YYYY-00-00" -> "YYYY" "YYYY- - " -> "YYYY" ... """ date = [] for num in datestr.split("-"): try: num = int(num.strip()) except ValueError: break if num: date.append(num) return ("", "%04d", "%04d-%02d", "%04d-%02d-%02d")[len(date)] % tuple(date) _re_win32_incompat = re.compile(r'["*:<>?|]', re.UNICODE) def replace_win32_incompat(string, repl=u"_"): """Replace win32 filename incompatible characters from ``string`` by ``repl``.""" # Don't replace : with _ for windows drive if sys.platform == "win32" and os.path.isabs(string): drive, rest = ntpath.splitdrive(string) return drive + _re_win32_incompat.sub(repl, rest) else: return _re_win32_incompat.sub(repl, string) _re_non_alphanum = re.compile(r'\W+', re.UNICODE) def strip_non_alnum(string): """Remove all non-alphanumeric characters from ``string``.""" return _re_non_alphanum.sub(u" ", string).strip() _re_slashes = re.compile(r'[\\/]', re.UNICODE) def sanitize_filename(string, repl="_"): return _re_slashes.sub(repl, string) def _reverse_sortname(sortname): """Reverse sortnames.""" chunks = [a.strip() for a in sortname.split(",")] if len(chunks) == 2: return "%s %s" % (chunks[1], chunks[0]) elif len(chunks) == 3: return "%s %s %s" % (chunks[2], chunks[1], chunks[0]) elif len(chunks) == 4: return "%s %s, %s %s" % (chunks[1], chunks[0], chunks[3], chunks[2]) else: return sortname.strip() def translate_from_sortname(name, sortname): """'Translate' the artist name by reversing the sortname.""" for c in name: ctg = unicodedata.category(c) if ctg[0] == "L" and unicodedata.name(c).find("LATIN") == -1: for separator in (" & ", "; ", " and ", " vs. ", " with ", " y "): if separator in sortname: parts = sortname.split(separator) break else: parts = [sortname] separator = "" return separator.join(map(_reverse_sortname, parts)) return name def find_existing_path(path): path = encode_filename(path) while path and not os.path.isdir(path): head, tail = os.path.split(path) if head == path: break path = head return decode_filename(path) def find_executable(*executables): if sys.platform == 'win32': executables = [e + '.exe' for e in executables] paths = [os.path.dirname(sys.executable)] if sys.executable else [] paths += os.environ.get('PATH', '').split(os.pathsep) for path in paths: for executable in executables: f = os.path.join(path, executable) if os.path.isfile(f): return f _mbid_format = Template('$h{8}-$h$l-$h$l-$h$l-$h{12}').safe_substitute(h='[0-9a-fA-F]', l='{4}') _re_mbid_val = re.compile(_mbid_format) def mbid_validate(string): return _re_mbid_val.match(string) def parse_amazon_url(url): """Extract host and asin from an amazon url. It returns a dict with host and asin keys on success, None else """ r = re.compile(r'^https?://(?:www.)?(?P.*?)(?:\:[0-9]+)?/.*/(?P[0-9B][0-9A-Z]{9})(?:[^0-9A-Z]|$)') match = r.match(url) if match is not None: return match.groupdict() return None def throttle(interval): """ Throttle a function so that it will only execute once per ``interval`` (specified in milliseconds). """ mutex = QtCore.QMutex() def decorator(func): def later(): mutex.lock() func(*decorator.args, **decorator.kwargs) decorator.prev = time() decorator.is_ticking = False mutex.unlock() def throttled_func(*args, **kwargs): if decorator.is_ticking: mutex.lock() decorator.args = args decorator.kwargs = kwargs mutex.unlock() return mutex.lock() now = time() r = interval - (now-decorator.prev)*1000.0 if r <= 0: func(*args, **kwargs) decorator.prev = now else: decorator.args = args decorator.kwargs = kwargs QtCore.QTimer.singleShot(r, later) decorator.is_ticking = True mutex.unlock() return throttled_func decorator.prev = 0 decorator.is_ticking = False return decorator def uniqify(seq): """Uniqify a list, preserving order""" # Courtesy of Dave Kirby # See http://www.peterbe.com/plog/uniqifiers-benchmark seen = set() add_seen = seen.add return [x for x in seq if x not in seen and not add_seen(x)] # order is important _tracknum_regexps = ( # search for explicit track number (prefix "track") r"track[\s_-]*(?:no|nr)?[\s_-]*(\d+)", # search for 2-digit number at start of string r"^(\d{2})\D?", # search for 2-digit number at end of string r"\D?(\d{2})$", ) def tracknum_from_filename(base_filename): """Guess and extract track number from filename Returns -1 if none found, the number as integer else """ filename, _ = os.path.splitext(base_filename) for r in _tracknum_regexps: match = re.search(r, filename, re.I) if match: n = int(match.group(1)) if n > 0: return n # find all numbers between 1 and 99 # 4-digit or more numbers are very unlikely to be a track number # smaller number is preferred in any case numbers = sorted([int(n) for n in re.findall(r'\d+', filename) if int(n) <= 99 and int(n) > 0]) if numbers: return numbers[0] return -1 # Provide os.path.samefile equivalent which is missing in Python under Windows if sys.platform == 'win32': def os_path_samefile(p1, p2): ap1 = os.path.abspath(p1) ap2 = os.path.abspath(p2) return ap1 == ap2 else: os_path_samefile = os.path.samefile def is_hidden(filepath): """Test whether a file or directory is hidden. A file is considered hidden if it starts with a dot on non-Windows systems or if it has the "hidden" flag set on Windows.""" name = os.path.basename(os.path.abspath(filepath)) return (name.startswith('.') and sys.platform != 'win32') \ or _has_hidden_attribute(filepath) def _has_hidden_attribute(filepath): if sys.platform != 'win32': return False # FIXME: On OSX detecting hidden files involves more # than just checking for dot files, see # https://stackoverflow.com/questions/284115/cross-platform-hidden-file-detection try: attrs = windll.kernel32.GetFileAttributesW(unicode(filepath)) assert attrs != -1 return bool(attrs & 2) except (AttributeError, AssertionError): return False def linear_combination_of_weights(parts): """Produces a probability as a linear combination of weights Parts should be a list of tuples in the form: [(v0, w0), (v1, w1), ..., (vn, wn)] where vn is a value between 0.0 and 1.0 and wn corresponding weight as a positive number """ total = 0.0 sum_of_products = 0.0 for value, weight in parts: if value < 0.0: raise ValueError("Value must be greater than or equal to 0.0") if value > 1.0: raise ValueError("Value must be lesser than or equal to 1.0") if weight < 0: raise ValueError("Weight must be greater than or equal to 0.0") total += weight sum_of_products += value * weight if total == 0.0: return 0.0 return sum_of_products / total def album_artist_from_path(filename, album, artist): """If album is not set, try to extract album and artist from path """ if not album: dirs = os.path.dirname(filename).replace('\\','/').lstrip('/').split('/') if len(dirs) == 0: return album, artist # Strip disc subdirectory from list if len(dirs) > 0: if re.search(r'(^|\s)(CD|DVD|Disc)\s*\d+(\s|$)', dirs[-1], re.I): del dirs[-1] if len(dirs) > 0: # For clustering assume %artist%/%album%/file or %artist% - %album%/file album = dirs[-1] if ' - ' in album: new_artist, album = album.split(' - ', 1) if not artist: artist = new_artist elif not artist and len(dirs) >= 2: artist = dirs[-2] return album, artist def build_qurl(host, port=80, path=None, queryargs=None): """ Builds and returns a QUrl object from `host`, `port` and `path` and automatically enables HTTPS if necessary. Encoded query arguments can be provided in `queryargs`, a dictionary mapping field names to values. """ url = QtCore.QUrl() url.setHost(host) url.setPort(port) if (host in MUSICBRAINZ_SERVERS or port == 443): url.setScheme("https") url.setPort(443) else: url.setScheme("http") if path is not None: url.setPath(path) if queryargs is not None: for k, v in queryargs.iteritems(): url.addEncodedQueryItem(k, unicode(v)) return url def union_sorted_lists(list1, list2): """ Returns union of two sorted lists. >> list1 = [1, 2, 2, 2, 3] >> list2 = [2, 3, 4] >> union_sorted_lists(list1, list2) >> [1, 2, 2, 2, 3, 4] """ union = [] i = 0 j = 0 while i != len(list1) and j != len(list2): if list1[i] > list2[j]: union.append(list2[j]) j += 1 elif list1[i] < list2[j]: union.append(list1[i]) i += 1 else: union.append(list1[i]) i += 1 j += 1 if i == len(list1): union.extend(list2[j:]) else: union.extend(list1[i:]) return union picard-release-1.4.2/picard/util/astrcmp.c000066400000000000000000000110031310410472100204160ustar00rootroot00000000000000/* * Picard, the next-generation MusicBrainz tagger * Copyright (C) 2006 Lukáš Lalinský * Copyright (C) 2003 Benbuck Nason * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /*** * * Approximate string comparison * * This work is based on the Levenshtein Metric or "edit distance", which is * well known, simple, and seems to be unencumbered by any usage restrictions. * For more information on the Levenshtein Distance you can refer to the web, * e.g. http://www.merriampark.com/ld.htm * * Accuracy and speed enhancements could probably be made to this algorithm by * implementing the improvements suggested by such people as Esko Ukkonen, Hal * Berghel & David Roach, and Sun Wu and Udi Manber. * * This has been succesfully compiled using: * Microsoft Visual C++ 6 SP 5 * GNU gcc 3.2 & Cygwin * GNU gcc 3.2 & MinGW * * Benbuck Nason, February 28th, 2003 * ***/ #include #include #include #include /*** * Compute Levenshtein distance. Levenshtein distance, also known as * "edit distance," is a measure of the cost to transform one string * into another. ***/ #define MIN(x, y) (((x) > (y)) ? (y) : (x)) #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #define MATRIX(a, b) matrix[(b) * (len1 + 1) + (a)] float LevenshteinDistance(const Py_UNICODE * s1, int len1, const Py_UNICODE * s2, int len2) { int *matrix, index1, index2; float result; /* Step 1 */ /* Check string lengths */ if (len1 == 0) return 0.0f; if (len2 == 0) return 0.0f; /* Step 2 */ /* Allocate matrix for algorithm and fill it with default values */ matrix = malloc(sizeof(int) * (len1 + 1) * (len2 + 1)); for (index1 = 0; index1 <= len1; index1++) MATRIX(index1, 0) = index1; for (index2 = 0; index2 <= len2; index2++) MATRIX(0, index2) = index2; /* Step 3 */ /* Loop through first string */ for (index1 = 1; index1 <= len1; index1++) { Py_UNICODE s1_current = s1[index1 - 1]; /* Step 4 */ /* Loop through second string */ for (index2 = 1; index2 <= len2; index2++) { Py_UNICODE s2_current = s2[index2 - 1]; /* Step 5 */ /* Calculate cost of this iteration (handles deletion, insertion, and substitution) */ int cost = (s1_current == s2_current) ? 0 : 1; /* Step 6 */ /* Calculate the total cost up to this point */ int above = MATRIX(index1 - 1, index2); int left = MATRIX(index1, index2 - 1); int diagonal = MATRIX(index1 - 1, index2 - 1); int cell = MIN(MIN(above + 1, left + 1), diagonal + cost); /* Step 6a */ /* Also cover transposition. This step is taken from: Berghel, Hal ; Roach, David : "An Extension of Ukkonen's Enhanced Dynamic Programming ASM Algorithm" (http://berghel.net/publications/asm/asm.php) */ if (index1 > 2 && index2 > 2) { int trans = MATRIX(index1 - 2, index2 - 2) + 1; if (s1[index1 - 2] != s2_current) trans++; if (s1_current != s2[index2 - 2]) trans++; if (cell > trans) cell = trans; } MATRIX(index1, index2) = cell; } } /* Step 7 */ /* Return result */ result = ((float)1 - ((float)MATRIX(len1, len2) / (float)MAX(len1, len2))); free(matrix); return result; } static PyObject * astrcmp(PyObject *self, PyObject *args) { PyObject *s1, *s2; float d; const Py_UNICODE *us1, *us2; int len1, len2; PyThreadState *_save; if (!PyArg_ParseTuple(args, "UU", &s1, &s2)) return NULL; us1 = PyUnicode_AS_UNICODE(s1); us2 = PyUnicode_AS_UNICODE(s2); len1 = PyUnicode_GetSize(s1); len2 = PyUnicode_GetSize(s2); Py_UNBLOCK_THREADS d = LevenshteinDistance(us1, len1, us2, len2); Py_BLOCK_THREADS return Py_BuildValue("f", d); } static PyMethodDef AstrcmpMethods[] = { {"astrcmp", astrcmp, METH_VARARGS, "Compute Levenshtein distance"}, {NULL, NULL, 0, NULL} }; PyMODINIT_FUNC initastrcmp(void) { (void)Py_InitModule("astrcmp", AstrcmpMethods); } picard-release-1.4.2/picard/util/bytes2human.py000066400000000000000000000062501310410472100214240ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2013 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import locale """ Helper class to convert bytes to human-readable form It supports i18n through gettext, decimal and binary units. >>> n = 1572864 >>> [binary(n), decimal(n)] ['1.5 MiB', '1.6 MB'] """ # used to force gettextization _BYTES_STRINGS_I18N = ( N_('%s B'), N_('%s kB'), N_('%s KiB'), N_('%s MB'), N_('%s MiB'), N_('%s GB'), N_('%s GiB'), N_('%s TB'), N_('%s TiB'), N_('%s PB'), N_('%s PiB'), ) def decimal(number, scale=1): """ Convert bytes to short human-readable string, decimal mode >>> [decimal(n) for n in [1000, 1024, 15500]] ['1 kB', '1 kB', '15.5 kB'] """ return short_string(int(number), 1000, scale) def binary(number, scale=1): """ Convert bytes to short human-readable string, binary mode >>> [binary(n) for n in [1000, 1024, 15500]] ['1000 B', '1 KiB', '15.1 KiB'] """ return short_string(int(number), 1024, scale) def short_string(number, multiple, scale=1): """ Returns short human-readable string for `number` bytes >>> [short_string(n, 1024, 2) for n in [1000, 1100, 15500]] ['1000 B', '1.07 KiB', '15.14 KiB'] >>> [short_string(n, 1000, 1) for n in [10000, 11000, 1550000]] ['10 kB', '11 kB', '1.6 MB'] """ num, unit = calc_unit(number, multiple) n = int(num) nr = round(num, scale) if n == nr or unit == 'B': fmt = '%d' num = n else: fmt = '%%0.%df' % scale num = nr fmtnum = locale.format(fmt, num) return _("%s " + unit) % fmtnum def calc_unit(number, multiple=1000): """ Calculate rounded number of multiple * bytes, finding best unit >>> calc_unit(12456, 1024) (12.1640625, 'KiB') >>> calc_unit(-12456, 1000) (-12.456, 'kB') >>> calc_unit(0, 1001) Traceback (most recent call last): ... ValueError: multiple parameter has to be 1000 or 1024 """ if number < 0: sign = -1 number = -number else: sign = 1 n = float(number) if multiple == 1000: k, b = 'k', 'B' elif multiple == 1024: k, b = 'K', 'iB' else: raise ValueError('multiple parameter has to be 1000 or 1024') suffixes = ["B"] + [i + b for i in k + "MGTP"] for suffix in suffixes: if n < multiple or suffix == suffixes[-1]: return (sign * n, suffix) else: n /= multiple picard-release-1.4.2/picard/util/cdrom.py000066400000000000000000000074241310410472100202730ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (c) 2004 Robert Kaye # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import sys if sys.platform == 'win32': from ctypes import windll from PyQt4.QtCore import QFile, QIODevice from picard import config from picard.util import uniqify try: from libdiscid.compat import discid except ImportError: try: import discid except ImportError: discid = None DEFAULT_DRIVES = [] if discid is not None: device = discid.get_default_device() if device: DEFAULT_DRIVES.append(device) LINUX_CDROM_INFO = '/proc/sys/dev/cdrom/info' # if get_cdrom_drives() lists ALL drives available on the machine if sys.platform == 'win32': AUTO_DETECT_DRIVES = True elif sys.platform == 'linux2' and QFile.exists(LINUX_CDROM_INFO): AUTO_DETECT_DRIVES = True else: # There might be more drives we couldn't detect # setting uses a text field instead of a drop-down AUTO_DETECT_DRIVES = False def get_cdrom_drives(): """List available disc drives on the machine """ # add default drive from libdiscid to the list drives = list(DEFAULT_DRIVES) if sys.platform == 'win32': GetLogicalDrives = windll.kernel32.GetLogicalDrives GetDriveType = windll.kernel32.GetDriveTypeA DRIVE_CDROM = 5 mask = GetLogicalDrives() for i in range(26): if mask >> i & 1: drive = chr(i + ord("A")) + ":" if GetDriveType(drive) == DRIVE_CDROM: drives.append(drive) elif sys.platform == 'linux2' and QFile.exists(LINUX_CDROM_INFO): # Read info from /proc/sys/dev/cdrom/info cdinfo = QFile(LINUX_CDROM_INFO) if cdinfo.open(QIODevice.ReadOnly | QIODevice.Text): drive_names = [] drive_audio_caps = [] line = unicode(cdinfo.readLine()) while line: if ":" in line: key, values = line.split(':') if key == 'drive name': drive_names = values.split() elif key == 'Can play audio': drive_audio_caps = [v == '1' for v in values.split()] break # no need to continue past this line line = unicode(cdinfo.readLine()) # Show only drives that are capable of playing audio for index, drive in enumerate(drive_names): if drive_audio_caps[index]: device = u'/dev/%s' % drive symlink_target = QFile.symLinkTarget(device) if symlink_target != '': device = symlink_target drives.append(device) else: for device in config.setting["cd_lookup_device"].split(","): # Need to filter out empty strings, # particularly if the device list is empty if device.strip() != u'': drives.append(device.strip()) # make sure no drive is listed twice (given by multiple sources) return sorted(uniqify(drives)) picard-release-1.4.2/picard/util/devutil/000077500000000000000000000000001310410472100202625ustar00rootroot00000000000000picard-release-1.4.2/picard/util/devutil/__init__.py000066400000000000000000000066571310410472100224110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2014 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. def printable_node(node, indent=0): """ Print a XmlNode so output can used in test scripts Useful to debug mbxml.py mostly and to create unit tests using "real" nodes. Example of usage: from picard.util.devutil import printable_node from picard import log def _translate_artist_node(node): log.debug(printable_node(node)) ... will output to debug log something like: D: 01:35:56 XmlNode( attribs={u'id': u'28503ab7-8bf2-4666-a7bd-2644bfc7cb1d'}, children={ u'name': [XmlNode(text=u'Dream Theater')], u'alias_list': [XmlNode( attribs={u'count': u'3'}, children={u'alias': [ XmlNode( text=u'Dream Theatre', attribs={u'sort_name': u'Dream Theatre'} ), XmlNode( text=u'DreamTheater', attribs={u'sort_name': u'DreamTheater'} ), XmlNode( text=u'Majesty', attribs={u'sort_name': u'Majesty'} ) ]} )], u'sort_name': [XmlNode(text=u'Dream Theater')] } ) """ indentstr = u" "*4 def indented(front, l, back, indent): ind0 = indentstr*indent ind1 = indentstr*(indent+1) if not l: return front + back if len(l) > 1: return front + u"\n" + u',\n'.join([ind1 + x for x in l]) + u"\n" + ind0 + back else: return front + l[0] + back el = [] if node.text: el.append(u'text=' + repr(node.text).decode('unicode-escape')) if node.attribs: l = [] for k,v in node.attribs.iteritems(): l.append(repr(k).decode('unicode-escape') + u': ' + repr(v).decode('unicode-escape')) el.append(indented(u'attribs={', l, u'}', indent+1)) if node.children: l = [] for k, v in node.children.iteritems(): l.append( indented( repr(k).decode('unicode-escape') + u': [', [printable_node(x, indent+3) for x in v], u']', indent+2 ) ) el.append(indented(u'children={', l, u'}', indent+1)) return indented(u'XmlNode(', el, u')', indent) picard-release-1.4.2/picard/util/filenaming.py000066400000000000000000000325031310410472100212740ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import math import os import re import struct import sys import unicodedata from picard.util import _io_encoding, decode_filename, encode_filename def _get_utf16_length(text): """Returns the number of code points used by a unicode object in its UTF-16 representation. """ if isinstance(text, str): return len(text) # if this is a narrow Python build, len will in fact return exactly # what we're looking for if sys.maxunicode == 0xFFFF: return len(text) # otherwise, encode the string in UTF-16 using the system's endianness, # and divide the resulting length by 2 return len(text.encode("utf-16%ce" % sys.byteorder[0])) // 2 def _shorten_to_utf16_length(text, length): """Truncates a unicode object to the given number of UTF-16 code points. """ assert isinstance(text, unicode), "This function only works on unicode" # if this is a narrow Python build, regular slicing will do exactly # what we're looking for if sys.maxunicode == 0xFFFF: shortened = text[:length] # before returning, we need to check if we didn't cut in the middle # of a surrogate pair last = shortened[-1:] if last and 0xD800 <= ord(last) <= 0xDBFF: # it's a leading surrogate alright return shortened[:-1] # else... return shortened # otherwise, encode the string in UTF-16 using the system's endianness, # and shorten by twice the length enc = "utf-16%ce" % sys.byteorder[0] shortened = text.encode(enc)[:length * 2] # if we hit a surrogate pair, get rid of the last codepoint last = shortened[-2:] if last and 0xD800 <= struct.unpack("=H", last)[0] <= 0xDBFF: shortened = shortened[:-2] return shortened.decode(enc) def _shorten_to_utf16_nfd_length(text, length): text = unicodedata.normalize('NFD', text) newtext = _shorten_to_utf16_length(text, length) # if the first cut-off character was a combining one, remove our last try: if unicodedata.combining(text[len(newtext)]): newtext = newtext[:-1] except IndexError: pass return unicodedata.normalize('NFC', newtext) _re_utf8 = re.compile(r'^utf([-_]?8)$', re.IGNORECASE) def _shorten_to_bytes_length(text, length): """Truncates a unicode object to the given number of bytes it would take when encoded in the "filesystem encoding". """ assert isinstance(text, unicode), "This function only works on unicode" raw = encode_filename(text) # maybe there's no need to truncate anything if len(raw) <= length: return text # or maybe there's nothing multi-byte here if len(raw) == len(text): return text[:length] # if we're dealing with utf-8, we can use an efficient algorithm # to deal with character boundaries if _re_utf8.match(_io_encoding): i = length # a UTF-8 intermediate byte starts with the bits 10xxxxxx, # so ord(char) & 0b11000000 = 0b10000000 while i > 0 and (ord(raw[i]) & 0xC0) == 0x80: i -= 1 return decode_filename(raw[:i]) # finally, a brute force approach i = length while i > 0: try: return decode_filename(raw[:i]) except UnicodeDecodeError: pass i -= 1 # hmm. we got here? return u"" SHORTEN_BYTES, SHORTEN_UTF16, SHORTEN_UTF16_NFD = 0, 1, 2 def shorten_filename(filename, length, mode): """Truncates a filename to the given number of thingies, as implied by `mode`. """ if isinstance(filename, str): return filename[:length] if mode == SHORTEN_BYTES: return _shorten_to_bytes_length(filename, length) if mode == SHORTEN_UTF16: return _shorten_to_utf16_length(filename, length) if mode == SHORTEN_UTF16_NFD: return _shorten_to_utf16_nfd_length(filename, length) def shorten_path(path, length, mode): """Reduce path nodes' length to given limit(s). path: Absolute or relative path to shorten. length: Maximum number of code points / bytes allowed in a node. mode: One of SHORTEN_BYTES, SHORTEN_UTF16, SHORTEN_UTF16_NFD. """ shorten = lambda n, l: n and shorten_filename(n, l, mode).strip() or u"" dirpath, filename = os.path.split(path) fileroot, ext = os.path.splitext(filename) return os.path.join( os.path.join(*[shorten(node, length) for node in dirpath.split(os.path.sep)]), shorten(fileroot, length - len(ext)) + ext ) def _shorten_to_utf16_ratio(text, ratio): """Shortens the string to the given ratio (and strips it).""" length = _get_utf16_length(text) limit = max(1, int(math.floor(length / ratio))) if isinstance(text, str): return text[:limit].strip() else: return _shorten_to_utf16_length(text, limit).strip() def _make_win_short_filename(relpath, reserved=0): r"""Shorten a relative file path according to WinAPI quirks. relpath: The file's path. reserved: Number of characters reserved for the parent path to be joined with, e.g. 3 if it will be joined with "X:\", respectively 5 for "X:\y\". (note the inclusion of the final backslash) """ # See: # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx # # The MAX_PATH is 260 characters, with this possible format for a file: # "X:\<244-char dir path>\<11-char filename>". # Our constraints: # the entire path's length MAX_FILEPATH_LEN = 259 # the entire parent directory path's length, *excluding* the final separator MAX_DIRPATH_LEN = 247 # a single node's length (this seems to be the case for older NTFS) MAX_NODE_LEN = 226 # to make predictable directory paths we need to fit the directories in # MAX_DIRPATH_LEN, and truncate the filename to whatever's left remaining = MAX_DIRPATH_LEN - reserved # to make things more readable... shorten = lambda p, l: shorten_path(p, l, mode=SHORTEN_UTF16) xlength = _get_utf16_length # shorten to MAX_NODE_LEN from the beginning relpath = shorten(relpath, MAX_NODE_LEN) dirpath, filename = os.path.split(relpath) # what if dirpath is already the right size? dplen = xlength(dirpath) if dplen <= remaining: filename_max = MAX_FILEPATH_LEN - (reserved + dplen + 1) # the final separator filename = shorten(filename, filename_max) return os.path.join(dirpath, filename) # compute the directory path and the maximum number of characters # in a filename, and cache them try: computed = _make_win_short_filename._computed except AttributeError: computed = _make_win_short_filename._computed = {} try: finaldirpath, filename_max = computed[(dirpath, reserved)] except KeyError: dirnames = dirpath.split(os.path.sep) # allocate space for the separators, # but don't include the final one remaining -= len(dirnames) - 1 # make sure we can have at least single-character dirnames average = float(remaining) / len(dirnames) if average < 1: # TODO: a nice exception raise IOError("Path too long. You need to move renamed files \ to a different directory.") # try to reduce directories exceeding average with a ratio proportional # to how much they exceed with; if not possible, reduce all dirs # proportionally to their initial length shortdirnames = [dn for dn in dirnames if len(dn) <= average] totalchars = sum(map(xlength, dirnames)) shortdirchars = sum(map(xlength, shortdirnames)) # do we have at least 1 char for longdirs? if remaining > shortdirchars + len(dirnames) - len(shortdirnames): ratio = float(totalchars - shortdirchars) / (remaining - shortdirchars) for i, dn in enumerate(dirnames): if len(dn) > average: dirnames[i] = _shorten_to_utf16_ratio(dn, ratio) else: ratio = float(totalchars) / remaining dirnames = [_shorten_to_utf16_ratio(dn, ratio) for dn in dirnames] # here it is: finaldirpath = os.path.join(*dirnames) # did we win back some chars from .floor()s and .strip()s? recovered = remaining - sum(map(xlength, dirnames)) # so how much do we have left for the filename? filename_max = MAX_FILEPATH_LEN - MAX_DIRPATH_LEN - 1 + recovered # ^ the final separator # and don't forget to cache computed[(dirpath, reserved)] = (finaldirpath, filename_max) # finally... filename = shorten(filename, filename_max) return os.path.join(finaldirpath, filename) def _get_mount_point(target): """Finds the target's mountpoint.""" # and caches it for future lookups try: mounts = _get_mount_point._mounts except AttributeError: mounts = _get_mount_point._mounts = {} try: mount = mounts[target] except KeyError: mount = target while mount and not os.path.ismount(mount): mount = os.path.dirname(mount) mounts[target] = mount return mount # NOTE: this could be merged with the function above, and get all needed info # in a single call, returning the filesystem type as well. (but python's # posix.statvfs_result doesn't implement f_fsid) def _get_filename_limit(target): """Finds the maximum filename length under the given directory.""" # and caches it try: limits = _get_filename_limit._limits except AttributeError: limits = _get_filename_limit._limits = {} try: limit = limits[target] except KeyError: # we need to call statvfs on an existing target d = target while not os.path.exists(d): d = os.path.dirname(d) # XXX http://bugs.python.org/issue18695 try: limit = os.statvfs(d).f_namemax except UnicodeEncodeError: limit = os.statvfs(d.encode(_io_encoding)).f_namemax limits[target] = limit return limit def make_short_filename(basedir, relpath, win_compat=False, relative_to=""): """Shorten a filename's path to proper limits. basedir: Absolute path of the base directory where files will be moved. relpath: File path, relative from the base directory. win_compat: Windows is quirky. relative_to: An ancestor directory of basedir, against which win_compat will be applied. """ # only deal with absolute paths. it saves a lot of grief, # and is the right thing to do, even for renames. basedir = os.path.abspath(basedir) # also, make sure the relative path is clean relpath = os.path.normpath(relpath) if win_compat and relative_to: relative_to = os.path.abspath(relative_to) assert basedir.startswith(relative_to) and \ basedir.split(relative_to)[1][:1] in (os.path.sep, ''), \ "`relative_to` must be an ancestor of `basedir`" # always strip the relpath parts relpath = os.path.join(*[part.strip() for part in relpath.split(os.path.sep)]) # if we're on windows, delegate the work to a windows-specific function if sys.platform == "win32": reserved = len(basedir) if not basedir.endswith(os.path.sep): reserved += 1 return _make_win_short_filename(relpath, reserved) # if we're being windows compatible, figure out how much # needs to be reserved for the basedir part if win_compat: # if a relative ancestor wasn't provided, # use the basedir's mount point if not relative_to: relative_to = _get_mount_point(basedir) # if it's root, presume the parent will be copied over # to windows, and hope for the best if relative_to == os.path.sep: relative_to = os.path.dirname(basedir) reserved = len(basedir) - len(relative_to) + 3 + 1 # the drive name ^ + ^ the final separator relpath = _make_win_short_filename(relpath, reserved) # on *nix we can consider there is no path limit, but there is # a filename length limit. if sys.platform == "darwin": # on OS X (i.e. HFS+), this is expressed in UTF-16 code points, # in NFD normalization form relpath = shorten_path(relpath, 255, mode=SHORTEN_UTF16_NFD) else: # on everything else the limit is expressed in bytes, # and filesystem-dependent limit = _get_filename_limit(basedir) relpath = shorten_path(relpath, limit, mode=SHORTEN_BYTES) return relpath picard-release-1.4.2/picard/util/icontheme.py000066400000000000000000000046261310410472100211430ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import sys import os.path from PyQt4 import QtGui if sys.platform == 'win32': _search_paths = [] else: _search_paths = [ os.path.expanduser('~/.icons'), os.path.join(os.environ.get('XDG_DATA_DIRS', '/usr/share'), 'icons'), '/usr/share/pixmaps', ] _current_theme = None if 'XDG_CURRENT_DESKTOP' in os.environ: desktop = os.environ['XDG_CURRENT_DESKTOP'].lower() if desktop in ('gnome', 'unity'): _current_theme = (os.popen('gconftool-2 -g /desktop/gnome/interface/icon_theme').read().strip() or os.popen('dconf read /org/gnome/desktop/interface/icon-theme').read().strip()[1:-1] or None) elif os.environ.get('KDE_FULL_SESSION'): _current_theme = (os.popen("kreadconfig --file kdeglobals --group Icons --key Theme --default crystalsvg").read().strip() or None) ICON_SIZE_MENU = ('16x16',) ICON_SIZE_TOOLBAR = ('22x22',) ICON_SIZE_ALL = ('22x22', '16x16') def lookup(name, size=ICON_SIZE_ALL): icon = QtGui.QIcon() if _current_theme: for path in _search_paths: for subdir in ('actions', 'places', 'devices'): fullpath = os.path.join(path, _current_theme, size[0], subdir, name) if os.path.exists(fullpath + '.png'): icon.addFile(fullpath + '.png') for s in size[1:]: icon.addFile(os.path.join(path, _current_theme, s, subdir, name) + '.png') return icon for s in size: icon.addFile('/'.join([':', 'images', s, name]) + '.png') return icon picard-release-1.4.2/picard/util/imageinfo.py000066400000000000000000000074641310410472100211310ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2014 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import StringIO import struct class IdentificationError(Exception): pass class NotEnoughData(IdentificationError): pass class UnrecognizedFormat(IdentificationError): pass class UnexpectedError(IdentificationError): pass def identify(data): """Parse data for jpg, gif, png metadata If successfully recognized, it returns a tuple with: - width - height - mimetype - extension - data length Exceptions: - `NotEnoughData` if data has less than 16 bytes. - `UnrecognizedFormat` if data isn't recognized as a known format. - `UnexpectedError` if unhandled cases (shouldn't happen). - `IdentificationError` is parent class for all preceding exceptions. """ datalen = len(data) if datalen < 16: raise NotEnoughData('Not enough data') w = -1 h = -1 mime = '' extension = '' # http://en.wikipedia.org/wiki/Graphics_Interchange_Format if data[:6] in ('GIF87a', 'GIF89a'): w, h = struct.unpack('LL', data[16:24]) mime = 'image/png' extension = '.png' # http://en.wikipedia.org/wiki/JPEG elif data[:2] == '\xFF\xD8': # Start Of Image (SOI) marker jpeg = StringIO.StringIO(data) # skip SOI jpeg.read(2) b = jpeg.read(1) try: while (b and ord(b) != 0xDA): # Start Of Scan (SOS) while (ord(b) != 0xFF): b = jpeg.read(1) while (ord(b) == 0xFF): b = jpeg.read(1) if ord(b) in (0xC0, 0xC1, 0xC2, 0xC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF): jpeg.read(2) # parameter length (2 bytes) jpeg.read(1) # data precision (1 byte) h, w = struct.unpack('>HH', jpeg.read(4)) mime = 'image/jpeg' extension = '.jpg' break else: # read 2 bytes as integer length = int(struct.unpack('>H', jpeg.read(2))[0]) # skip data jpeg.read(length - 2) b = jpeg.read(1) except struct.error: pass except ValueError: pass # PDF elif data[:4] == '%PDF': h, w = 0, 0 mime = 'application/pdf' extension = '.pdf' else: raise UnrecognizedFormat('Unrecognized image data') # this shouldn't happen if w == -1 or h == -1 or mime == '' or extension == '': raise UnexpectedError("Unexpected error: w=%d h=%d mime=%s extension=%s" % (w, h, mime, extension)) return (int(w), int(h), mime, extension, datalen) picard-release-1.4.2/picard/util/imagelist.py000066400000000000000000000073561310410472100211510ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2017 Antonio Larrosa # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. def get_image_type(image): return image.types_as_string() class ImageList(list): def __eq__(self, other): return sorted(self, key=get_image_type) == sorted(other, key=get_image_type) def __getslice__(self, i, j): length = len(self) i = max(0, min(i, length)) j = max(0, min(j, length)) return ImageList([self[it] for it in range(i, j)]) def _process_images(state, src_obj): from picard.track import Track # Check new images if state.update_new_metadata: if state.first_new_obj: state.new_images = src_obj.metadata.images[:] state.first_new_obj = False else: if state.new_images != src_obj.metadata.images: state.has_common_new_images = False state.new_images.extend([image for image in src_obj.metadata.images if image not in state.new_images]) if state.update_orig_metadata and not isinstance(src_obj, Track): # Check orig images, but not for Tracks (which don't have a useful orig_metadata) if state.first_orig_obj: state.orig_images = src_obj.orig_metadata.images[:] state.first_orig_obj = False else: if state.orig_images != src_obj.orig_metadata.images: state.has_common_orig_images = False state.orig_images.extend([image for image in src_obj.orig_metadata.images if image not in state.orig_images]) class State: def __init__(self): self.new_images = ImageList() self.orig_images = ImageList() self.has_common_new_images = True self.has_common_orig_images = True self.first_new_obj = True self.first_orig_obj = True # The next variables specify what will be updated self.update_new_metadata = False self.update_orig_metadata = False # TODO: use functools.singledispatch when py3 is supported def update_metadata_images(obj): from picard.track import Track from picard.cluster import Cluster from picard.album import Album state = State() if isinstance(obj, Album): sources = [] for track in obj.tracks: sources.append(track) sources += track.linked_files sources += obj.unmatched_files.files state.update_new_metadata = True state.update_orig_metadata = True elif isinstance(obj, Track): sources = obj.linked_files state.update_orig_metadata = True elif isinstance(obj, Cluster): sources = obj.files state.update_new_metadata = True for src_obj in sources: _process_images(state, src_obj) if state.update_new_metadata: obj.metadata.images = state.new_images obj.metadata.has_common_images = state.has_common_new_images if state.update_orig_metadata: obj.orig_metadata.images = state.orig_images obj.orig_metadata.has_common_images = state.has_common_orig_images picard-release-1.4.2/picard/util/lrucache.py000066400000000000000000000054021310410472100207470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2017 Antonio Larrosa # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class LRUCache(dict): """ Helper class to cache items using a Least Recently Used policy. It's originally used to cache generated pixmaps in the CoverArtBox object but it's generic enough to be used for other purposes if necessary. The cache will never hold more than max_size items and the item least recently used will be discarded. >>> cache = LRUCache(3) >>> cache['item1'] = 'some value' >>> cache['item2'] = 'some other value' >>> cache['item3'] = 'yet another value' >>> cache['item1'] 'some value' >>> cache['item4'] = 'This will push item 2 out of the cache' >>> cache['item2'] Traceback (most recent call last): File "", line 1, in File "lrucache.py", line 48, in __getitem__ return super(LRUCache, self).__getitem__(key) KeyError: 'item2' >>> cache['item5'] = 'This will push item3 out of the cache' >>> cache['item3'] Traceback (most recent call last): File "", line 1, in File "lrucache.py", line 48, in __getitem__ return super(LRUCache, self).__getitem__(key) KeyError: 'item3' >>> cache['item1'] 'some value' """ def __init__(self, max_size): self._ordered_keys = [] self._max_size = max_size def __getitem__(self, key): if key in self: self._ordered_keys.remove(key) self._ordered_keys.insert(0, key) return super(LRUCache, self).__getitem__(key) def __setitem__(self, key, value): if key in self: self._ordered_keys.remove(key) self._ordered_keys.insert(0, key) r = super(LRUCache, self).__setitem__(key, value) if len(self) > self._max_size: item = self._ordered_keys.pop() super(LRUCache, self).__delitem__(item) return r def __delitem__(self, key): self._ordered_keys.remove(key) super(LRUCache, self).__delitem__(key) picard-release-1.4.2/picard/util/tags.py000066400000000000000000000077651310410472100201350ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. TAG_NAMES = { 'album': N_('Album'), 'artist': N_('Artist'), 'title': N_('Title'), 'date': N_('Date'), 'originaldate': N_('Original Release Date'), 'originalyear': N_('Original Year'), 'albumartist': N_('Album Artist'), 'tracknumber': N_('Track Number'), 'totaltracks': N_('Total Tracks'), 'discnumber': N_('Disc Number'), 'totaldiscs': N_('Total Discs'), 'albumartistsort': N_('Album Artist Sort Order'), 'artistsort': N_('Artist Sort Order'), 'titlesort': N_('Title Sort Order'), 'albumsort': N_('Album Sort Order'), 'composersort': N_('Composer Sort Order'), 'asin': N_('ASIN'), 'grouping': N_('Grouping'), 'isrc': N_('ISRC'), 'mood': N_('Mood'), 'bpm': N_('BPM'), 'key': N_('Key'), 'copyright': N_('Copyright'), 'license': N_('License'), 'composer': N_('Composer'), 'writer': N_('Writer'), 'conductor': N_('Conductor'), 'lyricist': N_('Lyricist'), 'arranger': N_('Arranger'), 'producer': N_('Producer'), 'engineer': N_('Engineer'), 'subtitle': N_('Subtitle'), 'discsubtitle': N_('Disc Subtitle'), 'remixer': N_('Remixer'), 'musicbrainz_recordingid': N_('MusicBrainz Recording Id'), 'musicbrainz_trackid': N_('MusicBrainz Track Id'), 'musicbrainz_albumid': N_('MusicBrainz Release Id'), 'musicbrainz_artistid': N_('MusicBrainz Artist Id'), 'musicbrainz_albumartistid': N_('MusicBrainz Release Artist Id'), 'musicbrainz_workid': N_('MusicBrainz Work Id'), 'musicbrainz_releasegroupid': N_('MusicBrainz Release Group Id'), 'musicbrainz_discid': N_('MusicBrainz Disc Id'), 'musicip_puid': N_('MusicIP PUID'), 'musicip_fingerprint': N_('MusicIP Fingerprint'), 'acoustid_id': N_('AcoustID'), 'acoustid_fingerprint': N_('AcoustID Fingerprint'), 'discid': N_('Disc Id'), 'website': N_('Artist Website'), 'compilation': N_('Compilation (iTunes)'), 'comment:': N_('Comment'), 'genre': N_('Genre'), 'encodedby': N_('Encoded By'), 'encodersettings': N_('Encoder Settings'), 'performer:': N_('Performer'), 'releasetype': N_('Release Type'), 'releasestatus': N_('Release Status'), 'releasecountry': N_('Release Country'), 'label': N_('Record Label'), 'barcode': N_('Barcode'), 'catalognumber': N_('Catalog Number'), 'djmixer': N_('DJ-Mixer'), 'media': N_('Media'), 'lyrics': N_('Lyrics'), 'mixer': N_('Mixer'), 'language': N_('Language'), 'script': N_('Script'), '~length': N_('Length'), '~rating': N_('Rating'), 'artists': N_('Artists'), 'work': N_('Work'), 'originalartist': N_('Original Artist'), 'musicbrainz_originalartistid': N_('MusicBrainz Original Artist Id'), 'originalalbum': N_('Original Album'), 'musicbrainz_originalalbumid': N_('MusicBrainz Orignal Release Id'), } PRESERVED_TAGS = [ "~bitrate", "~bits_per_sample", "~format", "~channels", "~sample_rate", "~dirname", "~filename", "~extension", ] def display_tag_name(name): desc = '' if ':' in name: name, desc = name.split(':', 1) name = TAG_NAMES.get(name + ':', TAG_NAMES.get(name, name)) if desc: return '%s [%s]' % (_(name), desc) else: return _(name) picard-release-1.4.2/picard/util/textencoding.py000066400000000000000000000616411310410472100216630ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2004 Robert Kaye # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # This modules provides functionality for simplifying unicode strings. # The unicode character set (of over 1m codepoints and 24,000 characters) includes: # Normal ascii (latin) non-accented characters # Combined latin characters e.g. ae in normal usage # Compatibility combined latin characters (retained for compatibility with other character sets) # These can look very similar to normal characters and can be confusing for searches, sort orders etc. # Non-latin (e.g. japanese, greek, hebrew etc.) characters # Both latin and non-latin characters can be accented. Accents can be either: # Provided by separate nonspacing_mark characters which are visually overlaid (visually 1 character is actually 2); or # Integrated accented characters (i.e. non-accented characters combined with a nonspace_mark into a single character) # Again these can be confusing for searches, sort orders etc. # Punctuation can also be confusing in unicode e.g. several types of single or double quote mark. # For latin script: # Combined characters, accents and punctuation can be visually similar but look different to search engines, # sort orders etc. and the number of ways to use similar looking characters can (does) result in inconsistent # usage inside Music metadata. # # Simplifying # the unicode character sets by many-to-one mappings can improve consistency and reduce confusion, # however sometimes the choice of specific characters can be a deliberate part of an album, song title or artist name # (and should not therefore be changed without careful thought) and occasionally the choice of characters can be # malicious (i.e. to defeat firewalls or spam filters or to appear to be something else). # # Finally, given the size of the unicode character set, fonts are unlikely to display all characters, # making simplification a necessity. # # Simplification may also be needed to make tags conform to ISO-8859-1 (extended ascii) or to make tags or filenames # into ascii, perhaps because the file system or player cannot support unicode. # # Non-latin scripts may also need to be converted to latin scripts through: # Translation (e.g. hebrew word for mother is translated to "mother"); or # Transliteration (e.g. the SOUND of the hebrew letter or word is spelt out in latin) # These are non-trivial, and the software to do these is far from comprehensive. # This module provides utility functions to enable simplification of latin and punctuation unicode: # 1. simplify compatibility characters; # 2. split combined characters; # 3. remove accents (entirely or if not in ISO-8859-1 as applicable); # 4. replace remaining non-ascii or non-ISO-8859-1 characters with a default character # This module also provides an extension infrastructure to allow translation and / or transliteration plugins to be added. import re import unicodedata import codecs from functools import partial ######################### LATIN SIMPLIFICATION ########################### # The translation tables for punctuation and latin combined-characters are taken from # http://unicode.org/repos/cldr/trunk/common/transforms/Latin-ASCII.xml # Various bugs and mistakes in this have been ironed out during testing. def _re_any(iterable): return re.compile('([' + ''.join(iterable) + '])', re.UNICODE) _additional_compatibility = { u"\u0276": u"Œ", # LATIN LETTER SMALL CAPITAL OE u"\u1D00": u"A", # LATIN LETTER SMALL CAPITAL A u"\u1D01": u"Æ", # LATIN LETTER SMALL CAPITAL AE u"\u1D04": u"C", # LATIN LETTER SMALL CAPITAL C u"\u1D05": u"D", # LATIN LETTER SMALL CAPITAL D u"\u1D07": u"E", # LATIN LETTER SMALL CAPITAL E u"\u1D0A": u"J", # LATIN LETTER SMALL CAPITAL J u"\u1D0B": u"K", # LATIN LETTER SMALL CAPITAL K u"\u1D0D": u"M", # LATIN LETTER SMALL CAPITAL M u"\u1D0F": u"O", # LATIN LETTER SMALL CAPITAL O u"\u1D18": u"P", # LATIN LETTER SMALL CAPITAL P u"\u1D1B": u"T", # LATIN LETTER SMALL CAPITAL T u"\u1D1C": u"U", # LATIN LETTER SMALL CAPITAL U u"\u1D20": u"V", # LATIN LETTER SMALL CAPITAL V u"\u1D21": u"W", # LATIN LETTER SMALL CAPITAL W u"\u1D22": u"Z", # LATIN LETTER SMALL CAPITAL Z u"\u3007": u"0", # IDEOGRAPHIC NUMBER ZERO u"\u00A0": u" ", # NO-BREAK SPACE u"\u3000": u" ", # IDEOGRAPHIC SPACE (from ‹character-fallback›) u"\u2033": u"”", # DOUBLE PRIME } _re_additional_compatibility = _re_any(_additional_compatibility.keys()) def unicode_simplify_compatibility(string): interim = _re_additional_compatibility.sub(lambda m: _additional_compatibility[m.group(0)], string) return unicodedata.normalize("NFKC", interim) _simplify_punctuation = { u"\u013F": u"L", # LATIN CAPITAL LETTER L WITH MIDDLE DOT (compat) u"\u0140": u"l", # LATIN SMALL LETTER L WITH MIDDLE DOT (compat) u"\u2018": u"'", # LEFT SINGLE QUOTATION MARK (from ‹character-fallback›) u"\u2019": u"'", # RIGHT SINGLE QUOTATION MARK (from ‹character-fallback›) u"\u201A": u"'", # SINGLE LOW-9 QUOTATION MARK (from ‹character-fallback›) u"\u201B": u"'", # SINGLE HIGH-REVERSED-9 QUOTATION MARK (from ‹character-fallback›) u"\u201C": u"\"", # LEFT DOUBLE QUOTATION MARK (from ‹character-fallback›) u"\u201D": u"\"", # RIGHT DOUBLE QUOTATION MARK (from ‹character-fallback›) u"\u201E": u"\"", # DOUBLE LOW-9 QUOTATION MARK (from ‹character-fallback›) u"\u201F": u"\"", # DOUBLE HIGH-REVERSED-9 QUOTATION MARK (from ‹character-fallback›) u"\u2032": u"'", # PRIME u"\u2033": u"\"", # DOUBLE PRIME u"\u301D": u"\"", # REVERSED DOUBLE PRIME QUOTATION MARK u"\u301E": u"\"", # DOUBLE PRIME QUOTATION MARK u"\u00AB": u"<<", # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK (from ‹character-fallback›) u"\u00BB": u">>", # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (from ‹character-fallback›) u"\u2039": u"<", # SINGLE LEFT-POINTING ANGLE QUOTATION MARK u"\u203A": u">", # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK u"\u00AD": u"", # SOFT HYPHEN (from ‹character-fallback›) u"\u2010": u"-", # HYPHEN (from ‹character-fallback›) u"\u2011": u"-", # NON-BREAKING HYPHEN (from ‹character-fallback›) u"\u2012": u"-", # FIGURE DASH (from ‹character-fallback›) u"\u2013": u"-", # EN DASH (from ‹character-fallback›) u"\u2014": u"-", # EM DASH (from ‹character-fallback›) u"\u2015": u"-", # HORIZONTAL BAR (from ‹character-fallback›) u"\uFE31": u"|", # PRESENTATION FORM FOR VERTICAL EM DASH (compat) u"\uFE32": u"|", # PRESENTATION FORM FOR VERTICAL EN DASH (compat) u"\uFE58": u"-", # SMALL EM DASH (compat) u"\u2016": u"||", # DOUBLE VERTICAL LINE u"\u2044": u"/", # FRACTION SLASH (from ‹character-fallback›) u"\u2045": u"[", # LEFT SQUARE BRACKET WITH QUILL u"\u2046": u"]", # RIGHT SQUARE BRACKET WITH QUILL u"\u204E": u"*", # LOW ASTERISK u"\u3008": u"<", # LEFT ANGLE BRACKET u"\u3009": u">", # RIGHT ANGLE BRACKET u"\u300A": u"<<", # LEFT DOUBLE ANGLE BRACKET u"\u300B": u">>", # RIGHT DOUBLE ANGLE BRACKET u"\u3014": u"[", # LEFT TORTOISE SHELL BRACKET u"\u3015": u"]", # RIGHT TORTOISE SHELL BRACKET u"\u3018": u"[", # LEFT WHITE TORTOISE SHELL BRACKET u"\u3019": u"]", # RIGHT WHITE TORTOISE SHELL BRACKET u"\u301A": u"[", # LEFT WHITE SQUARE BRACKET u"\u301B": u"]", # RIGHT WHITE SQUARE BRACKET u"\uFE11": u",", # PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC COMMA (compat) u"\uFE12": u".", # PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC FULL STOP (compat) u"\uFE39": u"[", # PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET (compat) u"\uFE3A": u"]", # PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET (compat) u"\uFE3D": u"<<", # PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET (compat) u"\uFE3E": u">>", # PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET (compat) u"\uFE3F": u"<", # PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET (compat) u"\uFE40": u">", # PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET (compat) u"\uFE51": u",", # SMALL IDEOGRAPHIC COMMA (compat) u"\uFE5D": u"[", # SMALL LEFT TORTOISE SHELL BRACKET (compat) u"\uFE5E": u"]", # SMALL RIGHT TORTOISE SHELL BRACKET (compat) u"\uFF5F": u"((", # FULLWIDTH LEFT WHITE PARENTHESIS (compat)(from ‹character-fallback›) u"\uFF60": u"))", # FULLWIDTH RIGHT WHITE PARENTHESIS (compat)(from ‹character-fallback›) u"\uFF61": u".", # HALFWIDTH IDEOGRAPHIC FULL STOP (compat) u"\uFF64": u",", # HALFWIDTH IDEOGRAPHIC COMMA (compat) u"\u2212": u"-", # MINUS SIGN (from ‹character-fallback›) u"\u2215": u"/", # DIVISION SLASH (from ‹character-fallback›) u"\u2216": u"\\", # SET MINUS (from ‹character-fallback›) u"\u2223": u"|", # DIVIDES (from ‹character-fallback›) u"\u2225": u"||", # PARALLEL TO (from ‹character-fallback›) u"\u226A": u"<<", # MUCH LESS-THAN u"\u226B": u">>", # MUCH GREATER-THAN u"\u2985": u"((", # LEFT WHITE PARENTHESIS u"\u2986": u"))", # RIGHT WHITE PARENTHESIS u"\u200B": u"", # Zero Width Space } _re_simplify_punctuation = _re_any(_simplify_punctuation.keys()) def unicode_simplify_punctuation(string): return _re_simplify_punctuation.sub(lambda m: _simplify_punctuation[m.group(0)], string) _simplify_combinations = { u"\u00C6": u"AE", # LATIN CAPITAL LETTER AE (from ‹character-fallback›) u"\u00D0": u"D", # LATIN CAPITAL LETTER ETH u"\u00D8": u"OE", # LATIN CAPITAL LETTER O WITH STROKE (see https://en.wikipedia.org/wiki/%C3%98) u"\u00DE": u"TH", # LATIN CAPITAL LETTER THORN u"\u00DF": u"ss", # LATIN SMALL LETTER SHARP S (from ‹character-fallback›) u"\u00E6": u"ae", # LATIN SMALL LETTER AE (from ‹character-fallback›) u"\u00F0": u"d", # LATIN SMALL LETTER ETH u"\u00F8": u"oe", # LATIN SMALL LETTER O WITH STROKE (see https://en.wikipedia.org/wiki/%C3%98) u"\u00FE": u"th", # LATIN SMALL LETTER THORN u"\u0110": u"D", # LATIN CAPITAL LETTER D WITH STROKE u"\u0111": u"d", # LATIN SMALL LETTER D WITH STROKE u"\u0126": u"H", # LATIN CAPITAL LETTER H WITH STROKE u"\u0127": u"h", # LATIN CAPITAL LETTER H WITH STROKE u"\u0131": u"i", # LATIN SMALL LETTER DOTLESS I u"\u0138": u"q", # LATIN SMALL LETTER KRA (collates with q in DUCET) u"\u0141": u"L", # LATIN CAPITAL LETTER L WITH STROKE u"\u0142": u"l", # LATIN SMALL LETTER L WITH STROKE u"\u0149": u"'n", # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE (from ‹character-fallback›) u"\u014A": u"N", # LATIN CAPITAL LETTER ENG u"\u014B": u"n", # LATIN SMALL LETTER ENG u"\u0152": u"OE", # LATIN CAPITAL LIGATURE OE (from ‹character-fallback›) u"\u0153": u"oe", # LATIN SMALL LIGATURE OE (from ‹character-fallback›) u"\u0166": u"T", # LATIN CAPITAL LETTER T WITH STROKE u"\u0167": u"t", # LATIN SMALL LETTER T WITH STROKE u"\u0180": u"b", # LATIN SMALL LETTER B WITH STROKE u"\u0181": u"B", # LATIN CAPITAL LETTER B WITH HOOK u"\u0182": u"B", # LATIN CAPITAL LETTER B WITH TOPBAR u"\u0183": u"b", # LATIN SMALL LETTER B WITH TOPBAR u"\u0187": u"C", # LATIN CAPITAL LETTER C WITH HOOK u"\u0188": u"c", # LATIN SMALL LETTER C WITH HOOK u"\u0189": u"D", # LATIN CAPITAL LETTER AFRICAN D u"\u018A": u"D", # LATIN CAPITAL LETTER D WITH HOOK u"\u018B": u"D", # LATIN CAPITAL LETTER D WITH TOPBAR u"\u018C": u"d", # LATIN SMALL LETTER D WITH TOPBAR u"\u0190": u"E", # LATIN CAPITAL LETTER OPEN E u"\u0191": u"F", # LATIN CAPITAL LETTER F WITH HOOK u"\u0192": u"f", # LATIN SMALL LETTER F WITH HOOK u"\u0193": u"G", # LATIN CAPITAL LETTER G WITH HOOK u"\u0195": u"hv", # LATIN SMALL LETTER HV u"\u0196": u"I", # LATIN CAPITAL LETTER IOTA u"\u0197": u"I", # LATIN CAPITAL LETTER I WITH STROKE u"\u0198": u"K", # LATIN CAPITAL LETTER K WITH HOOK u"\u0199": u"k", # LATIN SMALL LETTER K WITH HOOK u"\u019A": u"l", # LATIN SMALL LETTER L WITH BAR u"\u019D": u"N", # LATIN CAPITAL LETTER N WITH LEFT HOOK u"\u019E": u"n", # LATIN SMALL LETTER N WITH LONG RIGHT LEG u"\u01A2": u"GH", # LATIN CAPITAL LETTER GHA (see http://unicode.org/notes/tn27/) u"\u01A3": u"gh", # LATIN SMALL LETTER GHA (see http://unicode.org/notes/tn27/) u"\u01A4": u"P", # LATIN CAPITAL LETTER P WITH HOOK u"\u01A5": u"p", # LATIN SMALL LETTER P WITH HOOK u"\u01AB": u"t", # LATIN SMALL LETTER T WITH PALATAL HOOK u"\u01AC": u"T", # LATIN CAPITAL LETTER T WITH HOOK u"\u01AD": u"t", # LATIN SMALL LETTER T WITH HOOK u"\u01AE": u"T", # LATIN CAPITAL LETTER T WITH RETROFLEX HOOK u"\u01B2": u"V", # LATIN CAPITAL LETTER V WITH HOOK u"\u01B3": u"Y", # LATIN CAPITAL LETTER Y WITH HOOK u"\u01B4": u"y", # LATIN SMALL LETTER Y WITH HOOK u"\u01B5": u"Z", # LATIN CAPITAL LETTER Z WITH STROKE u"\u01B6": u"z", # LATIN SMALL LETTER Z WITH STROKE u"\u01C4": u"DZ", # LATIN CAPITAL LETTER DZ WITH CARON (compat) u"\u01C5": u"Dz", # LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON (compat) u"\u01C6": u"dz", # LATIN SMALL LETTER DZ WITH CARON (compat) u"\u01E4": u"G", # LATIN CAPITAL LETTER G WITH STROKE u"\u01E5": u"g", # LATIN SMALL LETTER G WITH STROKE u"\u0221": u"d", # LATIN SMALL LETTER D WITH CURL u"\u0224": u"Z", # LATIN CAPITAL LETTER Z WITH HOOK u"\u0225": u"z", # LATIN SMALL LETTER Z WITH HOOK u"\u0234": u"l", # LATIN SMALL LETTER L WITH CURL u"\u0235": u"n", # LATIN SMALL LETTER N WITH CURL u"\u0236": u"t", # LATIN SMALL LETTER T WITH CURL u"\u0237": u"j", # LATIN SMALL LETTER DOTLESS J u"\u0238": u"db", # LATIN SMALL LETTER DB DIGRAPH u"\u0239": u"qp", # LATIN SMALL LETTER QP DIGRAPH u"\u023A": u"A", # LATIN CAPITAL LETTER A WITH STROKE u"\u023B": u"C", # LATIN CAPITAL LETTER C WITH STROKE u"\u023C": u"c", # LATIN SMALL LETTER C WITH STROKE u"\u023D": u"L", # LATIN CAPITAL LETTER L WITH BAR u"\u023E": u"T", # LATIN CAPITAL LETTER T WITH DIAGONAL STROKE u"\u023F": u"s", # LATIN SMALL LETTER S WITH SWASH TAIL u"\u0240": u"z", # LATIN SMALL LETTER Z WITH SWASH TAIL u"\u0243": u"B", # LATIN CAPITAL LETTER B WITH STROKE u"\u0244": u"U", # LATIN CAPITAL LETTER U BAR u"\u0246": u"E", # LATIN CAPITAL LETTER E WITH STROKE u"\u0247": u"e", # LATIN SMALL LETTER E WITH STROKE u"\u0248": u"J", # LATIN CAPITAL LETTER J WITH STROKE u"\u0249": u"j", # LATIN SMALL LETTER J WITH STROKE u"\u024C": u"R", # LATIN CAPITAL LETTER R WITH STROKE u"\u024D": u"r", # LATIN SMALL LETTER R WITH STROKE u"\u024E": u"Y", # LATIN CAPITAL LETTER Y WITH STROKE u"\u024F": u"y", # LATIN SMALL LETTER Y WITH STROKE u"\u0253": u"b", # LATIN SMALL LETTER B WITH HOOK u"\u0255": u"c", # LATIN SMALL LETTER C WITH CURL u"\u0256": u"d", # LATIN SMALL LETTER D WITH TAIL u"\u0257": u"d", # LATIN SMALL LETTER D WITH HOOK u"\u025B": u"e", # LATIN SMALL LETTER OPEN E u"\u025F": u"j", # LATIN SMALL LETTER DOTLESS J WITH STROKE u"\u0260": u"g", # LATIN SMALL LETTER G WITH HOOK u"\u0261": u"g", # LATIN SMALL LETTER SCRIPT G u"\u0262": u"G", # LATIN LETTER SMALL CAPITAL G u"\u0266": u"h", # LATIN SMALL LETTER H WITH HOOK u"\u0267": u"h", # LATIN SMALL LETTER HENG WITH HOOK u"\u0268": u"i", # LATIN SMALL LETTER I WITH STROKE u"\u026A": u"I", # LATIN LETTER SMALL CAPITAL I u"\u026B": u"l", # LATIN SMALL LETTER L WITH MIDDLE TILDE u"\u026C": u"l", # LATIN SMALL LETTER L WITH BELT u"\u026D": u"l", # LATIN SMALL LETTER L WITH RETROFLEX HOOK u"\u0271": u"m", # LATIN SMALL LETTER M WITH HOOK u"\u0272": u"n", # LATIN SMALL LETTER N WITH LEFT HOOK u"\u0273": u"n", # LATIN SMALL LETTER N WITH RETROFLEX HOOK u"\u0274": u"N", # LATIN LETTER SMALL CAPITAL N u"\u0276": u"OE", # LATIN LETTER SMALL CAPITAL OE u"\u027C": u"r", # LATIN SMALL LETTER R WITH LONG LEG u"\u027D": u"r", # LATIN SMALL LETTER R WITH TAIL u"\u027E": u"r", # LATIN SMALL LETTER R WITH FISHHOOK u"\u0280": u"R", # LATIN LETTER SMALL CAPITAL R u"\u0282": u"s", # LATIN SMALL LETTER S WITH HOOK u"\u0288": u"t", # LATIN SMALL LETTER T WITH RETROFLEX HOOK u"\u0289": u"u", # LATIN SMALL LETTER U BAR u"\u028B": u"v", # LATIN SMALL LETTER V WITH HOOK u"\u028F": u"Y", # LATIN LETTER SMALL CAPITAL Y u"\u0290": u"z", # LATIN SMALL LETTER Z WITH RETROFLEX HOOK u"\u0291": u"z", # LATIN SMALL LETTER Z WITH CURL u"\u0299": u"B", # LATIN LETTER SMALL CAPITAL B u"\u029B": u"G", # LATIN LETTER SMALL CAPITAL G WITH HOOK u"\u029C": u"H", # LATIN LETTER SMALL CAPITAL H u"\u029D": u"j", # LATIN SMALL LETTER J WITH CROSSED-TAIL u"\u029F": u"L", # LATIN LETTER SMALL CAPITAL L u"\u02A0": u"q", # LATIN SMALL LETTER Q WITH HOOK u"\u02A3": u"dz", # LATIN SMALL LETTER DZ DIGRAPH u"\u02A5": u"dz", # LATIN SMALL LETTER DZ DIGRAPH WITH CURL u"\u02A6": u"ts", # LATIN SMALL LETTER TS DIGRAPH u"\u02AA": u"ls", # LATIN SMALL LETTER LS DIGRAPH u"\u02AB": u"lz", # LATIN SMALL LETTER LZ DIGRAPH u"\u1D01": u"AE", # LATIN LETTER SMALL CAPITAL AE u"\u1D03": u"B", # LATIN LETTER SMALL CAPITAL BARRED B u"\u1D06": u"D", # LATIN LETTER SMALL CAPITAL ETH u"\u1D0C": u"L", # LATIN LETTER SMALL CAPITAL L WITH STROKE u"\u1D6B": u"ue", # LATIN SMALL LETTER UE u"\u1D6C": u"b", # LATIN SMALL LETTER B WITH MIDDLE TILDE u"\u1D6D": u"d", # LATIN SMALL LETTER D WITH MIDDLE TILDE u"\u1D6E": u"f", # LATIN SMALL LETTER F WITH MIDDLE TILDE u"\u1D6F": u"m", # LATIN SMALL LETTER M WITH MIDDLE TILDE u"\u1D70": u"n", # LATIN SMALL LETTER N WITH MIDDLE TILDE u"\u1D71": u"p", # LATIN SMALL LETTER P WITH MIDDLE TILDE u"\u1D72": u"r", # LATIN SMALL LETTER R WITH MIDDLE TILDE u"\u1D73": u"r", # LATIN SMALL LETTER R WITH FISHHOOK AND MIDDLE TILDE u"\u1D74": u"s", # LATIN SMALL LETTER S WITH MIDDLE TILDE u"\u1D75": u"t", # LATIN SMALL LETTER T WITH MIDDLE TILDE u"\u1D76": u"z", # LATIN SMALL LETTER Z WITH MIDDLE TILDE u"\u1D7A": u"th", # LATIN SMALL LETTER TH WITH STRIKETHROUGH u"\u1D7B": u"I", # LATIN SMALL CAPITAL LETTER I WITH STROKE u"\u1D7D": u"p", # LATIN SMALL LETTER P WITH STROKE u"\u1D7E": u"U", # LATIN SMALL CAPITAL LETTER U WITH STROKE u"\u1D80": u"b", # LATIN SMALL LETTER B WITH PALATAL HOOK u"\u1D81": u"d", # LATIN SMALL LETTER D WITH PALATAL HOOK u"\u1D82": u"f", # LATIN SMALL LETTER F WITH PALATAL HOOK u"\u1D83": u"g", # LATIN SMALL LETTER G WITH PALATAL HOOK u"\u1D84": u"k", # LATIN SMALL LETTER K WITH PALATAL HOOK u"\u1D85": u"l", # LATIN SMALL LETTER L WITH PALATAL HOOK u"\u1D86": u"m", # LATIN SMALL LETTER M WITH PALATAL HOOK u"\u1D87": u"n", # LATIN SMALL LETTER N WITH PALATAL HOOK u"\u1D88": u"p", # LATIN SMALL LETTER P WITH PALATAL HOOK u"\u1D89": u"r", # LATIN SMALL LETTER R WITH PALATAL HOOK u"\u1D8A": u"s", # LATIN SMALL LETTER S WITH PALATAL HOOK u"\u1D8C": u"v", # LATIN SMALL LETTER V WITH PALATAL HOOK u"\u1D8D": u"x", # LATIN SMALL LETTER X WITH PALATAL HOOK u"\u1D8E": u"z", # LATIN SMALL LETTER Z WITH PALATAL HOOK u"\u1D8F": u"a", # LATIN SMALL LETTER A WITH RETROFLEX HOOK u"\u1D91": u"d", # LATIN SMALL LETTER D WITH HOOK AND TAIL u"\u1D92": u"e", # LATIN SMALL LETTER E WITH RETROFLEX HOOK u"\u1D93": u"e", # LATIN SMALL LETTER OPEN E WITH RETROFLEX HOOK u"\u1D96": u"i", # LATIN SMALL LETTER I WITH RETROFLEX HOOK u"\u1D99": u"u", # LATIN SMALL LETTER U WITH RETROFLEX HOOK u"\u1E9A": u"a", # LATIN SMALL LETTER A WITH RIGHT HALF RING u"\u1E9C": u"s", # LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE u"\u1E9D": u"s", # LATIN SMALL LETTER LONG S WITH HIGH STROKE u"\u1E9E": u"SS", # LATIN CAPITAL LETTER SHARP S u"\u1EFA": u"LL", # LATIN CAPITAL LETTER MIDDLE-WELSH LL u"\u1EFB": u"ll", # LATIN SMALL LETTER MIDDLE-WELSH LL u"\u1EFC": u"V", # LATIN CAPITAL LETTER MIDDLE-WELSH V u"\u1EFD": u"v", # LATIN SMALL LETTER MIDDLE-WELSH V u"\u1EFE": u"Y", # LATIN CAPITAL LETTER Y WITH LOOP u"\u1EFF": u"y", # LATIN SMALL LETTER Y WITH LOOP u"\u00A9": u"(C)", # COPYRIGHT SIGN (from ‹character-fallback›) u"\u00AE": u"(R)", # REGISTERED SIGN (from ‹character-fallback›) u"\u20A0": u"CE", # EURO-CURRENCY SIGN (from ‹character-fallback›) u"\u20A2": u"Cr", # CRUZEIRO SIGN (from ‹character-fallback›) u"\u20A3": u"Fr.", # FRENCH FRANC SIGN (from ‹character-fallback›) u"\u20A4": u"L.", # LIRA SIGN (from ‹character-fallback›) u"\u20A7": u"Pts", # PESETA SIGN (from ‹character-fallback›) u"\u20BA": u"TL", # TURKISH LIRA SIGN (from ‹character-fallback›) u"\u20B9": u"Rs", # INDIAN RUPEE SIGN (from ‹character-fallback›) u"\u211E": u"Rx", # PRESCRIPTION TAKE (from ‹character-fallback›) u"\u33A7": u"m/s", # SQUARE M OVER S (compat) (from ‹character-fallback›) u"\u33AE": u"rad/s", # SQUARE RAD OVER S (compat) (from ‹character-fallback›) u"\u33C6": u"C/kg", # SQUARE C OVER KG (compat) (from ‹character-fallback›) u"\u33DE": u"V/m", # SQUARE V OVER M (compat) (from ‹character-fallback›) u"\u33DF": u"A/m", # SQUARE A OVER M (compat) (from ‹character-fallback›) u"\u00BC": u" 1/4", # VULGAR FRACTION ONE QUARTER (from ‹character-fallback›) u"\u00BD": u" 1/2", # VULGAR FRACTION ONE HALF (from ‹character-fallback›) u"\u00BE": u" 3/4", # VULGAR FRACTION THREE QUARTERS (from ‹character-fallback›) u"\u2153": u" 1/3", # VULGAR FRACTION ONE THIRD (from ‹character-fallback›) u"\u2154": u" 2/3", # VULGAR FRACTION TWO THIRDS (from ‹character-fallback›) u"\u2155": u" 1/5", # VULGAR FRACTION ONE FIFTH (from ‹character-fallback›) u"\u2156": u" 2/5", # VULGAR FRACTION TWO FIFTHS (from ‹character-fallback›) u"\u2157": u" 3/5", # VULGAR FRACTION THREE FIFTHS (from ‹character-fallback›) u"\u2158": u" 4/5", # VULGAR FRACTION FOUR FIFTHS (from ‹character-fallback›) u"\u2159": u" 1/6", # VULGAR FRACTION ONE SIXTH (from ‹character-fallback›) u"\u215A": u" 5/6", # VULGAR FRACTION FIVE SIXTHS (from ‹character-fallback›) u"\u215B": u" 1/8", # VULGAR FRACTION ONE EIGHTH (from ‹character-fallback›) u"\u215C": u" 3/8", # VULGAR FRACTION THREE EIGHTHS (from ‹character-fallback›) u"\u215D": u" 5/8", # VULGAR FRACTION FIVE EIGHTHS (from ‹character-fallback›) u"\u215E": u" 7/8", # VULGAR FRACTION SEVEN EIGHTHS (from ‹character-fallback›) u"\u215F": u" 1/", # FRACTION NUMERATOR ONE (from ‹character-fallback›) u"\u3001": u",", # IDEOGRAPHIC COMMA u"\u3002": u".", # IDEOGRAPHIC FULL STOP u"\u00D7": u"x", # MULTIPLICATION SIGN u"\u00F7": u"/", # DIVISION SIGN u"\u00B7": u".", # MIDDLE DOT u"\u1E9F": u"dd", # LATIN SMALL LETTER DELTA u"\u0184": u"H", # LATIN CAPITAL LETTER TONE SIX u"\u0185": u"h", # LATIN SMALL LETTER TONE SIX u"\u01BE": u"ts", # LATIN LETTER TS LIGATION (see http://unicode.org/notes/tn27/) } _re_simplify_combinations = _re_any(_simplify_combinations) def unicode_simplify_combinations(string): return _re_simplify_combinations.sub(lambda m: _simplify_combinations[m.group(0)], string) def unicode_simplify_accents(string): result = ''.join(c for c in unicodedata.normalize('NFKD', string) if not unicodedata.combining(c)) return result def asciipunct(string): interim = unicode_simplify_compatibility(string) return unicode_simplify_punctuation(interim) def unaccent(string): """Remove accents ``string``.""" return unicode_simplify_accents(string) def replace_non_ascii(string, repl="_"): """Replace non-ASCII characters from ``string`` by ``repl``.""" interim = unicode_simplify_combinations(string) interim = unicode_simplify_accents(interim) interim = unicode_simplify_punctuation(interim) interim = unicode_simplify_compatibility(interim) def error_repl(e, repl=u"_"): return(repl, e.start + 1) codecs.register_error('repl', partial(error_repl, repl=unicode(repl))) return interim.encode('ascii', 'repl') picard-release-1.4.2/picard/util/thread.py000066400000000000000000000036741310410472100204410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import sys import traceback from PyQt4.QtCore import QRunnable, QCoreApplication, QEvent class ProxyToMainEvent(QEvent): def __init__(self, func, *args, **kwargs): QEvent.__init__(self, QEvent.User) self.func = func self.args = args self.kwargs = kwargs def run(self): self.func(*self.args, **self.kwargs) class Runnable(QRunnable): def __init__(self, func, next): QRunnable.__init__(self) self.func = func self.next = next def run(self): try: result = self.func() except: from picard import log log.error(traceback.format_exc()) to_main(self.next, error=sys.exc_info()[1]) else: to_main(self.next, result=result) def run_task(func, next, priority=0, thread_pool=None): if thread_pool is None: thread_pool = QCoreApplication.instance().thread_pool thread_pool.start(Runnable(func, next), priority) def to_main(func, *args, **kwargs): QCoreApplication.postEvent(QCoreApplication.instance(), ProxyToMainEvent(func, *args, **kwargs)) picard-release-1.4.2/picard/util/versions.py000066400000000000000000000040151310410472100210300ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006-2014 Lukáš Lalinský # Copyright (C) 2014 Laurent Monin # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from collections import OrderedDict from mutagen import version_string as mutagen_version from PyQt4.QtCore import PYQT_VERSION_STR as pyqt_version, QT_VERSION_STR from picard import PICARD_FANCY_VERSION_STR from picard.disc import discid_version _versions = OrderedDict([ ("version", PICARD_FANCY_VERSION_STR), ("pyqt-version", pyqt_version), ("qt-version", QT_VERSION_STR), ("mutagen-version", mutagen_version), ("discid-version", discid_version), ]) _names = { "version": "Picard", "pyqt-version": "PyQt", "qt-version": "Qt", "mutagen-version": "Mutagen", "discid-version": "Discid", } def _value_as_text(value, i18n=False): if not value: value = N_("is not installed") if i18n: return _(value) return value def version_name(key): return _names[key] def as_dict(i18n=False): return OrderedDict([(key, _value_as_text(value, i18n)) for key, value in _versions.items()]) def as_string(i18n=False, separator=", "): values = as_dict(i18n) return separator.join([_names[key] + " " + value for key, value in values.items()]) picard-release-1.4.2/picard/util/webbrowser2.py000066400000000000000000000076761310410472100214430ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import sys import webbrowser from PyQt4 import QtGui from picard.const import PICARD_URLS """ A webbrowser extension that respects user's preferred browser on each platform. Python 2.5 already has *some* support for this, but it's not enough, in my opinion. See also: http://sourceforge.net/tracker/index.php?func=detail&aid=1681228&group_id=5470&atid=105470 """ if sys.version_info >= (2, 5): # Cross-platform default tool if webbrowser._iscommand('xdg-open'): webbrowser.register('xdg-open', None, webbrowser.BackgroundBrowser(["xdg-open", "%s"]), update_tryorder=-1) else: # KDE default browser if 'KDE_FULL_SESSION' in os.environ and os.environ['KDE_FULL_SESSION'] == 'true' and webbrowser._iscommand('kfmclient'): webbrowser.register('kfmclient', None, webbrowser.BackgroundBrowser(["kfmclient", "exec", "%s"]), update_tryorder=-1) # GNOME default browser if 'XDG_CURRENT_DESKTOP' in os.environ and os.environ['XDG_CURRENT_DESKTOP'].lower() == 'gnome' and webbrowser._iscommand('gnome-open'): webbrowser.register('gnome-open', None, webbrowser.BackgroundBrowser(["gnome-open", "%s"]), update_tryorder=-1) else: # KDE default browser if 'KDE_FULL_SESSION' in os.environ and os.environ['KDE_FULL_SESSION'] == 'true' and webbrowser._iscommand('kfmclient'): webbrowser.register('kfmclient', None, webbrowser.GenericBrowser("kfmclient exec '%s' &")) if 'BROWSER' in os.environ: webbrowser._tryorder.insert(len(os.environ['BROWSER'].split(os.pathsep)), 'kfmclient') else: webbrowser._tryorder.insert(0, 'kfmclient') # GNOME default browser if 'DESKTOP_SESSION' in os.environ and os.environ['DESKTOP_SESSION'].lower() == 'gnome' and webbrowser._iscommand('gnome-open'): webbrowser.register('gnome-open', None, webbrowser.GenericBrowser("gnome-open '%s' &")) if 'BROWSER' in os.environ: webbrowser._tryorder.insert(len(os.environ['BROWSER'].split(os.pathsep)), 'gnome-open') else: webbrowser._tryorder.insert(0, 'gnome-open') if 'windows-default' in webbrowser._tryorder: class WindowsDefault2(webbrowser.BaseBrowser): def open(self, url, new=0, autoraise=1): try: os.startfile(url) except WindowsError: # [Error 22] No application is associated with the specified # file for this operation: '' return False else: return True webbrowser._tryorder.remove('windows-default') webbrowser.register('windows-default-2', WindowsDefault2, update_tryorder=-1) iexplore = webbrowser.BackgroundBrowser( os.path.join(os.environ.get('PROGRAMFILES', 'C:\\Program Files'), 'Internet Explorer\\IEXPLORE.EXE')) webbrowser.register('iexplore', None, iexplore) def open(url): try: webbrowser.open(url) except webbrowser.Error as e: QtGui.QMessageBox.critical(None, _("Web Browser Error"), _("Error while launching a web browser:\n\n%s") % (e,)) def goto(url_id): open(PICARD_URLS[url_id]) picard-release-1.4.2/picard/webservice.py000066400000000000000000000634571310410472100203600ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2007 Lukáš Lalinský # Copyright (C) 2009 Carlin Mangar # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ Asynchronous XML web service. """ import sys import re import time import os.path import platform import math from collections import deque, defaultdict from functools import partial from PyQt4 import QtCore, QtNetwork from PyQt4.QtGui import QDesktopServices from PyQt4.QtCore import QUrl, QXmlStreamReader from picard import (PICARD_APP_NAME, PICARD_ORG_NAME, PICARD_VERSION_STR, config, log) from picard.const import (ACOUSTID_KEY, ACOUSTID_HOST, ACOUSTID_PORT, CAA_HOST, CAA_PORT) from picard.oauth import OAuthManager from picard.util import build_qurl COUNT_REQUESTS_DELAY_MS = 250 REQUEST_DELAY = defaultdict(lambda: 1000) REQUEST_DELAY[(ACOUSTID_HOST, ACOUSTID_PORT)] = 333 REQUEST_DELAY[(CAA_HOST, CAA_PORT)] = 0 USER_AGENT_STRING = '%s-%s/%s (%s;%s-%s)' % (PICARD_ORG_NAME, PICARD_APP_NAME, PICARD_VERSION_STR, platform.platform(), platform.python_implementation(), platform.python_version()) CLIENT_STRING = str(QUrl.toPercentEncoding('%s %s-%s' % (PICARD_ORG_NAME, PICARD_APP_NAME, PICARD_VERSION_STR))) def escape_lucene_query(text): return re.sub(r'([+\-&|!(){}\[\]\^"~*?:\\/])', r'\\\1', text) def _wrap_xml_metadata(data): return ('' + '%s' % data) class XmlNode(object): def __init__(self): self.text = u'' self.children = {} self.attribs = {} def __repr__(self): return repr(self.__dict__) def append_child(self, name, node=None): if node is None: node = XmlNode() self.children.setdefault(name, []).append(node) return node def __getattr__(self, name): try: return self.children[name] except KeyError: try: return self.attribs[name] except KeyError: raise AttributeError(name) _node_name_re = re.compile('[^a-zA-Z0-9]') def _node_name(n): return _node_name_re.sub('_', unicode(n)) def _read_xml(stream): document = XmlNode() current_node = document path = [] while not stream.atEnd(): stream.readNext() if stream.isStartElement(): node = XmlNode() attrs = stream.attributes() for i in xrange(attrs.count()): attr = attrs.at(i) node.attribs[_node_name(attr.name())] = unicode(attr.value()) current_node.append_child(_node_name(stream.name()), node) path.append(current_node) current_node = node elif stream.isEndElement(): current_node = path.pop() elif stream.isCharacters(): current_node.text += unicode(stream.text()) return document class XmlWebService(QtCore.QObject): def __init__(self, parent=None): QtCore.QObject.__init__(self, parent) self.manager = QtNetwork.QNetworkAccessManager() self.oauth_manager = OAuthManager(self) self.set_cache() self.setup_proxy() self.manager.finished.connect(self._process_reply) self._last_request_times = defaultdict(lambda: 0) self._request_methods = { "GET": self.manager.get, "POST": self.manager.post, "PUT": self.manager.put, "DELETE": self.manager.deleteResource } self._init_queues() self._init_timers() def _init_queues(self): self._active_requests = {} self._queues = defaultdict(lambda: defaultdict(deque)) self.num_pending_web_requests = 0 self._last_num_pending_web_requests = -1 def _init_timers(self): self._timer_run_next_task = QtCore.QTimer(self) self._timer_run_next_task.setSingleShot(True) self._timer_run_next_task.timeout.connect(self._run_next_task) self._timer_count_pending_requests = QtCore.QTimer(self) self._timer_count_pending_requests.setSingleShot(True) self._timer_count_pending_requests.timeout.connect(self._count_pending_requests) def set_cache(self, cache_size_in_mb=100): cache = QtNetwork.QNetworkDiskCache() location = QDesktopServices.storageLocation(QDesktopServices.CacheLocation) cache.setCacheDirectory(os.path.join(unicode(location), u'picard')) cache.setMaximumCacheSize(cache_size_in_mb * 1024 * 1024) self.manager.setCache(cache) log.debug("NetworkDiskCache dir: %s", cache.cacheDirectory()) log.debug("NetworkDiskCache size: %s / %s", cache.cacheSize(), cache.maximumCacheSize()) def setup_proxy(self): proxy = QtNetwork.QNetworkProxy() if config.setting["use_proxy"]: proxy.setType(QtNetwork.QNetworkProxy.HttpProxy) proxy.setHostName(config.setting["proxy_server_host"]) proxy.setPort(config.setting["proxy_server_port"]) proxy.setUser(config.setting["proxy_username"]) proxy.setPassword(config.setting["proxy_password"]) self.manager.setProxy(proxy) def _start_request_continue(self, method, host, port, path, data, handler, xml, mblogin=False, cacheloadcontrol=None, refresh=None, access_token=None, queryargs=None): url = build_qurl(host, port, path=path, queryargs=queryargs) request = QtNetwork.QNetworkRequest(url) if mblogin and access_token: request.setRawHeader("Authorization", "Bearer %s" % access_token) if mblogin or (method == "GET" and refresh): request.setPriority(QtNetwork.QNetworkRequest.HighPriority) request.setAttribute(QtNetwork.QNetworkRequest.CacheLoadControlAttribute, QtNetwork.QNetworkRequest.AlwaysNetwork) elif method == "PUT" or method == "DELETE": request.setPriority(QtNetwork.QNetworkRequest.HighPriority) elif cacheloadcontrol is not None: request.setAttribute(QtNetwork.QNetworkRequest.CacheLoadControlAttribute, cacheloadcontrol) request.setRawHeader("User-Agent", USER_AGENT_STRING) if xml: request.setRawHeader("Accept", "application/xml") if data is not None: if method == "POST" and host == config.setting["server_host"] and xml: request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/xml; charset=utf-8") else: request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/x-www-form-urlencoded") send = self._request_methods[method] reply = send(request, data) if data is not None else send(request) self._remember_request_time((host, port)) self._active_requests[reply] = (request, handler, xml, refresh) def _start_request(self, method, host, port, path, data, handler, xml, mblogin=False, cacheloadcontrol=None, refresh=None, queryargs=None): def start_request_continue(access_token=None): self._start_request_continue( method, host, port, path, data, handler, xml, mblogin=mblogin, cacheloadcontrol=cacheloadcontrol, refresh=refresh, access_token=access_token, queryargs=queryargs) if mblogin and path != "/oauth2/token": self.oauth_manager.get_access_token(start_request_continue) else: start_request_continue() @staticmethod def urls_equivalent(leftUrl, rightUrl): """ Lazy method to determine whether two QUrls are equivalent. At the moment it assumes that if ports are unset that they are port 80 - in absence of a URL normalization function in QUrl or ability to use qHash from QT 4.7 """ return leftUrl.port(80) == rightUrl.port(80) and \ leftUrl.toString(QUrl.RemovePort) == rightUrl.toString(QUrl.RemovePort) @staticmethod def url_port(url): if url.scheme() == 'https': return url.port(443) return url.port(80) def _handle_reply(self, reply, request, handler, xml, refresh): error = int(reply.error()) if error: log.error("Network request error for %s: %s (QT code %d, HTTP code %s)", reply.request().url().toString(QUrl.RemoveUserInfo), reply.errorString(), error, repr(reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)) ) if handler is not None: handler(str(reply.readAll()), reply, error) else: redirect = reply.attribute(QtNetwork.QNetworkRequest.RedirectionTargetAttribute) fromCache = reply.attribute(QtNetwork.QNetworkRequest.SourceIsFromCacheAttribute) cached = ' (CACHED)' if fromCache else '' log.debug("Received reply for %s: HTTP %d (%s) %s", reply.request().url().toString(QUrl.RemoveUserInfo), reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute), reply.attribute(QtNetwork.QNetworkRequest.HttpReasonPhraseAttribute), cached ) if handler is not None: # Redirect if found and not infinite if redirect: url = request.url() # merge with base url (to cover the possibility of the URL being relative) redirect = url.resolved(redirect) if not XmlWebService.urls_equivalent(redirect, reply.request().url()): log.debug("Redirect to %s requested", redirect.toString(QUrl.RemoveUserInfo)) redirect_host = str(redirect.host()) redirect_port = self.url_port(redirect) redirect_query = dict(redirect.encodedQueryItems()) redirect_path = redirect.path() original_host = str(url.host()) original_port = self.url_port(url) if ((original_host, original_port) in REQUEST_DELAY and (redirect_host, redirect_port) not in REQUEST_DELAY): log.debug("Setting rate limit for %s:%i to %i" % (redirect_host, redirect_port, REQUEST_DELAY[(original_host, original_port)])) REQUEST_DELAY[(redirect_host, redirect_port)] =\ REQUEST_DELAY[(original_host, original_port)] self.get(redirect_host, redirect_port, redirect_path, handler, xml, priority=True, important=True, refresh=refresh, queryargs=redirect_query, cacheloadcontrol=request.attribute(QtNetwork.QNetworkRequest.CacheLoadControlAttribute)) else: log.error("Redirect loop: %s", reply.request().url().toString(QUrl.RemoveUserInfo) ) handler(str(reply.readAll()), reply, error) elif xml: document = _read_xml(QXmlStreamReader(reply)) handler(document, reply, error) else: handler(str(reply.readAll()), reply, error) def _process_reply(self, reply): try: request, handler, xml, refresh = self._active_requests.pop(reply) except KeyError: log.error("Request not found for %s" % reply.request().url().toString(QUrl.RemoveUserInfo)) return try: self._handle_reply(reply, request, handler, xml, refresh) finally: reply.close() reply.deleteLater() def get(self, host, port, path, handler, xml=True, priority=False, important=False, mblogin=False, cacheloadcontrol=None, refresh=False, queryargs=None): func = partial(self._start_request, "GET", host, port, path, None, handler, xml, mblogin, cacheloadcontrol=cacheloadcontrol, refresh=refresh, queryargs=queryargs) return self.add_task(func, host, port, priority, important=important) def post(self, host, port, path, data, handler, xml=True, priority=False, important=False, mblogin=True, queryargs=None): log.debug("POST-DATA %r", data) func = partial(self._start_request, "POST", host, port, path, data, handler, xml, mblogin, queryargs=queryargs) return self.add_task(func, host, port, priority, important=important) def put(self, host, port, path, data, handler, priority=True, important=False, mblogin=True, queryargs=None): func = partial(self._start_request, "PUT", host, port, path, data, handler, False, mblogin, queryargs=queryargs) return self.add_task(func, host, port, priority, important=important) def delete(self, host, port, path, handler, priority=True, important=False, mblogin=True, queryargs=None): func = partial(self._start_request, "DELETE", host, port, path, None, handler, False, mblogin, queryargs=queryargs) return self.add_task(func, host, port, priority, important=important) def stop(self): for reply in self._active_requests.keys(): reply.abort() self._init_queues() def _count_pending_requests(self): count = len(self._active_requests) for prio_queue in self._queues.values(): for queue in prio_queue.values(): count += len(queue) self.num_pending_web_requests = count if count != self._last_num_pending_web_requests: self._last_num_pending_web_requests = count self.tagger.tagger_stats_changed.emit() if count: self._timer_count_pending_requests.start(COUNT_REQUESTS_DELAY_MS) def _get_delay_to_next_request(self, hostkey): """Calculate delay to next request to hostkey (host, port) returns a tuple (wait, delay) where: wait is True if a delay is needed delay is the delay in milliseconds to next request """ interval = REQUEST_DELAY[hostkey] if not interval: log.debug("WSREQ: Starting another request to %s without delay", hostkey) return (False, 0) last_request = self._last_request_times[hostkey] if not last_request: log.debug("WSREQ: First request to %s", hostkey) self._remember_request_time(hostkey) # set it on first run return (False, interval) elapsed = (time.time() - last_request) * 1000 if elapsed >= interval: log.debug("WSREQ: Last request to %s was %d ms ago, starting another one", hostkey, elapsed) return (False, interval) delay = int(math.ceil(interval - elapsed)) log.debug("WSREQ: Last request to %s was %d ms ago, waiting %d ms before starting another one", hostkey, elapsed, delay) return (True, delay) def _remember_request_time(self, hostkey): if REQUEST_DELAY[hostkey]: self._last_request_times[hostkey] = time.time() def _run_next_task(self): delay = sys.maxsize for prio in sorted(self._queues.keys(), reverse=True): prio_queue = self._queues[prio] if not prio_queue: del(self._queues[prio]) continue for hostkey in sorted(prio_queue.keys(), key=lambda hostkey: REQUEST_DELAY[hostkey]): queue = self._queues[prio][hostkey] if not queue: del(self._queues[prio][hostkey]) continue wait, d = self._get_delay_to_next_request(hostkey) if not wait: queue.popleft()() if d < delay: delay = d if delay < sys.maxsize: self._timer_run_next_task.start(delay) def add_task(self, func, host, port, priority, important=False): hostkey = (host, port) prio = int(priority) # priority is a boolean if important: self._queues[prio][hostkey].appendleft(func) else: self._queues[prio][hostkey].append(func) if not self._timer_run_next_task.isActive(): self._timer_run_next_task.start(0) if not self._timer_count_pending_requests.isActive(): self._timer_count_pending_requests.start(0) return (hostkey, func, prio) def remove_task(self, task): hostkey, func, prio = task try: self._queues[prio][hostkey].remove(func) if not self._timer_count_pending_requests.isActive(): self._timer_count_pending_requests.start(0) except: pass def _get_by_id(self, entitytype, entityid, handler, inc=[], queryargs=None, priority=False, important=False, mblogin=False, refresh=False): host = config.setting["server_host"] port = config.setting["server_port"] path = "/ws/2/%s/%s" % (entitytype, entityid) if queryargs is None: queryargs = {} if inc: queryargs["inc"] = "+".join(inc) return self.get(host, port, path, handler, priority=priority, important=important, mblogin=mblogin, refresh=refresh, queryargs=queryargs) def get_release_by_id(self, releaseid, handler, inc=[], priority=False, important=False, mblogin=False, refresh=False): return self._get_by_id('release', releaseid, handler, inc, priority=priority, important=important, mblogin=mblogin, refresh=refresh) def get_track_by_id(self, trackid, handler, inc=[], priority=False, important=False, mblogin=False, refresh=False): return self._get_by_id('recording', trackid, handler, inc, priority=priority, important=important, mblogin=mblogin, refresh=refresh) def lookup_discid(self, discid, handler, priority=True, important=True, refresh=False): inc = ['artist-credits', 'labels'] return self._get_by_id('discid', discid, handler, inc, queryargs={"cdstubs": "no"}, priority=priority, important=important, refresh=refresh) def _find(self, entitytype, handler, **kwargs): host = config.setting["server_host"] port = config.setting["server_port"] filters = [] limit = kwargs.pop("limit") if limit: filters.append(("limit", limit)) is_search = kwargs.pop("search", False) if is_search: if config.setting["use_adv_search_syntax"]: query = kwargs["query"] else: query = escape_lucene_query(kwargs["query"]).strip().lower() filters.append(("dismax", 'true')) else: query = [] for name, value in kwargs.items(): value = escape_lucene_query(value).strip().lower() if value: query.append('%s:(%s)' % (name, value)) query = ' '.join(query) if query: filters.append(("query", query)) queryargs = {} for name, value in filters: value = QUrl.toPercentEncoding(unicode(value)) queryargs[str(name)] = value path = "/ws/2/%s" % (entitytype) return self.get(host, port, path, handler, queryargs=queryargs, xml=True, priority=True, important=True, mblogin=False, cacheloadcontrol=None, refresh=False) def find_releases(self, handler, **kwargs): return self._find('release', handler, **kwargs) def find_tracks(self, handler, **kwargs): return self._find('recording', handler, **kwargs) def find_artists(self, handler, **kwargs): return self._find('artist', handler, **kwargs) def _browse(self, entitytype, handler, inc=[], **kwargs): host = config.setting["server_host"] port = config.setting["server_port"] path = "/ws/2/%s" % (entitytype) queryargs = kwargs if inc: queryargs["inc"] = "+".join(inc) return self.get(host, port, path, handler, queryargs=queryargs, xml=True, priority=True, important=True, mblogin=False, cacheloadcontrol=None, refresh=False) def browse_releases(self, handler, **kwargs): inc = ["media", "labels"] return self._browse("release", handler, inc, **kwargs) def submit_ratings(self, ratings, handler): host = config.setting['server_host'] port = config.setting['server_port'] path = '/ws/2/rating/' params = {"client": CLIENT_STRING} recordings = (''.join(['%s' % (i[1], j*20) for i, j in ratings.items() if i[0] == 'recording'])) data = _wrap_xml_metadata('%s' % recordings) return self.post(host, port, path, data, handler, priority=True, queryargs=params) def _encode_acoustid_args(self, args, format='xml'): filters = [] args['client'] = ACOUSTID_KEY args['clientversion'] = PICARD_VERSION_STR args['format'] = format for name, value in args.items(): value = str(QUrl.toPercentEncoding(value)) filters.append('%s=%s' % (str(name), value)) return '&'.join(filters) def query_acoustid(self, handler, **args): host, port = ACOUSTID_HOST, ACOUSTID_PORT body = self._encode_acoustid_args(args) return self.post(host, port, '/v2/lookup', body, handler, priority=False, important=False, mblogin=False) def submit_acoustid_fingerprints(self, submissions, handler): args = {'user': config.setting["acoustid_apikey"]} for i, submission in enumerate(submissions): args['fingerprint.%d' % i] = str(submission.fingerprint) args['duration.%d' % i] = str(submission.duration) args['mbid.%d' % i] = str(submission.recordingid) if submission.puid: args['puid.%d' % i] = str(submission.puid) host, port = ACOUSTID_HOST, ACOUSTID_PORT body = self._encode_acoustid_args(args, format='json') return self.post(host, port, '/v2/submit', body, handler, priority=True, important=False, mblogin=False) def download(self, host, port, path, handler, priority=False, important=False, cacheloadcontrol=None, refresh=False, queryargs=None): return self.get(host, port, path, handler, xml=False, priority=priority, important=important, cacheloadcontrol=cacheloadcontrol, refresh=refresh, queryargs=queryargs) def get_collection(self, id, handler, limit=100, offset=0): host, port = config.setting['server_host'], config.setting['server_port'] path = "/ws/2/collection" queryargs = None if id is not None: inc = ["releases", "artist-credits", "media"] path += "/%s/releases" % (id) queryargs = {} queryargs["inc"] = "+".join(inc) queryargs["limit"] = limit queryargs["offset"] = offset return self.get(host, port, path, handler, priority=True, important=True, mblogin=True, queryargs=queryargs) def get_collection_list(self, handler): return self.get_collection(None, handler) def _collection_request(self, id, releases): while releases: ids = ";".join(releases if len(releases) <= 400 else releases[:400]) releases = releases[400:] yield "/ws/2/collection/%s/releases/%s" % (id, ids) def _get_client_queryarg(self): return {"client": CLIENT_STRING} def put_to_collection(self, id, releases, handler): host, port = config.setting['server_host'], config.setting['server_port'] for path in self._collection_request(id, releases): self.put(host, port, path, "", handler, queryargs=self._get_client_queryarg()) def delete_from_collection(self, id, releases, handler): host, port = config.setting['server_host'], config.setting['server_port'] for path in self._collection_request(id, releases): self.delete(host, port, path, handler, queryargs=self._get_client_queryarg()) picard-release-1.4.2/po/000077500000000000000000000000001310410472100150055ustar00rootroot00000000000000picard-release-1.4.2/po/README.md000066400000000000000000000032571310410472100162730ustar00rootroot00000000000000Translations ============ Picard translations are handled by [Transifex](https://www.transifex.com). _Please do not manually edit the PO files._ Required tools -------------- * [Transifex client](http://support.transifex.com/customer/portal/topics/440187-transifex-client/articles) * [Babel](http://babel.pocoo.org/) Picard source tree strings -------------------------- Their translations are handled at https://www.transifex.com/projects/p/musicbrainz/resource/picard/ One can update `picard.pot` using: ```bash $ python setup.py regen_pot_file ``` Transifex will _automatically_ pick `picard.pot` from [Picard git repository master branch](https://github.com/metabrainz/picard/tree/master) once per day. `picard/countries.py` strings ----------------------------- Their translations are handled at https://www.transifex.com/projects/p/musicbrainz/resource/countries/ `countries.pot` is updated by [musicbrainz-server project](https://bitbucket.org/metabrainz/musicbrainz-server/), outside the Picard project. Picard maintainers can regenerate `picard/countries.py`, which is using `countries.pot` as base, using the command: ```bash $ python setup.py update_countries ``` It will retrieve and parse latest `countries.pot` to rebuild `picard/countries.py`. To fetch latest translations from Transifex ------------------------------------------- Use the following command: ```bash $ python setup.py get_po_files ``` It will fetch all po files from Transifex, but the most incomplete ones. The minimum acceptable percentage of a translation in order to download it can be seen using: ```bash $ python setup.py get_po_files --help ``` The percentage value is passed to the `tx pull` command. picard-release-1.4.2/po/af.po000066400000000000000000001350321310410472100157370ustar00rootroot00000000000000# Translations template for PROJECT. # Copyright (C) 2012 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: # FIRST AUTHOR , 2009. msgid "" msgstr "" "Project-Id-Version: MusicBrainz\n" "Report-Msgid-Bugs-To: http://tickets.musicbrainz.org/\n" "POT-Creation-Date: 2012-08-28 15:50+0200\n" "PO-Revision-Date: 2012-08-29 08:53+0000\n" "Last-Translator: Lukáš Lalinský \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" "Language: af\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: contrib/plugins/no_release.py:48 msgid "Enable plugin for all releases by default" msgstr "" #: contrib/plugins/no_release.py:49 msgid "Tags to strip (comma-separated)" msgstr "" #: contrib/plugins/no_release.py:59 msgid "Remove specific release information..." msgstr "" #: contrib/plugins/open_in_gui.py:30 msgid "Open Error" msgstr "" #: contrib/plugins/open_in_gui.py:30 #, python-format msgid "" "Error while opening file:\n" "\n" "%s" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:115 msgid "Last.fm" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:116 msgid "Use track tags" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:117 msgid "Use artist tags" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:118 #: picard/ui/options/tags.py:30 msgid "Tags" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:119 #: picard/ui/ui_options_folksonomy.py:107 msgid "Ignore tags:" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:120 #: picard/ui/ui_options_folksonomy.py:112 msgid "Join multiple tags with:" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:121 #: picard/ui/ui_options_folksonomy.py:113 msgid " / " msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:122 #: picard/ui/ui_options_folksonomy.py:114 msgid ", " msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:123 #: picard/ui/ui_options_folksonomy.py:109 msgid "Minimal tag usage:" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:124 #: picard/ui/ui_options_folksonomy.py:110 picard/ui/ui_options_matching.py:79 #: picard/ui/ui_options_matching.py:80 picard/ui/ui_options_matching.py:81 msgid " %" msgstr "" #: contrib/plugins/replaygain/__init__.py:48 msgid "Calculate replay &gain..." msgstr "" #: contrib/plugins/replaygain/__init__.py:65 #, python-format msgid "Calculating replay gain for \"%s\"..." msgstr "" #: contrib/plugins/replaygain/__init__.py:70 #, python-format msgid "Replay gain for \"%s\" successfully calculated." msgstr "" #: contrib/plugins/replaygain/__init__.py:72 #, python-format msgid "Could not calculate replay gain for \"%s\"." msgstr "" #: contrib/plugins/replaygain/__init__.py:75 msgid "Calculate album &gain..." msgstr "" #: contrib/plugins/replaygain/__init__.py:98 #, python-format msgid "Calculating album gain for \"%s\"..." msgstr "" #: contrib/plugins/replaygain/__init__.py:106 #, python-format msgid "Album gain for \"%s\" successfully calculated." msgstr "" #: contrib/plugins/replaygain/__init__.py:108 #, python-format msgid "Could not calculate album gain for \"%s\"." msgstr "" #: picard/acoustid.py:116 #, python-format msgid "Could not find AcoustID for file %s" msgstr "" #: picard/acoustid.py:120 picard/musicdns/__init__.py:103 #, python-format msgid "Looking up the fingerprint for file %s..." msgstr "" #: picard/acoustidmanager.py:78 msgid "Submitting AcoustIDs..." msgstr "" #: picard/acoustidmanager.py:83 #, python-format msgid "AcoustID submission failed: %s" msgstr "" #: picard/acoustidmanager.py:85 msgid "AcoustIDs successfully submitted!" msgstr "" #: picard/album.py:55 picard/cluster.py:260 msgid "Unmatched Files" msgstr "" #: picard/album.py:180 #, python-format msgid "[could not load album %s]" msgstr "" #: picard/album.py:266 #, python-format msgid "Album %s loaded" msgstr "" #: picard/album.py:282 msgid "[loading album information]" msgstr "" #: picard/cluster.py:177 picard/cluster.py:188 #, python-format msgid "No matching releases for cluster %s" msgstr "" #: picard/cluster.py:190 #, python-format msgid "Cluster %s identified!" msgstr "" #: picard/cluster.py:195 #, python-format msgid "Looking up the metadata for cluster %s..." msgstr "" #: picard/const.py:39 msgid "CD" msgstr "CD" #: picard/const.py:40 msgid "CD-R" msgstr "" #: picard/const.py:41 msgid "HDCD" msgstr "" #: picard/const.py:42 msgid "8cm CD" msgstr "" #: picard/const.py:43 msgid "Vinyl" msgstr "" #: picard/const.py:44 msgid "7\" Vinyl" msgstr "" #: picard/const.py:45 msgid "10\" Vinyl" msgstr "" #: picard/const.py:46 msgid "12\" Vinyl" msgstr "" #: picard/const.py:47 msgid "Digital Media" msgstr "" #: picard/const.py:48 msgid "USB Flash Drive" msgstr "" #: picard/const.py:49 msgid "slotMusic" msgstr "" #: picard/const.py:50 msgid "Cassette" msgstr "Kasset" #: picard/const.py:51 msgid "DVD" msgstr "DVD" #: picard/const.py:52 msgid "DVD-Audio" msgstr "" #: picard/const.py:53 msgid "DVD-Video" msgstr "" #: picard/const.py:54 msgid "SACD" msgstr "SACD" #: picard/const.py:55 msgid "DualDisc" msgstr "" #: picard/const.py:56 msgid "MiniDisc" msgstr "" #: picard/const.py:57 msgid "Blu-ray" msgstr "" #: picard/const.py:58 msgid "HD-DVD" msgstr "" #: picard/const.py:59 msgid "Videotape" msgstr "" #: picard/const.py:60 msgid "VHS" msgstr "" #: picard/const.py:61 msgid "Betamax" msgstr "" #: picard/const.py:62 msgid "VCD" msgstr "" #: picard/const.py:63 msgid "SVCD" msgstr "" #: picard/const.py:64 msgid "UMD" msgstr "" #: picard/const.py:65 picard/ui/ui_options_releases.py:226 msgid "Other" msgstr "" #: picard/const.py:66 msgid "LaserDisc" msgstr "Laser Skyf" #: picard/const.py:67 msgid "Cartridge" msgstr "" #: picard/const.py:68 msgid "Reel-to-reel" msgstr "" #: picard/const.py:69 msgid "DAT" msgstr "" #: picard/const.py:70 msgid "Wax Cylinder" msgstr "" #: picard/const.py:71 msgid "Piano Roll" msgstr "" #: picard/const.py:72 msgid "DCC" msgstr "" #: picard/const.py:77 msgid "Bangladesh" msgstr "" #: picard/const.py:78 msgid "Belgium" msgstr "" #: picard/const.py:79 msgid "Burkina Faso" msgstr "" #: picard/const.py:80 msgid "Bulgaria" msgstr "" #: picard/const.py:81 msgid "Barbados" msgstr "" #: picard/const.py:82 msgid "Wallis and Futuna Islands" msgstr "" #: picard/const.py:83 msgid "Bermuda" msgstr "" #: picard/const.py:84 msgid "Brunei Darussalam" msgstr "" #: picard/const.py:85 msgid "Bolivia" msgstr "" #: picard/const.py:86 msgid "Bahrain" msgstr "" #: picard/const.py:87 msgid "Burundi" msgstr "" #: picard/const.py:88 msgid "Benin" msgstr "" #: picard/const.py:89 msgid "Bhutan" msgstr "" #: picard/const.py:90 msgid "Jamaica" msgstr "" #: picard/const.py:91 msgid "Bouvet Island" msgstr "" #: picard/const.py:92 msgid "Botswana" msgstr "" #: picard/const.py:93 msgid "Samoa" msgstr "" #: picard/const.py:94 msgid "Brazil" msgstr "" #: picard/const.py:95 msgid "Bahamas" msgstr "" #: picard/const.py:96 msgid "Belarus" msgstr "" #: picard/const.py:97 msgid "Belize" msgstr "" #: picard/const.py:98 msgid "Russian Federation" msgstr "Russiese state bond" #: picard/const.py:99 msgid "Rwanda" msgstr "" #: picard/const.py:100 msgid "Reunion" msgstr "" #: picard/const.py:101 msgid "Turkmenistan" msgstr "" #: picard/const.py:102 msgid "Tajikistan" msgstr "" #: picard/const.py:103 msgid "Romania" msgstr "" #: picard/const.py:104 msgid "Tokelau" msgstr "" #: picard/const.py:105 msgid "Guinea-Bissa" msgstr "" #: picard/const.py:106 msgid "Guam" msgstr "" #: picard/const.py:107 msgid "Guatemala" msgstr "" #: picard/const.py:108 msgid "Greece" msgstr "" #: picard/const.py:109 msgid "Equatorial Guinea" msgstr "" #: picard/const.py:110 msgid "Guadeloupe" msgstr "" #: picard/const.py:111 msgid "Japan" msgstr "" #: picard/const.py:112 msgid "Guyana" msgstr "" #: picard/const.py:113 msgid "French Guiana" msgstr "" #: picard/const.py:114 msgid "Georgia" msgstr "" #: picard/const.py:115 msgid "Grenada" msgstr "" #: picard/const.py:116 msgid "United Kingdom" msgstr "" #: picard/const.py:117 msgid "Gabon" msgstr "" #: picard/const.py:118 msgid "El Salvador" msgstr "" #: picard/const.py:119 msgid "Guinea" msgstr "" #: picard/const.py:120 msgid "Gambia" msgstr "" #: picard/const.py:121 msgid "Greenland" msgstr "" #: picard/const.py:122 msgid "Gibraltar" msgstr "" #: picard/const.py:123 msgid "Ghana" msgstr "" #: picard/const.py:124 msgid "Oman" msgstr "" #: picard/const.py:125 msgid "Tunisia" msgstr "" #: picard/const.py:126 msgid "Jordan" msgstr "" #: picard/const.py:127 msgid "Haiti" msgstr "" #: picard/const.py:128 msgid "Hungary" msgstr "" #: picard/const.py:129 msgid "Hong Kong" msgstr "" #: picard/const.py:130 msgid "Honduras" msgstr "" #: picard/const.py:131 msgid "Heard and Mc Donald Islands" msgstr "" #: picard/const.py:132 msgid "Venezuela" msgstr "" #: picard/const.py:133 msgid "Puerto Rico" msgstr "" #: picard/const.py:134 msgid "Palau" msgstr "" #: picard/const.py:135 msgid "Portugal" msgstr "" #: picard/const.py:136 msgid "Svalbard and Jan Mayen Islands" msgstr "" #: picard/const.py:137 msgid "Paraguay" msgstr "" #: picard/const.py:138 msgid "Iraq" msgstr "" #: picard/const.py:139 msgid "Panama" msgstr "" #: picard/const.py:140 msgid "French Polynesia" msgstr "" #: picard/const.py:141 msgid "Papua New Guinea" msgstr "" #: picard/const.py:142 msgid "Peru" msgstr "" #: picard/const.py:143 msgid "Pakistan" msgstr "" #: picard/const.py:144 msgid "Philippines" msgstr "" #: picard/const.py:145 msgid "Pitcairn" msgstr "" #: picard/const.py:146 msgid "Poland" msgstr "" #: picard/const.py:147 msgid "St. Pierre and Miquelon" msgstr "" #: picard/const.py:148 msgid "Zambia" msgstr "" #: picard/const.py:149 msgid "Western Sahara" msgstr "" #: picard/const.py:150 msgid "Estonia" msgstr "" #: picard/const.py:151 msgid "Egypt" msgstr "" #: picard/const.py:152 msgid "South Africa" msgstr "Suid Afrika" #: picard/const.py:153 msgid "Ecuador" msgstr "" #: picard/const.py:154 msgid "Italy" msgstr "Italië" #: picard/const.py:155 msgid "Viet Nam" msgstr "" #: picard/const.py:156 msgid "Solomon Islands" msgstr "" #: picard/const.py:157 msgid "Ethiopia" msgstr "" #: picard/const.py:158 msgid "Somalia" msgstr "" #: picard/const.py:159 msgid "Zimbabwe" msgstr "" #: picard/const.py:160 msgid "Saudi Arabia" msgstr "" #: picard/const.py:161 msgid "Spain" msgstr "Spanje" #: picard/const.py:162 msgid "Eritrea" msgstr "" #: picard/const.py:163 msgid "Moldova, Republic of" msgstr "" #: picard/const.py:164 msgid "Madagascar" msgstr "" #: picard/const.py:165 msgid "Morocco" msgstr "" #: picard/const.py:166 msgid "Monaco" msgstr "" #: picard/const.py:167 msgid "Uzbekistan" msgstr "" #: picard/const.py:168 msgid "Myanmar" msgstr "" #: picard/const.py:169 msgid "Mali" msgstr "" #: picard/const.py:170 msgid "Macau" msgstr "" #: picard/const.py:171 msgid "Mongolia" msgstr "" #: picard/const.py:172 msgid "Marshall Islands" msgstr "" #: picard/const.py:173 msgid "Macedonia, The Former Yugoslav Republic of" msgstr "" #: picard/const.py:174 msgid "Mauritius" msgstr "" #: picard/const.py:175 msgid "Malta" msgstr "" #: picard/const.py:176 msgid "Malawi" msgstr "" #: picard/const.py:177 msgid "Maldives" msgstr "" #: picard/const.py:178 msgid "Martinique" msgstr "" #: picard/const.py:179 msgid "Northern Mariana Islands" msgstr "" #: picard/const.py:180 msgid "Montserrat" msgstr "" #: picard/const.py:181 msgid "Mauritania" msgstr "" #: picard/const.py:182 msgid "Uganda" msgstr "" #: picard/const.py:183 msgid "Malaysia" msgstr "" #: picard/const.py:184 msgid "Mexico" msgstr "" #: picard/const.py:185 msgid "Israel" msgstr "" #: picard/const.py:186 msgid "France" msgstr "Frankryk" #: picard/const.py:187 msgid "British Indian Ocean Territory" msgstr "" #: picard/const.py:188 msgid "St. Helena" msgstr "" #: picard/const.py:189 msgid "Finland" msgstr "" #: picard/const.py:190 msgid "Fiji" msgstr "" #: picard/const.py:191 msgid "Falkland Islands (Malvinas)" msgstr "" #: picard/const.py:192 msgid "Micronesia, Federated States of" msgstr "" #: picard/const.py:193 msgid "Faroe Islands" msgstr "" #: picard/const.py:194 msgid "Nicaragua" msgstr "" #: picard/const.py:195 msgid "Netherlands" msgstr "Nederland" #: picard/const.py:196 msgid "Norway" msgstr "Noorweë" #: picard/const.py:197 msgid "Namibia" msgstr "" #: picard/const.py:198 msgid "Vanuatu" msgstr "" #: picard/const.py:199 msgid "New Caledonia" msgstr "" #: picard/const.py:200 msgid "Niger" msgstr "" #: picard/const.py:201 msgid "Norfolk Island" msgstr "" #: picard/const.py:202 msgid "Nigeria" msgstr "Nigeria" #: picard/const.py:203 msgid "New Zealand" msgstr "" #: picard/const.py:204 msgid "Zaire" msgstr "" #: picard/const.py:205 msgid "Nepal" msgstr "" #: picard/const.py:206 msgid "Nauru" msgstr "" #: picard/const.py:207 msgid "Niue" msgstr "" #: picard/const.py:208 msgid "Cook Islands" msgstr "" #: picard/const.py:209 msgid "Cote d'Ivoire" msgstr "" #: picard/const.py:210 msgid "Switzerland" msgstr "" #: picard/const.py:211 msgid "Colombia" msgstr "" #: picard/const.py:212 msgid "China" msgstr "" #: picard/const.py:213 msgid "Cameroon" msgstr "" #: picard/const.py:214 msgid "Chile" msgstr "" #: picard/const.py:215 msgid "Cocos (Keeling) Islands" msgstr "" #: picard/const.py:216 msgid "Canada" msgstr "" #: picard/const.py:217 msgid "Congo" msgstr "" #: picard/const.py:218 msgid "Central African Republic" msgstr "" #: picard/const.py:219 msgid "Czech Republic" msgstr "" #: picard/const.py:220 msgid "Cyprus" msgstr "" #: picard/const.py:221 msgid "Christmas Island" msgstr "" #: picard/const.py:222 msgid "Costa Rica" msgstr "" #: picard/const.py:223 msgid "Cape Verde" msgstr "" #: picard/const.py:224 msgid "Cuba" msgstr "" #: picard/const.py:225 msgid "Swaziland" msgstr "" #: picard/const.py:226 msgid "Syrian Arab Republic" msgstr "" #: picard/const.py:227 msgid "Kyrgyzstan" msgstr "" #: picard/const.py:228 msgid "Kenya" msgstr "" #: picard/const.py:229 msgid "Suriname" msgstr "" #: picard/const.py:230 msgid "Kiribati" msgstr "" #: picard/const.py:231 msgid "Cambodia" msgstr "" #: picard/const.py:232 msgid "Saint Kitts and Nevis" msgstr "" #: picard/const.py:233 msgid "Comoros" msgstr "" #: picard/const.py:234 msgid "Sao Tome and Principe" msgstr "" #: picard/const.py:235 msgid "Slovenia" msgstr "" #: picard/const.py:236 msgid "Kuwait" msgstr "" #: picard/const.py:237 msgid "Senegal" msgstr "" #: picard/const.py:238 msgid "San Marino" msgstr "" #: picard/const.py:239 msgid "Sierra Leone" msgstr "" #: picard/const.py:240 msgid "Seychelles" msgstr "" #: picard/const.py:241 msgid "Kazakhstan" msgstr "" #: picard/const.py:242 msgid "Cayman Islands" msgstr "" #: picard/const.py:243 msgid "Singapore" msgstr "" #: picard/const.py:244 msgid "Sweden" msgstr "" #: picard/const.py:245 msgid "Sudan" msgstr "" #: picard/const.py:246 msgid "Dominican Republic" msgstr "" #: picard/const.py:247 msgid "Dominica" msgstr "" #: picard/const.py:248 msgid "Djibouti" msgstr "" #: picard/const.py:249 msgid "Denmark" msgstr "" #: picard/const.py:250 msgid "Virgin Islands (British)" msgstr "" #: picard/const.py:251 msgid "Germany" msgstr "" #: picard/const.py:252 msgid "Yemen" msgstr "" #: picard/const.py:253 msgid "Algeria" msgstr "" #: picard/const.py:254 msgid "United States" msgstr "" #: picard/const.py:255 msgid "Uruguay" msgstr "" #: picard/const.py:256 msgid "Mayotte" msgstr "" #: picard/const.py:257 msgid "United States Minor Outlying Islands" msgstr "" #: picard/const.py:258 msgid "Lebanon" msgstr "" #: picard/const.py:259 msgid "Saint Lucia" msgstr "" #: picard/const.py:260 msgid "Lao People's Democratic Republic" msgstr "" #: picard/const.py:261 msgid "Tuvalu" msgstr "" #: picard/const.py:262 msgid "Taiwan" msgstr "" #: picard/const.py:263 msgid "Trinidad and Tobago" msgstr "" #: picard/const.py:264 msgid "Turkey" msgstr "" #: picard/const.py:265 msgid "Sri Lanka" msgstr "" #: picard/const.py:266 msgid "Liechtenstein" msgstr "" #: picard/const.py:267 msgid "Latvia" msgstr "" #: picard/const.py:268 msgid "Tonga" msgstr "" #: picard/const.py:269 msgid "Lithuania" msgstr "" #: picard/const.py:270 msgid "Luxembourg" msgstr "" #: picard/const.py:271 msgid "Liberia" msgstr "" #: picard/const.py:272 msgid "Lesotho" msgstr "" #: picard/const.py:273 msgid "Thailand" msgstr "" #: picard/const.py:274 msgid "French Southern Territories" msgstr "" #: picard/const.py:275 msgid "Togo" msgstr "" #: picard/const.py:276 msgid "Chad" msgstr "" #: picard/const.py:277 msgid "Turks and Caicos Islands" msgstr "" #: picard/const.py:278 msgid "Libyan Arab Jamahiriya" msgstr "" #: picard/const.py:279 msgid "Vatican City State (Holy See)" msgstr "" #: picard/const.py:280 msgid "Saint Vincent and The Grenadines" msgstr "" #: picard/const.py:281 msgid "United Arab Emirates" msgstr "" #: picard/const.py:282 msgid "Andorra" msgstr "" #: picard/const.py:283 msgid "Antigua and Barbuda" msgstr "" #: picard/const.py:284 msgid "Afghanistan" msgstr "" #: picard/const.py:285 msgid "Anguilla" msgstr "" #: picard/const.py:286 msgid "Virgin Islands (U.S.)" msgstr "" #: picard/const.py:287 msgid "Iceland" msgstr "" #: picard/const.py:288 msgid "Iran (Islamic Republic of)" msgstr "" #: picard/const.py:289 msgid "Armenia" msgstr "" #: picard/const.py:290 msgid "Albania" msgstr "" #: picard/const.py:291 msgid "Angola" msgstr "" #: picard/const.py:292 msgid "Netherlands Antilles" msgstr "" #: picard/const.py:293 msgid "Antarctica" msgstr "" #: picard/const.py:294 msgid "American Samoa" msgstr "" #: picard/const.py:295 msgid "Argentina" msgstr "" #: picard/const.py:296 msgid "Australia" msgstr "" #: picard/const.py:297 msgid "Austria" msgstr "" #: picard/const.py:298 msgid "Aruba" msgstr "" #: picard/const.py:299 msgid "India" msgstr "" #: picard/const.py:300 msgid "Tanzania, United Republic of" msgstr "" #: picard/const.py:301 msgid "Azerbaijan" msgstr "" #: picard/const.py:302 msgid "Ireland" msgstr "" #: picard/const.py:303 msgid "Indonesia" msgstr "" #: picard/const.py:304 msgid "Ukraine" msgstr "" #: picard/const.py:305 msgid "Qatar" msgstr "" #: picard/const.py:306 msgid "Mozambique" msgstr "" #: picard/const.py:307 msgid "Bosnia and Herzegovina" msgstr "" #: picard/const.py:308 msgid "Congo, The Democratic Republic of the" msgstr "" #: picard/const.py:309 msgid "Serbia and Montenegro (historical, 2003-2006)" msgstr "" #: picard/const.py:310 msgid "Serbia" msgstr "" #: picard/const.py:311 msgid "Montenegro" msgstr "" #: picard/const.py:312 msgid "Croatia" msgstr "" #: picard/const.py:313 msgid "Korea (North), Democratic People's Republic of" msgstr "" #: picard/const.py:314 msgid "Korea (South), Republic of" msgstr "" #: picard/const.py:315 msgid "Slovakia" msgstr "" #: picard/const.py:316 msgid "Soviet Union (historical, 1922-1991)" msgstr "" #: picard/const.py:317 msgid "East Timor" msgstr "" #: picard/const.py:318 msgid "Czechoslovakia (historical, 1918-1992)" msgstr "" #: picard/const.py:319 msgid "Europe" msgstr "" #: picard/const.py:320 msgid "East Germany (historical, 1949-1990)" msgstr "" #: picard/const.py:321 msgid "[Unknown Country]" msgstr "" #: picard/const.py:322 msgid "[Worldwide]" msgstr "" #: picard/const.py:323 msgid "Yugoslavia (historical, 1918-1992)" msgstr "" #: picard/const.py:335 msgid "Danish" msgstr "" #: picard/const.py:336 msgid "German" msgstr "" #: picard/const.py:338 msgid "English" msgstr "" #: picard/const.py:339 msgid "English (Canada)" msgstr "" #: picard/const.py:340 msgid "English (UK)" msgstr "" #: picard/const.py:342 msgid "Spanish" msgstr "" #: picard/const.py:343 msgid "Estonian" msgstr "" #: picard/const.py:345 msgid "Finnish" msgstr "" #: picard/const.py:347 msgid "French" msgstr "" #: picard/const.py:356 msgid "Italian" msgstr "" #: picard/const.py:363 msgid "Dutch" msgstr "" #: picard/const.py:365 msgid "Polish" msgstr "" #: picard/const.py:367 msgid "Brazilian Portuguese" msgstr "" #: picard/const.py:374 msgid "Swedish" msgstr "" #: picard/file.py:589 #, python-format msgid "No matching tracks for file %s" msgstr "" #: picard/file.py:604 #, python-format msgid "No matching tracks above the threshold for file %s" msgstr "" #: picard/file.py:607 #, python-format msgid "File %s identified!" msgstr "" #: picard/file.py:623 #, python-format msgid "Looking up the PUID for file %s..." msgstr "" #: picard/file.py:629 #, python-format msgid "Looking up the metadata for file %s..." msgstr "" #: picard/puidmanager.py:62 msgid "Submitting PUIDs..." msgstr "" #: picard/puidmanager.py:68 #, python-format msgid "PUIDs submission failed: %d" msgstr "" #: picard/puidmanager.py:70 msgid "PUIDs successfully submitted!" msgstr "" #: picard/tagger.py:528 msgid "CD Lookup Error" msgstr "" #: picard/tagger.py:529 #, python-format msgid "" "Error while reading CD:\n" "\n" "%s" msgstr "" #: picard/tagger.py:555 #, python-format msgid "Could not find PUID for file %s" msgstr "" #: picard/ui/cdlookup.py:33 picard/ui/mainwindow.py:525 #: picard/ui/ui_options_releases.py:216 picard/util/tags.py:21 msgid "Album" msgstr "" #: picard/ui/cdlookup.py:33 picard/ui/itemviews.py:89 #: picard/ui/mainwindow.py:526 picard/util/tags.py:22 msgid "Artist" msgstr "" #: picard/ui/cdlookup.py:33 picard/util/tags.py:24 msgid "Date" msgstr "" #: picard/ui/cdlookup.py:33 msgid "Country" msgstr "" #: picard/ui/cdlookup.py:34 msgid "Labels" msgstr "" #: picard/ui/cdlookup.py:34 msgid "Catalog #s" msgstr "" #: picard/ui/cdlookup.py:34 picard/util/tags.py:75 msgid "Barcode" msgstr "" #: picard/ui/coverartbox.py:122 msgid "View release on MusicBrainz" msgstr "" #: picard/ui/filebrowser.py:38 msgid "&Move Tagged Files Here" msgstr "" #: picard/ui/filebrowser.py:41 msgid "Show &Hidden Files" msgstr "" #: picard/ui/infodialog.py:36 msgid "Info" msgstr "" #: picard/ui/infodialog.py:42 msgid "Filename:" msgstr "" #: picard/ui/infodialog.py:44 msgid "Format:" msgstr "" #: picard/ui/infodialog.py:53 msgid "Size:" msgstr "" #: picard/ui/infodialog.py:57 msgid "Length:" msgstr "" #: picard/ui/infodialog.py:59 msgid "Bitrate:" msgstr "" #: picard/ui/infodialog.py:61 msgid "Sample rate:" msgstr "" #: picard/ui/infodialog.py:63 msgid "Bits per sample:" msgstr "" #: picard/ui/infodialog.py:66 msgid "Mono" msgstr "" #: picard/ui/infodialog.py:67 msgid "Stereo" msgstr "" #: picard/ui/infodialog.py:69 msgid "Channels:" msgstr "" #: picard/ui/itemviews.py:87 picard/util/tags.py:23 msgid "Title" msgstr "" #: picard/ui/itemviews.py:88 picard/util/tags.py:84 msgid "Length" msgstr "" #: picard/ui/itemviews.py:214 msgid "&Expand all" msgstr "" #: picard/ui/itemviews.py:216 msgid "&Collapse all" msgstr "" #: picard/ui/itemviews.py:273 msgid "&Other versions" msgstr "" #: picard/ui/itemviews.py:276 msgid "Loading..." msgstr "" #: picard/ui/itemviews.py:286 msgid "[no release info]" msgstr "" #: picard/ui/itemviews.py:309 msgid "&Plugins" msgstr "" #: picard/ui/itemviews.py:527 msgid "Clusters" msgstr "" #: picard/ui/logview.py:30 msgid "Log" msgstr "" #: picard/ui/mainwindow.py:73 msgid "MusicBrainz Picard" msgstr "" #: picard/ui/mainwindow.py:151 msgid "Unsaved Changes" msgstr "" #: picard/ui/mainwindow.py:152 msgid "Are you sure you want to quit Picard?" msgstr "" #: picard/ui/mainwindow.py:153 #, python-format msgid "" "There is %d unsaved file. Closing Picard will lose all unsaved changes." msgid_plural "" "There are %d unsaved files. Closing Picard will lose all unsaved changes." msgstr[0] "" msgstr[1] "" #: picard/ui/mainwindow.py:160 msgid "&Quit Picard" msgstr "" #: picard/ui/mainwindow.py:210 msgid "Ready" msgstr "" #: picard/ui/mainwindow.py:214 msgid "" "Picard listens on a port to integrate with your browser and downloads " "release information when you click the \"Tagger\" buttons on the MusicBrainz" " website" msgstr "" #: picard/ui/mainwindow.py:224 #, python-format msgid " Files: %(files)d, Pending Files: %(pending)d " msgstr "" #: picard/ui/mainwindow.py:229 #, python-format msgid " Listening on port %(port)d " msgstr "" #: picard/ui/mainwindow.py:257 msgid "Submission Error" msgstr "" #: picard/ui/mainwindow.py:258 msgid "" "You need to configure your AcoustID API key before you can submit " "fingerprints." msgstr "" #: picard/ui/mainwindow.py:265 msgid "&Options..." msgstr "" #: picard/ui/mainwindow.py:268 msgid "&Cut" msgstr "" #: picard/ui/mainwindow.py:273 msgid "&Paste" msgstr "" #: picard/ui/mainwindow.py:278 msgid "&Help..." msgstr "" #: picard/ui/mainwindow.py:283 msgid "&About..." msgstr "" #: picard/ui/mainwindow.py:286 msgid "&Donate..." msgstr "" #: picard/ui/mainwindow.py:289 msgid "&Report a Bug..." msgstr "" #: picard/ui/mainwindow.py:292 msgid "&Support Forum..." msgstr "" #: picard/ui/mainwindow.py:295 msgid "&Add Files..." msgstr "" #: picard/ui/mainwindow.py:296 msgid "Add files to the tagger" msgstr "" #: picard/ui/mainwindow.py:301 msgid "A&dd Folder..." msgstr "" #: picard/ui/mainwindow.py:302 msgid "Add a folder to the tagger" msgstr "" #: picard/ui/mainwindow.py:304 msgid "Ctrl+D" msgstr "" #: picard/ui/mainwindow.py:308 msgid "&Save" msgstr "" #: picard/ui/mainwindow.py:309 msgid "Save selected files" msgstr "" #: picard/ui/mainwindow.py:315 msgid "S&ubmit" msgstr "" #: picard/ui/mainwindow.py:316 msgid "Submit fingerprints" msgstr "" #: picard/ui/mainwindow.py:320 msgid "E&xit" msgstr "" #: picard/ui/mainwindow.py:322 msgid "Ctrl+Q" msgstr "" #: picard/ui/mainwindow.py:325 msgid "&Remove" msgstr "" #: picard/ui/mainwindow.py:326 msgid "Remove selected files/albums" msgstr "" #: picard/ui/mainwindow.py:330 msgid "Lookup in &Browser" msgstr "" #: picard/ui/mainwindow.py:331 msgid "Lookup selected item on MusicBrainz website" msgstr "" #: picard/ui/mainwindow.py:335 msgid "File &Browser" msgstr "" #: picard/ui/mainwindow.py:339 msgid "Ctrl+B" msgstr "" #: picard/ui/mainwindow.py:342 msgid "&Cover Art" msgstr "" #: picard/ui/mainwindow.py:348 msgid "Search" msgstr "" #: picard/ui/mainwindow.py:351 msgid "&CD Lookup..." msgstr "" #: picard/ui/mainwindow.py:352 picard/ui/mainwindow.py:353 msgid "Lookup CD" msgstr "" #: picard/ui/mainwindow.py:355 msgid "Ctrl+K" msgstr "" #: picard/ui/mainwindow.py:358 msgid "&Scan" msgstr "" #: picard/ui/mainwindow.py:361 msgid "Ctrl+Y" msgstr "" #: picard/ui/mainwindow.py:364 msgid "Cl&uster" msgstr "" #: picard/ui/mainwindow.py:367 msgid "Ctrl+U" msgstr "" #: picard/ui/mainwindow.py:370 msgid "&Lookup" msgstr "" #: picard/ui/mainwindow.py:371 picard/ui/mainwindow.py:372 msgid "Lookup metadata" msgstr "" #: picard/ui/mainwindow.py:375 msgid "Ctrl+L" msgstr "" #: picard/ui/mainwindow.py:378 msgid "&Info..." msgstr "" #: picard/ui/mainwindow.py:381 msgid "Ctrl+I" msgstr "" #: picard/ui/mainwindow.py:384 msgid "&Refresh" msgstr "" #: picard/ui/mainwindow.py:385 msgid "Ctrl+R" msgstr "" #: picard/ui/mainwindow.py:388 msgid "&Rename Files" msgstr "" #: picard/ui/mainwindow.py:393 msgid "&Move Files" msgstr "" #: picard/ui/mainwindow.py:398 msgid "Save &Tags" msgstr "" #: picard/ui/mainwindow.py:403 msgid "Tags From &File Names..." msgstr "" #: picard/ui/mainwindow.py:406 msgid "View &Log..." msgstr "" #: picard/ui/mainwindow.py:412 msgid "&Open..." msgstr "" #: picard/ui/mainwindow.py:413 msgid "Open the file" msgstr "" #: picard/ui/mainwindow.py:416 msgid "Open &Folder..." msgstr "" #: picard/ui/mainwindow.py:417 msgid "Open the containing folder" msgstr "" #: picard/ui/mainwindow.py:438 msgid "&File" msgstr "" #: picard/ui/mainwindow.py:446 msgid "&Edit" msgstr "" #: picard/ui/mainwindow.py:452 msgid "&View" msgstr "" #: picard/ui/mainwindow.py:460 msgid "&Options" msgstr "" #: picard/ui/mainwindow.py:466 msgid "&Tools" msgstr "" #: picard/ui/mainwindow.py:475 picard/ui/util.py:33 msgid "&Help" msgstr "" #: picard/ui/mainwindow.py:493 msgid "&Toolbar" msgstr "" #: picard/ui/mainwindow.py:520 msgid "&Search Bar" msgstr "" #: picard/ui/mainwindow.py:527 picard/ui/puidsubmit.py:31 msgid "Track" msgstr "" #: picard/ui/mainwindow.py:566 msgid "All Supported Formats" msgstr "" #: picard/ui/mainwindow.py:624 picard/ui/mainwindow.py:633 msgid "Various Artists file naming scheme removal" msgstr "" #: picard/ui/mainwindow.py:625 msgid "" "The separate file naming scheme for various artists albums has been\n" "removed in this version of Picard. You currently do not use the this option,\n" "but have a separate file naming scheme defined. Do you want to remove it or\n" "merge it with your file naming scheme for single artist albums?" msgstr "" #: picard/ui/mainwindow.py:629 msgid "Merge" msgstr "" #: picard/ui/mainwindow.py:629 picard/ui/metadatabox.py:185 msgid "Remove" msgstr "" #: picard/ui/mainwindow.py:634 msgid "" "The separate file naming scheme for various artists albums has been\n" "removed in this version of Picard. Your file naming scheme has automatically\n" "been merged with that of single artist albums." msgstr "" #: picard/ui/mainwindow.py:682 msgid "Configuration Required" msgstr "" #: picard/ui/mainwindow.py:683 msgid "" "Audio fingerprinting is not yet configured. Would you like to configure it " "now?" msgstr "" #: picard/ui/mainwindow.py:756 picard/ui/mainwindow.py:763 #, python-format msgid " (Error: %s)" msgstr "" #: picard/ui/metadatabox.py:64 #, python-format msgid "(missing from %d item)" msgid_plural "(missing from %d items)" msgstr[0] "" msgstr[1] "" #: picard/ui/metadatabox.py:66 #, python-format msgid "(different across %d item)" msgid_plural "(different across %d items)" msgstr[0] "" msgstr[1] "" #: picard/ui/metadatabox.py:90 msgid "Tag" msgstr "" #: picard/ui/metadatabox.py:90 msgid "Original Value" msgstr "" #: picard/ui/metadatabox.py:90 msgid "New Value" msgstr "" #: picard/ui/metadatabox.py:117 msgid "Add New Tag..." msgstr "" #: picard/ui/metadatabox.py:119 msgid "Show Changes First" msgstr "" #: picard/ui/metadatabox.py:166 msgid "Edit..." msgstr "" #: picard/ui/metadatabox.py:189 msgid "Use Original Value" msgid_plural "Use Original Values" msgstr[0] "" msgstr[1] "" #: picard/ui/passworddialog.py:38 #, python-format msgid "" "The server %s requires you to login. Please enter your username and " "password." msgstr "" #: picard/ui/passworddialog.py:76 #, python-format msgid "" "The proxy %s requires you to login. Please enter your username and password." msgstr "" #: picard/ui/puidsubmit.py:31 picard/ui/options/plugins.py:125 msgid "File" msgstr "" #: picard/ui/puidsubmit.py:31 msgid "PUID" msgstr "" #: picard/ui/puidsubmit.py:31 msgid "Release" msgstr "" #: picard/ui/puidsubmit.py:31 msgid "Release ID" msgstr "" #: picard/ui/tagsfromfilenames.py:54 picard/ui/tagsfromfilenames.py:99 msgid "File Name" msgstr "" #: picard/ui/ui_cdlookup.py:57 picard/ui/ui_options_cdlookup.py:46 #: picard/ui/ui_options_cdlookup_select.py:53 picard/ui/options/cdlookup.py:36 msgid "CD Lookup" msgstr "" #: picard/ui/ui_cdlookup.py:58 msgid "The following releases on MusicBrainz match the CD:" msgstr "" #: picard/ui/ui_cdlookup.py:59 picard/ui/ui_puidsubmit.py:52 msgid "OK" msgstr "" #: picard/ui/ui_cdlookup.py:60 msgid " Lookup manually " msgstr "" #: picard/ui/ui_cdlookup.py:61 picard/ui/ui_puidsubmit.py:53 msgid "Cancel" msgstr "" #: picard/ui/ui_edittagdialog.py:92 msgid "Edit Tag" msgstr "" #: picard/ui/ui_edittagdialog.py:93 msgid "Edit value" msgstr "" #: picard/ui/ui_edittagdialog.py:94 msgid "Add value" msgstr "" #: picard/ui/ui_edittagdialog.py:95 msgid "Remove value" msgstr "" #: picard/ui/ui_infodialog.py:66 msgid "&Info" msgstr "" #: picard/ui/ui_infodialog.py:67 msgid "A&rtwork" msgstr "" #: picard/ui/ui_options.py:42 msgid "Options" msgstr "" #: picard/ui/ui_options_cdlookup.py:47 msgid "CD-ROM device to use for lookups:" msgstr "" #: picard/ui/ui_options_cdlookup_select.py:54 msgid "Default CD-ROM drive to use for lookups:" msgstr "" #: picard/ui/ui_options_cover.py:53 msgid "Location" msgstr "" #: picard/ui/ui_options_cover.py:54 msgid "Embed cover images into tags" msgstr "" #: picard/ui/ui_options_cover.py:55 msgid "Save cover images as separate files" msgstr "" #: picard/ui/ui_options_cover.py:56 msgid "Overwrite the file if it already exists" msgstr "" #: picard/ui/ui_options_fingerprinting.py:74 msgid "Audio Fingerprinting" msgstr "" #: picard/ui/ui_options_fingerprinting.py:75 msgid "Use AcoustID" msgstr "" #: picard/ui/ui_options_fingerprinting.py:76 msgid "Use AmpliFIND (formerly MusicDNS)" msgstr "" #: picard/ui/ui_options_fingerprinting.py:77 msgid "AcoustID Settings" msgstr "" #: picard/ui/ui_options_fingerprinting.py:78 msgid "Fingerprint calculator:" msgstr "" #: picard/ui/ui_options_fingerprinting.py:79 #: picard/ui/ui_options_renaming.py:138 msgid "Browse..." msgstr "" #: picard/ui/ui_options_fingerprinting.py:80 msgid "Download..." msgstr "" #: picard/ui/ui_options_fingerprinting.py:81 msgid "API key:" msgstr "" #: picard/ui/ui_options_fingerprinting.py:82 msgid "Get API key..." msgstr "" #: picard/ui/ui_options_folksonomy.py:106 picard/ui/options/folksonomy.py:29 msgid "Folksonomy Tags" msgstr "" #: picard/ui/ui_options_folksonomy.py:108 msgid "Only use my tags" msgstr "" #: picard/ui/ui_options_folksonomy.py:111 msgid "Maximum number of tags:" msgstr "" #: picard/ui/ui_options_general.py:92 msgid "MusicBrainz Server" msgstr "" #: picard/ui/ui_options_general.py:93 picard/ui/ui_options_proxy.py:77 msgid "Port:" msgstr "" #: picard/ui/ui_options_general.py:94 picard/ui/ui_options_proxy.py:78 msgid "Server address:" msgstr "" #: picard/ui/ui_options_general.py:95 msgid "Account Information" msgstr "" #: picard/ui/ui_options_general.py:96 picard/ui/ui_options_proxy.py:75 #: picard/ui/ui_passworddialog.py:74 msgid "Password:" msgstr "" #: picard/ui/ui_options_general.py:97 picard/ui/ui_options_proxy.py:76 #: picard/ui/ui_passworddialog.py:73 msgid "Username:" msgstr "" #: picard/ui/ui_options_general.py:98 picard/ui/options/general.py:29 msgid "General" msgstr "" #: picard/ui/ui_options_general.py:99 msgid "Automatically scan all new files" msgstr "" #: picard/ui/ui_options_general.py:100 msgid "Ignore MBIDs when loading new files" msgstr "" #: picard/ui/ui_options_interface.py:58 msgid "Miscellaneous" msgstr "" #: picard/ui/ui_options_interface.py:59 msgid "Show text labels under icons" msgstr "" #: picard/ui/ui_options_interface.py:60 msgid "Allow selection of multiple directories" msgstr "" #: picard/ui/ui_options_interface.py:61 msgid "Use advanced query syntax" msgstr "" #: picard/ui/ui_options_interface.py:62 msgid "Show a quit confirmation dialog for unsaved changes" msgstr "" #: picard/ui/ui_options_interface.py:63 msgid "User interface language:" msgstr "" #: picard/ui/ui_options_matching.py:77 msgid "Thresholds" msgstr "" #: picard/ui/ui_options_matching.py:78 msgid "Minimal similarity for matching files to tracks:" msgstr "" #: picard/ui/ui_options_matching.py:82 msgid "Minimal similarity for file lookups:" msgstr "" #: picard/ui/ui_options_matching.py:83 msgid "Minimal similarity for cluster lookups:" msgstr "" #: picard/ui/ui_options_metadata.py:101 picard/ui/options/metadata.py:30 msgid "Metadata" msgstr "" #: picard/ui/ui_options_metadata.py:102 msgid "Translate artist names to this locale where possible:" msgstr "" #: picard/ui/ui_options_metadata.py:103 msgid "Use standardized artist names" msgstr "" #: picard/ui/ui_options_metadata.py:104 msgid "Convert Unicode punctuation characters to ASCII" msgstr "" #: picard/ui/ui_options_metadata.py:105 msgid "Use release relationships" msgstr "" #: picard/ui/ui_options_metadata.py:106 msgid "Use track relationships" msgstr "" #: picard/ui/ui_options_metadata.py:107 msgid "Use folksonomy tags as genre" msgstr "" #: picard/ui/ui_options_metadata.py:108 msgid "Custom Fields" msgstr "" #: picard/ui/ui_options_metadata.py:109 msgid "Various artists:" msgstr "" #: picard/ui/ui_options_metadata.py:110 msgid "Non-album tracks:" msgstr "" #: picard/ui/ui_options_metadata.py:111 picard/ui/ui_options_metadata.py:112 #: picard/ui/ui_options_renaming.py:147 msgid "Default" msgstr "" #: picard/ui/ui_options_plugins.py:131 picard/ui/options/plugins.py:37 msgid "Plugins" msgstr "" #: picard/ui/ui_options_plugins.py:132 picard/ui/options/plugins.py:121 msgid "Name" msgstr "" #: picard/ui/ui_options_plugins.py:133 picard/util/tags.py:37 msgid "Version" msgstr "" #: picard/ui/ui_options_plugins.py:134 picard/ui/options/plugins.py:124 msgid "Author" msgstr "" #: picard/ui/ui_options_plugins.py:135 msgid "Install plugin..." msgstr "" #: picard/ui/ui_options_plugins.py:136 msgid "Open plugin folder" msgstr "" #: picard/ui/ui_options_plugins.py:137 msgid "Download plugins" msgstr "" #: picard/ui/ui_options_plugins.py:138 msgid "Details" msgstr "" #: picard/ui/ui_options_proxy.py:74 picard/ui/options/proxy.py:28 msgid "Web Proxy" msgstr "" #: picard/ui/ui_options_ratings.py:53 msgid "Enable track ratings" msgstr "" #: picard/ui/ui_options_ratings.py:54 msgid "" "Picard saves the ratings together with an e-mail address identifying the " "user who did the rating. That way different ratings for different users can " "be stored in the files. Please specify the e-mail you want to use to save " "your ratings." msgstr "" #: picard/ui/ui_options_ratings.py:55 msgid "E-mail:" msgstr "" #: picard/ui/ui_options_ratings.py:56 msgid "Submit ratings to MusicBrainz" msgstr "" #: picard/ui/ui_options_releases.py:215 msgid "Preferred release types" msgstr "" #: picard/ui/ui_options_releases.py:217 msgid "Single" msgstr "" #: picard/ui/ui_options_releases.py:218 msgid "EP" msgstr "" #: picard/ui/ui_options_releases.py:219 picard/util/tags.py:66 msgid "Compilation" msgstr "Kompilasie" #: picard/ui/ui_options_releases.py:220 msgid "Soundtrack" msgstr "" #: picard/ui/ui_options_releases.py:221 msgid "Spokenword" msgstr "" #: picard/ui/ui_options_releases.py:222 msgid "Interview" msgstr "" #: picard/ui/ui_options_releases.py:223 msgid "Audiobook" msgstr "" #: picard/ui/ui_options_releases.py:224 msgid "Live" msgstr "" #: picard/ui/ui_options_releases.py:225 msgid "Remix" msgstr "" #: picard/ui/ui_options_releases.py:227 msgid "Reset all" msgstr "" #: picard/ui/ui_options_releases.py:228 msgid "Preferred release countries" msgstr "" #: picard/ui/ui_options_releases.py:229 picard/ui/ui_options_releases.py:232 msgid ">" msgstr "" #: picard/ui/ui_options_releases.py:230 picard/ui/ui_options_releases.py:233 msgid "<" msgstr "" #: picard/ui/ui_options_releases.py:231 msgid "Preferred release formats" msgstr "" #: picard/ui/ui_options_renaming.py:134 msgid "Rename files when saving" msgstr "" #: picard/ui/ui_options_renaming.py:135 msgid "Replace non-ASCII characters" msgstr "" #: picard/ui/ui_options_renaming.py:136 msgid "Replace Windows-incompatible characters" msgstr "" #: picard/ui/ui_options_renaming.py:137 msgid "Move files to this directory when saving:" msgstr "" #: picard/ui/ui_options_renaming.py:139 msgid "Delete empty directories" msgstr "" #: picard/ui/ui_options_renaming.py:140 msgid "Move additional files:" msgstr "" #: picard/ui/ui_options_renaming.py:141 msgid "Name files like this" msgstr "" #: picard/ui/ui_options_renaming.py:148 msgid "Examples" msgstr "" #: picard/ui/ui_options_script.py:49 msgid "Tagger Script" msgstr "" #: picard/ui/ui_options_tags.py:114 msgid "Write tags to files" msgstr "" #: picard/ui/ui_options_tags.py:115 msgid "Preserve timestamps of tagged files" msgstr "" #: picard/ui/ui_options_tags.py:116 msgid "Before tagging" msgstr "" #: picard/ui/ui_options_tags.py:117 msgid "Clear existing tags" msgstr "" #: picard/ui/ui_options_tags.py:118 msgid "Remove ID3 tags from FLAC files" msgstr "" #: picard/ui/ui_options_tags.py:119 msgid "Remove APEv2 tags from MP3 files" msgstr "" #: picard/ui/ui_options_tags.py:120 msgid "" "Preserve these tags from being cleared or overwritten with MusicBrainz data:" msgstr "" #: picard/ui/ui_options_tags.py:121 msgid "Tags are separated by spaces, and are case-sensitive." msgstr "" #: picard/ui/ui_options_tags.py:122 msgid "Tag compatibility" msgstr "" #: picard/ui/ui_options_tags.py:123 msgid "ID3v2 version" msgstr "" #: picard/ui/ui_options_tags.py:124 msgid "2.4" msgstr "" #: picard/ui/ui_options_tags.py:125 msgid "2.3" msgstr "" #: picard/ui/ui_options_tags.py:126 msgid "Also include ID3v1 tags in the files" msgstr "" #: picard/ui/ui_options_tags.py:127 msgid "ID3v2 text encoding" msgstr "" #: picard/ui/ui_options_tags.py:128 msgid "UTF-8" msgstr "" #: picard/ui/ui_options_tags.py:129 msgid "UTF-16" msgstr "" #: picard/ui/ui_options_tags.py:130 msgid "ISO-8859-1" msgstr "" #: picard/ui/ui_passworddialog.py:72 msgid "Authentication required" msgstr "" #: picard/ui/ui_passworddialog.py:75 msgid "Save username and password" msgstr "" #: picard/ui/ui_puidsubmit.py:51 msgid "Submit PUIDs" msgstr "" #: picard/ui/ui_tagsfromfilenames.py:54 msgid "Convert File Names to Tags" msgstr "" #: picard/ui/ui_tagsfromfilenames.py:55 msgid "Replace underscores with spaces" msgstr "" #: picard/ui/ui_tagsfromfilenames.py:56 msgid "&Preview" msgstr "" #: picard/ui/util.py:31 msgid "&Ok" msgstr "" #: picard/ui/util.py:32 msgid "&Cancel" msgstr "" #: picard/ui/options/about.py:29 msgid "About" msgstr "" #: picard/ui/options/about.py:48 msgid "translator-credits" msgstr "Launchpad Contributions:\n Darryl Smith https://launchpad.net/~semiintel" #: picard/ui/options/about.py:51 #, python-format msgid "
Translated to LANG by %s" msgstr "" #: picard/ui/options/about.py:55 #, python-format msgid "" "

MusicBrainz Picard
\n" "Version %(version)s

\n" "

Supported formats
%(formats)s

\n" "

Please donate
\n" "Thank you for using Picard. Picard relies on the MusicBrainz database, which is operated by the MetaBrainz Foundation with the help of thousands of volunteers. If you like this application please consider donating to the MetaBrainz Foundation to keep the service running.

\n" "

Donate now!

\n" "

Credits
\n" "Copyright © 2004-2011 Robert Kaye, Lukáš Lalinský and others%(translator-credits)s

\n" "

http://musicbrainz.org/doc/MusicBrainz_Picard

\n" msgstr "" #: picard/ui/options/advanced.py:26 msgid "Advanced" msgstr "" #: picard/ui/options/cover.py:29 msgid "Cover Art" msgstr "" #: picard/ui/options/fingerprinting.py:32 msgid "Fingerprinting" msgstr "" #: picard/ui/options/interface.py:32 msgid "User Interface" msgstr "" #: picard/ui/options/interface.py:49 msgid "System default" msgstr "" #: picard/ui/options/interface.py:76 msgid "Language changed" msgstr "" #: picard/ui/options/interface.py:76 msgid "" "You have changed the interface language. You have to restart Picard in order" " for the change to take effect." msgstr "" #: picard/ui/options/matching.py:29 msgid "Matching" msgstr "" #: picard/ui/options/ratings.py:29 msgid "Ratings" msgstr "" #: picard/ui/options/releases.py:33 msgid "Preferred Releases" msgstr "" #: picard/ui/options/renaming.py:34 msgid "File naming" msgstr "" #: picard/ui/options/renaming.py:143 msgid "Error" msgstr "" #: picard/ui/options/renaming.py:143 msgid "The location to move files to must not be empty." msgstr "" #: picard/ui/options/renaming.py:153 msgid "The file naming format must not be empty." msgstr "" #: picard/ui/options/scripting.py:63 msgid "Scripting" msgstr "" #: picard/ui/options/scripting.py:97 msgid "Script Error" msgstr "" #: picard/util/tags.py:25 msgid "Original Release Date" msgstr "" #: picard/util/tags.py:26 msgid "Album Artist" msgstr "" #: picard/util/tags.py:27 msgid "Track Number" msgstr "" #: picard/util/tags.py:28 msgid "Total Tracks" msgstr "" #: picard/util/tags.py:29 msgid "Disc Number" msgstr "" #: picard/util/tags.py:30 msgid "Total Discs" msgstr "" #: picard/util/tags.py:31 msgid "Album Artist Sort Order" msgstr "" #: picard/util/tags.py:32 msgid "Artist Sort Order" msgstr "" #: picard/util/tags.py:33 msgid "Title Sort Order" msgstr "" #: picard/util/tags.py:34 msgid "Album Sort Order" msgstr "" #: picard/util/tags.py:35 msgid "ASIN" msgstr "" #: picard/util/tags.py:36 msgid "Grouping" msgstr "" #: picard/util/tags.py:38 msgid "ISRC" msgstr "" #: picard/util/tags.py:39 msgid "Mood" msgstr "" #: picard/util/tags.py:40 msgid "BPM" msgstr "" #: picard/util/tags.py:41 msgid "Copyright" msgstr "" #: picard/util/tags.py:42 msgid "License" msgstr "" #: picard/util/tags.py:43 msgid "Composer" msgstr "" #: picard/util/tags.py:44 msgid "Writer" msgstr "" #: picard/util/tags.py:45 msgid "Conductor" msgstr "" #: picard/util/tags.py:46 msgid "Lyricist" msgstr "" #: picard/util/tags.py:47 msgid "Arranger" msgstr "" #: picard/util/tags.py:48 msgid "Producer" msgstr "" #: picard/util/tags.py:49 msgid "Engineer" msgstr "" #: picard/util/tags.py:50 msgid "Subtitle" msgstr "" #: picard/util/tags.py:51 msgid "Disc Subtitle" msgstr "" #: picard/util/tags.py:52 msgid "Remixer" msgstr "" #: picard/util/tags.py:53 msgid "MusicBrainz Recording Id" msgstr "" #: picard/util/tags.py:54 msgid "MusicBrainz Release Id" msgstr "" #: picard/util/tags.py:55 msgid "MusicBrainz Artist Id" msgstr "" #: picard/util/tags.py:56 msgid "MusicBrainz Release Artist Id" msgstr "" #: picard/util/tags.py:57 msgid "MusicBrainz Work Id" msgstr "" #: picard/util/tags.py:58 msgid "MusicBrainz Disc Id" msgstr "" #: picard/util/tags.py:59 msgid "MusicBrainz Sort Name" msgstr "" #: picard/util/tags.py:60 msgid "MusicIP PUID" msgstr "" #: picard/util/tags.py:61 msgid "MusicIP Fingerprint" msgstr "" #: picard/util/tags.py:62 msgid "AcoustID" msgstr "" #: picard/util/tags.py:63 msgid "AcoustID Fingerprint" msgstr "" #: picard/util/tags.py:64 msgid "Disc Id" msgstr "" #: picard/util/tags.py:65 msgid "Website" msgstr "Webwerf" #: picard/util/tags.py:67 msgid "Comment" msgstr "Aanmerking" #: picard/util/tags.py:68 msgid "Genre" msgstr "" #: picard/util/tags.py:69 msgid "Encoded By" msgstr "" #: picard/util/tags.py:70 msgid "Performer" msgstr "" #: picard/util/tags.py:71 msgid "Release Type" msgstr "" #: picard/util/tags.py:72 msgid "Release Status" msgstr "" #: picard/util/tags.py:73 msgid "Release Country" msgstr "" #: picard/util/tags.py:74 msgid "Record Label" msgstr "" #: picard/util/tags.py:76 msgid "Catalog Number" msgstr "" #: picard/util/tags.py:77 msgid "Format" msgstr "" #: picard/util/tags.py:78 msgid "DJ-Mixer" msgstr "" #: picard/util/tags.py:79 msgid "Media" msgstr "Media" #: picard/util/tags.py:80 msgid "Lyrics" msgstr "" #: picard/util/tags.py:81 msgid "Mixer" msgstr "" #: picard/util/tags.py:82 msgid "Language" msgstr "Taal" #: picard/util/tags.py:83 msgid "Script" msgstr "" #: picard/util/tags.py:85 msgid "Rating" msgstr "" #: picard/util/webbrowser2.py:88 msgid "Web Browser Error" msgstr "" #: picard/util/webbrowser2.py:88 #, python-format msgid "" "Error while launching a web browser:\n" "\n" "%s" msgstr "" picard-release-1.4.2/po/ar.po000066400000000000000000001436461310410472100157650ustar00rootroot00000000000000# Translations template for PROJECT. # Copyright (C) 2012 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: # FIRST AUTHOR , 2009. msgid "" msgstr "" "Project-Id-Version: MusicBrainz\n" "Report-Msgid-Bugs-To: http://tickets.musicbrainz.org/\n" "POT-Creation-Date: 2012-08-28 15:50+0200\n" "PO-Revision-Date: 2012-08-29 08:53+0000\n" "Last-Translator: Lukáš Lalinský \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" "Language: ar\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" #: contrib/plugins/no_release.py:48 msgid "Enable plugin for all releases by default" msgstr "" #: contrib/plugins/no_release.py:49 msgid "Tags to strip (comma-separated)" msgstr "" #: contrib/plugins/no_release.py:59 msgid "Remove specific release information..." msgstr "" #: contrib/plugins/open_in_gui.py:30 msgid "Open Error" msgstr "" #: contrib/plugins/open_in_gui.py:30 #, python-format msgid "" "Error while opening file:\n" "\n" "%s" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:115 msgid "Last.fm" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:116 msgid "Use track tags" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:117 msgid "Use artist tags" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:118 #: picard/ui/options/tags.py:30 msgid "Tags" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:119 #: picard/ui/ui_options_folksonomy.py:107 msgid "Ignore tags:" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:120 #: picard/ui/ui_options_folksonomy.py:112 msgid "Join multiple tags with:" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:121 #: picard/ui/ui_options_folksonomy.py:113 msgid " / " msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:122 #: picard/ui/ui_options_folksonomy.py:114 msgid ", " msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:123 #: picard/ui/ui_options_folksonomy.py:109 msgid "Minimal tag usage:" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:124 #: picard/ui/ui_options_folksonomy.py:110 picard/ui/ui_options_matching.py:79 #: picard/ui/ui_options_matching.py:80 picard/ui/ui_options_matching.py:81 msgid " %" msgstr "" #: contrib/plugins/replaygain/__init__.py:48 msgid "Calculate replay &gain..." msgstr "" #: contrib/plugins/replaygain/__init__.py:65 #, python-format msgid "Calculating replay gain for \"%s\"..." msgstr "" #: contrib/plugins/replaygain/__init__.py:70 #, python-format msgid "Replay gain for \"%s\" successfully calculated." msgstr "" #: contrib/plugins/replaygain/__init__.py:72 #, python-format msgid "Could not calculate replay gain for \"%s\"." msgstr "" #: contrib/plugins/replaygain/__init__.py:75 msgid "Calculate album &gain..." msgstr "" #: contrib/plugins/replaygain/__init__.py:98 #, python-format msgid "Calculating album gain for \"%s\"..." msgstr "" #: contrib/plugins/replaygain/__init__.py:106 #, python-format msgid "Album gain for \"%s\" successfully calculated." msgstr "" #: contrib/plugins/replaygain/__init__.py:108 #, python-format msgid "Could not calculate album gain for \"%s\"." msgstr "" #: picard/acoustid.py:116 #, python-format msgid "Could not find AcoustID for file %s" msgstr "" #: picard/acoustid.py:120 picard/musicdns/__init__.py:103 #, python-format msgid "Looking up the fingerprint for file %s..." msgstr "" #: picard/acoustidmanager.py:78 msgid "Submitting AcoustIDs..." msgstr "" #: picard/acoustidmanager.py:83 #, python-format msgid "AcoustID submission failed: %s" msgstr "" #: picard/acoustidmanager.py:85 msgid "AcoustIDs successfully submitted!" msgstr "" #: picard/album.py:55 picard/cluster.py:260 msgid "Unmatched Files" msgstr "" #: picard/album.py:180 #, python-format msgid "[could not load album %s]" msgstr "" #: picard/album.py:266 #, python-format msgid "Album %s loaded" msgstr "" #: picard/album.py:282 msgid "[loading album information]" msgstr "تحميل معلومات الألبوم" #: picard/cluster.py:177 picard/cluster.py:188 #, python-format msgid "No matching releases for cluster %s" msgstr "" #: picard/cluster.py:190 #, python-format msgid "Cluster %s identified!" msgstr "" #: picard/cluster.py:195 #, python-format msgid "Looking up the metadata for cluster %s..." msgstr "" #: picard/const.py:39 msgid "CD" msgstr "قرص مدمج" #: picard/const.py:40 msgid "CD-R" msgstr "" #: picard/const.py:41 msgid "HDCD" msgstr "" #: picard/const.py:42 msgid "8cm CD" msgstr "" #: picard/const.py:43 msgid "Vinyl" msgstr "Vinyl" #: picard/const.py:44 msgid "7\" Vinyl" msgstr "" #: picard/const.py:45 msgid "10\" Vinyl" msgstr "" #: picard/const.py:46 msgid "12\" Vinyl" msgstr "" #: picard/const.py:47 msgid "Digital Media" msgstr "وسائط رقمية" #: picard/const.py:48 msgid "USB Flash Drive" msgstr "" #: picard/const.py:49 msgid "slotMusic" msgstr "" #: picard/const.py:50 msgid "Cassette" msgstr "Cassette" #: picard/const.py:51 msgid "DVD" msgstr "دي‌ڤي‌دي" #: picard/const.py:52 msgid "DVD-Audio" msgstr "" #: picard/const.py:53 msgid "DVD-Video" msgstr "" #: picard/const.py:54 msgid "SACD" msgstr "SACD" #: picard/const.py:55 msgid "DualDisc" msgstr "" #: picard/const.py:56 msgid "MiniDisc" msgstr "MiniDisc" #: picard/const.py:57 msgid "Blu-ray" msgstr "" #: picard/const.py:58 msgid "HD-DVD" msgstr "" #: picard/const.py:59 msgid "Videotape" msgstr "" #: picard/const.py:60 msgid "VHS" msgstr "" #: picard/const.py:61 msgid "Betamax" msgstr "" #: picard/const.py:62 msgid "VCD" msgstr "" #: picard/const.py:63 msgid "SVCD" msgstr "" #: picard/const.py:64 msgid "UMD" msgstr "" #: picard/const.py:65 picard/ui/ui_options_releases.py:226 msgid "Other" msgstr "أخرى" #: picard/const.py:66 msgid "LaserDisc" msgstr "" #: picard/const.py:67 msgid "Cartridge" msgstr "" #: picard/const.py:68 msgid "Reel-to-reel" msgstr "" #: picard/const.py:69 msgid "DAT" msgstr "DAT" #: picard/const.py:70 msgid "Wax Cylinder" msgstr "" #: picard/const.py:71 msgid "Piano Roll" msgstr "" #: picard/const.py:72 msgid "DCC" msgstr "" #: picard/const.py:77 msgid "Bangladesh" msgstr "بنجلاديش" #: picard/const.py:78 msgid "Belgium" msgstr "بلجيكا" #: picard/const.py:79 msgid "Burkina Faso" msgstr "بوركينا فاسو" #: picard/const.py:80 msgid "Bulgaria" msgstr "بلغاريا" #: picard/const.py:81 msgid "Barbados" msgstr "باربادوس" #: picard/const.py:82 msgid "Wallis and Futuna Islands" msgstr "" #: picard/const.py:83 msgid "Bermuda" msgstr "برمودا" #: picard/const.py:84 msgid "Brunei Darussalam" msgstr "بروناي دار السّلام" #: picard/const.py:85 msgid "Bolivia" msgstr "بوليفيا" #: picard/const.py:86 msgid "Bahrain" msgstr "البحرين" #: picard/const.py:87 msgid "Burundi" msgstr "بوروندي" #: picard/const.py:88 msgid "Benin" msgstr "بينين" #: picard/const.py:89 msgid "Bhutan" msgstr "بوتان" #: picard/const.py:90 msgid "Jamaica" msgstr "جامايكا" #: picard/const.py:91 msgid "Bouvet Island" msgstr "جزيرة بوفي" #: picard/const.py:92 msgid "Botswana" msgstr "بوتسوانا" #: picard/const.py:93 msgid "Samoa" msgstr "ساماوا" #: picard/const.py:94 msgid "Brazil" msgstr "البرازيل" #: picard/const.py:95 msgid "Bahamas" msgstr "جزر الباهاما" #: picard/const.py:96 msgid "Belarus" msgstr "روسيا البيضاء" #: picard/const.py:97 msgid "Belize" msgstr "بيليز" #: picard/const.py:98 msgid "Russian Federation" msgstr "روسيا" #: picard/const.py:99 msgid "Rwanda" msgstr "راوندا" #: picard/const.py:100 msgid "Reunion" msgstr "" #: picard/const.py:101 msgid "Turkmenistan" msgstr "تركمانستان" #: picard/const.py:102 msgid "Tajikistan" msgstr "طاجاكستان" #: picard/const.py:103 msgid "Romania" msgstr "رومانيا" #: picard/const.py:104 msgid "Tokelau" msgstr "" #: picard/const.py:105 msgid "Guinea-Bissa" msgstr "غينيا-بيساو" #: picard/const.py:106 msgid "Guam" msgstr "غوام" #: picard/const.py:107 msgid "Guatemala" msgstr "غواتيمالا" #: picard/const.py:108 msgid "Greece" msgstr "اليونان" #: picard/const.py:109 msgid "Equatorial Guinea" msgstr "غينيا الاستوائية" #: picard/const.py:110 msgid "Guadeloupe" msgstr "جوادلوب" #: picard/const.py:111 msgid "Japan" msgstr "اليابان" #: picard/const.py:112 msgid "Guyana" msgstr "غويانا" #: picard/const.py:113 msgid "French Guiana" msgstr "جويانا الفرنسية" #: picard/const.py:114 msgid "Georgia" msgstr "جورجيا" #: picard/const.py:115 msgid "Grenada" msgstr "" #: picard/const.py:116 msgid "United Kingdom" msgstr "المملكة المتحدة" #: picard/const.py:117 msgid "Gabon" msgstr "الجابون" #: picard/const.py:118 msgid "El Salvador" msgstr "السلفادور" #: picard/const.py:119 msgid "Guinea" msgstr "غينيا" #: picard/const.py:120 msgid "Gambia" msgstr "جامبيا" #: picard/const.py:121 msgid "Greenland" msgstr "جرينلاند" #: picard/const.py:122 msgid "Gibraltar" msgstr "" #: picard/const.py:123 msgid "Ghana" msgstr "غانا" #: picard/const.py:124 msgid "Oman" msgstr "عُمان" #: picard/const.py:125 msgid "Tunisia" msgstr "تونس" #: picard/const.py:126 msgid "Jordan" msgstr "الأردن" #: picard/const.py:127 msgid "Haiti" msgstr "هاييتي" #: picard/const.py:128 msgid "Hungary" msgstr "هنغاريا" #: picard/const.py:129 msgid "Hong Kong" msgstr "هونج كونج" #: picard/const.py:130 msgid "Honduras" msgstr "هندوراس" #: picard/const.py:131 msgid "Heard and Mc Donald Islands" msgstr "" #: picard/const.py:132 msgid "Venezuela" msgstr "فنزويلا" #: picard/const.py:133 msgid "Puerto Rico" msgstr "بورتو ريكو" #: picard/const.py:134 msgid "Palau" msgstr "" #: picard/const.py:135 msgid "Portugal" msgstr "البرتغال" #: picard/const.py:136 msgid "Svalbard and Jan Mayen Islands" msgstr "" #: picard/const.py:137 msgid "Paraguay" msgstr "البارجواي" #: picard/const.py:138 msgid "Iraq" msgstr "العراق" #: picard/const.py:139 msgid "Panama" msgstr "بنما" #: picard/const.py:140 msgid "French Polynesia" msgstr "" #: picard/const.py:141 msgid "Papua New Guinea" msgstr "" #: picard/const.py:142 msgid "Peru" msgstr "البيرو" #: picard/const.py:143 msgid "Pakistan" msgstr "باكستان" #: picard/const.py:144 msgid "Philippines" msgstr "الفلبين" #: picard/const.py:145 msgid "Pitcairn" msgstr "" #: picard/const.py:146 msgid "Poland" msgstr "بولندا" #: picard/const.py:147 msgid "St. Pierre and Miquelon" msgstr "" #: picard/const.py:148 msgid "Zambia" msgstr "زامبيا" #: picard/const.py:149 msgid "Western Sahara" msgstr "الصحراء الغربية" #: picard/const.py:150 msgid "Estonia" msgstr "إستونيا" #: picard/const.py:151 msgid "Egypt" msgstr "مصر" #: picard/const.py:152 msgid "South Africa" msgstr "جنوب أفريقيا" #: picard/const.py:153 msgid "Ecuador" msgstr "الإكوادور" #: picard/const.py:154 msgid "Italy" msgstr "إيطاليا" #: picard/const.py:155 msgid "Viet Nam" msgstr "فيتنام" #: picard/const.py:156 msgid "Solomon Islands" msgstr "جزر سليمان" #: picard/const.py:157 msgid "Ethiopia" msgstr "إثيوبيا" #: picard/const.py:158 msgid "Somalia" msgstr "الصومال" #: picard/const.py:159 msgid "Zimbabwe" msgstr "زمبابوي" #: picard/const.py:160 msgid "Saudi Arabia" msgstr "السعودية" #: picard/const.py:161 msgid "Spain" msgstr "إسبانيا" #: picard/const.py:162 msgid "Eritrea" msgstr "إرتيريا" #: picard/const.py:163 msgid "Moldova, Republic of" msgstr "مولدوفا" #: picard/const.py:164 msgid "Madagascar" msgstr "مدغشقر" #: picard/const.py:165 msgid "Morocco" msgstr "المغرب" #: picard/const.py:166 msgid "Monaco" msgstr "موناكو" #: picard/const.py:167 msgid "Uzbekistan" msgstr "أوزباكستان" #: picard/const.py:168 msgid "Myanmar" msgstr "مينمار" #: picard/const.py:169 msgid "Mali" msgstr "مالي" #: picard/const.py:170 msgid "Macau" msgstr "موناكو" #: picard/const.py:171 msgid "Mongolia" msgstr "منغوليا" #: picard/const.py:172 msgid "Marshall Islands" msgstr "جزر المارشال" #: picard/const.py:173 msgid "Macedonia, The Former Yugoslav Republic of" msgstr "مقدونيا" #: picard/const.py:174 msgid "Mauritius" msgstr "موريشيوس" #: picard/const.py:175 msgid "Malta" msgstr "مالطا" #: picard/const.py:176 msgid "Malawi" msgstr "الملاوي" #: picard/const.py:177 msgid "Maldives" msgstr "مولدافيا" #: picard/const.py:178 msgid "Martinique" msgstr "" #: picard/const.py:179 msgid "Northern Mariana Islands" msgstr "جزر ماريانا الشمالية" #: picard/const.py:180 msgid "Montserrat" msgstr "" #: picard/const.py:181 msgid "Mauritania" msgstr "موريتانيا" #: picard/const.py:182 msgid "Uganda" msgstr "أوغندا" #: picard/const.py:183 msgid "Malaysia" msgstr "ماليزيا" #: picard/const.py:184 msgid "Mexico" msgstr "المكسيك" #: picard/const.py:185 msgid "Israel" msgstr "فلسطين المحتلة" #: picard/const.py:186 msgid "France" msgstr "فرنسا" #: picard/const.py:187 msgid "British Indian Ocean Territory" msgstr "إقليم المحيط الهندي البريطاني" #: picard/const.py:188 msgid "St. Helena" msgstr "سانت هيلانة" #: picard/const.py:189 msgid "Finland" msgstr "فنلندا" #: picard/const.py:190 msgid "Fiji" msgstr "فيجي" #: picard/const.py:191 msgid "Falkland Islands (Malvinas)" msgstr "جزر فوكلاند (مالفيناس)" #: picard/const.py:192 msgid "Micronesia, Federated States of" msgstr "ميكرونيزيا" #: picard/const.py:193 msgid "Faroe Islands" msgstr "جزر فورو" #: picard/const.py:194 msgid "Nicaragua" msgstr "نيكاراجوا" #: picard/const.py:195 msgid "Netherlands" msgstr "هولندا" #: picard/const.py:196 msgid "Norway" msgstr "النرويج" #: picard/const.py:197 msgid "Namibia" msgstr "ناميبيا" #: picard/const.py:198 msgid "Vanuatu" msgstr "" #: picard/const.py:199 msgid "New Caledonia" msgstr "كاليدونيا الجديدة" #: picard/const.py:200 msgid "Niger" msgstr "النجير" #: picard/const.py:201 msgid "Norfolk Island" msgstr "جزيرة نورفولك" #: picard/const.py:202 msgid "Nigeria" msgstr "نيجيريا" #: picard/const.py:203 msgid "New Zealand" msgstr "نيوزيلاندا" #: picard/const.py:204 msgid "Zaire" msgstr "زائير" #: picard/const.py:205 msgid "Nepal" msgstr "نيبال" #: picard/const.py:206 msgid "Nauru" msgstr "ناورو" #: picard/const.py:207 msgid "Niue" msgstr "نيوي" #: picard/const.py:208 msgid "Cook Islands" msgstr "جزر كوك" #: picard/const.py:209 msgid "Cote d'Ivoire" msgstr "ساحل العاج" #: picard/const.py:210 msgid "Switzerland" msgstr "سويسرا" #: picard/const.py:211 msgid "Colombia" msgstr "كولومبيا" #: picard/const.py:212 msgid "China" msgstr "الصين" #: picard/const.py:213 msgid "Cameroon" msgstr "الكاميرون" #: picard/const.py:214 msgid "Chile" msgstr "تشيلي" #: picard/const.py:215 msgid "Cocos (Keeling) Islands" msgstr "جزر كوكوس (كيلينج)" #: picard/const.py:216 msgid "Canada" msgstr "كندا" #: picard/const.py:217 msgid "Congo" msgstr "الكونغو" #: picard/const.py:218 msgid "Central African Republic" msgstr "جمهورية افريقيا الوسطى" #: picard/const.py:219 msgid "Czech Republic" msgstr "التشيك" #: picard/const.py:220 msgid "Cyprus" msgstr "قبرص" #: picard/const.py:221 msgid "Christmas Island" msgstr "جزر الكريسماس" #: picard/const.py:222 msgid "Costa Rica" msgstr "كوستاريكا" #: picard/const.py:223 msgid "Cape Verde" msgstr "الرأس الأخضر" #: picard/const.py:224 msgid "Cuba" msgstr "كوبا" #: picard/const.py:225 msgid "Swaziland" msgstr "سوازيلاند" #: picard/const.py:226 msgid "Syrian Arab Republic" msgstr "سوريا" #: picard/const.py:227 msgid "Kyrgyzstan" msgstr "قيرغيزستان" #: picard/const.py:228 msgid "Kenya" msgstr "كينيا" #: picard/const.py:229 msgid "Suriname" msgstr "سوارينام" #: picard/const.py:230 msgid "Kiribati" msgstr "كيريباس" #: picard/const.py:231 msgid "Cambodia" msgstr "كمبوديا" #: picard/const.py:232 msgid "Saint Kitts and Nevis" msgstr "سانت كيتس ونيفيس" #: picard/const.py:233 msgid "Comoros" msgstr "جزر القمر" #: picard/const.py:234 msgid "Sao Tome and Principe" msgstr "ساو تومي وبرينسيبي" #: picard/const.py:235 msgid "Slovenia" msgstr "سلوفينا" #: picard/const.py:236 msgid "Kuwait" msgstr "الكويت" #: picard/const.py:237 msgid "Senegal" msgstr "السنغال" #: picard/const.py:238 msgid "San Marino" msgstr "سان مارينو" #: picard/const.py:239 msgid "Sierra Leone" msgstr "سيراليون" #: picard/const.py:240 msgid "Seychelles" msgstr "سيشيل" #: picard/const.py:241 msgid "Kazakhstan" msgstr "كازاخستان" #: picard/const.py:242 msgid "Cayman Islands" msgstr "جزر كايمان" #: picard/const.py:243 msgid "Singapore" msgstr "سنغافورة" #: picard/const.py:244 msgid "Sweden" msgstr "السويد" #: picard/const.py:245 msgid "Sudan" msgstr "السودان" #: picard/const.py:246 msgid "Dominican Republic" msgstr "الدومينيكان" #: picard/const.py:247 msgid "Dominica" msgstr "دومينيكا" #: picard/const.py:248 msgid "Djibouti" msgstr "جيبوتي" #: picard/const.py:249 msgid "Denmark" msgstr "الدنمارك" #: picard/const.py:250 msgid "Virgin Islands (British)" msgstr "جزر العذراء (بريطانيا)" #: picard/const.py:251 msgid "Germany" msgstr "المانيا" #: picard/const.py:252 msgid "Yemen" msgstr "اليمن" #: picard/const.py:253 msgid "Algeria" msgstr "الجزائر" #: picard/const.py:254 msgid "United States" msgstr "الولايات المتحدة" #: picard/const.py:255 msgid "Uruguay" msgstr "الأورغواي" #: picard/const.py:256 msgid "Mayotte" msgstr "مايوت" #: picard/const.py:257 msgid "United States Minor Outlying Islands" msgstr "جزر الولايات المتحدة البعيدة الصغيرة" #: picard/const.py:258 msgid "Lebanon" msgstr "لبنان" #: picard/const.py:259 msgid "Saint Lucia" msgstr "سانت لوسيا" #: picard/const.py:260 msgid "Lao People's Democratic Republic" msgstr "لاوس" #: picard/const.py:261 msgid "Tuvalu" msgstr "توفالو" #: picard/const.py:262 msgid "Taiwan" msgstr "تايوان" #: picard/const.py:263 msgid "Trinidad and Tobago" msgstr "ترينيداد وتوباغو" #: picard/const.py:264 msgid "Turkey" msgstr "تركيا" #: picard/const.py:265 msgid "Sri Lanka" msgstr "سريلانكا" #: picard/const.py:266 msgid "Liechtenstein" msgstr "ليختنشتاين" #: picard/const.py:267 msgid "Latvia" msgstr "لاتيفيا" #: picard/const.py:268 msgid "Tonga" msgstr "تونجا" #: picard/const.py:269 msgid "Lithuania" msgstr "ليتوانيا" #: picard/const.py:270 msgid "Luxembourg" msgstr "لكسمبورج" #: picard/const.py:271 msgid "Liberia" msgstr "ليبيريا" #: picard/const.py:272 msgid "Lesotho" msgstr "ليسوتو" #: picard/const.py:273 msgid "Thailand" msgstr "تايلاند" #: picard/const.py:274 msgid "French Southern Territories" msgstr "المقاطعات الجنوبية الفرنسية" #: picard/const.py:275 msgid "Togo" msgstr "توغو" #: picard/const.py:276 msgid "Chad" msgstr "تشاد" #: picard/const.py:277 msgid "Turks and Caicos Islands" msgstr "" #: picard/const.py:278 msgid "Libyan Arab Jamahiriya" msgstr "" #: picard/const.py:279 msgid "Vatican City State (Holy See)" msgstr "" #: picard/const.py:280 msgid "Saint Vincent and The Grenadines" msgstr "" #: picard/const.py:281 msgid "United Arab Emirates" msgstr "" #: picard/const.py:282 msgid "Andorra" msgstr "" #: picard/const.py:283 msgid "Antigua and Barbuda" msgstr "" #: picard/const.py:284 msgid "Afghanistan" msgstr "" #: picard/const.py:285 msgid "Anguilla" msgstr "" #: picard/const.py:286 msgid "Virgin Islands (U.S.)" msgstr "" #: picard/const.py:287 msgid "Iceland" msgstr "" #: picard/const.py:288 msgid "Iran (Islamic Republic of)" msgstr "" #: picard/const.py:289 msgid "Armenia" msgstr "" #: picard/const.py:290 msgid "Albania" msgstr "" #: picard/const.py:291 msgid "Angola" msgstr "" #: picard/const.py:292 msgid "Netherlands Antilles" msgstr "" #: picard/const.py:293 msgid "Antarctica" msgstr "" #: picard/const.py:294 msgid "American Samoa" msgstr "" #: picard/const.py:295 msgid "Argentina" msgstr "" #: picard/const.py:296 msgid "Australia" msgstr "" #: picard/const.py:297 msgid "Austria" msgstr "" #: picard/const.py:298 msgid "Aruba" msgstr "" #: picard/const.py:299 msgid "India" msgstr "" #: picard/const.py:300 msgid "Tanzania, United Republic of" msgstr "" #: picard/const.py:301 msgid "Azerbaijan" msgstr "" #: picard/const.py:302 msgid "Ireland" msgstr "" #: picard/const.py:303 msgid "Indonesia" msgstr "" #: picard/const.py:304 msgid "Ukraine" msgstr "" #: picard/const.py:305 msgid "Qatar" msgstr "" #: picard/const.py:306 msgid "Mozambique" msgstr "" #: picard/const.py:307 msgid "Bosnia and Herzegovina" msgstr "" #: picard/const.py:308 msgid "Congo, The Democratic Republic of the" msgstr "" #: picard/const.py:309 msgid "Serbia and Montenegro (historical, 2003-2006)" msgstr "" #: picard/const.py:310 msgid "Serbia" msgstr "" #: picard/const.py:311 msgid "Montenegro" msgstr "" #: picard/const.py:312 msgid "Croatia" msgstr "" #: picard/const.py:313 msgid "Korea (North), Democratic People's Republic of" msgstr "" #: picard/const.py:314 msgid "Korea (South), Republic of" msgstr "" #: picard/const.py:315 msgid "Slovakia" msgstr "" #: picard/const.py:316 msgid "Soviet Union (historical, 1922-1991)" msgstr "" #: picard/const.py:317 msgid "East Timor" msgstr "" #: picard/const.py:318 msgid "Czechoslovakia (historical, 1918-1992)" msgstr "" #: picard/const.py:319 msgid "Europe" msgstr "" #: picard/const.py:320 msgid "East Germany (historical, 1949-1990)" msgstr "" #: picard/const.py:321 msgid "[Unknown Country]" msgstr "" #: picard/const.py:322 msgid "[Worldwide]" msgstr "" #: picard/const.py:323 msgid "Yugoslavia (historical, 1918-1992)" msgstr "" #: picard/const.py:335 msgid "Danish" msgstr "" #: picard/const.py:336 msgid "German" msgstr "" #: picard/const.py:338 msgid "English" msgstr "" #: picard/const.py:339 msgid "English (Canada)" msgstr "" #: picard/const.py:340 msgid "English (UK)" msgstr "" #: picard/const.py:342 msgid "Spanish" msgstr "" #: picard/const.py:343 msgid "Estonian" msgstr "" #: picard/const.py:345 msgid "Finnish" msgstr "" #: picard/const.py:347 msgid "French" msgstr "" #: picard/const.py:356 msgid "Italian" msgstr "" #: picard/const.py:363 msgid "Dutch" msgstr "" #: picard/const.py:365 msgid "Polish" msgstr "" #: picard/const.py:367 msgid "Brazilian Portuguese" msgstr "" #: picard/const.py:374 msgid "Swedish" msgstr "" #: picard/file.py:589 #, python-format msgid "No matching tracks for file %s" msgstr "" #: picard/file.py:604 #, python-format msgid "No matching tracks above the threshold for file %s" msgstr "" #: picard/file.py:607 #, python-format msgid "File %s identified!" msgstr "" #: picard/file.py:623 #, python-format msgid "Looking up the PUID for file %s..." msgstr "" #: picard/file.py:629 #, python-format msgid "Looking up the metadata for file %s..." msgstr "" #: picard/puidmanager.py:62 msgid "Submitting PUIDs..." msgstr "" #: picard/puidmanager.py:68 #, python-format msgid "PUIDs submission failed: %d" msgstr "" #: picard/puidmanager.py:70 msgid "PUIDs successfully submitted!" msgstr "" #: picard/tagger.py:528 msgid "CD Lookup Error" msgstr "" #: picard/tagger.py:529 #, python-format msgid "" "Error while reading CD:\n" "\n" "%s" msgstr "" #: picard/tagger.py:555 #, python-format msgid "Could not find PUID for file %s" msgstr "" #: picard/ui/cdlookup.py:33 picard/ui/mainwindow.py:525 #: picard/ui/ui_options_releases.py:216 picard/util/tags.py:21 msgid "Album" msgstr "" #: picard/ui/cdlookup.py:33 picard/ui/itemviews.py:89 #: picard/ui/mainwindow.py:526 picard/util/tags.py:22 msgid "Artist" msgstr "" #: picard/ui/cdlookup.py:33 picard/util/tags.py:24 msgid "Date" msgstr "" #: picard/ui/cdlookup.py:33 msgid "Country" msgstr "" #: picard/ui/cdlookup.py:34 msgid "Labels" msgstr "" #: picard/ui/cdlookup.py:34 msgid "Catalog #s" msgstr "" #: picard/ui/cdlookup.py:34 picard/util/tags.py:75 msgid "Barcode" msgstr "" #: picard/ui/coverartbox.py:122 msgid "View release on MusicBrainz" msgstr "" #: picard/ui/filebrowser.py:38 msgid "&Move Tagged Files Here" msgstr "" #: picard/ui/filebrowser.py:41 msgid "Show &Hidden Files" msgstr "" #: picard/ui/infodialog.py:36 msgid "Info" msgstr "" #: picard/ui/infodialog.py:42 msgid "Filename:" msgstr "" #: picard/ui/infodialog.py:44 msgid "Format:" msgstr "" #: picard/ui/infodialog.py:53 msgid "Size:" msgstr "" #: picard/ui/infodialog.py:57 msgid "Length:" msgstr "" #: picard/ui/infodialog.py:59 msgid "Bitrate:" msgstr "" #: picard/ui/infodialog.py:61 msgid "Sample rate:" msgstr "" #: picard/ui/infodialog.py:63 msgid "Bits per sample:" msgstr "" #: picard/ui/infodialog.py:66 msgid "Mono" msgstr "" #: picard/ui/infodialog.py:67 msgid "Stereo" msgstr "" #: picard/ui/infodialog.py:69 msgid "Channels:" msgstr "" #: picard/ui/itemviews.py:87 picard/util/tags.py:23 msgid "Title" msgstr "" #: picard/ui/itemviews.py:88 picard/util/tags.py:84 msgid "Length" msgstr "" #: picard/ui/itemviews.py:214 msgid "&Expand all" msgstr "" #: picard/ui/itemviews.py:216 msgid "&Collapse all" msgstr "" #: picard/ui/itemviews.py:273 msgid "&Other versions" msgstr "" #: picard/ui/itemviews.py:276 msgid "Loading..." msgstr "" #: picard/ui/itemviews.py:286 msgid "[no release info]" msgstr "" #: picard/ui/itemviews.py:309 msgid "&Plugins" msgstr "" #: picard/ui/itemviews.py:527 msgid "Clusters" msgstr "" #: picard/ui/logview.py:30 msgid "Log" msgstr "" #: picard/ui/mainwindow.py:73 msgid "MusicBrainz Picard" msgstr "" #: picard/ui/mainwindow.py:151 msgid "Unsaved Changes" msgstr "" #: picard/ui/mainwindow.py:152 msgid "Are you sure you want to quit Picard?" msgstr "" #: picard/ui/mainwindow.py:153 #, python-format msgid "" "There is %d unsaved file. Closing Picard will lose all unsaved changes." msgid_plural "" "There are %d unsaved files. Closing Picard will lose all unsaved changes." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" msgstr[5] "" #: picard/ui/mainwindow.py:160 msgid "&Quit Picard" msgstr "" #: picard/ui/mainwindow.py:210 msgid "Ready" msgstr "" #: picard/ui/mainwindow.py:214 msgid "" "Picard listens on a port to integrate with your browser and downloads " "release information when you click the \"Tagger\" buttons on the MusicBrainz" " website" msgstr "" #: picard/ui/mainwindow.py:224 #, python-format msgid " Files: %(files)d, Pending Files: %(pending)d " msgstr "" #: picard/ui/mainwindow.py:229 #, python-format msgid " Listening on port %(port)d " msgstr "" #: picard/ui/mainwindow.py:257 msgid "Submission Error" msgstr "" #: picard/ui/mainwindow.py:258 msgid "" "You need to configure your AcoustID API key before you can submit " "fingerprints." msgstr "" #: picard/ui/mainwindow.py:265 msgid "&Options..." msgstr "" #: picard/ui/mainwindow.py:268 msgid "&Cut" msgstr "" #: picard/ui/mainwindow.py:273 msgid "&Paste" msgstr "" #: picard/ui/mainwindow.py:278 msgid "&Help..." msgstr "" #: picard/ui/mainwindow.py:283 msgid "&About..." msgstr "" #: picard/ui/mainwindow.py:286 msgid "&Donate..." msgstr "" #: picard/ui/mainwindow.py:289 msgid "&Report a Bug..." msgstr "" #: picard/ui/mainwindow.py:292 msgid "&Support Forum..." msgstr "" #: picard/ui/mainwindow.py:295 msgid "&Add Files..." msgstr "" #: picard/ui/mainwindow.py:296 msgid "Add files to the tagger" msgstr "" #: picard/ui/mainwindow.py:301 msgid "A&dd Folder..." msgstr "" #: picard/ui/mainwindow.py:302 msgid "Add a folder to the tagger" msgstr "" #: picard/ui/mainwindow.py:304 msgid "Ctrl+D" msgstr "" #: picard/ui/mainwindow.py:308 msgid "&Save" msgstr "" #: picard/ui/mainwindow.py:309 msgid "Save selected files" msgstr "" #: picard/ui/mainwindow.py:315 msgid "S&ubmit" msgstr "" #: picard/ui/mainwindow.py:316 msgid "Submit fingerprints" msgstr "" #: picard/ui/mainwindow.py:320 msgid "E&xit" msgstr "" #: picard/ui/mainwindow.py:322 msgid "Ctrl+Q" msgstr "" #: picard/ui/mainwindow.py:325 msgid "&Remove" msgstr "" #: picard/ui/mainwindow.py:326 msgid "Remove selected files/albums" msgstr "" #: picard/ui/mainwindow.py:330 msgid "Lookup in &Browser" msgstr "" #: picard/ui/mainwindow.py:331 msgid "Lookup selected item on MusicBrainz website" msgstr "" #: picard/ui/mainwindow.py:335 msgid "File &Browser" msgstr "" #: picard/ui/mainwindow.py:339 msgid "Ctrl+B" msgstr "" #: picard/ui/mainwindow.py:342 msgid "&Cover Art" msgstr "" #: picard/ui/mainwindow.py:348 msgid "Search" msgstr "" #: picard/ui/mainwindow.py:351 msgid "&CD Lookup..." msgstr "" #: picard/ui/mainwindow.py:352 picard/ui/mainwindow.py:353 msgid "Lookup CD" msgstr "" #: picard/ui/mainwindow.py:355 msgid "Ctrl+K" msgstr "" #: picard/ui/mainwindow.py:358 msgid "&Scan" msgstr "" #: picard/ui/mainwindow.py:361 msgid "Ctrl+Y" msgstr "" #: picard/ui/mainwindow.py:364 msgid "Cl&uster" msgstr "" #: picard/ui/mainwindow.py:367 msgid "Ctrl+U" msgstr "" #: picard/ui/mainwindow.py:370 msgid "&Lookup" msgstr "" #: picard/ui/mainwindow.py:371 picard/ui/mainwindow.py:372 msgid "Lookup metadata" msgstr "" #: picard/ui/mainwindow.py:375 msgid "Ctrl+L" msgstr "" #: picard/ui/mainwindow.py:378 msgid "&Info..." msgstr "" #: picard/ui/mainwindow.py:381 msgid "Ctrl+I" msgstr "" #: picard/ui/mainwindow.py:384 msgid "&Refresh" msgstr "" #: picard/ui/mainwindow.py:385 msgid "Ctrl+R" msgstr "" #: picard/ui/mainwindow.py:388 msgid "&Rename Files" msgstr "" #: picard/ui/mainwindow.py:393 msgid "&Move Files" msgstr "" #: picard/ui/mainwindow.py:398 msgid "Save &Tags" msgstr "" #: picard/ui/mainwindow.py:403 msgid "Tags From &File Names..." msgstr "" #: picard/ui/mainwindow.py:406 msgid "View &Log..." msgstr "" #: picard/ui/mainwindow.py:412 msgid "&Open..." msgstr "" #: picard/ui/mainwindow.py:413 msgid "Open the file" msgstr "" #: picard/ui/mainwindow.py:416 msgid "Open &Folder..." msgstr "" #: picard/ui/mainwindow.py:417 msgid "Open the containing folder" msgstr "" #: picard/ui/mainwindow.py:438 msgid "&File" msgstr "" #: picard/ui/mainwindow.py:446 msgid "&Edit" msgstr "" #: picard/ui/mainwindow.py:452 msgid "&View" msgstr "" #: picard/ui/mainwindow.py:460 msgid "&Options" msgstr "" #: picard/ui/mainwindow.py:466 msgid "&Tools" msgstr "" #: picard/ui/mainwindow.py:475 picard/ui/util.py:33 msgid "&Help" msgstr "" #: picard/ui/mainwindow.py:493 msgid "&Toolbar" msgstr "" #: picard/ui/mainwindow.py:520 msgid "&Search Bar" msgstr "" #: picard/ui/mainwindow.py:527 picard/ui/puidsubmit.py:31 msgid "Track" msgstr "" #: picard/ui/mainwindow.py:566 msgid "All Supported Formats" msgstr "" #: picard/ui/mainwindow.py:624 picard/ui/mainwindow.py:633 msgid "Various Artists file naming scheme removal" msgstr "" #: picard/ui/mainwindow.py:625 msgid "" "The separate file naming scheme for various artists albums has been\n" "removed in this version of Picard. You currently do not use the this option,\n" "but have a separate file naming scheme defined. Do you want to remove it or\n" "merge it with your file naming scheme for single artist albums?" msgstr "" #: picard/ui/mainwindow.py:629 msgid "Merge" msgstr "" #: picard/ui/mainwindow.py:629 picard/ui/metadatabox.py:185 msgid "Remove" msgstr "" #: picard/ui/mainwindow.py:634 msgid "" "The separate file naming scheme for various artists albums has been\n" "removed in this version of Picard. Your file naming scheme has automatically\n" "been merged with that of single artist albums." msgstr "" #: picard/ui/mainwindow.py:682 msgid "Configuration Required" msgstr "" #: picard/ui/mainwindow.py:683 msgid "" "Audio fingerprinting is not yet configured. Would you like to configure it " "now?" msgstr "" #: picard/ui/mainwindow.py:756 picard/ui/mainwindow.py:763 #, python-format msgid " (Error: %s)" msgstr "" #: picard/ui/metadatabox.py:64 #, python-format msgid "(missing from %d item)" msgid_plural "(missing from %d items)" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" msgstr[5] "" #: picard/ui/metadatabox.py:66 #, python-format msgid "(different across %d item)" msgid_plural "(different across %d items)" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" msgstr[5] "" #: picard/ui/metadatabox.py:90 msgid "Tag" msgstr "" #: picard/ui/metadatabox.py:90 msgid "Original Value" msgstr "" #: picard/ui/metadatabox.py:90 msgid "New Value" msgstr "" #: picard/ui/metadatabox.py:117 msgid "Add New Tag..." msgstr "" #: picard/ui/metadatabox.py:119 msgid "Show Changes First" msgstr "" #: picard/ui/metadatabox.py:166 msgid "Edit..." msgstr "" #: picard/ui/metadatabox.py:189 msgid "Use Original Value" msgid_plural "Use Original Values" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" msgstr[5] "" #: picard/ui/passworddialog.py:38 #, python-format msgid "" "The server %s requires you to login. Please enter your username and " "password." msgstr "" #: picard/ui/passworddialog.py:76 #, python-format msgid "" "The proxy %s requires you to login. Please enter your username and password." msgstr "" #: picard/ui/puidsubmit.py:31 picard/ui/options/plugins.py:125 msgid "File" msgstr "" #: picard/ui/puidsubmit.py:31 msgid "PUID" msgstr "" #: picard/ui/puidsubmit.py:31 msgid "Release" msgstr "" #: picard/ui/puidsubmit.py:31 msgid "Release ID" msgstr "" #: picard/ui/tagsfromfilenames.py:54 picard/ui/tagsfromfilenames.py:99 msgid "File Name" msgstr "" #: picard/ui/ui_cdlookup.py:57 picard/ui/ui_options_cdlookup.py:46 #: picard/ui/ui_options_cdlookup_select.py:53 picard/ui/options/cdlookup.py:36 msgid "CD Lookup" msgstr "" #: picard/ui/ui_cdlookup.py:58 msgid "The following releases on MusicBrainz match the CD:" msgstr "" #: picard/ui/ui_cdlookup.py:59 picard/ui/ui_puidsubmit.py:52 msgid "OK" msgstr "" #: picard/ui/ui_cdlookup.py:60 msgid " Lookup manually " msgstr "" #: picard/ui/ui_cdlookup.py:61 picard/ui/ui_puidsubmit.py:53 msgid "Cancel" msgstr "" #: picard/ui/ui_edittagdialog.py:92 msgid "Edit Tag" msgstr "" #: picard/ui/ui_edittagdialog.py:93 msgid "Edit value" msgstr "" #: picard/ui/ui_edittagdialog.py:94 msgid "Add value" msgstr "" #: picard/ui/ui_edittagdialog.py:95 msgid "Remove value" msgstr "" #: picard/ui/ui_infodialog.py:66 msgid "&Info" msgstr "" #: picard/ui/ui_infodialog.py:67 msgid "A&rtwork" msgstr "" #: picard/ui/ui_options.py:42 msgid "Options" msgstr "" #: picard/ui/ui_options_cdlookup.py:47 msgid "CD-ROM device to use for lookups:" msgstr "" #: picard/ui/ui_options_cdlookup_select.py:54 msgid "Default CD-ROM drive to use for lookups:" msgstr "" #: picard/ui/ui_options_cover.py:53 msgid "Location" msgstr "" #: picard/ui/ui_options_cover.py:54 msgid "Embed cover images into tags" msgstr "" #: picard/ui/ui_options_cover.py:55 msgid "Save cover images as separate files" msgstr "" #: picard/ui/ui_options_cover.py:56 msgid "Overwrite the file if it already exists" msgstr "" #: picard/ui/ui_options_fingerprinting.py:74 msgid "Audio Fingerprinting" msgstr "" #: picard/ui/ui_options_fingerprinting.py:75 msgid "Use AcoustID" msgstr "" #: picard/ui/ui_options_fingerprinting.py:76 msgid "Use AmpliFIND (formerly MusicDNS)" msgstr "" #: picard/ui/ui_options_fingerprinting.py:77 msgid "AcoustID Settings" msgstr "" #: picard/ui/ui_options_fingerprinting.py:78 msgid "Fingerprint calculator:" msgstr "" #: picard/ui/ui_options_fingerprinting.py:79 #: picard/ui/ui_options_renaming.py:138 msgid "Browse..." msgstr "" #: picard/ui/ui_options_fingerprinting.py:80 msgid "Download..." msgstr "" #: picard/ui/ui_options_fingerprinting.py:81 msgid "API key:" msgstr "" #: picard/ui/ui_options_fingerprinting.py:82 msgid "Get API key..." msgstr "" #: picard/ui/ui_options_folksonomy.py:106 picard/ui/options/folksonomy.py:29 msgid "Folksonomy Tags" msgstr "" #: picard/ui/ui_options_folksonomy.py:108 msgid "Only use my tags" msgstr "" #: picard/ui/ui_options_folksonomy.py:111 msgid "Maximum number of tags:" msgstr "" #: picard/ui/ui_options_general.py:92 msgid "MusicBrainz Server" msgstr "" #: picard/ui/ui_options_general.py:93 picard/ui/ui_options_proxy.py:77 msgid "Port:" msgstr "" #: picard/ui/ui_options_general.py:94 picard/ui/ui_options_proxy.py:78 msgid "Server address:" msgstr "" #: picard/ui/ui_options_general.py:95 msgid "Account Information" msgstr "" #: picard/ui/ui_options_general.py:96 picard/ui/ui_options_proxy.py:75 #: picard/ui/ui_passworddialog.py:74 msgid "Password:" msgstr "" #: picard/ui/ui_options_general.py:97 picard/ui/ui_options_proxy.py:76 #: picard/ui/ui_passworddialog.py:73 msgid "Username:" msgstr "" #: picard/ui/ui_options_general.py:98 picard/ui/options/general.py:29 msgid "General" msgstr "" #: picard/ui/ui_options_general.py:99 msgid "Automatically scan all new files" msgstr "" #: picard/ui/ui_options_general.py:100 msgid "Ignore MBIDs when loading new files" msgstr "" #: picard/ui/ui_options_interface.py:58 msgid "Miscellaneous" msgstr "" #: picard/ui/ui_options_interface.py:59 msgid "Show text labels under icons" msgstr "" #: picard/ui/ui_options_interface.py:60 msgid "Allow selection of multiple directories" msgstr "" #: picard/ui/ui_options_interface.py:61 msgid "Use advanced query syntax" msgstr "" #: picard/ui/ui_options_interface.py:62 msgid "Show a quit confirmation dialog for unsaved changes" msgstr "" #: picard/ui/ui_options_interface.py:63 msgid "User interface language:" msgstr "" #: picard/ui/ui_options_matching.py:77 msgid "Thresholds" msgstr "" #: picard/ui/ui_options_matching.py:78 msgid "Minimal similarity for matching files to tracks:" msgstr "" #: picard/ui/ui_options_matching.py:82 msgid "Minimal similarity for file lookups:" msgstr "" #: picard/ui/ui_options_matching.py:83 msgid "Minimal similarity for cluster lookups:" msgstr "" #: picard/ui/ui_options_metadata.py:101 picard/ui/options/metadata.py:30 msgid "Metadata" msgstr "" #: picard/ui/ui_options_metadata.py:102 msgid "Translate artist names to this locale where possible:" msgstr "" #: picard/ui/ui_options_metadata.py:103 msgid "Use standardized artist names" msgstr "" #: picard/ui/ui_options_metadata.py:104 msgid "Convert Unicode punctuation characters to ASCII" msgstr "" #: picard/ui/ui_options_metadata.py:105 msgid "Use release relationships" msgstr "" #: picard/ui/ui_options_metadata.py:106 msgid "Use track relationships" msgstr "" #: picard/ui/ui_options_metadata.py:107 msgid "Use folksonomy tags as genre" msgstr "" #: picard/ui/ui_options_metadata.py:108 msgid "Custom Fields" msgstr "" #: picard/ui/ui_options_metadata.py:109 msgid "Various artists:" msgstr "" #: picard/ui/ui_options_metadata.py:110 msgid "Non-album tracks:" msgstr "" #: picard/ui/ui_options_metadata.py:111 picard/ui/ui_options_metadata.py:112 #: picard/ui/ui_options_renaming.py:147 msgid "Default" msgstr "" #: picard/ui/ui_options_plugins.py:131 picard/ui/options/plugins.py:37 msgid "Plugins" msgstr "" #: picard/ui/ui_options_plugins.py:132 picard/ui/options/plugins.py:121 msgid "Name" msgstr "" #: picard/ui/ui_options_plugins.py:133 picard/util/tags.py:37 msgid "Version" msgstr "" #: picard/ui/ui_options_plugins.py:134 picard/ui/options/plugins.py:124 msgid "Author" msgstr "" #: picard/ui/ui_options_plugins.py:135 msgid "Install plugin..." msgstr "" #: picard/ui/ui_options_plugins.py:136 msgid "Open plugin folder" msgstr "" #: picard/ui/ui_options_plugins.py:137 msgid "Download plugins" msgstr "" #: picard/ui/ui_options_plugins.py:138 msgid "Details" msgstr "" #: picard/ui/ui_options_proxy.py:74 picard/ui/options/proxy.py:28 msgid "Web Proxy" msgstr "" #: picard/ui/ui_options_ratings.py:53 msgid "Enable track ratings" msgstr "" #: picard/ui/ui_options_ratings.py:54 msgid "" "Picard saves the ratings together with an e-mail address identifying the " "user who did the rating. That way different ratings for different users can " "be stored in the files. Please specify the e-mail you want to use to save " "your ratings." msgstr "" #: picard/ui/ui_options_ratings.py:55 msgid "E-mail:" msgstr "" #: picard/ui/ui_options_ratings.py:56 msgid "Submit ratings to MusicBrainz" msgstr "" #: picard/ui/ui_options_releases.py:215 msgid "Preferred release types" msgstr "" #: picard/ui/ui_options_releases.py:217 msgid "Single" msgstr "" #: picard/ui/ui_options_releases.py:218 msgid "EP" msgstr "" #: picard/ui/ui_options_releases.py:219 picard/util/tags.py:66 msgid "Compilation" msgstr "" #: picard/ui/ui_options_releases.py:220 msgid "Soundtrack" msgstr "" #: picard/ui/ui_options_releases.py:221 msgid "Spokenword" msgstr "" #: picard/ui/ui_options_releases.py:222 msgid "Interview" msgstr "" #: picard/ui/ui_options_releases.py:223 msgid "Audiobook" msgstr "" #: picard/ui/ui_options_releases.py:224 msgid "Live" msgstr "" #: picard/ui/ui_options_releases.py:225 msgid "Remix" msgstr "" #: picard/ui/ui_options_releases.py:227 msgid "Reset all" msgstr "" #: picard/ui/ui_options_releases.py:228 msgid "Preferred release countries" msgstr "" #: picard/ui/ui_options_releases.py:229 picard/ui/ui_options_releases.py:232 msgid ">" msgstr "" #: picard/ui/ui_options_releases.py:230 picard/ui/ui_options_releases.py:233 msgid "<" msgstr "" #: picard/ui/ui_options_releases.py:231 msgid "Preferred release formats" msgstr "" #: picard/ui/ui_options_renaming.py:134 msgid "Rename files when saving" msgstr "" #: picard/ui/ui_options_renaming.py:135 msgid "Replace non-ASCII characters" msgstr "" #: picard/ui/ui_options_renaming.py:136 msgid "Replace Windows-incompatible characters" msgstr "" #: picard/ui/ui_options_renaming.py:137 msgid "Move files to this directory when saving:" msgstr "" #: picard/ui/ui_options_renaming.py:139 msgid "Delete empty directories" msgstr "" #: picard/ui/ui_options_renaming.py:140 msgid "Move additional files:" msgstr "" #: picard/ui/ui_options_renaming.py:141 msgid "Name files like this" msgstr "" #: picard/ui/ui_options_renaming.py:148 msgid "Examples" msgstr "" #: picard/ui/ui_options_script.py:49 msgid "Tagger Script" msgstr "" #: picard/ui/ui_options_tags.py:114 msgid "Write tags to files" msgstr "" #: picard/ui/ui_options_tags.py:115 msgid "Preserve timestamps of tagged files" msgstr "" #: picard/ui/ui_options_tags.py:116 msgid "Before tagging" msgstr "" #: picard/ui/ui_options_tags.py:117 msgid "Clear existing tags" msgstr "" #: picard/ui/ui_options_tags.py:118 msgid "Remove ID3 tags from FLAC files" msgstr "" #: picard/ui/ui_options_tags.py:119 msgid "Remove APEv2 tags from MP3 files" msgstr "" #: picard/ui/ui_options_tags.py:120 msgid "" "Preserve these tags from being cleared or overwritten with MusicBrainz data:" msgstr "" #: picard/ui/ui_options_tags.py:121 msgid "Tags are separated by spaces, and are case-sensitive." msgstr "" #: picard/ui/ui_options_tags.py:122 msgid "Tag compatibility" msgstr "" #: picard/ui/ui_options_tags.py:123 msgid "ID3v2 version" msgstr "" #: picard/ui/ui_options_tags.py:124 msgid "2.4" msgstr "" #: picard/ui/ui_options_tags.py:125 msgid "2.3" msgstr "" #: picard/ui/ui_options_tags.py:126 msgid "Also include ID3v1 tags in the files" msgstr "" #: picard/ui/ui_options_tags.py:127 msgid "ID3v2 text encoding" msgstr "" #: picard/ui/ui_options_tags.py:128 msgid "UTF-8" msgstr "" #: picard/ui/ui_options_tags.py:129 msgid "UTF-16" msgstr "" #: picard/ui/ui_options_tags.py:130 msgid "ISO-8859-1" msgstr "" #: picard/ui/ui_passworddialog.py:72 msgid "Authentication required" msgstr "" #: picard/ui/ui_passworddialog.py:75 msgid "Save username and password" msgstr "" #: picard/ui/ui_puidsubmit.py:51 msgid "Submit PUIDs" msgstr "" #: picard/ui/ui_tagsfromfilenames.py:54 msgid "Convert File Names to Tags" msgstr "" #: picard/ui/ui_tagsfromfilenames.py:55 msgid "Replace underscores with spaces" msgstr "" #: picard/ui/ui_tagsfromfilenames.py:56 msgid "&Preview" msgstr "" #: picard/ui/util.py:31 msgid "&Ok" msgstr "" #: picard/ui/util.py:32 msgid "&Cancel" msgstr "" #: picard/ui/options/about.py:29 msgid "About" msgstr "" #: picard/ui/options/about.py:48 msgid "translator-credits" msgstr "Launchpad Contributions:\n Ahmed Toulan https://launchpad.net/~thelinuxer\n Hajex https://launchpad.net/~hajex\n MaXeR https://launchpad.net/~themaxer\n Philipp Wolfer https://launchpad.net/~phw" #: picard/ui/options/about.py:51 #, python-format msgid "
Translated to LANG by %s" msgstr "" #: picard/ui/options/about.py:55 #, python-format msgid "" "

MusicBrainz Picard
\n" "Version %(version)s

\n" "

Supported formats
%(formats)s

\n" "

Please donate
\n" "Thank you for using Picard. Picard relies on the MusicBrainz database, which is operated by the MetaBrainz Foundation with the help of thousands of volunteers. If you like this application please consider donating to the MetaBrainz Foundation to keep the service running.

\n" "

Donate now!

\n" "

Credits
\n" "Copyright © 2004-2011 Robert Kaye, Lukáš Lalinský and others%(translator-credits)s

\n" "

http://musicbrainz.org/doc/MusicBrainz_Picard

\n" msgstr "" #: picard/ui/options/advanced.py:26 msgid "Advanced" msgstr "" #: picard/ui/options/cover.py:29 msgid "Cover Art" msgstr "" #: picard/ui/options/fingerprinting.py:32 msgid "Fingerprinting" msgstr "" #: picard/ui/options/interface.py:32 msgid "User Interface" msgstr "" #: picard/ui/options/interface.py:49 msgid "System default" msgstr "" #: picard/ui/options/interface.py:76 msgid "Language changed" msgstr "" #: picard/ui/options/interface.py:76 msgid "" "You have changed the interface language. You have to restart Picard in order" " for the change to take effect." msgstr "" #: picard/ui/options/matching.py:29 msgid "Matching" msgstr "" #: picard/ui/options/ratings.py:29 msgid "Ratings" msgstr "" #: picard/ui/options/releases.py:33 msgid "Preferred Releases" msgstr "" #: picard/ui/options/renaming.py:34 msgid "File naming" msgstr "" #: picard/ui/options/renaming.py:143 msgid "Error" msgstr "" #: picard/ui/options/renaming.py:143 msgid "The location to move files to must not be empty." msgstr "" #: picard/ui/options/renaming.py:153 msgid "The file naming format must not be empty." msgstr "" #: picard/ui/options/scripting.py:63 msgid "Scripting" msgstr "" #: picard/ui/options/scripting.py:97 msgid "Script Error" msgstr "" #: picard/util/tags.py:25 msgid "Original Release Date" msgstr "" #: picard/util/tags.py:26 msgid "Album Artist" msgstr "" #: picard/util/tags.py:27 msgid "Track Number" msgstr "" #: picard/util/tags.py:28 msgid "Total Tracks" msgstr "" #: picard/util/tags.py:29 msgid "Disc Number" msgstr "" #: picard/util/tags.py:30 msgid "Total Discs" msgstr "" #: picard/util/tags.py:31 msgid "Album Artist Sort Order" msgstr "" #: picard/util/tags.py:32 msgid "Artist Sort Order" msgstr "" #: picard/util/tags.py:33 msgid "Title Sort Order" msgstr "" #: picard/util/tags.py:34 msgid "Album Sort Order" msgstr "" #: picard/util/tags.py:35 msgid "ASIN" msgstr "" #: picard/util/tags.py:36 msgid "Grouping" msgstr "" #: picard/util/tags.py:38 msgid "ISRC" msgstr "" #: picard/util/tags.py:39 msgid "Mood" msgstr "" #: picard/util/tags.py:40 msgid "BPM" msgstr "" #: picard/util/tags.py:41 msgid "Copyright" msgstr "" #: picard/util/tags.py:42 msgid "License" msgstr "" #: picard/util/tags.py:43 msgid "Composer" msgstr "" #: picard/util/tags.py:44 msgid "Writer" msgstr "" #: picard/util/tags.py:45 msgid "Conductor" msgstr "" #: picard/util/tags.py:46 msgid "Lyricist" msgstr "" #: picard/util/tags.py:47 msgid "Arranger" msgstr "" #: picard/util/tags.py:48 msgid "Producer" msgstr "" #: picard/util/tags.py:49 msgid "Engineer" msgstr "" #: picard/util/tags.py:50 msgid "Subtitle" msgstr "" #: picard/util/tags.py:51 msgid "Disc Subtitle" msgstr "" #: picard/util/tags.py:52 msgid "Remixer" msgstr "" #: picard/util/tags.py:53 msgid "MusicBrainz Recording Id" msgstr "" #: picard/util/tags.py:54 msgid "MusicBrainz Release Id" msgstr "" #: picard/util/tags.py:55 msgid "MusicBrainz Artist Id" msgstr "" #: picard/util/tags.py:56 msgid "MusicBrainz Release Artist Id" msgstr "" #: picard/util/tags.py:57 msgid "MusicBrainz Work Id" msgstr "" #: picard/util/tags.py:58 msgid "MusicBrainz Disc Id" msgstr "" #: picard/util/tags.py:59 msgid "MusicBrainz Sort Name" msgstr "" #: picard/util/tags.py:60 msgid "MusicIP PUID" msgstr "" #: picard/util/tags.py:61 msgid "MusicIP Fingerprint" msgstr "" #: picard/util/tags.py:62 msgid "AcoustID" msgstr "" #: picard/util/tags.py:63 msgid "AcoustID Fingerprint" msgstr "" #: picard/util/tags.py:64 msgid "Disc Id" msgstr "" #: picard/util/tags.py:65 msgid "Website" msgstr "" #: picard/util/tags.py:67 msgid "Comment" msgstr "" #: picard/util/tags.py:68 msgid "Genre" msgstr "" #: picard/util/tags.py:69 msgid "Encoded By" msgstr "" #: picard/util/tags.py:70 msgid "Performer" msgstr "" #: picard/util/tags.py:71 msgid "Release Type" msgstr "" #: picard/util/tags.py:72 msgid "Release Status" msgstr "" #: picard/util/tags.py:73 msgid "Release Country" msgstr "" #: picard/util/tags.py:74 msgid "Record Label" msgstr "" #: picard/util/tags.py:76 msgid "Catalog Number" msgstr "" #: picard/util/tags.py:77 msgid "Format" msgstr "" #: picard/util/tags.py:78 msgid "DJ-Mixer" msgstr "" #: picard/util/tags.py:79 msgid "Media" msgstr "" #: picard/util/tags.py:80 msgid "Lyrics" msgstr "" #: picard/util/tags.py:81 msgid "Mixer" msgstr "" #: picard/util/tags.py:82 msgid "Language" msgstr "" #: picard/util/tags.py:83 msgid "Script" msgstr "" #: picard/util/tags.py:85 msgid "Rating" msgstr "" #: picard/util/webbrowser2.py:88 msgid "Web Browser Error" msgstr "" #: picard/util/webbrowser2.py:88 #, python-format msgid "" "Error while launching a web browser:\n" "\n" "%s" msgstr "" picard-release-1.4.2/po/ast.po000066400000000000000000001172741310410472100161500ustar00rootroot00000000000000# Translations template for picard. # Copyright (C) 2014 ORGANIZATION # This file is distributed under the same license as the picard project. # # Translators: # FIRST AUTHOR , 2011 msgid "" msgstr "" "Project-Id-Version: MusicBrainz\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2014-03-20 11:12+0100\n" "PO-Revision-Date: 2014-03-21 08:44+0000\n" "Last-Translator: nikki\n" "Language-Team: Asturian (http://www.transifex.com/projects/p/musicbrainz/language/ast/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" "Language: ast\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: contrib/plugins/no_release.py:50 msgid "Enable plugin for all releases by default" msgstr "" #: contrib/plugins/no_release.py:51 msgid "Tags to strip (comma-separated)" msgstr "" #: contrib/plugins/no_release.py:63 msgid "Remove specific release information..." msgstr "" #: contrib/plugins/open_in_gui.py:32 msgid "Open Error" msgstr "" #: contrib/plugins/open_in_gui.py:32 #, python-format msgid "" "Error while opening file:\n" "\n" "%s" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:117 msgid "Last.fm" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:118 msgid "Use track tags" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:119 msgid "Use artist tags" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:120 #: picard/ui/options/tags.py:30 msgid "Tags" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:121 #: picard/ui/ui_options_folksonomy.py:116 msgid "Ignore tags:" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:122 #: picard/ui/ui_options_folksonomy.py:121 msgid "Join multiple tags with:" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:123 #: picard/ui/ui_options_folksonomy.py:122 msgid " / " msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:124 #: picard/ui/ui_options_folksonomy.py:123 msgid ", " msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:125 #: picard/ui/ui_options_folksonomy.py:118 msgid "Minimal tag usage:" msgstr "" #: contrib/plugins/lastfm/ui_options_lastfm.py:126 #: picard/ui/ui_options_folksonomy.py:119 picard/ui/ui_options_matching.py:88 #: picard/ui/ui_options_matching.py:89 picard/ui/ui_options_matching.py:90 msgid " %" msgstr "" #: contrib/plugins/replaygain/__init__.py:50 msgid "Calculate replay &gain..." msgstr "" #: contrib/plugins/replaygain/__init__.py:66 #, python-format msgid "Calculating replay gain for \"%s\"..." msgstr "" #: contrib/plugins/replaygain/__init__.py:71 #, python-format msgid "Replay gain for \"%s\" successfully calculated." msgstr "" #: contrib/plugins/replaygain/__init__.py:73 #, python-format msgid "Could not calculate replay gain for \"%s\"." msgstr "" #: contrib/plugins/replaygain/__init__.py:76 msgid "Calculate album &gain..." msgstr "" #: contrib/plugins/replaygain/__init__.py:103 #: contrib/plugins/replaygain/__init__.py:111 #, python-format msgid "Calculating album gain for \"%s\"..." msgstr "" #: contrib/plugins/replaygain/__init__.py:119 #, python-format msgid "Album gain for \"%s\" successfully calculated." msgstr "" #: contrib/plugins/replaygain/__init__.py:121 #, python-format msgid "Could not calculate album gain for \"%s\"." msgstr "" #: picard/acoustid.py:102 #, python-format msgid "AcoustID lookup network error for '%s'!" msgstr "" #: picard/acoustid.py:117 #, python-format msgid "AcoustID lookup failed for '%s'!" msgstr "" #: picard/acoustid.py:128 #, python-format msgid "Acoustid lookup returned no result for file '%s'" msgstr "" #: picard/acoustid.py:132 #, python-format msgid "Looking up the fingerprint for file %s..." msgstr "" #: picard/acoustidmanager.py:78 msgid "Submitting AcoustIDs..." msgstr "" #: picard/acoustidmanager.py:83 #, python-format msgid "AcoustID submission failed with error '%s'" msgstr "" #: picard/acoustidmanager.py:85 msgid "AcoustIDs successfully submitted." msgstr "" #: picard/album.py:64 picard/cluster.py:234 msgid "Unmatched Files" msgstr "Ficheros que nun concasen" #: picard/album.py:187 #, python-format msgid "[could not load album %s]" msgstr "[nun se pudo cargar l'álbum %s]" #: picard/album.py:272 #, python-format msgid "Album %s loaded" msgstr "" #: picard/album.py:288 msgid "[loading album information]" msgstr "[cargando información del álbum]" #: picard/album.py:463 #, python-format msgid "; %i image" msgid_plural "; %i images" msgstr[0] "" msgstr[1] "" #: picard/cluster.py:150 picard/cluster.py:159 #, python-format msgid "No matching releases for cluster %s" msgstr "" #: picard/cluster.py:161 #, python-format msgid "Cluster %s identified!" msgstr "" #: picard/cluster.py:168 #, python-format msgid "Looking up the metadata for cluster %s..." msgstr "" #: picard/collection.py:60 #, python-format msgid "Added %i release to collection \"%s\"" msgid_plural "Added %i releases to collection \"%s\"" msgstr[0] "" msgstr[1] "" #: picard/collection.py:72 #, python-format msgid "Removed %i release from collection \"%s\"" msgid_plural "Removed %i releases from collection \"%s\"" msgstr[0] "" msgstr[1] "" #: picard/collection.py:81 #, python-format msgid "Error loading collections: %s" msgstr "" #: picard/config_upgrade.py:57 picard/config_upgrade.py:70 msgid "Various Artists file naming scheme removal" msgstr "" #: picard/config_upgrade.py:58 msgid "" "The separate file naming scheme for various artists albums has been removed in this version of Picard.\n" "Your file naming scheme has automatically been merged with that of single artist albums." msgstr "" #: picard/config_upgrade.py:71 msgid "" "The separate file naming scheme for various artists albums has been removed in this version of Picard.\n" "You currently do not use this option, but have a separate file naming scheme defined.\n" "Do you want to remove it or merge it with your file naming scheme for single artist albums?" msgstr "" #: picard/config_upgrade.py:77 msgid "Merge" msgstr "" #: picard/config_upgrade.py:77 picard/ui/metadatabox.py:254 msgid "Remove" msgstr "" #: picard/const.py:67 msgid "CD" msgstr "CD" #: picard/const.py:68 msgid "CD-R" msgstr "" #: picard/const.py:69 msgid "HDCD" msgstr "" #: picard/const.py:70 msgid "8cm CD" msgstr "" #: picard/const.py:71 msgid "Vinyl" msgstr "Vinilu" #: picard/const.py:72 msgid "7\" Vinyl" msgstr "" #: picard/const.py:73 msgid "10\" Vinyl" msgstr "" #: picard/const.py:74 msgid "12\" Vinyl" msgstr "" #: picard/const.py:75 msgid "Digital Media" msgstr "Mediu Dixital" #: picard/const.py:76 msgid "USB Flash Drive" msgstr "" #: picard/const.py:77 msgid "slotMusic" msgstr "" #: picard/const.py:78 msgid "Cassette" msgstr "Casé" #: picard/const.py:79 msgid "DVD" msgstr "DVD" #: picard/const.py:80 msgid "DVD-Audio" msgstr "" #: picard/const.py:81 msgid "DVD-Video" msgstr "" #: picard/const.py:82 msgid "SACD" msgstr "SACD" #: picard/const.py:83 msgid "DualDisc" msgstr "" #: picard/const.py:84 msgid "MiniDisc" msgstr "Mini Discu" #: picard/const.py:85 msgid "Blu-ray" msgstr "" #: picard/const.py:86 msgid "HD-DVD" msgstr "" #: picard/const.py:87 msgid "Videotape" msgstr "" #: picard/const.py:88 msgid "VHS" msgstr "" #: picard/const.py:89 msgid "Betamax" msgstr "" #: picard/const.py:90 msgid "VCD" msgstr "" #: picard/const.py:91 msgid "SVCD" msgstr "" #: picard/const.py:92 msgid "UMD" msgstr "" #: picard/const.py:93 picard/coverartarchive.py:33 #: picard/ui/ui_options_releases.py:226 msgid "Other" msgstr "Otru" #: picard/const.py:94 msgid "LaserDisc" msgstr "Discu laser" #: picard/const.py:95 msgid "Cartridge" msgstr "" #: picard/const.py:96 msgid "Reel-to-reel" msgstr "" #: picard/const.py:97 msgid "DAT" msgstr "DAT" #: picard/const.py:98 msgid "Wax Cylinder" msgstr "Cilindru Wax" #: picard/const.py:99 msgid "Piano Roll" msgstr "Pianola" #: picard/const.py:100 msgid "DCC" msgstr "" #: picard/const.py:115 msgid "Danish" msgstr "" #: picard/const.py:116 msgid "German" msgstr "Alemán" #: picard/const.py:118 msgid "English" msgstr "Inglés" #: picard/const.py:119 msgid "English (Canada)" msgstr "Inglés (Canadá)" #: picard/const.py:120 msgid "English (UK)" msgstr "Inglés (RX)" #: picard/const.py:122 msgid "Spanish" msgstr "Castellán" #: picard/const.py:123 msgid "Estonian" msgstr "Estoniu" #: picard/const.py:125 msgid "Finnish" msgstr "Finlandés" #: picard/const.py:127 msgid "French" msgstr "Francés" #: picard/const.py:136 msgid "Italian" msgstr "Italianu" #: picard/const.py:143 msgid "Dutch" msgstr "Holandés" #: picard/const.py:145 msgid "Polish" msgstr "Polacu" #: picard/const.py:147 msgid "Brazilian Portuguese" msgstr "Portugés BR" #: picard/const.py:154 msgid "Swedish" msgstr "Suecu" #: picard/coverart.py:84 #, python-format msgid "Coverart %s downloaded" msgstr "" #: picard/coverart.py:240 #, python-format msgid "Downloading http://%s:%i%s" msgstr "" #: picard/coverartarchive.py:24 msgid "Front" msgstr "" #: picard/coverartarchive.py:25 msgid "Back" msgstr "" #: picard/coverartarchive.py:26 msgid "Booklet" msgstr "" #: picard/coverartarchive.py:27 msgid "Medium" msgstr "" #: picard/coverartarchive.py:28 msgid "Tray" msgstr "" #: picard/coverartarchive.py:29 msgid "Obi" msgstr "" #: picard/coverartarchive.py:30 msgid "Spine" msgstr "" #: picard/coverartarchive.py:31 picard/ui/mainwindow.py:546 msgid "Track" msgstr "" #: picard/coverartarchive.py:32 msgid "Sticker" msgstr "" #: picard/coverartarchive.py:34 msgid "Unknown" msgstr "" #: picard/file.py:536 #, python-format msgid "No matching tracks for file %s" msgstr "Nun concasa pista denguna pal ficheru %s" #: picard/file.py:548 #, python-format msgid "No matching tracks above the threshold for file %s" msgstr "Nun concasa pista denguna perriba del umbral pal ficheru %s" #: picard/file.py:551 #, python-format msgid "File %s identified!" msgstr "¡Identificáu el ficheru %s!" #: picard/file.py:567 #, python-format msgid "Looking up the metadata for file %s..." msgstr "Guetando los metadatos del ficheru %s..." #: picard/releasegroup.py:53 msgid "Tracks" msgstr "" #: picard/releasegroup.py:54 msgid "Year" msgstr "" #: picard/releasegroup.py:55 picard/ui/cdlookup.py:34 msgid "Country" msgstr "" #: picard/releasegroup.py:56 picard/util/tags.py:81 msgid "Format" msgstr "" #: picard/releasegroup.py:57 msgid "Label" msgstr "" #: picard/releasegroup.py:58 msgid "Cat No" msgstr "" #: picard/releasegroup.py:88 msgid "[no barcode]" msgstr "" #: picard/releasegroup.py:108 msgid "[no release info]" msgstr "" #: picard/tagger.py:316 #, python-format msgid "Loading directory %s" msgstr "" #: picard/tagger.py:452 msgid "CD Lookup Error" msgstr "" #: picard/tagger.py:453 #, python-format msgid "" "Error while reading CD:\n" "\n" "%s" msgstr "" #: picard/ui/cdlookup.py:34 picard/ui/mainwindow.py:544 #: picard/ui/ui_options_releases.py:216 picard/util/tags.py:21 msgid "Album" msgstr "" #: picard/ui/cdlookup.py:34 picard/ui/itemviews.py:96 #: picard/ui/mainwindow.py:545 picard/util/tags.py:22 msgid "Artist" msgstr "" #: picard/ui/cdlookup.py:34 picard/util/tags.py:24 msgid "Date" msgstr "" #: picard/ui/cdlookup.py:35 msgid "Labels" msgstr "" #: picard/ui/cdlookup.py:35 msgid "Catalog #s" msgstr "" #: picard/ui/cdlookup.py:35 picard/util/tags.py:79 msgid "Barcode" msgstr "" #: picard/ui/collectionmenu.py:31 msgid "Refresh List" msgstr "" #: picard/ui/collectionmenu.py:92 #, python-format msgid "%s (%i release)" msgid_plural "%s (%i releases)" msgstr[0] "" msgstr[1] "" #: picard/ui/coverartbox.py:142 msgid "View release on MusicBrainz" msgstr "" #: picard/ui/filebrowser.py:40 msgid "&Move Tagged Files Here" msgstr "" #: picard/ui/filebrowser.py:43 msgid "Show &Hidden Files" msgstr "" #: picard/ui/filebrowser.py:48 msgid "&Set as starting directory" msgstr "" #: picard/ui/infodialog.py:36 picard/ui/infodialog.py:75 msgid "Info" msgstr "" #: picard/ui/infodialog.py:80 msgid "Filename:" msgstr "" #: picard/ui/infodialog.py:82 msgid "Format:" msgstr "" #: picard/ui/infodialog.py:86 msgid "Size:" msgstr "" #: picard/ui/infodialog.py:90 msgid "Length:" msgstr "" #: picard/ui/infodialog.py:92 msgid "Bitrate:" msgstr "" #: picard/ui/infodialog.py:94 msgid "Sample rate:" msgstr "" #: picard/ui/infodialog.py:96 msgid "Bits per sample:" msgstr "" #: picard/ui/infodialog.py:100 msgid "Mono" msgstr "" #: picard/ui/infodialog.py:102 msgid "Stereo" msgstr "" #: picard/ui/infodialog.py:105 msgid "Channels:" msgstr "" #: picard/ui/infodialog.py:116 msgid "Album Info" msgstr "" #: picard/ui/infodialog.py:124 msgid "&Errors" msgstr "" #: picard/ui/infodialog.py:134 picard/ui/ui_infodialog.py:77 msgid "&Info" msgstr "" #: picard/ui/infostatus.py:51 msgid "Files" msgstr "" #: picard/ui/infostatus.py:52 msgid "Albums" msgstr "" #: picard/ui/infostatus.py:53 msgid "Pending files" msgstr "" #: picard/ui/infostatus.py:54 msgid "Pending requests" msgstr "" #: picard/ui/itemviews.py:94 picard/util/tags.py:23 msgid "Title" msgstr "" #: picard/ui/itemviews.py:95 picard/util/tags.py:88 msgid "Length" msgstr "" #: picard/ui/itemviews.py:231 msgid "&Expand all" msgstr "" #: picard/ui/itemviews.py:233 msgid "&Collapse all" msgstr "" #: picard/ui/itemviews.py:294 msgid "&Other versions" msgstr "" #: picard/ui/itemviews.py:297 msgid "Loading..." msgstr "" #: picard/ui/itemviews.py:362 msgid "Collections" msgstr "" #: picard/ui/itemviews.py:365 msgid "&Plugins" msgstr "" #: picard/ui/itemviews.py:541 msgid "file view" msgstr "" #: picard/ui/itemviews.py:542 msgid "Contains unmatched files and clusters" msgstr "" #: picard/ui/itemviews.py:562 msgid "Clusters" msgstr "" #: picard/ui/itemviews.py:571 msgid "album view" msgstr "" #: picard/ui/itemviews.py:572 msgid "Contains albums and matched files" msgstr "" #: picard/ui/logview.py:91 msgid "Log" msgstr "" #: picard/ui/logview.py:99 msgid "Status History" msgstr "" #: picard/ui/mainwindow.py:74 msgid "MusicBrainz Picard" msgstr "" #: picard/ui/mainwindow.py:155 msgid "Unsaved Changes" msgstr "" #: picard/ui/mainwindow.py:156 msgid "Are you sure you want to quit Picard?" msgstr "" #: picard/ui/mainwindow.py:157 #, python-format msgid "" "There is %d unsaved file. Closing Picard will lose all unsaved changes." msgid_plural "" "There are %d unsaved files. Closing Picard will lose all unsaved changes." msgstr[0] "" msgstr[1] "" #: picard/ui/mainwindow.py:164 msgid "&Quit Picard" msgstr "" #: picard/ui/mainwindow.py:216 msgid "Ready" msgstr "" #: picard/ui/mainwindow.py:220 msgid "" "Picard listens on a port to integrate with your browser and downloads " "release information when you click the \"Tagger\" buttons on the MusicBrainz" " website" msgstr "" #: picard/ui/mainwindow.py:240 #, python-format msgid " Listening on port %(port)d " msgstr "" #: picard/ui/mainwindow.py:263 msgid "Submission Error" msgstr "" #: picard/ui/mainwindow.py:264 msgid "" "You need to configure your AcoustID API key before you can submit " "fingerprints." msgstr "" #: picard/ui/mainwindow.py:269 msgid "&Options..." msgstr "" #: picard/ui/mainwindow.py:273 msgid "&Cut" msgstr "" #: picard/ui/mainwindow.py:278 msgid "&Paste" msgstr "" #: picard/ui/mainwindow.py:283 msgid "&Help..." msgstr "" #: picard/ui/mainwindow.py:288 msgid "&About..." msgstr "" #: picard/ui/mainwindow.py:292 msgid "&Donate..." msgstr "" #: picard/ui/mainwindow.py:295 msgid "&Report a Bug..." msgstr "" #: picard/ui/mainwindow.py:298 msgid "&Support Forum..." msgstr "" #: picard/ui/mainwindow.py:301 msgid "&Add Files..." msgstr "" #: picard/ui/mainwindow.py:302 msgid "Add files to the tagger" msgstr "" #: picard/ui/mainwindow.py:307 msgid "A&dd Folder..." msgstr "" #: picard/ui/mainwindow.py:308 msgid "Add a folder to the tagger" msgstr "" #: picard/ui/mainwindow.py:310 msgid "Ctrl+D" msgstr "" #: picard/ui/mainwindow.py:313 msgid "&Save" msgstr "" #: picard/ui/mainwindow.py:314 msgid "Save selected files" msgstr "" #: picard/ui/mainwindow.py:320 msgid "S&ubmit" msgstr "" #: picard/ui/mainwindow.py:321 msgid "Submit fingerprints" msgstr "" #: picard/ui/mainwindow.py:325 msgid "E&xit" msgstr "" #: picard/ui/mainwindow.py:328 msgid "Ctrl+Q" msgstr "" #: picard/ui/mainwindow.py:331 msgid "&Remove" msgstr "" #: picard/ui/mainwindow.py:332 msgid "Remove selected files/albums" msgstr "" #: picard/ui/mainwindow.py:336 msgid "Lookup in &Browser" msgstr "" #: picard/ui/mainwindow.py:337 msgid "Lookup selected item on MusicBrainz website" msgstr "" #: picard/ui/mainwindow.py:341 msgid "File &Browser" msgstr "" #: picard/ui/mainwindow.py:345 msgid "Ctrl+B" msgstr "" #: picard/ui/mainwindow.py:348 msgid "&Cover Art" msgstr "" #: picard/ui/mainwindow.py:354 picard/ui/mainwindow.py:539 msgid "Search" msgstr "" #: picard/ui/mainwindow.py:357 msgid "&CD Lookup..." msgstr "" #: picard/ui/mainwindow.py:358 picard/ui/mainwindow.py:359 msgid "Lookup CD" msgstr "" #: picard/ui/mainwindow.py:361 msgid "Ctrl+K" msgstr "" #: picard/ui/mainwindow.py:364 msgid "&Scan" msgstr "" #: picard/ui/mainwindow.py:367 msgid "Ctrl+Y" msgstr "" #: picard/ui/mainwindow.py:370 msgid "Cl&uster" msgstr "" #: picard/ui/mainwindow.py:373 msgid "Ctrl+U" msgstr "" #: picard/ui/mainwindow.py:376 msgid "&Lookup" msgstr "" #: picard/ui/mainwindow.py:377 picard/ui/mainwindow.py:378 msgid "Lookup metadata" msgstr "" #: picard/ui/mainwindow.py:381 msgid "Ctrl+L" msgstr "" #: picard/ui/mainwindow.py:384 msgid "&Info..." msgstr "" #: picard/ui/mainwindow.py:387 msgid "Ctrl+I" msgstr "" #: picard/ui/mainwindow.py:390 msgid "&Refresh" msgstr "" #: picard/ui/mainwindow.py:391 msgid "Ctrl+R" msgstr "" #: picard/ui/mainwindow.py:394 msgid "&Rename Files" msgstr "" #: picard/ui/mainwindow.py:399 msgid "&Move Files" msgstr "" #: picard/ui/mainwindow.py:404 msgid "Save &Tags" msgstr "" #: picard/ui/mainwindow.py:409 msgid "Tags From &File Names..." msgstr "" #: picard/ui/mainwindow.py:412 msgid "View &Log..." msgstr "" #: picard/ui/mainwindow.py:415 msgid "View Status &History..." msgstr "" #: picard/ui/mainwindow.py:422 msgid "&Open..." msgstr "" #: picard/ui/mainwindow.py:423 msgid "Open the file" msgstr "" #: picard/ui/mainwindow.py:426 msgid "Open &Folder..." msgstr "" #: picard/ui/mainwindow.py:427 msgid "Open the containing folder" msgstr "" #: picard/ui/mainwindow.py:448 msgid "&File" msgstr "" #: picard/ui/mainwindow.py:456 msgid "&Edit" msgstr "" #: picard/ui/mainwindow.py:462 msgid "&View" msgstr "" #: picard/ui/mainwindow.py:470 msgid "&Options" msgstr "" #: picard/ui/mainwindow.py:476 msgid "&Tools" msgstr "" #: picard/ui/mainwindow.py:485 picard/ui/util.py:35 msgid "&Help" msgstr "" #: picard/ui/mainwindow.py:505 msgid "Actions" msgstr "" #: picard/ui/mainwindow.py:608 msgid "All Supported Formats" msgstr "" #: picard/ui/mainwindow.py:705 msgid "Configuration Required" msgstr "" #: picard/ui/mainwindow.py:706 msgid "" "Audio fingerprinting is not yet configured. Would you like to configure it " "now?" msgstr "" #: picard/ui/mainwindow.py:784 picard/ui/mainwindow.py:791 #, python-format msgid " (Error: %s)" msgstr "" #: picard/ui/metadatabox.py:82 #, python-format msgid "(different across %d item)" msgid_plural "(different across %d items)" msgstr[0] "" msgstr[1] "" #: picard/ui/metadatabox.py:90 #, python-format msgid "(missing from %d item)" msgid_plural "(missing from %d items)" msgstr[0] "" msgstr[1] "" #: picard/ui/metadatabox.py:154 msgid "metadata view" msgstr "" #: picard/ui/metadatabox.py:155 msgid "Displays original and new tags for the selected files" msgstr "" #: picard/ui/metadatabox.py:157 msgid "Tag" msgstr "" #: picard/ui/metadatabox.py:157 msgid "Original Value" msgstr "" #: picard/ui/metadatabox.py:157 msgid "New Value" msgstr "" #: picard/ui/metadatabox.py:180 msgid "Add New Tag..." msgstr "" #: picard/ui/metadatabox.py:182 msgid "Show Changes First" msgstr "" #: picard/ui/metadatabox.py:237 msgid "Edit..." msgstr "" #: picard/ui/metadatabox.py:258 msgid "Use Original Value" msgid_plural "Use Original Values" msgstr[0] "" msgstr[1] "" #: picard/ui/passworddialog.py:37 #, python-format msgid "" "The server %s requires you to login. Please enter your username and " "password." msgstr "" #: picard/ui/passworddialog.py:75 #, python-format msgid "" "The proxy %s requires you to login. Please enter your username and password." msgstr "" #: picard/ui/tagsfromfilenames.py:55 picard/ui/tagsfromfilenames.py:100 msgid "File Name" msgstr "" #: picard/ui/ui_cdlookup.py:66 picard/ui/ui_options_cdlookup.py:55 #: picard/ui/ui_options_cdlookup_select.py:62 picard/ui/options/cdlookup.py:38 msgid "CD Lookup" msgstr "" #: picard/ui/ui_cdlookup.py:67 msgid "The following releases on MusicBrainz match the CD:" msgstr "" #: picard/ui/ui_cdlookup.py:68 msgid "OK" msgstr "" #: picard/ui/ui_cdlookup.py:69 msgid "Lookup manually" msgstr "" #: picard/ui/ui_cdlookup.py:70 msgid "Cancel" msgstr "" #: picard/ui/ui_edittagdialog.py:101 msgid "Edit Tag" msgstr "" #: picard/ui/ui_edittagdialog.py:102 msgid "Edit value" msgstr "" #: picard/ui/ui_edittagdialog.py:103 msgid "Add value" msgstr "" #: picard/ui/ui_edittagdialog.py:104 msgid "Remove value" msgstr "" #: picard/ui/ui_infodialog.py:78 msgid "A&rtwork" msgstr "" #: picard/ui/ui_infostatus.py:100 msgid "Form" msgstr "" #: picard/ui/ui_options.py:51 msgid "Options" msgstr "" #: picard/ui/ui_options_advanced.py:46 msgid "Advanced options" msgstr "" #: picard/ui/ui_options_advanced.py:47 msgid "Ignore file paths matching the following regular expression:" msgstr "" #: picard/ui/ui_options_cdlookup.py:56 msgid "CD-ROM device to use for lookups:" msgstr "" #: picard/ui/ui_options_cdlookup_select.py:63 msgid "Default CD-ROM drive to use for lookups:" msgstr "" #: picard/ui/ui_options_cover.py:145 msgid "Location" msgstr "" #: picard/ui/ui_options_cover.py:146 msgid "Embed cover images into tags" msgstr "" #: picard/ui/ui_options_cover.py:147 msgid "Only embed a front image" msgstr "" #: picard/ui/ui_options_cover.py:148 msgid "Save cover images as separate files" msgstr "" #: picard/ui/ui_options_cover.py:149 msgid "Use the following file name for images:" msgstr "" #: picard/ui/ui_options_cover.py:150 msgid "Overwrite the file if it already exists" msgstr "" #: picard/ui/ui_options_cover.py:151 msgid "Coverart Providers" msgstr "" #: picard/ui/ui_options_cover.py:152 msgid "Amazon" msgstr "" #: picard/ui/ui_options_cover.py:153 picard/ui/ui_options_cover.py:155 msgid "Cover Art Archive" msgstr "" #: picard/ui/ui_options_cover.py:154 msgid "Sites on the whitelist" msgstr "" #: picard/ui/ui_options_cover.py:156 msgid "Only use images of the following size:" msgstr "" #: picard/ui/ui_options_cover.py:157 msgid "250 px" msgstr "" #: picard/ui/ui_options_cover.py:158 msgid "500 px" msgstr "" #: picard/ui/ui_options_cover.py:159 msgid "Full size" msgstr "" #: picard/ui/ui_options_cover.py:160 msgid "Download only images of the following types:" msgstr "" #: picard/ui/ui_options_cover.py:161 msgid "Download only approved images" msgstr "" #: picard/ui/ui_options_cover.py:162 msgid "" "Use the first image type as the filename. This will not change the filename " "of front images." msgstr "" #: picard/ui/ui_options_fingerprinting.py:83 msgid "Audio Fingerprinting" msgstr "" #: picard/ui/ui_options_fingerprinting.py:84 msgid "Do not use audio fingerprinting" msgstr "" #: picard/ui/ui_options_fingerprinting.py:85 msgid "Use AcoustID" msgstr "" #: picard/ui/ui_options_fingerprinting.py:86 msgid "AcoustID Settings" msgstr "" #: picard/ui/ui_options_fingerprinting.py:87 msgid "Fingerprint calculator:" msgstr "" #: picard/ui/ui_options_fingerprinting.py:88 #: picard/ui/ui_options_interface.py:88 picard/ui/ui_options_renaming.py:138 msgid "Browse..." msgstr "" #: picard/ui/ui_options_fingerprinting.py:89 msgid "Download..." msgstr "" #: picard/ui/ui_options_fingerprinting.py:90 msgid "API key:" msgstr "" #: picard/ui/ui_options_fingerprinting.py:91 msgid "Get API key..." msgstr "" #: picard/ui/ui_options_folksonomy.py:115 picard/ui/options/folksonomy.py:28 msgid "Folksonomy Tags" msgstr "" #: picard/ui/ui_options_folksonomy.py:117 msgid "Only use my tags" msgstr "" #: picard/ui/ui_options_folksonomy.py:120 msgid "Maximum number of tags:" msgstr "" #: picard/ui/ui_options_general.py:92 msgid "MusicBrainz Server" msgstr "" #: picard/ui/ui_options_general.py:93 picard/ui/ui_options_network.py:123 msgid "Port:" msgstr "" #: picard/ui/ui_options_general.py:94 picard/ui/ui_options_network.py:124 msgid "Server address:" msgstr "" #: picard/ui/ui_options_general.py:95 msgid "Account Information" msgstr "" #: picard/ui/ui_options_general.py:96 picard/ui/ui_options_network.py:121 #: picard/ui/ui_passworddialog.py:74 msgid "Password:" msgstr "" #: picard/ui/ui_options_general.py:97 picard/ui/ui_options_network.py:122 #: picard/ui/ui_passworddialog.py:73 msgid "Username:" msgstr "" #: picard/ui/ui_options_general.py:98 picard/ui/options/general.py:31 msgid "General" msgstr "" #: picard/ui/ui_options_general.py:99 msgid "Automatically scan all new files" msgstr "" #: picard/ui/ui_options_general.py:100 msgid "Ignore MBIDs when loading new files" msgstr "" #: picard/ui/ui_options_interface.py:82 msgid "Miscellaneous" msgstr "" #: picard/ui/ui_options_interface.py:83 msgid "Show text labels under icons" msgstr "" #: picard/ui/ui_options_interface.py:84 msgid "Allow selection of multiple directories" msgstr "" #: picard/ui/ui_options_interface.py:85 msgid "Use advanced query syntax" msgstr "" #: picard/ui/ui_options_interface.py:86 msgid "Show a quit confirmation dialog for unsaved changes" msgstr "" #: picard/ui/ui_options_interface.py:87 msgid "Begin browsing in the following directory:" msgstr "" #: picard/ui/ui_options_interface.py:89 msgid "User interface language:" msgstr "" #: picard/ui/ui_options_matching.py:86 msgid "Thresholds" msgstr "" #: picard/ui/ui_options_matching.py:87 msgid "Minimal similarity for matching files to tracks:" msgstr "" #: picard/ui/ui_options_matching.py:91 msgid "Minimal similarity for file lookups:" msgstr "" #: picard/ui/ui_options_matching.py:92 msgid "Minimal similarity for cluster lookups:" msgstr "" #: picard/ui/ui_options_metadata.py:114 picard/ui/options/metadata.py:29 msgid "Metadata" msgstr "" #: picard/ui/ui_options_metadata.py:115 msgid "Translate artist names to this locale where possible:" msgstr "" #: picard/ui/ui_options_metadata.py:116 msgid "Use standardized artist names" msgstr "" #: picard/ui/ui_options_metadata.py:117 msgid "Convert Unicode punctuation characters to ASCII" msgstr "" #: picard/ui/ui_options_metadata.py:118 msgid "Use release relationships" msgstr "" #: picard/ui/ui_options_metadata.py:119 msgid "Use track relationships" msgstr "" #: picard/ui/ui_options_metadata.py:120 msgid "Use folksonomy tags as genre" msgstr "" #: picard/ui/ui_options_metadata.py:121 msgid "Custom Fields" msgstr "" #: picard/ui/ui_options_metadata.py:122 msgid "Various artists:" msgstr "" #: picard/ui/ui_options_metadata.py:123 msgid "Non-album tracks:" msgstr "" #: picard/ui/ui_options_metadata.py:124 picard/ui/ui_options_metadata.py:125 #: picard/ui/ui_options_renaming.py:147 msgid "Default" msgstr "" #: picard/ui/ui_options_network.py:120 msgid "Web Proxy" msgstr "" #: picard/ui/ui_options_network.py:125 msgid "Browser Integration" msgstr "" #: picard/ui/ui_options_network.py:126 msgid "Default listening port:" msgstr "" #: picard/ui/ui_options_network.py:127 msgid "Listen only on localhost" msgstr "" #: picard/ui/ui_options_plugins.py:131 picard/ui/options/plugins.py:38 msgid "Plugins" msgstr "" #: picard/ui/ui_options_plugins.py:132 picard/ui/options/plugins.py:122 msgid "Name" msgstr "" #: picard/ui/ui_options_plugins.py:133 picard/util/tags.py:39 msgid "Version" msgstr "" #: picard/ui/ui_options_plugins.py:134 picard/ui/options/plugins.py:125 msgid "Author" msgstr "" #: picard/ui/ui_options_plugins.py:135 msgid "Install plugin..." msgstr "" #: picard/ui/ui_options_plugins.py:136 msgid "Open plugin folder" msgstr "" #: picard/ui/ui_options_plugins.py:137 msgid "Download plugins" msgstr "" #: picard/ui/ui_options_plugins.py:138 msgid "Details" msgstr "" #: picard/ui/ui_options_ratings.py:53 msgid "Enable track ratings" msgstr "" #: picard/ui/ui_options_ratings.py:54 msgid "" "Picard saves the ratings together with an e-mail address identifying the " "user who did the rating. That way different ratings for different users can " "be stored in the files. Please specify the e-mail you want to use to save " "your ratings." msgstr "" #: picard/ui/ui_options_ratings.py:55 msgid "E-mail:" msgstr "" #: picard/ui/ui_options_ratings.py:56 msgid "Submit ratings to MusicBrainz" msgstr "" #: picard/ui/ui_options_releases.py:215 msgid "Preferred release types" msgstr "" #: picard/ui/ui_options_releases.py:217 msgid "Single" msgstr "" #: picard/ui/ui_options_releases.py:218 msgid "EP" msgstr "" #: picard/ui/ui_options_releases.py:219 msgid "Compilation" msgstr "" #: picard/ui/ui_options_releases.py:220 msgid "Soundtrack" msgstr "" #: picard/ui/ui_options_releases.py:221 msgid "Spokenword" msgstr "" #: picard/ui/ui_options_releases.py:222 msgid "Interview" msgstr "" #: picard/ui/ui_options_releases.py:223 msgid "Audiobook" msgstr "" #: picard/ui/ui_options_releases.py:224 msgid "Live" msgstr "" #: picard/ui/ui_options_releases.py:225 msgid "Remix" msgstr "" #: picard/ui/ui_options_releases.py:227 msgid "Reset all" msgstr "" #: picard/ui/ui_options_releases.py:228 msgid "Preferred release countries" msgstr "" #: picard/ui/ui_options_releases.py:229 picard/ui/ui_options_releases.py:232 msgid ">" msgstr "" #: picard/ui/ui_options_releases.py:230 picard/ui/ui_options_releases.py:233 msgid "<" msgstr "" #: picard/ui/ui_options_releases.py:231 msgid "Preferred release formats" msgstr "" #: picard/ui/ui_options_renaming.py:134 msgid "Rename files when saving" msgstr "" #: picard/ui/ui_options_renaming.py:135 msgid "Replace non-ASCII characters" msgstr "" #: picard/ui/ui_options_renaming.py:136 msgid "Windows compatibility" msgstr "" #: picard/ui/ui_options_renaming.py:137 msgid "Move files to this directory when saving:" msgstr "" #: picard/ui/ui_options_renaming.py:139 msgid "Delete empty directories" msgstr "" #: picard/ui/ui_options_renaming.py:140 msgid "Move additional files:" msgstr "" #: picard/ui/ui_options_renaming.py:141 msgid "Name files like this" msgstr "" #: picard/ui/ui_options_renaming.py:148 msgid "Examples" msgstr "" #: picard/ui/ui_options_script.py:49 msgid "Tagger Script" msgstr "" #: picard/ui/ui_options_tags.py:165 msgid "Write tags to files" msgstr "" #: picard/ui/ui_options_tags.py:166 msgid "Preserve timestamps of tagged files" msgstr "" #: picard/ui/ui_options_tags.py:167 msgid "Before Tagging" msgstr "" #: picard/ui/ui_options_tags.py:168 msgid "Clear existing tags" msgstr "" #: picard/ui/ui_options_tags.py:169 msgid "Remove ID3 tags from FLAC files" msgstr "" #: picard/ui/ui_options_tags.py:170 msgid "Remove APEv2 tags from MP3 files" msgstr "" #: picard/ui/ui_options_tags.py:171 msgid "" "Preserve these tags from being cleared or overwritten with MusicBrainz data:" msgstr "" #: picard/ui/ui_options_tags.py:172 msgid "Tags are separated by commas, and are case-sensitive." msgstr "" #: picard/ui/ui_options_tags.py:173 msgid "Tag Compatibility" msgstr "" #: picard/ui/ui_options_tags.py:174 msgid "ID3v2 Version" msgstr "" #: picard/ui/ui_options_tags.py:175 msgid "2.4" msgstr "" #: picard/ui/ui_options_tags.py:176 msgid "2.3" msgstr "" #: picard/ui/ui_options_tags.py:177 msgid "ID3v2 Text Encoding" msgstr "" #: picard/ui/ui_options_tags.py:178 msgid "UTF-8" msgstr "" #: picard/ui/ui_options_tags.py:179 msgid "UTF-16" msgstr "" #: picard/ui/ui_options_tags.py:180 msgid "ISO-8859-1" msgstr "" #: picard/ui/ui_options_tags.py:181 msgid "Join multiple ID3v2.3 tags with:" msgstr "" #: picard/ui/ui_options_tags.py:182 msgid "" "

Default is '/' to maintain compatibility with previous" " Picard releases.

New alternatives are ';_' or '_/_' or type your own." "

" msgstr "" #: picard/ui/ui_options_tags.py:183 msgid "Also include ID3v1 tags in the files" msgstr "" #: picard/ui/ui_passworddialog.py:72 msgid "Authentication required" msgstr "" #: picard/ui/ui_passworddialog.py:75 msgid "Save username and password" msgstr "" #: picard/ui/ui_tagsfromfilenames.py:54 msgid "Convert File Names to Tags" msgstr "" #: picard/ui/ui_tagsfromfilenames.py:55 msgid "Replace underscores with spaces" msgstr "" #: picard/ui/ui_tagsfromfilenames.py:56 msgid "&Preview" msgstr "" #: picard/ui/util.py:33 msgid "&Ok" msgstr "" #: picard/ui/util.py:34 msgid "&Cancel" msgstr "" #: picard/ui/options/about.py:33 msgid "About" msgstr "" #: picard/ui/options/about.py:48 msgid "is not installed" msgstr "" #: picard/ui/options/about.py:59 msgid "translator-credits" msgstr "Launchpad Contributions:\n ASTUR2000 https://launchpad.net/~astur2000\n Xuacu Saturio https://launchpad.net/~xuacusk8" #: picard/ui/options/about.py:62 #, python-format msgid "
Translated to LANG by %s" msgstr "" #: picard/ui/options/about.py:66 #, python-format msgid "" "

MusicBrainz Picard
\n" "Version %(version)s

\n" "

\n" "PyQt %(pyqt-version)s
\n" "Mutagen %(mutagen-version)s
\n" "Discid %(discid-version)s\n" "

\n" "

Supported formats
%(formats)s

\n" "

Please donate
\n" "Thank you for using Picard. Picard relies on the MusicBrainz database, which is operated by the MetaBrainz Foundation with the help of thousands of volunteers. If you like this application please consider donating to the MetaBrainz Foundation to keep the service running.

\n" "

Donate now!

\n" "

Credits
\n" "Copyright © 2004-2014 Robert Kaye, Lukáš Lalinský and others%(translator-credits)s

\n" "

%(picard-doc-url)s

\n" msgstr "" #: picard/ui/options/advanced.py:31 msgid "Advanced" msgstr "" #: picard/ui/options/advanced.py:66 msgid "Regex Error" msgstr "" #: picard/ui/options/cover.py:63 msgid "Cover Art" msgstr "" #: picard/ui/options/fingerprinting.py:32 msgid "Fingerprinting" msgstr "" #: picard/ui/options/interface.py:35 msgid "User Interface" msgstr "" #: picard/ui/options/interface.py:54 msgid "System default" msgstr "" #: picard/ui/options/interface.py:96 msgid "Language changed" msgstr "" #: picard/ui/options/interface.py:96 msgid "" "You have changed the interface language. You have to restart Picard in order" " for the change to take effect." msgstr "" #: picard/ui/options/matching.py:28 msgid "Matching" msgstr "" #: picard/ui/options/network.py:29 msgid "Network" msgstr "" #: picard/ui/options/plugins.py:126 msgid "File" msgstr "" #: picard/ui/options/ratings.py:28 msgid "Ratings" msgstr "" #: picard/ui/options/releases.py:33 msgid "Preferred Releases" msgstr "" #: picard/ui/options/renaming.py:37 msgid "File Naming" msgstr "" #: picard/ui/options/renaming.py:184 msgid "Error" msgstr "" #: picard/ui/options/renaming.py:184 msgid "The location to move files to must not be empty." msgstr "" #: picard/ui/options/renaming.py:194 msgid "The file naming format must not be empty." msgstr "" #: picard/ui/options/scripting.py:63 msgid "Scripting" msgstr "" #: picard/ui/options/scripting.py:95 msgid "Script Error" msgstr "" #: picard/util/bytes2human.py:33 #, python-format msgid "%s B" msgstr "" #: picard/util/bytes2human.py:34 #, python-format msgid "%s kB" msgstr "" #: picard/util/bytes2human.py:35 #, python-format msgid "%s KiB" msgstr "" #: picard/util/bytes2human.py:36 #, python-format msgid "%s MB" msgstr "" #: picard/util/bytes2human.py:37 #, python-format msgid "%s MiB" msgstr "" #: picard/util/bytes2human.py:38 #, python-format msgid "%s GB" msgstr "" #: picard/util/bytes2human.py:39 #, python-format msgid "%s GiB" msgstr "" #: picard/util/bytes2human.py:40 #, python-format msgid "%s TB" msgstr "" #: picard/util/bytes2human.py:41 #, python-format msgid "%s TiB" msgstr "" #: picard/util/bytes2human.py:42 #, python-format msgid "%s PB" msgstr "" #: picard/util/bytes2human.py:43 #, python-format msgid "%s PiB" msgstr "" #: picard/util/bytes2human.py:84 #, python-format msgid "%s " msgstr "" #: picard/util/tags.py:25 msgid "Original Release Date" msgstr "" #: picard/util/tags.py:26 msgid "Original Year" msgstr "" #: picard/util/tags.py:27 msgid "Album Artist" msgstr "" #: picard/util/tags.py:28 msgid "Track Number" msgstr "" #: picard/util/tags.py:29 msgid "Total Tracks" msgstr "" #: picard/util/tags.py:30 msgid "Disc Number" msgstr "" #: picard/util/tags.py:31 msgid "Total Discs" msgstr "" #: picard/util/tags.py:32 msgid "Album Artist Sort Order" msgstr "" #: picard/util/tags.py:33 msgid "Artist Sort Order" msgstr "" #: picard/util/tags.py:34 msgid "Title Sort Order" msgstr "" #: picard/util/tags.py:35 msgid "Album Sort Order" msgstr "" #: picard/util/tags.py:36 msgid "Composer Sort Order" msgstr "" #: picard/util/tags.py:37 msgid "ASIN" msgstr "" #: picard/util/tags.py:38 msgid "Grouping" msgstr "" #: picard/util/tags.py:40 msgid "ISRC" msgstr "" #: picard/util/tags.py:41 msgid "Mood" msgstr "" #: picard/util/tags.py:42 msgid "BPM" msgstr "" #: picard/util/tags.py:43 msgid "Copyright" msgstr "" #: picard/util/tags.py:44 msgid "License" msgstr "" #: picard/util/tags.py:45 msgid "Composer" msgstr "" #: picard/util/tags.py:46 msgid "Writer" msgstr "" #: picard/util/tags.py:47 msgid "Conductor" msgstr "" #: picard/util/tags.py:48 msgid "Lyricist" msgstr "" #: picard/util/tags.py:49 msgid "Arranger" msgstr "" #: picard/util/tags.py:50 msgid "Producer" msgstr "" #: picard/util/tags.py:51 msgid "Engineer" msgstr "" #: picard/util/tags.py:52 msgid "Subtitle" msgstr "" #: picard/util/tags.py:53 msgid "Disc Subtitle" msgstr "" #: picard/util/tags.py:54 msgid "Remixer" msgstr "" #: picard/util/tags.py:55 msgid "MusicBrainz Recording Id" msgstr "" #: picard/util/tags.py:56 msgid "MusicBrainz Track Id" msgstr "" #: picard/util/tags.py:57 msgid "MusicBrainz Release Id" msgstr "" #: picard/util/tags.py:58 msgid "MusicBrainz Artist Id" msgstr "" #: picard/util/tags.py:59 msgid "MusicBrainz Release Artist Id" msgstr "" #: picard/util/tags.py:60 msgid "MusicBrainz Work Id" msgstr "" #: picard/util/tags.py:61 msgid "MusicBrainz Release Group Id" msgstr "" #: picard/util/tags.py:62 msgid "MusicBrainz Disc Id" msgstr "" #: picard/util/tags.py:63 msgid "MusicBrainz Sort Name" msgstr "" #: picard/util/tags.py:64 msgid "MusicIP PUID" msgstr "" #: picard/util/tags.py:65 msgid "MusicIP Fingerprint" msgstr "" #: picard/util/tags.py:66 msgid "AcoustID" msgstr "" #: picard/util/tags.py:67 msgid "AcoustID Fingerprint" msgstr "" #: picard/util/tags.py:68 msgid "Disc Id" msgstr "" #: picard/util/tags.py:69 msgid "Website" msgstr "" #: picard/util/tags.py:70 msgid "Compilation (iTunes)" msgstr "" #: picard/util/tags.py:71 msgid "Comment" msgstr "" #: picard/util/tags.py:72 msgid "Genre" msgstr "" #: picard/util/tags.py:73 msgid "Encoded By" msgstr "" #: picard/util/tags.py:74 msgid "Performer" msgstr "" #: picard/util/tags.py:75 msgid "Release Type" msgstr "" #: picard/util/tags.py:76 msgid "Release Status" msgstr "" #: picard/util/tags.py:77 msgid "Release Country" msgstr "" #: picard/util/tags.py:78 msgid "Record Label" msgstr "" #: picard/util/tags.py:80 msgid "Catalog Number" msgstr "" #: picard/util/tags.py:82 msgid "DJ-Mixer" msgstr "" #: picard/util/tags.py:83 msgid "Media" msgstr "" #: picard/util/tags.py:84 msgid "Lyrics" msgstr "" #: picard/util/tags.py:85 msgid "Mixer" msgstr "" #: picard/util/tags.py:86 msgid "Language" msgstr "" #: picard/util/tags.py:87 msgid "Script" msgstr "" #: picard/util/tags.py:89 msgid "Rating" msgstr "" #: picard/util/tags.py:90 msgid "Artists" msgstr "" #: picard/util/tags.py:91 msgid "Work" msgstr "" #: picard/util/webbrowser2.py:90 msgid "Web Browser Error" msgstr "" #: picard/util/webbrowser2.py:90 #, python-format msgid "" "Error while launching a web browser:\n" "\n" "%s" msgstr "" picard-release-1.4.2/po/attributes/000077500000000000000000000000001310410472100171735ustar00rootroot00000000000000picard-release-1.4.2/po/attributes/.gitignore000066400000000000000000000000001310410472100211510ustar00rootroot00000000000000picard-release-1.4.2/po/attributes/da.po000066400000000000000000004255151310410472100201330ustar00rootroot00000000000000# Translators: # Translators: # Frederik “Freso” S. Olesen , 2012 # Frederik “Freso” S. Olesen , 2014-2015 msgid "" msgstr "" "Project-Id-Version: MusicBrainz\n" "PO-Revision-Date: 2017-01-16 20:46+0000\n" "Last-Translator: Laurent Monin \n" "Language-Team: Danish (http://www.transifex.com/musicbrainz/musicbrainz/language/da/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: da\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: DB:work_type/description:9 msgctxt "work_type" msgid "" "\"Motet\" is a term that applies to different types of (usually " "unaccompanied) choral works. What exactly is a motet depends quite a bit on " "the period." msgstr "" #: DB:work_type/description:5 msgctxt "work_type" msgid "" "\"Sonata\" is a general term used to describe small scale (very often solo " "or solo + keyboard) instrumental works, initially in baroque music." msgstr "" #: DB:medium_format/name:54 msgctxt "medium_format" msgid "10\" Shellac" msgstr "" #: DB:medium_format/name:30 msgctxt "medium_format" msgid "10\" Vinyl" msgstr "10\" vinyl" #: DB:medium_format/name:55 msgctxt "medium_format" msgid "12\" Shellac" msgstr "" #: DB:medium_format/name:31 msgctxt "medium_format" msgid "12\" Vinyl" msgstr "12\" vinyl" #: DB:medium_format/name:49 msgctxt "medium_format" msgid "3.5\" Floppy Disk" msgstr "" #: DB:medium_format/name:52 msgctxt "medium_format" msgid "7\" Flexi-disc" msgstr "" #: DB:medium_format/name:56 msgctxt "medium_format" msgid "7\" Shellac" msgstr "" #: DB:medium_format/name:29 msgctxt "medium_format" msgid "7\" Vinyl" msgstr "7\" vinyl" #: DB:medium_format/name:34 msgctxt "medium_format" msgid "8cm CD" msgstr "8cm CD" #: DB:medium_format/name:40 msgctxt "medium_format" msgid "8cm CD+G" msgstr "8cm CD+G" #: DB:medium_format/description:58 msgctxt "medium_format" msgid "" "90 rpm, vertical-cut shellac discs, produced by the Pathé label from 1906 to" " 1932." msgstr "" #: DB:work_attribute_type_allowed_value/value:794 msgctxt "work_attribute_type_allowed_value" msgid "A Dorian" msgstr "A‐dorisk" #: DB:work_attribute_type_allowed_value/value:806 msgctxt "work_attribute_type_allowed_value" msgid "A Mixolydian" msgstr "" #: DB:work_type/description:2 msgctxt "work_type" msgid "" "A ballet is music composed to be used, together with a choreography, for a " "ballet dance production." msgstr "" #: DB:work_type/description:3 msgctxt "work_type" msgid "" "A cantata is a vocal (often choral) composition with an instrumental " "(usually orchestral) accompaniment, typically in several movements." msgstr "" #: DB:work_type/description:4 msgctxt "work_type" msgid "" "A concerto is a musical work for soloist(s) accompanied by an orchestra." msgstr "" #: DB:event_type/description:4 msgctxt "event_type" msgid "" "A convention, expo or trade fair is an event which is not typically " "orientated around music performances, but can include them as side " "activities." msgstr "" #: DB:release_status/description:2 msgctxt "release_status" msgid "" "A give-away release or a release intended to promote an upcoming official " "release (e.g. pre-release versions, releases included with a magazine, " "versions supplied to radio DJs for air-play)." msgstr "" #: DB:work_attribute_type_allowed_value/value:28 msgctxt "work_attribute_type_allowed_value" msgid "A major" msgstr "A‐dur" #: DB:work_type/description:8 msgctxt "work_type" msgid "" "A mass is a choral composition that sets the invariable portions of the " "Christian Eucharistic liturgy (Kyrie - Gloria - Credo - Sanctus - Benedictus" " - Agnus Dei, with other portions sometimes added) to music." msgstr "" #: DB:event_type/description:5 msgctxt "event_type" msgid "" "A masterclass or clinic is an event where an artist meets with a small to " "medium-sized audience and instructs them individually and/or takes questions" " intended to improve the audience members' playing skills." msgstr "" #: DB:work_attribute_type_allowed_value/value:29 msgctxt "work_attribute_type_allowed_value" msgid "A minor" msgstr "A‐mol" #: DB:work_type/description:13 msgctxt "work_type" msgid "" "A partita is an instrumental piece composed of a series of variations, and " "it's by its current definition very similar to a suite." msgstr "" #: DB:event_type/description:3 msgctxt "event_type" msgid "" "A party, reception or other event held specifically for the launch of a " "release." msgstr "" #: DB:place_type/description:5 msgctxt "place_type" msgid "" "A place consisting of a large enclosed area with a central event space " "surrounded by tiered seating for spectators, which can be used for indoor " "sports, concerts and other entertainment events." msgstr "" #: DB:place_type/description:1 msgctxt "place_type" msgid "" "A place designed for non-live production of music, typically a recording " "studio." msgstr "" #: DB:place_type/description:2 msgctxt "place_type" msgid "" "A place that has live artistic performances as one of its primary functions," " such as a concert hall." msgstr "" #: DB:place_type/description:6 msgctxt "place_type" msgid "" "A place that has worship or religious studies as its main function. " "Religious buildings often host concerts and serve as recording locations, " "especially for classical music." msgstr "" #: DB:place_type/description:4 msgctxt "place_type" msgid "" "A place whose main purpose is to host outdoor sport events, typically " "consisting of a pitch surrounded by a structure for spectators with no roof," " or a roof which can be retracted." msgstr "" #: DB:work_type/description:28 msgctxt "work_type" msgid "" "A play is a form of literature usually consisting of scripted dialogue " "between characters, and intended for theatrical performance rather than just" " reading." msgstr "" #: DB:work_type/description:21 msgctxt "work_type" msgid "" "A poem is a literary piece, generally short and in verse, where words are " "usually chosen for their sound and for the images and ideas they suggest." msgstr "" #: DB:work_type/description:14 msgctxt "work_type" msgid "" "A quartet is a musical composition scored for four voices or instruments." msgstr "" #: DB:series_type/description:8 msgctxt "series_type" msgid "A recurring festival, usually happening annually in the same location." msgstr "" #: DB:series_type/description:6 msgctxt "series_type" msgid "A series of events." msgstr "" #: DB:series_type/description:9 msgctxt "series_type" msgid "A series of performances of the same show at the same venue." msgstr "" #: DB:series_type/description:3 msgctxt "series_type" msgid "A series of recordings." msgstr "" #: DB:series_type/description:7 msgctxt "series_type" msgid "A series of related concerts by an artist in different locations." msgstr "" #: DB:series_type/description:1 msgctxt "series_type" msgid "A series of release groups." msgstr "" #: DB:series_type/description:2 msgctxt "series_type" msgid "A series of releases." msgstr "" #: DB:series_type/description:5 msgctxt "series_type" msgid "A series of works which form a catalogue of classical compositions." msgstr "" #: DB:series_type/description:4 msgctxt "series_type" msgid "A series of works." msgstr "" #: DB:work_type/description:15 msgctxt "work_type" msgid "" "A song cycle is a group of songs designed to be performed in a sequence as a" " single entity. In most cases, all of the songs are by the same composer, " "and often use words from the same poet or lyricist." msgstr "" #: DB:work_type/description:17 msgctxt "work_type" msgid "" "A song is in its origin (and still in most cases) a composition for voice, " "with or without instruments, performed by singing. This is the most common " "form by far in folk and popular music, but also fairly common in a classical" " context (\"art songs\")." msgstr "" #: DB:work_type/description:22 msgctxt "work_type" msgid "" "A soundtrack is the music that accompanies a film, TV program, videogame, or" " even book." msgstr "" #: DB:work_type/description:6 msgctxt "work_type" msgid "" "A suite is an ordered set of instrumental or orchestral pieces normally " "performed in a concert setting. They may be extracts from a ballet or opera," " or entirely original movements." msgstr "" #: DB:work_type/description:18 msgctxt "work_type" msgid "" "A symphonic poem is a piece of programmatic orchestral music, usually in a " "single movement, that evokes a painting, a landscape, the content of a poem," " a story or novel, or other non-musical source." msgstr "" #: DB:work_type/description:16 msgctxt "work_type" msgid "" "A symphony is an extended composition, almost always scored for orchestra " "without soloists." msgstr "" #: DB:release_packaging/description:2 msgctxt "release_packaging" msgid "A thinner jewel case, commonly used for CD singles." msgstr "" #: DB:work_type/description:19 msgctxt "work_type" msgid "" "A zarzuela is a Spanish lyric-dramatic work that alternates between spoken " "and sung scenes, the latter incorporating operatic and popular song, as well" " as dance." msgstr "" #: DB:work_attribute_type_allowed_value/value:26 msgctxt "work_attribute_type_allowed_value" msgid "A-flat major" msgstr "Aes‐dur" #: DB:work_attribute_type_allowed_value/value:27 msgctxt "work_attribute_type_allowed_value" msgid "A-flat minor" msgstr "Aes‐mol" #: DB:work_attribute_type_allowed_value/value:30 msgctxt "work_attribute_type_allowed_value" msgid "A-sharp minor" msgstr "Ais‐mol" #: DB:work_attribute_type/name:23 msgctxt "work_attribute_type" msgid "AKM ID" msgstr "" #: DB:work_attribute_type/name:13 msgctxt "work_attribute_type" msgid "APRA ID" msgstr "APRA‐id" #: DB:work_attribute_type/name:6 msgctxt "work_attribute_type" msgid "ASCAP ID" msgstr "ASCAP‐id" #: DB:work_attribute_type_allowed_value/value:299 msgctxt "work_attribute_type_allowed_value" msgid "Acem" msgstr "Acem" #: DB:work_attribute_type_allowed_value/value:305 msgctxt "work_attribute_type_allowed_value" msgid "Acem Nevruz" msgstr "Acem Nevruz" #: DB:work_attribute_type_allowed_value/value:306 msgctxt "work_attribute_type_allowed_value" msgid "Acem Rast" msgstr "Acem Rast" #: DB:work_attribute_type_allowed_value/value:300 msgctxt "work_attribute_type_allowed_value" msgid "Acem Zemzeme" msgstr "Acem Zemzeme" #: DB:work_attribute_type_allowed_value/value:301 msgctxt "work_attribute_type_allowed_value" msgid "Acemaşiran" msgstr "Acemaşiran" #: DB:work_attribute_type_allowed_value/value:302 msgctxt "work_attribute_type_allowed_value" msgid "Acembuselik" msgstr "Acembuselik" #: DB:work_attribute_type_allowed_value/value:303 msgctxt "work_attribute_type_allowed_value" msgid "Acemkürdi" msgstr "Acemkürdi" #: DB:work_attribute_type_allowed_value/value:304 msgctxt "work_attribute_type_allowed_value" msgid "Acemli Yegah" msgstr "Acemli Yegah" #: DB:work_attribute_type_allowed_value/value:307 msgctxt "work_attribute_type_allowed_value" msgid "Acemtarab" msgstr "Acemtarab" #: DB:work_attribute_type_allowed_value/value:308 msgctxt "work_attribute_type_allowed_value" msgid "Aheng-i Tarab" msgstr "Aheng-i Tarab" #: DB:work_attribute_type_allowed_value/value:309 msgctxt "work_attribute_type_allowed_value" msgid "Ak Dügah" msgstr "Ak Dügah" #: DB:work_attribute_type_allowed_value/value:690 msgctxt "work_attribute_type_allowed_value" msgid "Aksak" msgstr "Aksak" #: DB:work_attribute_type_allowed_value/value:691 msgctxt "work_attribute_type_allowed_value" msgid "Aksaksemai" msgstr "Aksaksemai" #: DB:work_attribute_type_allowed_value/value:692 msgctxt "work_attribute_type_allowed_value" msgid "Aksaksemai Evferi" msgstr "Aksaksemai Evferi" #: DB:release_group_primary_type/name:1 msgctxt "release_group_primary_type" msgid "Album" msgstr "album" #: DB:series_ordering_type/description:2 msgctxt "series_ordering_type" msgid "Allows for manually setting the position of each item in the series." msgstr "Gør det muligt at sætte placeringen af hver genstand i serien manuelt." #: DB:work_attribute_type_allowed_value/value:40 msgctxt "work_attribute_type_allowed_value" msgid "Amṛtavarṣiṇi" msgstr "Amṛtavarṣiṇi" #: DB:work_attribute_type_allowed_value/value:39 msgctxt "work_attribute_type_allowed_value" msgid "Amṛtavāhiṇi" msgstr "Amṛtavāhiṇi" #: DB:release_status/description:4 msgctxt "release_status" msgid "" "An alternate version of a release where the titles have been changed. These " "don't correspond to any real release and should be linked to the original " "release using the transl(iter)ation relationship." msgstr "" #: DB:work_type/description:1 msgctxt "work_type" msgid "" "An aria is a self-contained piece for one voice usually with orchestral " "accompaniment. They are most common inside operas, but also appear in " "cantatas, oratorios and even on their own (concert arias)." msgstr "" #: DB:work_type/description:25 msgctxt "work_type" msgid "" "An audio drama is a dramatized, purely acoustic performance, broadcast on " "radio or published on an audio medium (tape, CD, etc.)." msgstr "" #: DB:event_type/description:2 msgctxt "event_type" msgid "" "An event where a number of different acts perform across the course of the " "day. Larger festivals may be spread across multiple days." msgstr "" #: DB:event_type/description:1 msgctxt "event_type" msgid "" "An individual concert by a single artist or collaboration, often with " "supporting artists who perform before the main act." msgstr "" #: DB:work_type/description:10 msgctxt "work_type" msgid "" "An opera is a dramatised work (text + musical score) for singers and " "orchestra/ensemble. In true operas all dialog is sung, through arias and " "recitatives, but some styles of opera include spoken dialogue." msgstr "" #: DB:work_type/description:11 msgctxt "work_type" msgid "" "An oratorio is a large (usually sacred) musical composition including an " "orchestra, a choir, and soloists. While it has characters and a plot, it is " "usually not performed theatrically (it lacks costumes, props and strong " "character interaction)." msgstr "" #: DB:work_type/description:12 msgctxt "work_type" msgid "" "An overture is, generally, the instrumental introduction to an opera. " "Independent (\"concert\") overtures also exist, which are generally " "programmatic works shorter than a symphonic poem." msgstr "" #: DB:release_status/description:3 msgctxt "release_status" msgid "" "An unofficial/underground release that was not sanctioned by the artist " "and/or the record company. This includes unofficial live recordings and " "pirated releases." msgstr "" #: DB:work_type/description:20 msgctxt "work_type" msgid "" "An étude is an instrumental musical composition, most commonly of " "considerable difficulty, usually designed to provide practice material for " "perfecting a particular technical skill." msgstr "" #: DB:work_attribute_type_allowed_value/value:310 msgctxt "work_attribute_type_allowed_value" msgid "Anberefşan" msgstr "" #: DB:release_status/description:1 msgctxt "release_status" msgid "" "Any release officially sanctioned by the artist and/or their record company." " Most releases will fit into this category." msgstr "" #: DB:work_attribute_type_allowed_value/value:311 msgctxt "work_attribute_type_allowed_value" msgid "Araban" msgstr "" #: DB:work_attribute_type_allowed_value/value:312 msgctxt "work_attribute_type_allowed_value" msgid "Arabanbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:313 msgctxt "work_attribute_type_allowed_value" msgid "Arabankurdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:314 msgctxt "work_attribute_type_allowed_value" msgid "Aram-ı Can" msgstr "" #: DB:work_attribute_type_allowed_value/value:315 msgctxt "work_attribute_type_allowed_value" msgid "Aram-ı Dil" msgstr "" #: DB:work_attribute_type_allowed_value/value:604 msgctxt "work_attribute_type_allowed_value" msgid "Aranağme" msgstr "" #: DB:work_attribute_type_allowed_value/value:316 msgctxt "work_attribute_type_allowed_value" msgid "Arazbar" msgstr "" #: DB:work_attribute_type_allowed_value/value:317 msgctxt "work_attribute_type_allowed_value" msgid "Arazbar Zemzeme" msgstr "" #: DB:work_attribute_type_allowed_value/value:318 msgctxt "work_attribute_type_allowed_value" msgid "Arazbarbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:319 msgctxt "work_attribute_type_allowed_value" msgid "Arazbarek" msgstr "" #: DB:editor_collection_type/name:7 msgctxt "collection_type" msgid "Area" msgstr "" #: DB:area_alias_type/name:1 msgctxt "alias_type" msgid "Area name" msgstr "områdenavn" #: DB:work_type/name:1 msgctxt "work_type" msgid "Aria" msgstr "arie" #: DB:editor_collection_type/name:8 msgctxt "collection_type" msgid "Artist" msgstr "" #: DB:artist_alias_type/name:1 msgctxt "alias_type" msgid "Artist name" msgstr "kunstnernavn" #: DB:work_attribute_type_allowed_value/value:693 msgctxt "work_attribute_type_allowed_value" msgid "Artık Aksaksemai" msgstr "" #: DB:work_attribute_type_allowed_value/value:44 msgctxt "work_attribute_type_allowed_value" msgid "Asāvēri" msgstr "Asāvēri" #: DB:editor_collection_type/name:5 msgctxt "collection_type" msgid "Attending" msgstr "" #: DB:work_type/name:25 msgctxt "work_type" msgid "Audio drama" msgstr "" #: DB:release_group_secondary_type/name:5 msgctxt "release_group_secondary_type" msgid "Audiobook" msgstr "lydbog" #: DB:series_ordering_type/name:1 msgctxt "series_ordering_type" msgid "Automatic" msgstr "" #: DB:work_attribute_type_allowed_value/value:694 msgctxt "work_attribute_type_allowed_value" msgid "Aydın" msgstr "" #: DB:work_attribute_type_allowed_value/value:695 msgctxt "work_attribute_type_allowed_value" msgid "Ayin Devr-i Revanı" msgstr "" #: DB:work_attribute_type_allowed_value/value:605 msgctxt "work_attribute_type_allowed_value" msgid "Ayin-i Şerif" msgstr "" #: DB:work_attribute_type_allowed_value/value:696 msgctxt "work_attribute_type_allowed_value" msgid "Azeri Yürüksemai" msgstr "" #: DB:work_attribute_type_allowed_value/value:682 msgctxt "work_attribute_type_allowed_value" msgid "Ağır Aksaksemai" msgstr "" #: DB:work_attribute_type_allowed_value/value:683 msgctxt "work_attribute_type_allowed_value" msgid "Ağıraksak" msgstr "" #: DB:work_attribute_type_allowed_value/value:684 msgctxt "work_attribute_type_allowed_value" msgid "Ağıraydın" msgstr "" #: DB:work_attribute_type_allowed_value/value:686 msgctxt "work_attribute_type_allowed_value" msgid "Ağırdarbıfetih" msgstr "" #: DB:work_attribute_type_allowed_value/value:687 msgctxt "work_attribute_type_allowed_value" msgid "Ağırdüyek" msgstr "" #: DB:work_attribute_type_allowed_value/value:688 msgctxt "work_attribute_type_allowed_value" msgid "Ağırevfer" msgstr "" #: DB:work_attribute_type_allowed_value/value:603 msgctxt "work_attribute_type_allowed_value" msgid "Ağırsemai" msgstr "" #: DB:work_attribute_type_allowed_value/value:689 msgctxt "work_attribute_type_allowed_value" msgid "Ağırsenginsemai" msgstr "" #: DB:work_attribute_type_allowed_value/value:685 msgctxt "work_attribute_type_allowed_value" msgid "Ağırçenber" msgstr "" #: DB:work_attribute_type_allowed_value/value:321 msgctxt "work_attribute_type_allowed_value" msgid "Aşiran Maye" msgstr "" #: DB:work_attribute_type_allowed_value/value:320 msgctxt "work_attribute_type_allowed_value" msgid "Aşiran Zemzeme" msgstr "" #: DB:work_attribute_type_allowed_value/value:322 msgctxt "work_attribute_type_allowed_value" msgid "Aşkefza" msgstr "" #: DB:work_attribute_type_allowed_value/value:45 msgctxt "work_attribute_type_allowed_value" msgid "Aṭāna" msgstr "Aṭāna" #: DB:work_attribute_type_allowed_value/value:289 msgctxt "work_attribute_type_allowed_value" msgid "Aṭṭa" msgstr "" #: DB:work_attribute_type_allowed_value/value:795 msgctxt "work_attribute_type_allowed_value" msgid "B Dorian" msgstr "" #: DB:work_attribute_type_allowed_value/value:807 msgctxt "work_attribute_type_allowed_value" msgid "B Mixolydian" msgstr "" #: DB:work_attribute_type_allowed_value/value:33 msgctxt "work_attribute_type_allowed_value" msgid "B major" msgstr "H-dur" #: DB:work_attribute_type_allowed_value/value:34 msgctxt "work_attribute_type_allowed_value" msgid "B minor" msgstr "H-mol" #: DB:work_attribute_type_allowed_value/value:31 msgctxt "work_attribute_type_allowed_value" msgid "B-flat major" msgstr "" #: DB:work_attribute_type_allowed_value/value:32 msgctxt "work_attribute_type_allowed_value" msgid "B-flat minor" msgstr "" #: DB:work_attribute_type/name:7 msgctxt "work_attribute_type" msgid "BMI ID" msgstr "BMI-id" #: DB:cover_art_archive.art_type/name:2 msgctxt "cover_art_type" msgid "Back" msgstr "bagside" #: DB:work_attribute_type_allowed_value/value:323 msgctxt "work_attribute_type_allowed_value" msgid "Bahr-ı Nazik" msgstr "" #: DB:work_attribute_type_allowed_value/value:47 msgctxt "work_attribute_type_allowed_value" msgid "Bahudāri" msgstr "Bahudāri" #: DB:work_attribute_type_allowed_value/value:48 msgctxt "work_attribute_type_allowed_value" msgid "Balahaṁsa" msgstr "Balahaṁsa" #: DB:work_type/name:2 msgctxt "work_type" msgid "Ballet" msgstr "ballet" #: DB:work_attribute_type_allowed_value/value:49 msgctxt "work_attribute_type_allowed_value" msgid "Bauḷi" msgstr "Bauḷi" #: DB:work_attribute_type_allowed_value/value:51 msgctxt "work_attribute_type_allowed_value" msgid "Behāg" msgstr "Behāg" #: DB:work_type/name:26 msgctxt "work_type" msgid "Beijing opera" msgstr "" #: DB:work_type/description:26 msgctxt "work_type" msgid "" "Beijing opera is a form of traditional Chinese theatre which combines music," " vocal performance, mime, dance, and acrobatics." msgstr "" #: DB:work_attribute_type_allowed_value/value:697 msgctxt "work_attribute_type_allowed_value" msgid "Bektaşi Devr-i Revanı" msgstr "" #: DB:work_attribute_type_allowed_value/value:698 msgctxt "work_attribute_type_allowed_value" msgid "Bektaşi Raksanı" msgstr "" #: DB:work_attribute_type_allowed_value/value:699 msgctxt "work_attribute_type_allowed_value" msgid "Bektaşi Raksı" msgstr "" #: DB:work_attribute_type_allowed_value/value:700 msgctxt "work_attribute_type_allowed_value" msgid "Bektaşi Raksı Evferi" msgstr "" #: DB:work_attribute_type_allowed_value/value:324 msgctxt "work_attribute_type_allowed_value" msgid "Bend-i Hisar" msgstr "" #: DB:work_attribute_type_allowed_value/value:701 msgctxt "work_attribute_type_allowed_value" msgid "Berefşan" msgstr "" #: DB:work_attribute_type_allowed_value/value:606 msgctxt "work_attribute_type_allowed_value" msgid "Beste" msgstr "" #: DB:work_attribute_type_allowed_value/value:702 msgctxt "work_attribute_type_allowed_value" msgid "Beste Devr-i Revanı" msgstr "" #: DB:work_attribute_type_allowed_value/value:325 msgctxt "work_attribute_type_allowed_value" msgid "Beste Hicaz" msgstr "" #: DB:work_attribute_type_allowed_value/value:326 msgctxt "work_attribute_type_allowed_value" msgid "Beste Hisar" msgstr "" #: DB:work_attribute_type_allowed_value/value:327 msgctxt "work_attribute_type_allowed_value" msgid "Beste Isfahan" msgstr "" #: DB:work_attribute_type_allowed_value/value:328 msgctxt "work_attribute_type_allowed_value" msgid "Bestenigar" msgstr "" #: DB:medium_format/name:24 msgctxt "medium_format" msgid "Betamax" msgstr "Betamax" #: DB:work_attribute_type_allowed_value/value:329 msgctxt "work_attribute_type_allowed_value" msgid "Beyati" msgstr "" #: DB:work_attribute_type_allowed_value/value:331 msgctxt "work_attribute_type_allowed_value" msgid "Beyati Araban" msgstr "" #: DB:work_attribute_type_allowed_value/value:332 msgctxt "work_attribute_type_allowed_value" msgid "Beyati Arabanbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:333 msgctxt "work_attribute_type_allowed_value" msgid "Beyati Arabankurdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:330 msgctxt "work_attribute_type_allowed_value" msgid "Beyati Ruy-i Acem" msgstr "" #: DB:work_attribute_type_allowed_value/value:334 msgctxt "work_attribute_type_allowed_value" msgid "Beyatibuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:335 msgctxt "work_attribute_type_allowed_value" msgid "Bezm-i Tarab" msgstr "" #: DB:work_attribute_type_allowed_value/value:52 msgctxt "work_attribute_type_allowed_value" msgid "Bhairavi" msgstr "Bhairavi" #: DB:work_attribute_type_allowed_value/value:53 msgctxt "work_attribute_type_allowed_value" msgid "Bhavāni" msgstr "Bhavāni" #: DB:work_attribute_type_allowed_value/value:54 msgctxt "work_attribute_type_allowed_value" msgid "Bhāvapriya" msgstr "Bhāvapriya" #: DB:work_attribute_type_allowed_value/value:55 msgctxt "work_attribute_type_allowed_value" msgid "Bhīmpalāsi" msgstr "Bhīmpalāsi" #: DB:work_attribute_type_allowed_value/value:56 msgctxt "work_attribute_type_allowed_value" msgid "Bhōga sāvēri" msgstr "Bhōga sāvēri" #: DB:work_attribute_type_allowed_value/value:57 msgctxt "work_attribute_type_allowed_value" msgid "Bhūpāḷaṁ" msgstr "Bhūpāḷaṁ" #: DB:work_attribute_type_allowed_value/value:58 msgctxt "work_attribute_type_allowed_value" msgid "Bhūṣāvaḷi" msgstr "Bhūṣāvaḷi" #: DB:work_attribute_type_allowed_value/value:59 msgctxt "work_attribute_type_allowed_value" msgid "Bilahari" msgstr "Bilahari" #: DB:medium_format/name:20 msgctxt "medium_format" msgid "Blu-ray" msgstr "Blu-ray" #: DB:medium_format/name:35 msgctxt "medium_format" msgid "Blu-spec CD" msgstr "Blu-spec CD" #: DB:release_packaging/name:9 msgctxt "release_packaging" msgid "Book" msgstr "bog" #: DB:cover_art_archive.art_type/name:3 msgctxt "cover_art_type" msgid "Booklet" msgstr "" #: DB:release_status/name:3 msgctxt "release_status" msgid "Bootleg" msgstr "" #: DB:label_type/name:5 msgctxt "label_type" msgid "Bootleg Production" msgstr "" #: DB:work_attribute_type_allowed_value/value:607 msgctxt "work_attribute_type_allowed_value" msgid "Bozlak" msgstr "" #: DB:release_group_primary_type/name:12 msgctxt "release_group_primary_type" msgid "Broadcast" msgstr "" #: DB:work_attribute_type_allowed_value/value:62 msgctxt "work_attribute_type_allowed_value" msgid "Budamanōhari" msgstr "Budamanōhari" #: DB:work_attribute_type_allowed_value/value:703 msgctxt "work_attribute_type_allowed_value" msgid "Bulgar Darbı" msgstr "" #: DB:work_attribute_type_allowed_value/value:336 msgctxt "work_attribute_type_allowed_value" msgid "Buselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:337 msgctxt "work_attribute_type_allowed_value" msgid "Buselikaşiran" msgstr "" #: DB:work_attribute_type_allowed_value/value:338 msgctxt "work_attribute_type_allowed_value" msgid "Büzürk" msgstr "" #: DB:work_attribute_type_allowed_value/value:46 msgctxt "work_attribute_type_allowed_value" msgid "Bāgēśrī" msgstr "Bāgēśrī" #: DB:work_attribute_type_allowed_value/value:50 msgctxt "work_attribute_type_allowed_value" msgid "Bēgaḍa" msgstr "Bēgaḍa" #: DB:work_attribute_type_allowed_value/value:60 msgctxt "work_attribute_type_allowed_value" msgid "Bṛndāvana sāranga" msgstr "Bṛndāvana sāranga" #: DB:work_attribute_type_allowed_value/value:61 msgctxt "work_attribute_type_allowed_value" msgid "Bṛndāvani" msgstr "Bṛndāvani" #: DB:work_attribute_type_allowed_value/value:789 msgctxt "work_attribute_type_allowed_value" msgid "C Dorian" msgstr "" #: DB:work_attribute_type_allowed_value/value:801 msgctxt "work_attribute_type_allowed_value" msgid "C Mixolydian" msgstr "" #: DB:work_attribute_type_allowed_value/value:2 msgctxt "work_attribute_type_allowed_value" msgid "C major" msgstr "C-dur" #: DB:work_attribute_type_allowed_value/value:3 msgctxt "work_attribute_type_allowed_value" msgid "C minor" msgstr "C-mol" #: DB:work_attribute_type_allowed_value/value:1 msgctxt "work_attribute_type_allowed_value" msgid "C-flat major" msgstr "Ces-dur" #: DB:work_attribute_type_allowed_value/value:4 msgctxt "work_attribute_type_allowed_value" msgid "C-sharp major" msgstr "Cis-dur" #: DB:work_attribute_type_allowed_value/value:5 msgctxt "work_attribute_type_allowed_value" msgid "C-sharp minor" msgstr "Cis-mol" #: DB:work_attribute_type/name:19 msgctxt "work_attribute_type" msgid "CASH ID" msgstr "" #: DB:work_attribute_type/name:22 msgctxt "work_attribute_type" msgid "CCLI ID" msgstr "" #: DB:medium_format/name:1 msgctxt "medium_format" msgid "CD" msgstr "CD" #: DB:medium_format/name:39 msgctxt "medium_format" msgid "CD+G" msgstr "CD+G" #: DB:medium_format/name:33 msgctxt "medium_format" msgid "CD-R" msgstr "CD-R" #: DB:medium_format/name:41 msgctxt "medium_format" msgid "CDV" msgstr "" #: DB:medium_format/name:60 msgctxt "medium_format" msgid "CED" msgstr "" #: DB:work_attribute_type_allowed_value/value:63 msgctxt "work_attribute_type_allowed_value" msgid "Cakravākaṁ" msgstr "Cakravākaṁ" #: DB:work_attribute_type_allowed_value/value:64 msgctxt "work_attribute_type_allowed_value" msgid "Candrajyōti" msgstr "Candrajyōti" #: DB:work_attribute_type_allowed_value/value:65 msgctxt "work_attribute_type_allowed_value" msgid "Candrakauns" msgstr "Candrakauns" #: DB:work_attribute_type_allowed_value/value:339 msgctxt "work_attribute_type_allowed_value" msgid "Canfeza" msgstr "" #: DB:work_type/name:3 msgctxt "work_type" msgid "Cantata" msgstr "kantate" #: DB:release_packaging/name:4 msgctxt "release_packaging" msgid "Cardboard/Paper Sleeve" msgstr "" #: DB:medium_format/name:9 msgctxt "medium_format" msgid "Cartridge" msgstr "" #: DB:work_attribute_type_allowed_value/value:66 msgctxt "work_attribute_type_allowed_value" msgid "Carturdaśa rāgamālika" msgstr "Carturdaśa rāgamālika" #: DB:medium_format/name:8 msgctxt "medium_format" msgid "Cassette" msgstr "kassette" #: DB:release_packaging/name:8 msgctxt "release_packaging" msgid "Cassette Case" msgstr "" #: DB:series_type/name:5 msgctxt "series_type" msgid "Catalogue" msgstr "" #: DB:work_attribute_type_allowed_value/value:291 msgctxt "work_attribute_type_allowed_value" msgid "Caturaśra-jāti jhaṁpe" msgstr "Caturaśra-jāti jhaṁpe" #: DB:work_attribute_type_allowed_value/value:70 msgctxt "work_attribute_type_allowed_value" msgid "Cencu kāmbhōji" msgstr "" #: DB:work_attribute_type_allowed_value/value:704 msgctxt "work_attribute_type_allowed_value" msgid "Cevher" msgstr "" #: DB:artist_type/name:4 msgctxt "artist_type" msgid "Character" msgstr "figur" #: DB:artist_type/name:6 msgctxt "artist_type" msgid "Choir" msgstr "kor" #: DB:work_attribute_type_allowed_value/value:71 msgctxt "work_attribute_type_allowed_value" msgid "Cintāmaṇi" msgstr "" #: DB:work_attribute_type_allowed_value/value:72 msgctxt "work_attribute_type_allowed_value" msgid "Cittaranjani" msgstr "" #: DB:area_type/name:3 msgctxt "area_type" msgid "City" msgstr "by" #: DB:area_type/description:3 msgctxt "area_type" msgid "" "City is used for settlements of any size, including towns and villages." msgstr "" #: DB:release_group_secondary_type/name:1 msgctxt "release_group_secondary_type" msgid "Compilation" msgstr "opsamling" #: DB:event_type/name:1 msgctxt "event_type" msgid "Concert" msgstr "" #: DB:work_type/name:4 msgctxt "work_type" msgid "Concerto" msgstr "koncert" #: DB:event_type/name:4 msgctxt "event_type" msgid "Convention/Expo" msgstr "" #: DB:medium_format/name:61 msgctxt "medium_format" msgid "Copy Control CD" msgstr "" #: DB:medium_format/description:61 msgctxt "medium_format" msgid "" "Copy Control CD (CCCD) is an umbrella term for CDs released circa 2001-2006 " "containing software that is ostensibly designed to prevent the CD from being" " ripped. There are a number of software variants: the most well-known are " "Macrovision's Cactus Data Shield (CDS) and SunnComm's MediaMax." msgstr "" #: DB:area_type/name:1 msgctxt "area_type" msgid "Country" msgstr "land" #: DB:area_type/description:1 msgctxt "area_type" msgid "" "Country is used for areas included (or previously included) in ISO 3166-1, " "e.g. United States." msgstr "" #: DB:area_type/name:7 msgctxt "area_type" msgid "County" msgstr "" #: DB:area_type/description:7 msgctxt "area_type" msgid "" "County is used for smaller administrative divisions of a country which are " "not the main administrative divisions but are also not municipalities, e.g. " "counties in the USA. These are not considered when displaying the parent " "areas for a given area." msgstr "" #: DB:work_attribute_type_allowed_value/value:608 msgctxt "work_attribute_type_allowed_value" msgid "Cumhurilahi" msgstr "" #: DB:work_attribute_type_allowed_value/value:705 msgctxt "work_attribute_type_allowed_value" msgid "Curcuna" msgstr "" #: DB:work_attribute_type_allowed_value/value:67 msgctxt "work_attribute_type_allowed_value" msgid "Cārukēśi" msgstr "" #: DB:work_attribute_type_allowed_value/value:68 msgctxt "work_attribute_type_allowed_value" msgid "Cāyānāṭa" msgstr "" #: DB:work_attribute_type_allowed_value/value:69 msgctxt "work_attribute_type_allowed_value" msgid "Cāyātarangiṇi" msgstr "Cāyātarangiṇi" #: DB:work_attribute_type_allowed_value/value:790 msgctxt "work_attribute_type_allowed_value" msgid "D Dorian" msgstr "" #: DB:work_attribute_type_allowed_value/value:802 msgctxt "work_attribute_type_allowed_value" msgid "D Mixolydian" msgstr "" #: DB:work_attribute_type_allowed_value/value:8 msgctxt "work_attribute_type_allowed_value" msgid "D major" msgstr "D-dur" #: DB:work_attribute_type_allowed_value/value:9 msgctxt "work_attribute_type_allowed_value" msgid "D minor" msgstr "D-mol" #: DB:work_attribute_type_allowed_value/value:6 msgctxt "work_attribute_type_allowed_value" msgid "D-flat major" msgstr "Dis-dur" #: DB:work_attribute_type_allowed_value/value:7 msgctxt "work_attribute_type_allowed_value" msgid "D-flat minor" msgstr "Des-mol" #: DB:work_attribute_type_allowed_value/value:10 msgctxt "work_attribute_type_allowed_value" msgid "D-sharp minor" msgstr "Dis-mol" #: DB:medium_format/name:11 msgctxt "medium_format" msgid "DAT" msgstr "DAT" #: DB:medium_format/name:16 msgctxt "medium_format" msgid "DCC" msgstr "DCC" #: DB:release_group_secondary_type/name:8 msgctxt "release_group_secondary_type" msgid "DJ-mix" msgstr "DJ-mix" #: DB:medium_format/name:44 msgctxt "medium_format" msgid "DTS CD" msgstr "" #: DB:medium_format/name:2 msgctxt "medium_format" msgid "DVD" msgstr "DVD" #: DB:medium_format/name:18 msgctxt "medium_format" msgid "DVD-Audio" msgstr "" #: DB:medium_format/name:19 msgctxt "medium_format" msgid "DVD-Video" msgstr "" #: DB:medium_format/name:47 msgctxt "medium_format" msgid "DVDplus" msgstr "" #: DB:medium_format/name:70 msgctxt "medium_format" msgid "DVDplus (CD side)" msgstr "" #: DB:medium_format/name:68 msgctxt "medium_format" msgid "DVDplus (DVD-Audio side)" msgstr "" #: DB:medium_format/name:69 msgctxt "medium_format" msgid "DVDplus (DVD-Video side)" msgstr "" #: DB:work_attribute_type_allowed_value/value:343 msgctxt "work_attribute_type_allowed_value" msgid "Danişveran" msgstr "" #: DB:work_attribute_type_allowed_value/value:711 msgctxt "work_attribute_type_allowed_value" msgid "Darb" msgstr "" #: DB:work_attribute_type_allowed_value/value:712 msgctxt "work_attribute_type_allowed_value" msgid "Darb-ı Fetih" msgstr "" #: DB:work_attribute_type_allowed_value/value:713 msgctxt "work_attribute_type_allowed_value" msgid "Darb-ı Hüner" msgstr "" #: DB:work_attribute_type_allowed_value/value:714 msgctxt "work_attribute_type_allowed_value" msgid "Darb-ı Kürdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:715 msgctxt "work_attribute_type_allowed_value" msgid "Darb-ı Türki" msgstr "" #: DB:work_attribute_type_allowed_value/value:796 msgctxt "work_attribute_type_allowed_value" msgid "Darbeyn" msgstr "" #: DB:work_attribute_type_allowed_value/value:73 msgctxt "work_attribute_type_allowed_value" msgid "Darbār" msgstr "Darbār" #: DB:work_attribute_type_allowed_value/value:74 msgctxt "work_attribute_type_allowed_value" msgid "Darbārī kānaḍa" msgstr "Darbārī kānaḍa" #: DB:medium_format/name:43 msgctxt "medium_format" msgid "Data CD" msgstr "" #: DB:release_group_secondary_type/name:10 msgctxt "release_group_secondary_type" msgid "Demo" msgstr "demo" #: DB:work_attribute_type_allowed_value/value:610 msgctxt "work_attribute_type_allowed_value" msgid "Destan" msgstr "" #: DB:work_attribute_type_allowed_value/value:716 msgctxt "work_attribute_type_allowed_value" msgid "Devr-i Aryan" msgstr "" #: DB:work_attribute_type_allowed_value/value:717 msgctxt "work_attribute_type_allowed_value" msgid "Devr-i Hindi" msgstr "" #: DB:work_attribute_type_allowed_value/value:718 msgctxt "work_attribute_type_allowed_value" msgid "Devr-i Hindi II" msgstr "" #: DB:work_attribute_type_allowed_value/value:719 msgctxt "work_attribute_type_allowed_value" msgid "Devr-i Kebir" msgstr "" #: DB:work_attribute_type_allowed_value/value:720 msgctxt "work_attribute_type_allowed_value" msgid "Devr-i Revan" msgstr "" #: DB:work_attribute_type_allowed_value/value:721 msgctxt "work_attribute_type_allowed_value" msgid "Devr-i Revan-ı Hindi" msgstr "" #: DB:work_attribute_type_allowed_value/value:722 msgctxt "work_attribute_type_allowed_value" msgid "Devr-i Süreyya" msgstr "" #: DB:work_attribute_type_allowed_value/value:723 msgctxt "work_attribute_type_allowed_value" msgid "Devr-i Süreyya Sofyanı" msgstr "" #: DB:work_attribute_type_allowed_value/value:724 msgctxt "work_attribute_type_allowed_value" msgid "Devr-i Turan" msgstr "" #: DB:work_attribute_type_allowed_value/value:797 msgctxt "work_attribute_type_allowed_value" msgid "Devr-i Turan II" msgstr "" #: DB:work_attribute_type_allowed_value/value:79 msgctxt "work_attribute_type_allowed_value" msgid "Devāmṛtavarṣiṇi" msgstr "Devāmṛtavarṣiṇi" #: DB:work_attribute_type_allowed_value/value:80 msgctxt "work_attribute_type_allowed_value" msgid "Dhanaśrī" msgstr "Dhanaśrī" #: DB:work_attribute_type_allowed_value/value:81 msgctxt "work_attribute_type_allowed_value" msgid "Dhanyāsi" msgstr "Dhanyāsi" #: DB:work_attribute_type_allowed_value/value:82 msgctxt "work_attribute_type_allowed_value" msgid "Dharmāvati" msgstr "Dharmāvati" #: DB:work_attribute_type_allowed_value/value:285 msgctxt "work_attribute_type_allowed_value" msgid "Dhr̥va" msgstr "Dhr̥va" #: DB:work_attribute_type_allowed_value/value:83 msgctxt "work_attribute_type_allowed_value" msgid "Dhēnuka" msgstr "Dhēnuka" #: DB:release_packaging/name:3 msgctxt "release_packaging" msgid "Digipak" msgstr "" #: DB:medium_format/name:12 msgctxt "medium_format" msgid "Digital Media" msgstr "digitalt medie" #: DB:work_attribute_type_allowed_value/value:345 msgctxt "work_attribute_type_allowed_value" msgid "Dil Efruz" msgstr "" #: DB:work_attribute_type_allowed_value/value:344 msgctxt "work_attribute_type_allowed_value" msgid "Dildar" msgstr "" #: DB:work_attribute_type_allowed_value/value:348 msgctxt "work_attribute_type_allowed_value" msgid "Dilkeside" msgstr "" #: DB:work_attribute_type_allowed_value/value:346 msgctxt "work_attribute_type_allowed_value" msgid "Dilkeş" msgstr "" #: DB:work_attribute_type_allowed_value/value:347 msgctxt "work_attribute_type_allowed_value" msgid "Dilkeşhaveran" msgstr "" #: DB:work_attribute_type_allowed_value/value:349 msgctxt "work_attribute_type_allowed_value" msgid "Dilküşa" msgstr "" #: DB:work_attribute_type_allowed_value/value:350 msgctxt "work_attribute_type_allowed_value" msgid "Dilnevaz" msgstr "" #: DB:work_attribute_type_allowed_value/value:351 msgctxt "work_attribute_type_allowed_value" msgid "Dilnişin" msgstr "" #: DB:release_packaging/name:13 msgctxt "release_packaging" msgid "Discbox Slider" msgstr "" #: DB:label_type/name:1 msgctxt "label_type" msgid "Distributor" msgstr "distributør" #: DB:area_type/name:5 msgctxt "area_type" msgid "District" msgstr "distrikt" #: DB:area_type/description:5 msgctxt "area_type" msgid "District is used for a division of a large city, e.g. Queens." msgstr "" #: DB:work_attribute_type_allowed_value/value:611 msgctxt "work_attribute_type_allowed_value" msgid "Divan" msgstr "" #: DB:work_attribute_type_allowed_value/value:725 msgctxt "work_attribute_type_allowed_value" msgid "Dolap" msgstr "" #: DB:medium_format/name:4 msgctxt "medium_format" msgid "DualDisc" msgstr "DualDisc" #: DB:medium_format/name:67 msgctxt "medium_format" msgid "DualDisc (CD side)" msgstr "" #: DB:medium_format/name:65 msgctxt "medium_format" msgid "DualDisc (DVD-Audio side)" msgstr "" #: DB:medium_format/name:66 msgctxt "medium_format" msgid "DualDisc (DVD-Video side)" msgstr "" #: DB:work_attribute_type_allowed_value/value:612 msgctxt "work_attribute_type_allowed_value" msgid "Durak" msgstr "" #: DB:work_attribute_type_allowed_value/value:726 msgctxt "work_attribute_type_allowed_value" msgid "Durak Evferi" msgstr "" #: DB:work_attribute_type_allowed_value/value:86 msgctxt "work_attribute_type_allowed_value" msgid "Durga" msgstr "Durga" #: DB:work_attribute_type_allowed_value/value:87 msgctxt "work_attribute_type_allowed_value" msgid "Dvijāvanti" msgstr "Dvijāvanti" #: DB:work_attribute_type_allowed_value/value:352 msgctxt "work_attribute_type_allowed_value" msgid "Dügah" msgstr "" #: DB:work_attribute_type_allowed_value/value:354 msgctxt "work_attribute_type_allowed_value" msgid "Dügah Maye" msgstr "" #: DB:work_attribute_type_allowed_value/value:353 msgctxt "work_attribute_type_allowed_value" msgid "Dügahbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:727 msgctxt "work_attribute_type_allowed_value" msgid "Dümen" msgstr "" #: DB:work_attribute_type_allowed_value/value:728 msgctxt "work_attribute_type_allowed_value" msgid "Düyek" msgstr "" #: DB:work_attribute_type_allowed_value/value:355 msgctxt "work_attribute_type_allowed_value" msgid "Düşems" msgstr "" #: DB:work_attribute_type_allowed_value/value:76 msgctxt "work_attribute_type_allowed_value" msgid "Dēvagāndhāri" msgstr "Dēvagāndhāri" #: DB:work_attribute_type_allowed_value/value:77 msgctxt "work_attribute_type_allowed_value" msgid "Dēvakriya" msgstr "Dēvakriya" #: DB:work_attribute_type_allowed_value/value:78 msgctxt "work_attribute_type_allowed_value" msgid "Dēvamanōhari" msgstr "Dēvamanōhari" #: DB:work_attribute_type_allowed_value/value:75 msgctxt "work_attribute_type_allowed_value" msgid "Dēś" msgstr "Dēś" #: DB:work_attribute_type_allowed_value/value:292 msgctxt "work_attribute_type_allowed_value" msgid "Dēśādi" msgstr "Dēśādi" #: DB:work_attribute_type_allowed_value/value:84 msgctxt "work_attribute_type_allowed_value" msgid "Dīpakaṁ" msgstr "Dīpakaṁ" #: DB:work_attribute_type_allowed_value/value:85 msgctxt "work_attribute_type_allowed_value" msgid "Dīpāḷi" msgstr "Dīpāḷi" #: DB:work_attribute_type_allowed_value/value:791 msgctxt "work_attribute_type_allowed_value" msgid "E Dorian" msgstr "" #: DB:work_attribute_type_allowed_value/value:803 msgctxt "work_attribute_type_allowed_value" msgid "E Mixolydian" msgstr "" #: DB:work_attribute_type_allowed_value/value:13 msgctxt "work_attribute_type_allowed_value" msgid "E major" msgstr "E-dur" #: DB:work_attribute_type_allowed_value/value:14 msgctxt "work_attribute_type_allowed_value" msgid "E minor" msgstr "E-mol" #: DB:work_attribute_type_allowed_value/value:11 msgctxt "work_attribute_type_allowed_value" msgid "E-flat major" msgstr "Es-dur" #: DB:work_attribute_type_allowed_value/value:12 msgctxt "work_attribute_type_allowed_value" msgid "E-flat minor" msgstr "Es-mol" #: DB:work_attribute_type_allowed_value/value:15 msgctxt "work_attribute_type_allowed_value" msgid "E-sharp minor" msgstr "Eis-mol" #: DB:release_group_primary_type/name:3 msgctxt "release_group_primary_type" msgid "EP" msgstr "EP" #: DB:medium_format/name:50 msgctxt "medium_format" msgid "Edison Diamond Disc" msgstr "" #: DB:instrument_type/name:4 msgctxt "instrument_type" msgid "Electronic instrument" msgstr "" #: DB:medium_format/name:42 msgctxt "medium_format" msgid "Enhanced CD" msgstr "" #: DB:work_attribute_type_allowed_value/value:356 msgctxt "work_attribute_type_allowed_value" msgid "Eski Sipihr" msgstr "" #: DB:work_attribute_type_allowed_value/value:613 msgctxt "work_attribute_type_allowed_value" msgid "Etüd" msgstr "" #: DB:work_attribute_type_allowed_value/value:357 msgctxt "work_attribute_type_allowed_value" msgid "Evcara" msgstr "" #: DB:editor_collection_type/name:4 msgctxt "collection_type" msgid "Event" msgstr "" #: DB:series_type/name:6 msgctxt "series_type" msgid "Event" msgstr "" #: DB:event_alias_type/name:1 msgctxt "alias_type" msgid "Event name" msgstr "" #: DB:work_attribute_type_allowed_value/value:729 msgctxt "work_attribute_type_allowed_value" msgid "Evfer" msgstr "" #: DB:work_attribute_type_allowed_value/value:358 msgctxt "work_attribute_type_allowed_value" msgid "Eviç" msgstr "" #: DB:work_attribute_type_allowed_value/value:359 msgctxt "work_attribute_type_allowed_value" msgid "Eviç Bahr-i Nazik" msgstr "" #: DB:work_attribute_type_allowed_value/value:360 msgctxt "work_attribute_type_allowed_value" msgid "Eviç Huzi" msgstr "" #: DB:work_attribute_type_allowed_value/value:364 msgctxt "work_attribute_type_allowed_value" msgid "Eviç Maye" msgstr "" #: DB:work_attribute_type_allowed_value/value:361 msgctxt "work_attribute_type_allowed_value" msgid "Eviç Ruy-i Neva" msgstr "" #: DB:work_attribute_type_allowed_value/value:365 msgctxt "work_attribute_type_allowed_value" msgid "Eviç Saba" msgstr "" #: DB:work_attribute_type_allowed_value/value:366 msgctxt "work_attribute_type_allowed_value" msgid "Eviç Şevk" msgstr "" #: DB:work_attribute_type_allowed_value/value:362 msgctxt "work_attribute_type_allowed_value" msgid "Eviçbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:363 msgctxt "work_attribute_type_allowed_value" msgid "Eviçkürdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:730 msgctxt "work_attribute_type_allowed_value" msgid "Evsat" msgstr "" #: DB:work_attribute_type_allowed_value/value:614 msgctxt "work_attribute_type_allowed_value" msgid "Ezan" msgstr "" #: DB:work_attribute_type_allowed_value/value:792 msgctxt "work_attribute_type_allowed_value" msgid "F Dorian" msgstr "" #: DB:work_attribute_type_allowed_value/value:804 msgctxt "work_attribute_type_allowed_value" msgid "F Mixolydian" msgstr "" #: DB:work_attribute_type_allowed_value/value:17 msgctxt "work_attribute_type_allowed_value" msgid "F major" msgstr "F-dur" #: DB:work_attribute_type_allowed_value/value:18 msgctxt "work_attribute_type_allowed_value" msgid "F minor" msgstr "F-mol" #: DB:work_attribute_type_allowed_value/value:16 msgctxt "work_attribute_type_allowed_value" msgid "F-flat major" msgstr "Fes-dur" #: DB:work_attribute_type_allowed_value/value:19 msgctxt "work_attribute_type_allowed_value" msgid "F-sharp major" msgstr "Fis-dur" #: DB:work_attribute_type_allowed_value/value:20 msgctxt "work_attribute_type_allowed_value" msgid "F-sharp minor" msgstr "Fis-mol" #: DB:work_attribute_type_allowed_value/value:731 msgctxt "work_attribute_type_allowed_value" msgid "Fahte" msgstr "" #: DB:work_attribute_type_allowed_value/value:615 msgctxt "work_attribute_type_allowed_value" msgid "Fantezi" msgstr "" #: DB:release_packaging/name:10 msgctxt "release_packaging" msgid "Fatbox" msgstr "" #: DB:gender/name:2 msgctxt "gender" msgid "Female" msgstr "" #: DB:work_attribute_type_allowed_value/value:732 msgctxt "work_attribute_type_allowed_value" msgid "Fer" msgstr "" #: DB:work_attribute_type_allowed_value/value:733 msgctxt "work_attribute_type_allowed_value" msgid "Fer'i Muhammes" msgstr "" #: DB:work_attribute_type_allowed_value/value:367 msgctxt "work_attribute_type_allowed_value" msgid "Ferahfeza" msgstr "" #: DB:work_attribute_type_allowed_value/value:368 msgctxt "work_attribute_type_allowed_value" msgid "Ferahnak" msgstr "" #: DB:work_attribute_type_allowed_value/value:369 msgctxt "work_attribute_type_allowed_value" msgid "Ferahnakaşiran" msgstr "" #: DB:work_attribute_type_allowed_value/value:370 msgctxt "work_attribute_type_allowed_value" msgid "Ferahnüma" msgstr "" #: DB:work_attribute_type_allowed_value/value:371 msgctxt "work_attribute_type_allowed_value" msgid "Ferahzad" msgstr "" #: DB:event_type/name:2 msgctxt "event_type" msgid "Festival" msgstr "" #: DB:series_type/name:8 msgctxt "series_type" msgid "Festival" msgstr "" #: DB:work_attribute_type_allowed_value/value:372 msgctxt "work_attribute_type_allowed_value" msgid "Feth-i Bağdad" msgstr "" #: DB:work_attribute_type_allowed_value/value:373 msgctxt "work_attribute_type_allowed_value" msgid "Feth-i Belgrad" msgstr "" #: DB:work_attribute_type_allowed_value/value:374 msgctxt "work_attribute_type_allowed_value" msgid "Feth-i Dil" msgstr "" #: DB:work_attribute_type_allowed_value/value:375 msgctxt "work_attribute_type_allowed_value" msgid "Feth-i Hicaz" msgstr "" #: DB:work_attribute_type_allowed_value/value:734 msgctxt "work_attribute_type_allowed_value" msgid "Fireng-i Fer" msgstr "" #: DB:medium_format/name:51 msgctxt "medium_format" msgid "Flexi-disc" msgstr "" #: DB:medium_format/description:51 msgctxt "medium_format" msgid "" "Flexi-discs are phonograph records made of a thin, flexible vinyl sheet with" " a molded-in groove, designed to be playable on a normal phonograph " "turntable." msgstr "" #: DB:work_attribute_type/name:16 msgctxt "work_attribute_type" msgid "Form (Ottoman, Turkish)" msgstr "" #: DB:area_alias_type/name:2 msgctxt "alias_type" msgid "Formal name" msgstr "formelt navn" #: DB:work_attribute_type_allowed_value/value:735 msgctxt "work_attribute_type_allowed_value" msgid "Frengçin" msgstr "" #: DB:cover_art_archive.art_type/name:1 msgctxt "cover_art_type" msgid "Front" msgstr "forside" #: DB:work_attribute_type_allowed_value/value:793 msgctxt "work_attribute_type_allowed_value" msgid "G Dorian" msgstr "" #: DB:work_attribute_type_allowed_value/value:805 msgctxt "work_attribute_type_allowed_value" msgid "G Mixolydian" msgstr "" #: DB:work_attribute_type_allowed_value/value:22 msgctxt "work_attribute_type_allowed_value" msgid "G major" msgstr "G-dur" #: DB:work_attribute_type_allowed_value/value:23 msgctxt "work_attribute_type_allowed_value" msgid "G minor" msgstr "G-mol" #: DB:work_attribute_type_allowed_value/value:21 msgctxt "work_attribute_type_allowed_value" msgid "G-flat major" msgstr "Ges-dur" #: DB:work_attribute_type_allowed_value/value:24 msgctxt "work_attribute_type_allowed_value" msgid "G-sharp major" msgstr "Gis-dur" #: DB:work_attribute_type_allowed_value/value:25 msgctxt "work_attribute_type_allowed_value" msgid "G-sharp minor" msgstr "Gis-mol" #: DB:work_attribute_type/name:9 msgctxt "work_attribute_type" msgid "GEMA ID" msgstr "GEMA-id" #: DB:work_attribute_type_allowed_value/value:88 msgctxt "work_attribute_type_allowed_value" msgid "Gamakakriya" msgstr "Gamakakriya" #: DB:work_attribute_type_allowed_value/value:89 msgctxt "work_attribute_type_allowed_value" msgid "Gamakakriya/Pūrvīkaḷyāṇi" msgstr "Gamakakriya/Pūrvīkaḷyāṇi" #: DB:work_attribute_type_allowed_value/value:95 msgctxt "work_attribute_type_allowed_value" msgid "Garuḍadhvani" msgstr "Garuḍadhvani" #: DB:release_packaging/name:12 msgctxt "release_packaging" msgid "Gatefold Cover" msgstr "" #: DB:work_attribute_type_allowed_value/value:99 msgctxt "work_attribute_type_allowed_value" msgid "Gaurīmanōhari" msgstr "Gaurīmanōhari" #: DB:work_attribute_type_allowed_value/value:96 msgctxt "work_attribute_type_allowed_value" msgid "Gauḍa malhār" msgstr "Gauḍa malhār" #: DB:work_attribute_type_allowed_value/value:97 msgctxt "work_attribute_type_allowed_value" msgid "Gauḷa" msgstr "Gauḷa" #: DB:work_attribute_type_allowed_value/value:98 msgctxt "work_attribute_type_allowed_value" msgid "Gauḷipantu" msgstr "Gauḷipantu" #: DB:work_attribute_type_allowed_value/value:616 msgctxt "work_attribute_type_allowed_value" msgid "Gazel" msgstr "" #: DB:work_attribute_type_allowed_value/value:90 msgctxt "work_attribute_type_allowed_value" msgid "Gaṁbhīra nāṭa" msgstr "Gaṁbhīra nāṭa" #: DB:work_attribute_type_allowed_value/value:91 msgctxt "work_attribute_type_allowed_value" msgid "Gaṁbhīra vāṇi" msgstr "Gaṁbhīra vāṇi" #: DB:work_attribute_type_allowed_value/value:376 msgctxt "work_attribute_type_allowed_value" msgid "Gerdaniye" msgstr "" #: DB:work_attribute_type_allowed_value/value:377 msgctxt "work_attribute_type_allowed_value" msgid "Gerdaniyebuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:378 msgctxt "work_attribute_type_allowed_value" msgid "Gerdaniyekürdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:380 msgctxt "work_attribute_type_allowed_value" msgid "Geveşt" msgstr "" #: DB:work_attribute_type_allowed_value/value:379 msgctxt "work_attribute_type_allowed_value" msgid "Geştü Güzar-ı Bahar" msgstr "" #: DB:work_attribute_type_allowed_value/value:100 msgctxt "work_attribute_type_allowed_value" msgid "Ghanṭa" msgstr "Ghanṭa" #: DB:work_attribute_type_allowed_value/value:381 msgctxt "work_attribute_type_allowed_value" msgid "Gonca-i Rana" msgstr "" #: DB:artist_type/name:2 msgctxt "artist_type" msgid "Group" msgstr "gruppe" #: DB:work_attribute_type_allowed_value/value:102 msgctxt "work_attribute_type_allowed_value" msgid "Gurjāri" msgstr "Gurjāri" #: DB:work_attribute_type_allowed_value/value:382 msgctxt "work_attribute_type_allowed_value" msgid "Güldeste" msgstr "" #: DB:work_attribute_type_allowed_value/value:384 msgctxt "work_attribute_type_allowed_value" msgid "Gülizar" msgstr "" #: DB:work_attribute_type_allowed_value/value:385 msgctxt "work_attribute_type_allowed_value" msgid "Gülizarbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:386 msgctxt "work_attribute_type_allowed_value" msgid "Gülnari" msgstr "" #: DB:work_attribute_type_allowed_value/value:387 msgctxt "work_attribute_type_allowed_value" msgid "Gülruh" msgstr "" #: DB:work_attribute_type_allowed_value/value:383 msgctxt "work_attribute_type_allowed_value" msgid "Gülzar" msgstr "" #: DB:work_attribute_type_allowed_value/value:388 msgctxt "work_attribute_type_allowed_value" msgid "Gülşen-i Vefa" msgstr "" #: DB:work_attribute_type_allowed_value/value:617 msgctxt "work_attribute_type_allowed_value" msgid "Güvende" msgstr "" #: DB:work_attribute_type_allowed_value/value:92 msgctxt "work_attribute_type_allowed_value" msgid "Gānamūrti" msgstr "Gānamūrti" #: DB:work_attribute_type_allowed_value/value:93 msgctxt "work_attribute_type_allowed_value" msgid "Gānavāridhi" msgstr "Gānavāridhi" #: DB:work_attribute_type_allowed_value/value:94 msgctxt "work_attribute_type_allowed_value" msgid "Gāngēyabhūṣaṇi" msgstr "Gāngēyabhūṣaṇi" #: DB:work_attribute_type_allowed_value/value:101 msgctxt "work_attribute_type_allowed_value" msgid "Gōpikāvasantaṁ" msgstr "Gōpikāvasantaṁ" #: DB:medium_format/name:17 msgctxt "medium_format" msgid "HD-DVD" msgstr "HD-DVD" #: DB:medium_format/name:25 msgctxt "medium_format" msgid "HDCD" msgstr "HDCD" #: DB:medium_format/name:37 msgctxt "medium_format" msgid "HQCD" msgstr "HQCD" #: DB:work_attribute_type_allowed_value/value:736 msgctxt "work_attribute_type_allowed_value" msgid "Hafif" msgstr "" #: DB:work_attribute_type_allowed_value/value:618 msgctxt "work_attribute_type_allowed_value" msgid "Halk Türküsü" msgstr "" #: DB:work_attribute_type_allowed_value/value:104 msgctxt "work_attribute_type_allowed_value" msgid "Hamsadhvani" msgstr "Hamsadhvani" #: DB:work_attribute_type_allowed_value/value:107 msgctxt "work_attribute_type_allowed_value" msgid "Hamsavinōdini" msgstr "Hamsavinōdini" #: DB:work_attribute_type_allowed_value/value:103 msgctxt "work_attribute_type_allowed_value" msgid "Hamīr kaḷyaṇi" msgstr "Hamīr kaḷyaṇi" #: DB:work_attribute_type_allowed_value/value:108 msgctxt "work_attribute_type_allowed_value" msgid "Harikāmbhōji" msgstr "Harikāmbhōji" #: DB:work_attribute_type_allowed_value/value:389 msgctxt "work_attribute_type_allowed_value" msgid "Haver" msgstr "" #: DB:work_attribute_type_allowed_value/value:737 msgctxt "work_attribute_type_allowed_value" msgid "Havi" msgstr "" #: DB:work_attribute_type_allowed_value/value:105 msgctxt "work_attribute_type_allowed_value" msgid "Haṁsanādaṁ" msgstr "Haṁsanādaṁ" #: DB:work_attribute_type_allowed_value/value:106 msgctxt "work_attribute_type_allowed_value" msgid "Haṁsānandi" msgstr "Haṁsānandi" #: DB:work_attribute_type_allowed_value/value:390 msgctxt "work_attribute_type_allowed_value" msgid "Heftgah" msgstr "" #: DB:work_attribute_type_allowed_value/value:738 msgctxt "work_attribute_type_allowed_value" msgid "Hezeç" msgstr "" #: DB:work_attribute_type_allowed_value/value:391 msgctxt "work_attribute_type_allowed_value" msgid "Hicaz" msgstr "" #: DB:work_attribute_type_allowed_value/value:395 msgctxt "work_attribute_type_allowed_value" msgid "Hicaz Büzürk" msgstr "" #: DB:work_attribute_type_allowed_value/value:396 msgctxt "work_attribute_type_allowed_value" msgid "Hicaz Dilkeş" msgstr "" #: DB:work_attribute_type_allowed_value/value:398 msgctxt "work_attribute_type_allowed_value" msgid "Hicaz Hümayun" msgstr "" #: DB:work_attribute_type_allowed_value/value:409 msgctxt "work_attribute_type_allowed_value" msgid "Hicaz Uzzal" msgstr "" #: DB:work_attribute_type_allowed_value/value:392 msgctxt "work_attribute_type_allowed_value" msgid "Hicaz Zemzeme" msgstr "" #: DB:work_attribute_type_allowed_value/value:410 msgctxt "work_attribute_type_allowed_value" msgid "Hicaz Zirgüle" msgstr "" #: DB:work_attribute_type_allowed_value/value:393 msgctxt "work_attribute_type_allowed_value" msgid "Hicazaşiran" msgstr "" #: DB:work_attribute_type_allowed_value/value:394 msgctxt "work_attribute_type_allowed_value" msgid "Hicazbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:397 msgctxt "work_attribute_type_allowed_value" msgid "Hicazeyn" msgstr "" #: DB:work_attribute_type_allowed_value/value:399 msgctxt "work_attribute_type_allowed_value" msgid "Hicazi Acem" msgstr "" #: DB:work_attribute_type_allowed_value/value:400 msgctxt "work_attribute_type_allowed_value" msgid "Hicazi Irak" msgstr "" #: DB:work_attribute_type_allowed_value/value:401 msgctxt "work_attribute_type_allowed_value" msgid "Hicazi Muhalif" msgstr "" #: DB:work_attribute_type_allowed_value/value:402 msgctxt "work_attribute_type_allowed_value" msgid "Hicazi Rumi" msgstr "" #: DB:work_attribute_type_allowed_value/value:403 msgctxt "work_attribute_type_allowed_value" msgid "Hicazi Türki" msgstr "" #: DB:work_attribute_type_allowed_value/value:404 msgctxt "work_attribute_type_allowed_value" msgid "Hicazi Uşşak" msgstr "" #: DB:work_attribute_type_allowed_value/value:405 msgctxt "work_attribute_type_allowed_value" msgid "Hicazkar" msgstr "" #: DB:work_attribute_type_allowed_value/value:408 msgctxt "work_attribute_type_allowed_value" msgid "Hicazkar-Kürdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:407 msgctxt "work_attribute_type_allowed_value" msgid "Hicazkar-ı Kadim" msgstr "" #: DB:work_attribute_type_allowed_value/value:406 msgctxt "work_attribute_type_allowed_value" msgid "Hicazkarbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:112 msgctxt "work_attribute_type_allowed_value" msgid "Hindustān gāndhāri" msgstr "Hindustān gāndhāri" #: DB:work_attribute_type_allowed_value/value:110 msgctxt "work_attribute_type_allowed_value" msgid "Hindōḷa vasantaṁ" msgstr "Hindōḷa vasantaṁ" #: DB:work_attribute_type_allowed_value/value:111 msgctxt "work_attribute_type_allowed_value" msgid "Hindōḷaṁ" msgstr "Hindōḷaṁ" #: DB:work_attribute_type_allowed_value/value:411 msgctxt "work_attribute_type_allowed_value" msgid "Hisar" msgstr "Hisar" #: DB:work_attribute_type_allowed_value/value:412 msgctxt "work_attribute_type_allowed_value" msgid "Hisar Vech-i Şehnaz" msgstr "Hisar Vech-i Şehnaz" #: DB:work_attribute_type_allowed_value/value:413 msgctxt "work_attribute_type_allowed_value" msgid "Hisarbuselik" msgstr "Hisarbuselik" #: DB:label_type/name:2 msgctxt "label_type" msgid "Holding" msgstr "" #: DB:work_attribute_type_allowed_value/value:414 msgctxt "work_attribute_type_allowed_value" msgid "Horasan" msgstr "Horasan" #: DB:work_attribute_type_allowed_value/value:415 msgctxt "work_attribute_type_allowed_value" msgid "Horosani Beyati" msgstr "Horosani Beyati" #: DB:work_attribute_type_allowed_value/value:416 msgctxt "work_attribute_type_allowed_value" msgid "Hoş Aver" msgstr "Hoş Aver" #: DB:work_attribute_type_allowed_value/value:113 msgctxt "work_attribute_type_allowed_value" msgid "Hussēnī" msgstr "Hussēnī" #: DB:work_attribute_type_allowed_value/value:619 msgctxt "work_attribute_type_allowed_value" msgid "Hutbe" msgstr "" #: DB:work_attribute_type_allowed_value/value:417 msgctxt "work_attribute_type_allowed_value" msgid "Huzi" msgstr "" #: DB:medium_format/name:38 msgctxt "medium_format" msgid "Hybrid SACD" msgstr "" #: DB:medium_format/name:63 msgctxt "medium_format" msgid "Hybrid SACD (CD layer)" msgstr "" #: DB:medium_format/name:64 msgctxt "medium_format" msgid "Hybrid SACD (SACD layer)" msgstr "" #: DB:work_attribute_type_allowed_value/value:418 msgctxt "work_attribute_type_allowed_value" msgid "Hüdavendigar" msgstr "" #: DB:work_attribute_type_allowed_value/value:419 msgctxt "work_attribute_type_allowed_value" msgid "Hüseyni" msgstr "" #: DB:work_attribute_type_allowed_value/value:422 msgctxt "work_attribute_type_allowed_value" msgid "Hüseyni Araban" msgstr "" #: DB:work_attribute_type_allowed_value/value:420 msgctxt "work_attribute_type_allowed_value" msgid "Hüseyni Gülizar" msgstr "" #: DB:work_attribute_type_allowed_value/value:421 msgctxt "work_attribute_type_allowed_value" msgid "Hüseyni Zemzeme" msgstr "" #: DB:work_attribute_type_allowed_value/value:423 msgctxt "work_attribute_type_allowed_value" msgid "Hüseyniaşiran" msgstr "" #: DB:work_attribute_type_allowed_value/value:424 msgctxt "work_attribute_type_allowed_value" msgid "Hüseynibuselik" msgstr "Hüseynibuselik" #: DB:work_attribute_type_allowed_value/value:425 msgctxt "work_attribute_type_allowed_value" msgid "Hüzzam" msgstr "Hüzzam" #: DB:work_attribute_type_allowed_value/value:426 msgctxt "work_attribute_type_allowed_value" msgid "Hüzzam-ı Cedid" msgstr "Hüzzam-ı Cedid" #: DB:work_attribute_type_allowed_value/value:109 msgctxt "work_attribute_type_allowed_value" msgid "Hēmavati" msgstr "Hēmavati" #: DB:work_attribute_type/name:14 msgctxt "work_attribute_type" msgid "Identifiers" msgstr "" #: DB:label_type/name:9 msgctxt "label_type" msgid "Imprint" msgstr "" #: DB:work_type/name:30 msgctxt "work_type" msgid "Incidental music" msgstr "" #: DB:work_type/description:30 msgctxt "work_type" msgid "" "Incidental music is music written as background for (usually) a theatre " "play." msgstr "" #: DB:place_type/name:5 msgctxt "place_type" msgid "Indoor arena" msgstr "Indendørs arena" #: DB:editor_collection_type/name:9 msgctxt "collection_type" msgid "Instrument" msgstr "" #: DB:instrument_alias_type/name:1 msgctxt "alias_type" msgid "Instrument name" msgstr "" #: DB:release_group_secondary_type/name:4 msgctxt "release_group_secondary_type" msgid "Interview" msgstr "Interview" #: DB:work_attribute_type_allowed_value/value:427 msgctxt "work_attribute_type_allowed_value" msgid "Irak" msgstr "" #: DB:work_attribute_type_allowed_value/value:428 msgctxt "work_attribute_type_allowed_value" msgid "Irakaşiran" msgstr "" #: DB:work_attribute_type_allowed_value/value:739 msgctxt "work_attribute_type_allowed_value" msgid "Iraksak" msgstr "" #: DB:work_attribute_type_allowed_value/value:429 msgctxt "work_attribute_type_allowed_value" msgid "Isfahan" msgstr "" #: DB:work_attribute_type_allowed_value/value:430 msgctxt "work_attribute_type_allowed_value" msgid "Isfahan Ruy-i Neva" msgstr "" #: DB:work_attribute_type_allowed_value/value:431 msgctxt "work_attribute_type_allowed_value" msgid "Isfahan Zemzeme" msgstr "" #: DB:work_attribute_type_allowed_value/value:434 msgctxt "work_attribute_type_allowed_value" msgid "Isfahan-ı Cedid" msgstr "" #: DB:work_attribute_type_allowed_value/value:432 msgctxt "work_attribute_type_allowed_value" msgid "Isfahanbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:433 msgctxt "work_attribute_type_allowed_value" msgid "Isfahanek" msgstr "" #: DB:work_attribute_type_allowed_value/value:435 msgctxt "work_attribute_type_allowed_value" msgid "Isfahani" msgstr "" #: DB:area_type/name:6 msgctxt "area_type" msgid "Island" msgstr "" #: DB:area_type/description:6 msgctxt "area_type" msgid "" "Island is used for islands and atolls which don't form subdivisions of their" " own, e.g. Skye. These are not considered when displaying the parent areas " "for a given area." msgstr "" #: DB:work_attribute_type/name:3 msgctxt "work_attribute_type" msgid "JASRAC ID" msgstr "JASRAC-id" #: DB:work_attribute_type_allowed_value/value:114 msgctxt "work_attribute_type_allowed_value" msgid "Jaganmōhini" msgstr "Jaganmōhini" #: DB:work_attribute_type_allowed_value/value:115 msgctxt "work_attribute_type_allowed_value" msgid "Janaranjani" msgstr "Janaranjani" #: DB:work_attribute_type_allowed_value/value:116 msgctxt "work_attribute_type_allowed_value" msgid "Jaya manōhari" msgstr "Jaya manōhari" #: DB:work_attribute_type_allowed_value/value:117 msgctxt "work_attribute_type_allowed_value" msgid "Jayantasēna" msgstr "Jayantasēna" #: DB:work_attribute_type_allowed_value/value:118 msgctxt "work_attribute_type_allowed_value" msgid "Jayantaśrī" msgstr "Jayantaśrī" #: DB:release_packaging/name:1 msgctxt "release_packaging" msgid "Jewel Case" msgstr "" #: DB:work_attribute_type_allowed_value/value:119 msgctxt "work_attribute_type_allowed_value" msgid "Jhankāradhvani" msgstr "Jhankāradhvani" #: DB:work_attribute_type_allowed_value/value:120 msgctxt "work_attribute_type_allowed_value" msgid "Jingala" msgstr "Jingala" #: DB:work_attribute_type_allowed_value/value:124 msgctxt "work_attribute_type_allowed_value" msgid "Jyōti svarūpiṇi" msgstr "Jyōti svarūpiṇi" #: DB:work_attribute_type_allowed_value/value:121 msgctxt "work_attribute_type_allowed_value" msgid "Jōg" msgstr "Jōg" #: DB:work_attribute_type_allowed_value/value:122 msgctxt "work_attribute_type_allowed_value" msgid "Jōgiya" msgstr "Jōgiya" #: DB:work_attribute_type_allowed_value/value:123 msgctxt "work_attribute_type_allowed_value" msgid "Jōnpuri" msgstr "Jōnpuri" #: DB:work_attribute_type/name:11 msgctxt "work_attribute_type" msgid "KOMCA ID" msgstr "KOMCA-id" #: DB:work_attribute_type_allowed_value/value:621 msgctxt "work_attribute_type_allowed_value" msgid "Kalenderi" msgstr "" #: DB:work_attribute_type_allowed_value/value:128 msgctxt "work_attribute_type_allowed_value" msgid "Kalgaḍa" msgstr "Kalgaḍa" #: DB:work_attribute_type_allowed_value/value:130 msgctxt "work_attribute_type_allowed_value" msgid "Kalyāṇi" msgstr "Kalyāṇi" #: DB:work_attribute_type_allowed_value/value:127 msgctxt "work_attribute_type_allowed_value" msgid "Kalāvati" msgstr "Kalāvati" #: DB:work_attribute_type_allowed_value/value:131 msgctxt "work_attribute_type_allowed_value" msgid "Kamalāmanōhari" msgstr "Kamalāmanōhari" #: DB:work_attribute_type_allowed_value/value:622 msgctxt "work_attribute_type_allowed_value" msgid "Kamet" msgstr "" #: DB:work_attribute_type_allowed_value/value:133 msgctxt "work_attribute_type_allowed_value" msgid "Kamās" msgstr "Kamās" #: DB:work_attribute_type_allowed_value/value:137 msgctxt "work_attribute_type_allowed_value" msgid "Kannaḍa gaula" msgstr "Kannaḍa gaula" #: DB:work_attribute_type_allowed_value/value:623 msgctxt "work_attribute_type_allowed_value" msgid "Kanto" msgstr "" #: DB:work_attribute_type_allowed_value/value:742 msgctxt "work_attribute_type_allowed_value" msgid "Kapalı Curcuna" msgstr "" #: DB:work_attribute_type_allowed_value/value:624 msgctxt "work_attribute_type_allowed_value" msgid "Kar" msgstr "" #: DB:work_attribute_type_allowed_value/value:626 msgctxt "work_attribute_type_allowed_value" msgid "Kar-ı Natık" msgstr "" #: DB:work_attribute_type_allowed_value/value:627 msgctxt "work_attribute_type_allowed_value" msgid "Kar-ı Nev" msgstr "" #: DB:work_attribute_type_allowed_value/value:437 msgctxt "work_attribute_type_allowed_value" msgid "Karabağı" msgstr "" #: DB:work_attribute_type_allowed_value/value:141 msgctxt "work_attribute_type_allowed_value" msgid "Karaharapriya" msgstr "Karaharapriya" #: DB:work_attribute_type_allowed_value/value:438 msgctxt "work_attribute_type_allowed_value" msgid "Karcığar" msgstr "" #: DB:work_attribute_type_allowed_value/value:625 msgctxt "work_attribute_type_allowed_value" msgid "Karçe" msgstr "" #: DB:work_attribute_type_allowed_value/value:628 msgctxt "work_attribute_type_allowed_value" msgid "Karşılama" msgstr "" #: DB:work_attribute_type_allowed_value/value:142 msgctxt "work_attribute_type_allowed_value" msgid "Karṇaranjani" msgstr "Karṇaranjani" #: DB:work_attribute_type_allowed_value/value:143 msgctxt "work_attribute_type_allowed_value" msgid "Karṇāṭaka behāg" msgstr "Karṇāṭaka behāg" #: DB:work_attribute_type_allowed_value/value:144 msgctxt "work_attribute_type_allowed_value" msgid "Karṇāṭaka dēvagāndhāri" msgstr "Karṇāṭaka dēvagāndhāri" #: DB:work_attribute_type_allowed_value/value:145 msgctxt "work_attribute_type_allowed_value" msgid "Karṇāṭaka kāpi" msgstr "Karṇāṭaka kāpi" #: DB:work_attribute_type_allowed_value/value:146 msgctxt "work_attribute_type_allowed_value" msgid "Karṇāṭaka śudda sāvēri" msgstr "Karṇāṭaka śudda sāvēri" #: DB:work_attribute_type_allowed_value/value:629 msgctxt "work_attribute_type_allowed_value" msgid "Kasaphavası" msgstr "" #: DB:work_attribute_type_allowed_value/value:147 msgctxt "work_attribute_type_allowed_value" msgid "Kathanakutūhalaṁ" msgstr "Kathanakutūhalaṁ" #: DB:work_attribute_type_allowed_value/value:743 msgctxt "work_attribute_type_allowed_value" msgid "Katikofti" msgstr "" #: DB:work_attribute_type_allowed_value/value:129 msgctxt "work_attribute_type_allowed_value" msgid "Kaḷyāṇa vasantaṁ" msgstr "Kaḷyāṇa vasantaṁ" #: DB:work_attribute_type_allowed_value/value:125 msgctxt "work_attribute_type_allowed_value" msgid "Kaḷā sāvēri" msgstr "Kaḷā sāvēri" #: DB:work_attribute_type_allowed_value/value:126 msgctxt "work_attribute_type_allowed_value" msgid "Kaḷānidhi" msgstr "Kaḷānidhi" #: DB:work_attribute_type_allowed_value/value:439 msgctxt "work_attribute_type_allowed_value" msgid "Kebüter" msgstr "" #: DB:work_attribute_type_allowed_value/value:150 msgctxt "work_attribute_type_allowed_value" msgid "Kedāraṁ" msgstr "Kedāraṁ" #: DB:release_packaging/name:6 msgctxt "release_packaging" msgid "Keep Case" msgstr "" #: DB:work_attribute_type/name:1 msgctxt "work_attribute_type" msgid "Key" msgstr "toneart" #: DB:work_attribute_type_allowed_value/value:282 msgctxt "work_attribute_type_allowed_value" msgid "Khaṇḍa chāpu" msgstr "Khaṇḍa chāpu" #: DB:work_attribute_type_allowed_value/value:293 msgctxt "work_attribute_type_allowed_value" msgid "Khaṇḍa-jāti tripuṭa" msgstr "Khaṇḍa-jāti tripuṭa" #: DB:work_attribute_type_allowed_value/value:294 msgctxt "work_attribute_type_allowed_value" msgid "Khaṇḍa-jāti ēka" msgstr "Khaṇḍa-jāti ēka" #: DB:work_attribute_type_allowed_value/value:630 msgctxt "work_attribute_type_allowed_value" msgid "Koda" msgstr "" #: DB:work_attribute_type_allowed_value/value:631 msgctxt "work_attribute_type_allowed_value" msgid "Koşma" msgstr "" #: DB:work_attribute_type_allowed_value/value:155 msgctxt "work_attribute_type_allowed_value" msgid "Kuntalavarāḷi" msgstr "Kuntalavarāḷi" #: DB:work_attribute_type_allowed_value/value:633 msgctxt "work_attribute_type_allowed_value" msgid "Kur'an-ı Kerim" msgstr "" #: DB:work_attribute_type_allowed_value/value:156 msgctxt "work_attribute_type_allowed_value" msgid "Kurinji" msgstr "Kurinji" #: DB:work_attribute_type_allowed_value/value:632 msgctxt "work_attribute_type_allowed_value" msgid "Köçekçe" msgstr "" #: DB:work_attribute_type_allowed_value/value:634 msgctxt "work_attribute_type_allowed_value" msgid "Küpe" msgstr "" #: DB:work_attribute_type_allowed_value/value:442 msgctxt "work_attribute_type_allowed_value" msgid "Kürdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:443 msgctxt "work_attribute_type_allowed_value" msgid "Kürdi Gerdaniye" msgstr "" #: DB:work_attribute_type_allowed_value/value:444 msgctxt "work_attribute_type_allowed_value" msgid "Kürdilihicazkar" msgstr "" #: DB:work_attribute_type_allowed_value/value:635 msgctxt "work_attribute_type_allowed_value" msgid "Kürthavası" msgstr "" #: DB:work_attribute_type_allowed_value/value:440 msgctxt "work_attribute_type_allowed_value" msgid "Küçek" msgstr "" #: DB:work_attribute_type_allowed_value/value:441 msgctxt "work_attribute_type_allowed_value" msgid "Küçekneva" msgstr "" #: DB:work_attribute_type_allowed_value/value:132 msgctxt "work_attribute_type_allowed_value" msgid "Kāmaranjani" msgstr "Kāmaranjani" #: DB:work_attribute_type_allowed_value/value:134 msgctxt "work_attribute_type_allowed_value" msgid "Kāmavardani/Pantuvarāḷi" msgstr "Kāmavardani/Pantuvarāḷi" #: DB:work_attribute_type_allowed_value/value:136 msgctxt "work_attribute_type_allowed_value" msgid "Kānaḍa" msgstr "Kānaḍa" #: DB:work_attribute_type_allowed_value/value:138 msgctxt "work_attribute_type_allowed_value" msgid "Kāntāmaṇi" msgstr "Kāntāmaṇi" #: DB:work_attribute_type_allowed_value/value:139 msgctxt "work_attribute_type_allowed_value" msgid "Kāpi" msgstr "Kāpi" #: DB:work_attribute_type_allowed_value/value:140 msgctxt "work_attribute_type_allowed_value" msgid "Kāpi nārāyaṇi" msgstr "Kāpi nārāyaṇi" #: DB:work_attribute_type_allowed_value/value:148 msgctxt "work_attribute_type_allowed_value" msgid "Kāvaḍicindu" msgstr "Kāvaḍicindu" #: DB:work_attribute_type_allowed_value/value:135 msgctxt "work_attribute_type_allowed_value" msgid "Kāṁbhōji" msgstr "Kāṁbhōji" #: DB:work_attribute_type_allowed_value/value:149 msgctxt "work_attribute_type_allowed_value" msgid "Kēdāragauḷa" msgstr "Kēdāragauḷa" #: DB:work_attribute_type_allowed_value/value:152 msgctxt "work_attribute_type_allowed_value" msgid "Kīravāṇi" msgstr "Kīravāṇi" #: DB:work_attribute_type_allowed_value/value:151 msgctxt "work_attribute_type_allowed_value" msgid "Kīraṇāvaḷi" msgstr "Kīraṇāvaḷi" #: DB:work_attribute_type_allowed_value/value:153 msgctxt "work_attribute_type_allowed_value" msgid "Kōkiladhvani" msgstr "Kōkiladhvani" #: DB:work_attribute_type_allowed_value/value:154 msgctxt "work_attribute_type_allowed_value" msgid "Kōkilapriya" msgstr "Kōkilapriya" #: DB:editor_collection_type/name:10 msgctxt "collection_type" msgid "Label" msgstr "" #: DB:label_alias_type/name:1 msgctxt "alias_type" msgid "Label name" msgstr "selskabsnavn" #: DB:work_attribute_type_allowed_value/value:445 msgctxt "work_attribute_type_allowed_value" msgid "Lalegül" msgstr "" #: DB:work_attribute_type_allowed_value/value:157 msgctxt "work_attribute_type_allowed_value" msgid "Lalita" msgstr "Lalita" #: DB:work_attribute_type_allowed_value/value:158 msgctxt "work_attribute_type_allowed_value" msgid "Lalita pancamaṁ" msgstr "Lalita pancamaṁ" #: DB:medium_format/name:5 msgctxt "medium_format" msgid "LaserDisc" msgstr "LaserDisc" #: DB:work_attribute_type_allowed_value/value:159 msgctxt "work_attribute_type_allowed_value" msgid "Latāngi" msgstr "Latāngi" #: DB:event_type/name:3 msgctxt "event_type" msgid "Launch event" msgstr "" #: DB:work_attribute_type_allowed_value/value:160 msgctxt "work_attribute_type_allowed_value" msgid "Lavāngi" msgstr "Lavāngi" #: DB:artist_alias_type/name:2 msgctxt "alias_type" msgid "Legal name" msgstr "juridisk navn" #: DB:work_attribute_type_allowed_value/value:744 msgctxt "work_attribute_type_allowed_value" msgid "Lenk Fahte" msgstr "" #: DB:cover_art_archive.art_type/name:12 msgctxt "cover_art_type" msgid "Liner" msgstr "" #: DB:release_group_secondary_type/name:6 msgctxt "release_group_secondary_type" msgid "Live" msgstr "live" #: DB:work_attribute_type_allowed_value/value:636 msgctxt "work_attribute_type_allowed_value" msgid "Longa" msgstr "" #: DB:work_attribute_type_allowed_value/value:161 msgctxt "work_attribute_type_allowed_value" msgid "Madhyamā varāḷi" msgstr "Madhyamā varāḷi" #: DB:work_attribute_type_allowed_value/value:162 msgctxt "work_attribute_type_allowed_value" msgid "Madhyamāvati" msgstr "Madhyamāvati" #: DB:work_attribute_type_allowed_value/value:295 msgctxt "work_attribute_type_allowed_value" msgid "Madhyādi" msgstr "Madhyādi" #: DB:work_type/name:7 msgctxt "work_type" msgid "Madrigal" msgstr "madrigal" #: DB:work_attribute_type_allowed_value/value:163 msgctxt "work_attribute_type_allowed_value" msgid "Maduvanti" msgstr "Maduvanti" #: DB:work_attribute_type_allowed_value/value:637 msgctxt "work_attribute_type_allowed_value" msgid "Mahfelsürmesi" msgstr "" #: DB:work_attribute_type_allowed_value/value:446 msgctxt "work_attribute_type_allowed_value" msgid "Mahur" msgstr "" #: DB:work_attribute_type_allowed_value/value:447 msgctxt "work_attribute_type_allowed_value" msgid "Mahurbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:448 msgctxt "work_attribute_type_allowed_value" msgid "Mahurhan" msgstr "" #: DB:work_attribute_type_allowed_value/value:296 msgctxt "work_attribute_type_allowed_value" msgid "Mahālakṣmi" msgstr "Mahālakṣmi" #: DB:work_attribute_type/name:15 msgctxt "work_attribute_type" msgid "Makam (Ottoman, Turkish)" msgstr "" #: DB:work_attribute_type_allowed_value/value:164 msgctxt "work_attribute_type_allowed_value" msgid "Malahari" msgstr "Malahari" #: DB:work_attribute_type_allowed_value/value:166 msgctxt "work_attribute_type_allowed_value" msgid "Malayamārutaṁ" msgstr "Malayamārutaṁ" #: DB:gender/name:1 msgctxt "gender" msgid "Male" msgstr "" #: DB:work_attribute_type_allowed_value/value:638 msgctxt "work_attribute_type_allowed_value" msgid "Mandra" msgstr "" #: DB:work_attribute_type_allowed_value/value:168 msgctxt "work_attribute_type_allowed_value" msgid "Mandāri" msgstr "Mandāri" #: DB:series_ordering_type/name:2 msgctxt "series_ordering_type" msgid "Manual" msgstr "" #: DB:work_attribute_type_allowed_value/value:172 msgctxt "work_attribute_type_allowed_value" msgid "Manōranjani" msgstr "Manōranjani" #: DB:work_attribute_type_allowed_value/value:639 msgctxt "work_attribute_type_allowed_value" msgid "Marş" msgstr "" #: DB:work_type/name:8 msgctxt "work_type" msgid "Mass" msgstr "messe" #: DB:event_type/name:5 msgctxt "event_type" msgid "Masterclass/Clinic" msgstr "" #: DB:work_attribute_type_allowed_value/value:452 msgctxt "work_attribute_type_allowed_value" msgid "Mate" msgstr "" #: DB:work_attribute_type_allowed_value/value:449 msgctxt "work_attribute_type_allowed_value" msgid "Maver" msgstr "" #: DB:work_attribute_type_allowed_value/value:450 msgctxt "work_attribute_type_allowed_value" msgid "Mavera" msgstr "" #: DB:work_attribute_type_allowed_value/value:451 msgctxt "work_attribute_type_allowed_value" msgid "Maveraünnehir" msgstr "" #: DB:editor_collection_type/name:6 msgctxt "collection_type" msgid "Maybe attending" msgstr "" #: DB:work_attribute_type_allowed_value/value:175 msgctxt "work_attribute_type_allowed_value" msgid "Mayūra sāvēri" msgstr "Mayūra sāvēri" #: DB:work_attribute_type_allowed_value/value:170 msgctxt "work_attribute_type_allowed_value" msgid "Maṇirangu" msgstr "Maṇirangu" #: DB:work_attribute_type_allowed_value/value:286 msgctxt "work_attribute_type_allowed_value" msgid "Maṭhya" msgstr "Maṭhya" #: DB:work_attribute_type_allowed_value/value:453 msgctxt "work_attribute_type_allowed_value" msgid "Meclis Efruz" msgstr "" #: DB:work_attribute_type_allowed_value/value:640 msgctxt "work_attribute_type_allowed_value" msgid "Medhal" msgstr "" #: DB:cover_art_archive.art_type/name:4 msgctxt "cover_art_type" msgid "Medium" msgstr "medie" #: DB:work_attribute_type_allowed_value/value:176 msgctxt "work_attribute_type_allowed_value" msgid "Meghamalhar" msgstr "Meghamalhar" #: DB:work_attribute_type_allowed_value/value:641 msgctxt "work_attribute_type_allowed_value" msgid "Mehter" msgstr "" #: DB:work_attribute_type_allowed_value/value:642 msgctxt "work_attribute_type_allowed_value" msgid "Mersiye" msgstr "" #: DB:work_attribute_type_allowed_value/value:745 msgctxt "work_attribute_type_allowed_value" msgid "Mevlevi Evferi" msgstr "" #: DB:work_attribute_type_allowed_value/value:643 msgctxt "work_attribute_type_allowed_value" msgid "Mevlidişerif" msgstr "" #: DB:medium_format/name:6 msgctxt "medium_format" msgid "MiniDisc" msgstr "MiniDisc" #: DB:work_attribute_type_allowed_value/value:644 msgctxt "work_attribute_type_allowed_value" msgid "Miraciye" msgstr "" #: DB:release_group_secondary_type/name:9 msgctxt "release_group_secondary_type" msgid "Mixtape/Street" msgstr "" #: DB:work_attribute_type_allowed_value/value:281 msgctxt "work_attribute_type_allowed_value" msgid "Miśra chāpu" msgstr "Miśra chāpu" #: DB:work_attribute_type_allowed_value/value:177 msgctxt "work_attribute_type_allowed_value" msgid "Miśra khamāj" msgstr "Miśra khamāj" #: DB:work_attribute_type_allowed_value/value:178 msgctxt "work_attribute_type_allowed_value" msgid "Miśra pahāḍi" msgstr "Miśra pahāḍi" #: DB:work_attribute_type_allowed_value/value:180 msgctxt "work_attribute_type_allowed_value" msgid "Miśra yaman" msgstr "Miśra yaman" #: DB:work_attribute_type_allowed_value/value:179 msgctxt "work_attribute_type_allowed_value" msgid "Miśra śivaranjani" msgstr "Miśra śivaranjani" #: DB:work_attribute_type_allowed_value/value:287 msgctxt "work_attribute_type_allowed_value" msgid "Miśra-jāti jhaṁpe" msgstr "Miśra-jāti jhaṁpe" #: DB:work_attribute_type_allowed_value/value:297 msgctxt "work_attribute_type_allowed_value" msgid "Miśra-jāti rūpaka" msgstr "Miśra-jāti rūpaka" #: DB:work_type/name:9 msgctxt "work_type" msgid "Motet" msgstr "motet" #: DB:work_attribute_type_allowed_value/value:746 msgctxt "work_attribute_type_allowed_value" msgid "Muaşşer" msgstr "" #: DB:work_attribute_type_allowed_value/value:454 msgctxt "work_attribute_type_allowed_value" msgid "Muhalif" msgstr "" #: DB:work_attribute_type_allowed_value/value:455 msgctxt "work_attribute_type_allowed_value" msgid "Muhalif-i Irak" msgstr "" #: DB:work_attribute_type_allowed_value/value:456 msgctxt "work_attribute_type_allowed_value" msgid "Muhalif-i Rast" msgstr "" #: DB:work_attribute_type_allowed_value/value:457 msgctxt "work_attribute_type_allowed_value" msgid "Muhalif-i Uşşak" msgstr "" #: DB:work_attribute_type_allowed_value/value:747 msgctxt "work_attribute_type_allowed_value" msgid "Muhammes" msgstr "" #: DB:work_attribute_type_allowed_value/value:458 msgctxt "work_attribute_type_allowed_value" msgid "Muhayyer" msgstr "" #: DB:work_attribute_type_allowed_value/value:459 msgctxt "work_attribute_type_allowed_value" msgid "Muhayyer Sünbüle" msgstr "" #: DB:work_attribute_type_allowed_value/value:462 msgctxt "work_attribute_type_allowed_value" msgid "Muhayyer Zengüle" msgstr "" #: DB:work_attribute_type_allowed_value/value:460 msgctxt "work_attribute_type_allowed_value" msgid "Muhayyerbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:461 msgctxt "work_attribute_type_allowed_value" msgid "Muhayyerkürdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:183 msgctxt "work_attribute_type_allowed_value" msgid "Mukhāri" msgstr "Mukhāri" #: DB:area_type/name:4 msgctxt "area_type" msgid "Municipality" msgstr "kommune" #: DB:area_type/description:4 msgctxt "area_type" msgid "" "Municipality is used for small administrative divisions which, for urban " "municipalities, often contain a single city and a few surrounding villages. " "Rural municipalities typically group several villages together." msgstr "" #: DB:work_attribute_type_allowed_value/value:645 msgctxt "work_attribute_type_allowed_value" msgid "Murabba" msgstr "" #: DB:medium_format/name:46 msgctxt "medium_format" msgid "Music Card" msgstr "" #: DB:work_type/name:29 msgctxt "work_type" msgid "Musical" msgstr "" #: DB:work_type/description:29 msgctxt "work_type" msgid "" "Musical theatre is a form of theatrical performance that combines songs, " "spoken dialogue, acting, and dance." msgstr "" #: DB:work_attribute_type_allowed_value/value:748 msgctxt "work_attribute_type_allowed_value" msgid "Muzaaf Devr-i Kebir" msgstr "" #: DB:work_attribute_type/name:12 msgctxt "work_attribute_type" msgid "MÜST ID" msgstr "MÜST-id" #: DB:work_attribute_type_allowed_value/value:463 msgctxt "work_attribute_type_allowed_value" msgid "Müberka" msgstr "" #: DB:work_attribute_type_allowed_value/value:646 msgctxt "work_attribute_type_allowed_value" msgid "Münacaat" msgstr "" #: DB:work_attribute_type_allowed_value/value:464 msgctxt "work_attribute_type_allowed_value" msgid "Mürekkep Isfahan" msgstr "" #: DB:work_attribute_type_allowed_value/value:749 msgctxt "work_attribute_type_allowed_value" msgid "Mürekkep Nimsofyan" msgstr "" #: DB:work_attribute_type_allowed_value/value:750 msgctxt "work_attribute_type_allowed_value" msgid "Mürekkep Semai/Üçleme" msgstr "" #: DB:work_attribute_type_allowed_value/value:751 msgctxt "work_attribute_type_allowed_value" msgid "Mürekkep Sofyan" msgstr "" #: DB:work_attribute_type_allowed_value/value:465 msgctxt "work_attribute_type_allowed_value" msgid "Müselles" msgstr "" #: DB:work_attribute_type_allowed_value/value:752 msgctxt "work_attribute_type_allowed_value" msgid "Müsemmen" msgstr "" #: DB:work_attribute_type_allowed_value/value:753 msgctxt "work_attribute_type_allowed_value" msgid "Müsemmen II" msgstr "" #: DB:work_attribute_type_allowed_value/value:466 msgctxt "work_attribute_type_allowed_value" msgid "Müstear" msgstr "" #: DB:work_attribute_type_allowed_value/value:467 msgctxt "work_attribute_type_allowed_value" msgid "Müşküye" msgstr "" #: DB:work_attribute_type_allowed_value/value:167 msgctxt "work_attribute_type_allowed_value" msgid "Mānavati" msgstr "Mānavati" #: DB:work_attribute_type_allowed_value/value:171 msgctxt "work_attribute_type_allowed_value" msgid "Mānji" msgstr "Mānji" #: DB:work_attribute_type_allowed_value/value:169 msgctxt "work_attribute_type_allowed_value" msgid "Mānḍu" msgstr "Mānḍu" #: DB:work_attribute_type_allowed_value/value:173 msgctxt "work_attribute_type_allowed_value" msgid "Mārgahindōḷaṁ" msgstr "Mārgahindōḷaṁ" #: DB:work_attribute_type_allowed_value/value:174 msgctxt "work_attribute_type_allowed_value" msgid "Māyāmāḷavagauḷa" msgstr "Māyāmāḷavagauḷa" #: DB:work_attribute_type_allowed_value/value:165 msgctxt "work_attribute_type_allowed_value" msgid "Māḷavi" msgstr "Māḷavi" #: DB:work_attribute_type_allowed_value/value:181 msgctxt "work_attribute_type_allowed_value" msgid "Mōhan kaḷyāṇi" msgstr "Mōhan kaḷyāṇi" #: DB:work_attribute_type_allowed_value/value:182 msgctxt "work_attribute_type_allowed_value" msgid "Mōhanaṁ" msgstr "Mōhanaṁ" #: DB:work_attribute_type_allowed_value/value:647 msgctxt "work_attribute_type_allowed_value" msgid "Naat" msgstr "" #: DB:work_attribute_type_allowed_value/value:648 msgctxt "work_attribute_type_allowed_value" msgid "Nakış" msgstr "" #: DB:work_attribute_type_allowed_value/value:468 msgctxt "work_attribute_type_allowed_value" msgid "Narefte" msgstr "" #: DB:work_attribute_type_allowed_value/value:649 msgctxt "work_attribute_type_allowed_value" msgid "Natipeygamberi" msgstr "" #: DB:work_attribute_type_allowed_value/value:196 msgctxt "work_attribute_type_allowed_value" msgid "Navarasa kannaḍa" msgstr "Navarasa kannaḍa" #: DB:work_attribute_type_allowed_value/value:195 msgctxt "work_attribute_type_allowed_value" msgid "Navarāgamālika" msgstr "Navarāgamālika" #: DB:work_attribute_type_allowed_value/value:197 msgctxt "work_attribute_type_allowed_value" msgid "Navrōj" msgstr "Navrōj" #: DB:work_attribute_type_allowed_value/value:754 msgctxt "work_attribute_type_allowed_value" msgid "Nazlı Düyek" msgstr "" #: DB:work_attribute_type_allowed_value/value:187 msgctxt "work_attribute_type_allowed_value" msgid "Naḷinakānti" msgstr "Naḷinakānti" #: DB:work_attribute_type_allowed_value/value:191 msgctxt "work_attribute_type_allowed_value" msgid "Naṭabhairavi" msgstr "Naṭabhairavi" #: DB:work_attribute_type_allowed_value/value:194 msgctxt "work_attribute_type_allowed_value" msgid "Naṭanārāyaṇi" msgstr "Naṭanārāyaṇi" #: DB:work_attribute_type_allowed_value/value:469 msgctxt "work_attribute_type_allowed_value" msgid "Necd-i Hüseyni" msgstr "" #: DB:work_attribute_type_allowed_value/value:650 msgctxt "work_attribute_type_allowed_value" msgid "Nefes" msgstr "" #: DB:work_attribute_type_allowed_value/value:470 msgctxt "work_attribute_type_allowed_value" msgid "Neresin" msgstr "" #: DB:work_attribute_type_allowed_value/value:472 msgctxt "work_attribute_type_allowed_value" msgid "Neva" msgstr "" #: DB:work_attribute_type_allowed_value/value:473 msgctxt "work_attribute_type_allowed_value" msgid "Neva Bağdat" msgstr "" #: DB:work_attribute_type_allowed_value/value:474 msgctxt "work_attribute_type_allowed_value" msgid "Neva Dilkeş" msgstr "" #: DB:work_attribute_type_allowed_value/value:475 msgctxt "work_attribute_type_allowed_value" msgid "Nevabuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:476 msgctxt "work_attribute_type_allowed_value" msgid "Nevakürdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:477 msgctxt "work_attribute_type_allowed_value" msgid "Nevaziş" msgstr "" #: DB:work_attribute_type_allowed_value/value:478 msgctxt "work_attribute_type_allowed_value" msgid "Neveda" msgstr "" #: DB:work_attribute_type_allowed_value/value:479 msgctxt "work_attribute_type_allowed_value" msgid "Neveser" msgstr "" #: DB:work_attribute_type_allowed_value/value:480 msgctxt "work_attribute_type_allowed_value" msgid "Nevkeş" msgstr "" #: DB:work_attribute_type_allowed_value/value:481 msgctxt "work_attribute_type_allowed_value" msgid "Nevruz" msgstr "" #: DB:work_attribute_type_allowed_value/value:482 msgctxt "work_attribute_type_allowed_value" msgid "Nevruzi Rumi" msgstr "" #: DB:work_attribute_type_allowed_value/value:471 msgctxt "work_attribute_type_allowed_value" msgid "Neşataver" msgstr "" #: DB:work_attribute_type_allowed_value/value:483 msgctxt "work_attribute_type_allowed_value" msgid "Nigar" msgstr "" #: DB:work_attribute_type_allowed_value/value:484 msgctxt "work_attribute_type_allowed_value" msgid "Nihavend-i Kebir" msgstr "" #: DB:work_attribute_type_allowed_value/value:485 msgctxt "work_attribute_type_allowed_value" msgid "Nihavend-i Rumi" msgstr "" #: DB:work_attribute_type_allowed_value/value:486 msgctxt "work_attribute_type_allowed_value" msgid "Nihavent" msgstr "" #: DB:work_attribute_type_allowed_value/value:487 msgctxt "work_attribute_type_allowed_value" msgid "Nikriz" msgstr "" #: DB:work_attribute_type_allowed_value/value:755 msgctxt "work_attribute_type_allowed_value" msgid "Nimberefşan" msgstr "" #: DB:work_attribute_type_allowed_value/value:757 msgctxt "work_attribute_type_allowed_value" msgid "Nimdevir" msgstr "" #: DB:work_attribute_type_allowed_value/value:758 msgctxt "work_attribute_type_allowed_value" msgid "Nimevsat" msgstr "" #: DB:work_attribute_type_allowed_value/value:759 msgctxt "work_attribute_type_allowed_value" msgid "Nimhafif" msgstr "" #: DB:work_attribute_type_allowed_value/value:760 msgctxt "work_attribute_type_allowed_value" msgid "Nimsakil" msgstr "" #: DB:work_attribute_type_allowed_value/value:761 msgctxt "work_attribute_type_allowed_value" msgid "Nimsofyan" msgstr "" #: DB:work_attribute_type_allowed_value/value:756 msgctxt "work_attribute_type_allowed_value" msgid "Nimçember" msgstr "" #: DB:work_attribute_type_allowed_value/value:651 msgctxt "work_attribute_type_allowed_value" msgid "Ninni" msgstr "" #: DB:work_attribute_type_allowed_value/value:200 msgctxt "work_attribute_type_allowed_value" msgid "Nirōṣita" msgstr "Nirōṣita" #: DB:work_attribute_type_allowed_value/value:488 msgctxt "work_attribute_type_allowed_value" msgid "Nişabur" msgstr "" #: DB:work_attribute_type_allowed_value/value:489 msgctxt "work_attribute_type_allowed_value" msgid "Nişaburek" msgstr "" #: DB:work_attribute_type_allowed_value/value:490 msgctxt "work_attribute_type_allowed_value" msgid "Nişabureyn" msgstr "" #: DB:release_packaging/name:7 msgctxt "release_packaging" msgid "None" msgstr "" #: DB:work_attribute_type_allowed_value/value:491 msgctxt "work_attribute_type_allowed_value" msgid "Nuşingül" msgstr "" #: DB:work_attribute_type_allowed_value/value:492 msgctxt "work_attribute_type_allowed_value" msgid "Nühüft" msgstr "" #: DB:work_attribute_type_allowed_value/value:184 msgctxt "work_attribute_type_allowed_value" msgid "Nādanāmakriya" msgstr "Nādanāmakriya" #: DB:work_attribute_type_allowed_value/value:185 msgctxt "work_attribute_type_allowed_value" msgid "Nāga gāndhāri" msgstr "Nāga gāndhāri" #: DB:work_attribute_type_allowed_value/value:186 msgctxt "work_attribute_type_allowed_value" msgid "Nāgasvarāvaḷi" msgstr "Nāgasvarāvaḷi" #: DB:work_attribute_type_allowed_value/value:188 msgctxt "work_attribute_type_allowed_value" msgid "Nārāyaṇi" msgstr "Nārāyaṇi" #: DB:work_attribute_type_allowed_value/value:189 msgctxt "work_attribute_type_allowed_value" msgid "Nāsikabhūṣaṇi" msgstr "Nāsikabhūṣaṇi" #: DB:work_attribute_type_allowed_value/value:198 msgctxt "work_attribute_type_allowed_value" msgid "Nāyaki" msgstr "Nāyaki" #: DB:work_attribute_type_allowed_value/value:190 msgctxt "work_attribute_type_allowed_value" msgid "Nāṭa" msgstr "Nāṭa" #: DB:work_attribute_type_allowed_value/value:192 msgctxt "work_attribute_type_allowed_value" msgid "Nāṭakapriya" msgstr "Nāṭakapriya" #: DB:work_attribute_type_allowed_value/value:193 msgctxt "work_attribute_type_allowed_value" msgid "Nāṭakurinji" msgstr "Nāṭakurinji" #: DB:work_attribute_type_allowed_value/value:199 msgctxt "work_attribute_type_allowed_value" msgid "Nīlāṁbari" msgstr "Nīlāṁbari" #: DB:cover_art_archive.art_type/name:5 msgctxt "cover_art_type" msgid "Obi" msgstr "obi" #: DB:release_status/name:1 msgctxt "release_status" msgid "Official" msgstr "Officiel" #: DB:work_type/name:10 msgctxt "work_type" msgid "Opera" msgstr "opera" #: DB:work_type/name:24 msgctxt "work_type" msgid "Operetta" msgstr "operette" #: DB:work_type/name:11 msgctxt "work_type" msgid "Oratorio" msgstr "oratorium" #: DB:artist_type/name:5 msgctxt "artist_type" msgid "Orchestra" msgstr "orkester" #: DB:label_type/name:4 msgctxt "label_type" msgid "Original Production" msgstr "" #: DB:artist_type/name:3 msgctxt "artist_type" msgid "Other" msgstr "" #: DB:cover_art_archive.art_type/name:8 msgctxt "cover_art_type" msgid "Other" msgstr "andet" #: DB:gender/name:3 msgctxt "gender" msgid "Other" msgstr "" #: DB:medium_format/name:13 msgctxt "medium_format" msgid "Other" msgstr "" #: DB:place_type/name:3 msgctxt "place_type" msgid "Other" msgstr "andet" #: DB:release_group_primary_type/name:11 msgctxt "release_group_primary_type" msgid "Other" msgstr "" #: DB:release_packaging/name:5 msgctxt "release_packaging" msgid "Other" msgstr "" #: DB:instrument_type/name:5 msgctxt "instrument_type" msgid "Other instrument" msgstr "" #: DB:work_type/name:12 msgctxt "work_type" msgid "Overture" msgstr "ouverture" #: DB:editor_collection_type/name:2 msgctxt "collection_type" msgid "Owned music" msgstr "" #: DB:work_attribute_type_allowed_value/value:762 msgctxt "work_attribute_type_allowed_value" msgid "Oynak" msgstr "" #: DB:work_attribute_type_allowed_value/value:652 msgctxt "work_attribute_type_allowed_value" msgid "Oyunhavası" msgstr "" #: DB:work_attribute_type_allowed_value/value:203 msgctxt "work_attribute_type_allowed_value" msgid "Paras" msgstr "Paras" #: DB:work_type/name:13 msgctxt "work_type" msgid "Partita" msgstr "partita" #: DB:medium_format/name:58 msgctxt "medium_format" msgid "Pathé disc" msgstr "" #: DB:work_attribute_type_allowed_value/value:204 msgctxt "work_attribute_type_allowed_value" msgid "Paṭdīp" msgstr "Paṭdīp" #: DB:work_attribute_type_allowed_value/value:493 msgctxt "work_attribute_type_allowed_value" msgid "Pençgah" msgstr "" #: DB:work_attribute_type_allowed_value/value:494 msgctxt "work_attribute_type_allowed_value" msgid "Pençgah-ı Asl" msgstr "" #: DB:work_attribute_type_allowed_value/value:495 msgctxt "work_attribute_type_allowed_value" msgid "Pençgah-ı Zaid" msgstr "" #: DB:instrument_type/name:3 msgctxt "instrument_type" msgid "Percussion instrument" msgstr "" #: DB:work_attribute_type_allowed_value/value:496 msgctxt "work_attribute_type_allowed_value" msgid "Perr-i Zerrin" msgstr "" #: DB:artist_type/name:1 msgctxt "artist_type" msgid "Person" msgstr "person" #: DB:work_attribute_type_allowed_value/value:497 msgctxt "work_attribute_type_allowed_value" msgid "Pesendide" msgstr "" #: DB:work_attribute_type_allowed_value/value:498 msgctxt "work_attribute_type_allowed_value" msgid "Peyk-i Neşat" msgstr "" #: DB:work_attribute_type_allowed_value/value:499 msgctxt "work_attribute_type_allowed_value" msgid "Peyk-i Safa" msgstr "" #: DB:work_attribute_type_allowed_value/value:654 msgctxt "work_attribute_type_allowed_value" msgid "Peşrev" msgstr "" #: DB:medium_format/name:15 msgctxt "medium_format" msgid "Piano Roll" msgstr "" #: DB:editor_collection_type/name:11 msgctxt "collection_type" msgid "Place" msgstr "" #: DB:place_alias_type/name:1 msgctxt "alias_type" msgid "Place name" msgstr "stednavn" #: DB:work_type/name:28 msgctxt "work_type" msgid "Play" msgstr "" #: DB:medium_format/name:45 msgctxt "medium_format" msgid "Playbutton" msgstr "" #: DB:work_type/name:21 msgctxt "work_type" msgid "Poem" msgstr "digt" #: DB:work_attribute_type_allowed_value/value:655 msgctxt "work_attribute_type_allowed_value" msgid "Pop şarkısı" msgstr "" #: DB:cover_art_archive.art_type/name:11 msgctxt "cover_art_type" msgid "Poster" msgstr "plakat" #: DB:label_type/name:3 msgctxt "label_type" msgid "Production" msgstr "" #: DB:release_status/name:2 msgctxt "release_status" msgid "Promotion" msgstr "" #: DB:work_type/name:23 msgctxt "work_type" msgid "Prose" msgstr "prosa" #: DB:release_status/name:4 msgctxt "release_status" msgid "Pseudo-Release" msgstr "pseudo-udgivelse" #: DB:label_type/name:7 msgctxt "label_type" msgid "Publisher" msgstr "" #: DB:work_attribute_type_allowed_value/value:205 msgctxt "work_attribute_type_allowed_value" msgid "Puṇṇāgavarāḷi" msgstr "Puṇṇāgavarāḷi" #: DB:work_attribute_type_allowed_value/value:209 msgctxt "work_attribute_type_allowed_value" msgid "Puṣpalatika" msgstr "Puṣpalatika" #: DB:work_attribute_type_allowed_value/value:202 msgctxt "work_attribute_type_allowed_value" msgid "Pālamanjari" msgstr "Pālamanjari" #: DB:work_attribute_type_allowed_value/value:201 msgctxt "work_attribute_type_allowed_value" msgid "Pāḍi" msgstr "Pāḍi" #: DB:work_attribute_type_allowed_value/value:208 msgctxt "work_attribute_type_allowed_value" msgid "Pūrvi" msgstr "Pūrvi" #: DB:work_attribute_type_allowed_value/value:206 msgctxt "work_attribute_type_allowed_value" msgid "Pūrṇa ṣaḍjaṁ" msgstr "Pūrṇa ṣaḍjaṁ" #: DB:work_attribute_type_allowed_value/value:207 msgctxt "work_attribute_type_allowed_value" msgid "Pūrṇacandrika" msgstr "Pūrṇacandrika" #: DB:work_type/name:14 msgctxt "work_type" msgid "Quartet" msgstr "kvartet" #: DB:work_attribute_type_allowed_value/value:500 msgctxt "work_attribute_type_allowed_value" msgid "Rahatfeza" msgstr "" #: DB:work_attribute_type_allowed_value/value:501 msgctxt "work_attribute_type_allowed_value" msgid "Rahatülervah" msgstr "" #: DB:work_attribute_type_allowed_value/value:763 msgctxt "work_attribute_type_allowed_value" msgid "Raksaksağı" msgstr "" #: DB:work_attribute_type_allowed_value/value:798 msgctxt "work_attribute_type_allowed_value" msgid "Raksaksağı II" msgstr "" #: DB:work_attribute_type_allowed_value/value:764 msgctxt "work_attribute_type_allowed_value" msgid "Raksan" msgstr "" #: DB:work_attribute_type_allowed_value/value:502 msgctxt "work_attribute_type_allowed_value" msgid "Ramiş-i Can" msgstr "" #: DB:work_attribute_type_allowed_value/value:215 msgctxt "work_attribute_type_allowed_value" msgid "Ranjani" msgstr "Ranjani" #: DB:work_attribute_type_allowed_value/value:216 msgctxt "work_attribute_type_allowed_value" msgid "Rasikapriya" msgstr "Rasikapriya" #: DB:work_attribute_type_allowed_value/value:503 msgctxt "work_attribute_type_allowed_value" msgid "Rast" msgstr "" #: DB:work_attribute_type_allowed_value/value:506 msgctxt "work_attribute_type_allowed_value" msgid "Rast Haveran" msgstr "" #: DB:work_attribute_type_allowed_value/value:513 msgctxt "work_attribute_type_allowed_value" msgid "Rast-Maye" msgstr "" #: DB:work_attribute_type_allowed_value/value:507 msgctxt "work_attribute_type_allowed_value" msgid "Rast-ı Atik" msgstr "" #: DB:work_attribute_type_allowed_value/value:508 msgctxt "work_attribute_type_allowed_value" msgid "Rast-ı Cedid" msgstr "" #: DB:work_attribute_type_allowed_value/value:509 msgctxt "work_attribute_type_allowed_value" msgid "Rast-ı Rumi" msgstr "" #: DB:work_attribute_type_allowed_value/value:510 msgctxt "work_attribute_type_allowed_value" msgid "Rast-ı Sağır" msgstr "" #: DB:work_attribute_type_allowed_value/value:511 msgctxt "work_attribute_type_allowed_value" msgid "Rast-ı Sultani" msgstr "" #: DB:work_attribute_type_allowed_value/value:504 msgctxt "work_attribute_type_allowed_value" msgid "Rastaşiran" msgstr "" #: DB:work_attribute_type_allowed_value/value:505 msgctxt "work_attribute_type_allowed_value" msgid "Rastdilara" msgstr "" #: DB:work_attribute_type_allowed_value/value:512 msgctxt "work_attribute_type_allowed_value" msgid "Rastkürdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:217 msgctxt "work_attribute_type_allowed_value" msgid "Ratipati priya" msgstr "Ratipati priya" #: DB:work_attribute_type_allowed_value/value:218 msgctxt "work_attribute_type_allowed_value" msgid "Ravicandrika" msgstr "Ravicandrika" #: DB:editor_collection_type/name:12 msgctxt "collection_type" msgid "Recording" msgstr "" #: DB:series_type/name:3 msgctxt "series_type" msgid "Recording" msgstr "" #: DB:recording_alias_type/name:1 msgctxt "alias_type" msgid "Recording name" msgstr "" #: DB:medium_format/name:10 msgctxt "medium_format" msgid "Reel-to-reel" msgstr "spolebånd" #: DB:work_attribute_type_allowed_value/value:514 msgctxt "work_attribute_type_allowed_value" msgid "Rehavi" msgstr "" #: DB:label_type/name:6 msgctxt "label_type" msgid "Reissue Production" msgstr "" #: DB:work_attribute_type_allowed_value/value:515 msgctxt "work_attribute_type_allowed_value" msgid "Rekb-i Zavil" msgstr "" #: DB:editor_collection_type/name:1 msgctxt "collection_type" msgid "Release" msgstr "" #: DB:series_type/name:2 msgctxt "series_type" msgid "Release" msgstr "" #: DB:editor_collection_type/name:13 msgctxt "collection_type" msgid "Release group" msgstr "" #: DB:series_type/name:1 msgctxt "series_type" msgid "Release group" msgstr "" #: DB:release_group_alias_type/name:1 msgctxt "alias_type" msgid "Release group name" msgstr "" #: DB:release_alias_type/name:1 msgctxt "alias_type" msgid "Release name" msgstr "" #: DB:place_type/name:6 msgctxt "place_type" msgid "Religious building" msgstr "" #: DB:work_attribute_type_allowed_value/value:765 msgctxt "work_attribute_type_allowed_value" msgid "Remel" msgstr "" #: DB:release_group_secondary_type/name:7 msgctxt "release_group_secondary_type" msgid "Remix" msgstr "remix" #: DB:work_attribute_type_allowed_value/value:516 msgctxt "work_attribute_type_allowed_value" msgid "Reng-i Dil" msgstr "" #: DB:work_attribute_type_allowed_value/value:517 msgctxt "work_attribute_type_allowed_value" msgid "Revnaknüma" msgstr "" #: DB:work_attribute_type_allowed_value/value:518 msgctxt "work_attribute_type_allowed_value" msgid "Reyya" msgstr "" #: DB:label_type/name:8 msgctxt "label_type" msgid "Rights Society" msgstr "" #: DB:work_attribute_type_allowed_value/value:222 msgctxt "work_attribute_type_allowed_value" msgid "Rudrapriya" msgstr "Rudrapriya" #: DB:work_attribute_type_allowed_value/value:519 msgctxt "work_attribute_type_allowed_value" msgid "Ruhnüvaz" msgstr "" #: DB:work_attribute_type_allowed_value/value:656 msgctxt "work_attribute_type_allowed_value" msgid "Rumeli Türküsü" msgstr "" #: DB:series_type/name:9 msgctxt "series_type" msgid "Run" msgstr "" #: DB:work_attribute_type_allowed_value/value:521 msgctxt "work_attribute_type_allowed_value" msgid "Ruy-i Dilara" msgstr "" #: DB:work_attribute_type_allowed_value/value:520 msgctxt "work_attribute_type_allowed_value" msgid "Ruy-i Irak" msgstr "" #: DB:work_attribute_type_allowed_value/value:600 msgctxt "work_attribute_type_allowed_value" msgid "Ruy-i Neva" msgstr "" #: DB:work_attribute_type/name:4 msgctxt "work_attribute_type" msgid "Rāga (Carnatic)" msgstr "" #: DB:work_attribute_type_allowed_value/value:210 msgctxt "work_attribute_type_allowed_value" msgid "Rāgamālika" msgstr "Rāgamālika" #: DB:work_attribute_type_allowed_value/value:211 msgctxt "work_attribute_type_allowed_value" msgid "Rāgavinōdini" msgstr "Rāgavinōdini" #: DB:work_attribute_type_allowed_value/value:212 msgctxt "work_attribute_type_allowed_value" msgid "Rāgēśrī" msgstr "Rāgēśrī" #: DB:work_attribute_type_allowed_value/value:213 msgctxt "work_attribute_type_allowed_value" msgid "Rāma manōhari" msgstr "Rāma manōhari" #: DB:work_attribute_type_allowed_value/value:214 msgctxt "work_attribute_type_allowed_value" msgid "Rāmapriya" msgstr "Rāmapriya" #: DB:work_attribute_type_allowed_value/value:219 msgctxt "work_attribute_type_allowed_value" msgid "Rēvagupti" msgstr "Rēvagupti" #: DB:work_attribute_type_allowed_value/value:220 msgctxt "work_attribute_type_allowed_value" msgid "Rēvati" msgstr "Rēvati" #: DB:work_attribute_type_allowed_value/value:221 msgctxt "work_attribute_type_allowed_value" msgid "Rītigauḷa" msgstr "Rītigauḷa" #: DB:work_attribute_type_allowed_value/value:280 msgctxt "work_attribute_type_allowed_value" msgid "Rūpaka" msgstr "Rūpaka" #: DB:medium_format/name:3 msgctxt "medium_format" msgid "SACD" msgstr "SACD" #: DB:work_attribute_type/name:21 msgctxt "work_attribute_type" msgid "SACEM ID" msgstr "" #: DB:work_attribute_type/name:24 msgctxt "work_attribute_type" msgid "SADAIC ID" msgstr "" #: DB:medium_format/name:62 msgctxt "medium_format" msgid "SD Card" msgstr "" #: DB:work_attribute_type/name:8 msgctxt "work_attribute_type" msgid "SESAC ID" msgstr "SESAC-id" #: DB:work_attribute_type/name:20 msgctxt "work_attribute_type" msgid "SGAE ID" msgstr "" #: DB:medium_format/name:36 msgctxt "medium_format" msgid "SHM-CD" msgstr "SHM-CD" #: DB:medium_format/name:57 msgctxt "medium_format" msgid "SHM-SACD" msgstr "" #: DB:work_attribute_type/name:10 msgctxt "work_attribute_type" msgid "SOCAN ID" msgstr "SOCAN-id" #: DB:work_attribute_type/name:18 msgctxt "work_attribute_type" msgid "SUISA ID" msgstr "" #: DB:medium_format/name:23 msgctxt "medium_format" msgid "SVCD" msgstr "SVCD" #: DB:work_attribute_type_allowed_value/value:522 msgctxt "work_attribute_type_allowed_value" msgid "Saba" msgstr "" #: DB:work_attribute_type_allowed_value/value:523 msgctxt "work_attribute_type_allowed_value" msgid "Saba Zemzeme" msgstr "" #: DB:work_attribute_type_allowed_value/value:524 msgctxt "work_attribute_type_allowed_value" msgid "Sabaaşiran" msgstr "" #: DB:work_attribute_type_allowed_value/value:525 msgctxt "work_attribute_type_allowed_value" msgid "Sababuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:526 msgctxt "work_attribute_type_allowed_value" msgid "Sabakürdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:223 msgctxt "work_attribute_type_allowed_value" msgid "Sahānā" msgstr "Sahānā" #: DB:work_attribute_type_allowed_value/value:767 msgctxt "work_attribute_type_allowed_value" msgid "Sakil" msgstr "" #: DB:work_attribute_type_allowed_value/value:658 msgctxt "work_attribute_type_allowed_value" msgid "Salatüselam" msgstr "" #: DB:work_attribute_type_allowed_value/value:657 msgctxt "work_attribute_type_allowed_value" msgid "Salatıümmiye" msgstr "" #: DB:work_attribute_type_allowed_value/value:231 msgctxt "work_attribute_type_allowed_value" msgid "Sarasvati" msgstr "Sarasvati" #: DB:work_attribute_type_allowed_value/value:232 msgctxt "work_attribute_type_allowed_value" msgid "Sarasvatī manōhari" msgstr "Sarasvatī manōhari" #: DB:work_attribute_type_allowed_value/value:230 msgctxt "work_attribute_type_allowed_value" msgid "Sarasāngi" msgstr "Sarasāngi" #: DB:work_attribute_type_allowed_value/value:233 msgctxt "work_attribute_type_allowed_value" msgid "Saurāṣtraṁ" msgstr "Saurāṣtraṁ" #: DB:work_attribute_type_allowed_value/value:659 msgctxt "work_attribute_type_allowed_value" msgid "Savt" msgstr "" #: DB:work_attribute_type_allowed_value/value:660 msgctxt "work_attribute_type_allowed_value" msgid "Sazeseri" msgstr "" #: DB:work_attribute_type_allowed_value/value:527 msgctxt "work_attribute_type_allowed_value" msgid "Sazkar" msgstr "" #: DB:work_attribute_type_allowed_value/value:661 msgctxt "work_attribute_type_allowed_value" msgid "Sazsemaisi" msgstr "" #: DB:artist_alias_type/name:3 DB:label_alias_type/name:2 #: DB:place_alias_type/name:2 DB:recording_alias_type/name:2 #: DB:release_alias_type/name:2 DB:release_group_alias_type/name:2 #: DB:work_alias_type/name:2 DB:area_alias_type/name:3 #: DB:instrument_alias_type/name:2 DB:series_alias_type/name:2 #: DB:event_alias_type/name:2 msgctxt "alias_type" msgid "Search hint" msgstr "søgetip" #: DB:work_attribute_type_allowed_value/value:528 msgctxt "work_attribute_type_allowed_value" msgid "Segah" msgstr "" #: DB:work_attribute_type_allowed_value/value:529 msgctxt "work_attribute_type_allowed_value" msgid "Segah Araban" msgstr "" #: DB:work_attribute_type_allowed_value/value:530 msgctxt "work_attribute_type_allowed_value" msgid "Segah Maye" msgstr "" #: DB:work_attribute_type_allowed_value/value:662 msgctxt "work_attribute_type_allowed_value" msgid "Selam" msgstr "" #: DB:work_attribute_type_allowed_value/value:531 msgctxt "work_attribute_type_allowed_value" msgid "Selmek" msgstr "" #: DB:work_attribute_type_allowed_value/value:768 msgctxt "work_attribute_type_allowed_value" msgid "Semai" msgstr "" #: DB:work_attribute_type_allowed_value/value:235 msgctxt "work_attribute_type_allowed_value" msgid "Sencuruṭṭi" msgstr "Sencuruṭṭi" #: DB:work_attribute_type_allowed_value/value:532 msgctxt "work_attribute_type_allowed_value" msgid "Sengendaz" msgstr "" #: DB:work_attribute_type_allowed_value/value:766 msgctxt "work_attribute_type_allowed_value" msgid "Sengin Türkaksağı" msgstr "" #: DB:work_attribute_type_allowed_value/value:769 msgctxt "work_attribute_type_allowed_value" msgid "Senginsemai" msgstr "" #: DB:work_attribute_type_allowed_value/value:770 msgctxt "work_attribute_type_allowed_value" msgid "Serbest" msgstr "" #: DB:editor_collection_type/name:14 msgctxt "collection_type" msgid "Series" msgstr "" #: DB:series_alias_type/name:1 msgctxt "alias_type" msgid "Series name" msgstr "" #: DB:work_attribute_type_allowed_value/value:663 msgctxt "work_attribute_type_allowed_value" msgid "Seyir" msgstr "" #: DB:medium_format/name:53 msgctxt "medium_format" msgid "Shellac" msgstr "" #: DB:medium_format/description:53 msgctxt "medium_format" msgid "" "Shellac records were the most predominant type of gramophone record during " "the first half of the 20th century." msgstr "" #: DB:work_attribute_type_allowed_value/value:236 msgctxt "work_attribute_type_allowed_value" msgid "Simhavāhini" msgstr "Simhavāhini" #: DB:work_attribute_type_allowed_value/value:237 msgctxt "work_attribute_type_allowed_value" msgid "Simhēndra madhyamaṁ" msgstr "Simhēndra madhyamaṁ" #: DB:work_attribute_type_allowed_value/value:238 msgctxt "work_attribute_type_allowed_value" msgid "Sindhubhairavi" msgstr "Sindhubhairavi" #: DB:work_attribute_type_allowed_value/value:239 msgctxt "work_attribute_type_allowed_value" msgid "Sindhumandāri" msgstr "Sindhumandāri" #: DB:release_group_primary_type/name:2 msgctxt "release_group_primary_type" msgid "Single" msgstr "single" #: DB:work_attribute_type_allowed_value/value:533 msgctxt "work_attribute_type_allowed_value" msgid "Sipihr" msgstr "" #: DB:work_attribute_type_allowed_value/value:664 msgctxt "work_attribute_type_allowed_value" msgid "Sirto" msgstr "" #: DB:release_packaging/name:2 msgctxt "release_packaging" msgid "Slim Jewel Case" msgstr "" #: DB:release_packaging/name:11 msgctxt "release_packaging" msgid "Snap Case" msgstr "" #: DB:work_attribute_type_allowed_value/value:771 msgctxt "work_attribute_type_allowed_value" msgid "Sofyan" msgstr "" #: DB:work_type/name:5 msgctxt "work_type" msgid "Sonata" msgstr "sonate" #: DB:work_type/name:17 msgctxt "work_type" msgid "Song" msgstr "sang" #: DB:work_type/name:15 msgctxt "work_type" msgid "Song-cycle" msgstr "sangcyklus" #: DB:series_ordering_type/description:1 msgctxt "series_ordering_type" msgid "" "Sorts the items in the series automatically by their number attributes, " "using a natural sort order." msgstr "" #: DB:release_group_secondary_type/name:2 msgctxt "release_group_secondary_type" msgid "Soundtrack" msgstr "" #: DB:work_type/name:22 msgctxt "work_type" msgid "Soundtrack" msgstr "" #: DB:cover_art_archive.art_type/name:6 msgctxt "cover_art_type" msgid "Spine" msgstr "ryg" #: DB:release_group_secondary_type/name:3 msgctxt "release_group_secondary_type" msgid "Spokenword" msgstr "spokenword" #: DB:place_type/name:4 msgctxt "place_type" msgid "Stadium" msgstr "stadion" #: DB:cover_art_archive.art_type/name:10 msgctxt "cover_art_type" msgid "Sticker" msgstr "klistermærke" #: DB:instrument_type/name:2 msgctxt "instrument_type" msgid "String instrument" msgstr "" #: DB:place_type/name:1 msgctxt "place_type" msgid "Studio" msgstr "studie" #: DB:area_type/name:2 msgctxt "area_type" msgid "Subdivision" msgstr "underafdeling" #: DB:area_type/description:2 msgctxt "area_type" msgid "" "Subdivision is used for the main administrative divisions of a country, e.g." " California, Ontario, Okinawa. These are considered when displaying the " "parent areas for a given area." msgstr "" #: DB:work_attribute_type_allowed_value/value:244 msgctxt "work_attribute_type_allowed_value" msgid "Sucaritra" msgstr "Sucaritra" #: DB:work_type/name:6 msgctxt "work_type" msgid "Suite" msgstr "suite" #: DB:work_attribute_type_allowed_value/value:534 msgctxt "work_attribute_type_allowed_value" msgid "Sultani" msgstr "" #: DB:work_attribute_type_allowed_value/value:535 msgctxt "work_attribute_type_allowed_value" msgid "Sultani Irak" msgstr "" #: DB:work_attribute_type_allowed_value/value:536 msgctxt "work_attribute_type_allowed_value" msgid "Sultanıbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:537 msgctxt "work_attribute_type_allowed_value" msgid "Sultanıcedid" msgstr "" #: DB:work_attribute_type_allowed_value/value:538 msgctxt "work_attribute_type_allowed_value" msgid "Sultanıeviç" msgstr "" #: DB:work_attribute_type_allowed_value/value:539 msgctxt "work_attribute_type_allowed_value" msgid "Sultanıhüzzam" msgstr "" #: DB:work_attribute_type_allowed_value/value:540 msgctxt "work_attribute_type_allowed_value" msgid "Sultanısegah" msgstr "" #: DB:work_attribute_type_allowed_value/value:541 msgctxt "work_attribute_type_allowed_value" msgid "Sultanıyegah" msgstr "" #: DB:work_attribute_type_allowed_value/value:250 msgctxt "work_attribute_type_allowed_value" msgid "Sumanēśaranjani" msgstr "Sumanēśaranjani" #: DB:work_attribute_type_allowed_value/value:251 msgctxt "work_attribute_type_allowed_value" msgid "Sunādavinōdini" msgstr "Sunādavinōdini" #: DB:release_packaging/name:16 msgctxt "release_packaging" msgid "Super Jewel Box" msgstr "" #: DB:work_attribute_type_allowed_value/value:252 msgctxt "work_attribute_type_allowed_value" msgid "Suraṭi" msgstr "Suraṭi" #: DB:work_attribute_type_allowed_value/value:542 msgctxt "work_attribute_type_allowed_value" msgid "Suzidil" msgstr "" #: DB:work_attribute_type_allowed_value/value:543 msgctxt "work_attribute_type_allowed_value" msgid "Suzidilara" msgstr "" #: DB:work_attribute_type_allowed_value/value:544 msgctxt "work_attribute_type_allowed_value" msgid "Suzinak" msgstr "" #: DB:work_attribute_type_allowed_value/value:546 msgctxt "work_attribute_type_allowed_value" msgid "Suzinak Zirgüle" msgstr "" #: DB:work_attribute_type_allowed_value/value:545 msgctxt "work_attribute_type_allowed_value" msgid "Suzinak-ı Nev" msgstr "" #: DB:work_attribute_type_allowed_value/value:253 msgctxt "work_attribute_type_allowed_value" msgid "Svararanjani" msgstr "Svararanjani" #: DB:work_type/name:18 msgctxt "work_type" msgid "Symphonic poem" msgstr "" #: DB:work_type/name:16 msgctxt "work_type" msgid "Symphony" msgstr "symfoni" #: DB:work_attribute_type_allowed_value/value:547 msgctxt "work_attribute_type_allowed_value" msgid "Sünbüle" msgstr "" #: DB:work_attribute_type_allowed_value/value:772 msgctxt "work_attribute_type_allowed_value" msgid "Süreyya" msgstr "" #: DB:work_attribute_type_allowed_value/value:548 msgctxt "work_attribute_type_allowed_value" msgid "Sürurefza" msgstr "" #: DB:work_attribute_type_allowed_value/value:224 msgctxt "work_attribute_type_allowed_value" msgid "Sālaga bhairavi" msgstr "Sālaga bhairavi" #: DB:work_attribute_type_allowed_value/value:225 msgctxt "work_attribute_type_allowed_value" msgid "Sāma" msgstr "Sāma" #: DB:work_attribute_type_allowed_value/value:229 msgctxt "work_attribute_type_allowed_value" msgid "Sāranga" msgstr "Sāranga" #: DB:work_attribute_type_allowed_value/value:228 msgctxt "work_attribute_type_allowed_value" msgid "Sārāmati" msgstr "Sārāmati" #: DB:work_attribute_type_allowed_value/value:234 msgctxt "work_attribute_type_allowed_value" msgid "Sāvēri" msgstr "Sāvēri" #: DB:work_attribute_type_allowed_value/value:573 msgctxt "work_attribute_type_allowed_value" msgid "Tahir" msgstr "" #: DB:work_attribute_type_allowed_value/value:574 msgctxt "work_attribute_type_allowed_value" msgid "Tahirbuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:667 msgctxt "work_attribute_type_allowed_value" msgid "Taksim" msgstr "" #: DB:work_attribute_type_allowed_value/value:255 msgctxt "work_attribute_type_allowed_value" msgid "Tanarūpi" msgstr "Tanarūpi" #: DB:work_attribute_type_allowed_value/value:668 msgctxt "work_attribute_type_allowed_value" msgid "Tango" msgstr "" #: DB:work_attribute_type_allowed_value/value:575 msgctxt "work_attribute_type_allowed_value" msgid "Tarz-ı Cedid" msgstr "" #: DB:work_attribute_type_allowed_value/value:576 msgctxt "work_attribute_type_allowed_value" msgid "Tarz-ı Cihan" msgstr "" #: DB:work_attribute_type_allowed_value/value:577 msgctxt "work_attribute_type_allowed_value" msgid "Tarz-ı Nevin" msgstr "" #: DB:work_attribute_type_allowed_value/value:669 msgctxt "work_attribute_type_allowed_value" msgid "Tavşanca" msgstr "" #: DB:work_attribute_type_allowed_value/value:578 msgctxt "work_attribute_type_allowed_value" msgid "Tebriz" msgstr "" #: DB:work_attribute_type_allowed_value/value:774 msgctxt "work_attribute_type_allowed_value" msgid "Tek Vuruş" msgstr "" #: DB:work_attribute_type_allowed_value/value:670 msgctxt "work_attribute_type_allowed_value" msgid "Tekbir" msgstr "" #: DB:work_attribute_type_allowed_value/value:671 msgctxt "work_attribute_type_allowed_value" msgid "Temcidmünacatı" msgstr "" #: DB:work_attribute_type_allowed_value/value:579 msgctxt "work_attribute_type_allowed_value" msgid "Tereşşüd" msgstr "" #: DB:work_attribute_type_allowed_value/value:672 msgctxt "work_attribute_type_allowed_value" msgid "Tesbih" msgstr "" #: DB:work_attribute_type_allowed_value/value:673 msgctxt "work_attribute_type_allowed_value" msgid "Tesbihilahi" msgstr "" #: DB:work_attribute_type_allowed_value/value:680 msgctxt "work_attribute_type_allowed_value" msgid "Tevhid" msgstr "" #: DB:work_attribute_type_allowed_value/value:674 msgctxt "work_attribute_type_allowed_value" msgid "Tevşihilahi" msgstr "" #: DB:medium_format/description:70 msgctxt "medium_format" msgid "" "The CD layer of a DVDplus. The DVD layer should be added as a separate " "medium." msgstr "" #: DB:medium_format/description:67 msgctxt "medium_format" msgid "" "The CD layer of a DualDisc. The DVD layer should be added as a separate " "medium." msgstr "" #: DB:medium_format/description:63 msgctxt "medium_format" msgid "" "The CD layer of a hybrid SACD. The SACD layer should be added as a separate " "medium." msgstr "" #: DB:medium_format/description:60 msgctxt "medium_format" msgid "" "The Capacitance Electronic Disc (CED) is an analog video disc playback " "system developed by RCA, in which video and audio could be played back on a " "TV set using a special needle and high-density groove system similar to " "phonograph records." msgstr "" #: DB:medium_format/description:68 DB:medium_format/description:69 msgctxt "medium_format" msgid "" "The DVD (audio) layer of a DVDplus. The CD layer should be added as a " "separate medium." msgstr "" #: DB:medium_format/description:65 msgctxt "medium_format" msgid "" "The DVD (audio) layer of a DualDisc. The CD layer should be added as a " "separate medium." msgstr "" #: DB:medium_format/description:66 msgctxt "medium_format" msgid "" "The DVD (video) layer of a DualDisc. The CD layer should be added as a " "separate medium." msgstr "" #: DB:medium_format/description:64 msgctxt "medium_format" msgid "" "The SACD layer of a hybrid SACD. The CD layer should be added as a separate " "medium." msgstr "" #: DB:work_type/description:7 msgctxt "work_type" msgid "" "The madrigal is a type of secular vocal music composition. In its original " "form, it had no instrumental accompaniment, although accompaniment is much " "more common in later madrigals." msgstr "" #: DB:work_type/description:24 msgctxt "work_type" msgid "" "The operetta is a genre of light opera, in terms both of music and subject " "matter. Operettas are generally short and include spoken parts." msgstr "" #: DB:release_packaging/description:1 msgctxt "release_packaging" msgid "The traditional CD case, made of hard, brittle plastic." msgstr "" #: DB:work_type/description:23 msgctxt "work_type" msgid "" "This represents literary works written in prose, that is, written in " "relatively ordinary language without metrical structure (e.g. novels, short " "stories, essays...)." msgstr "" #: DB:work_attribute_type_allowed_value/value:256 msgctxt "work_attribute_type_allowed_value" msgid "Tillāng" msgstr "Tillāng" #: DB:work_attribute_type_allowed_value/value:288 msgctxt "work_attribute_type_allowed_value" msgid "Tiśra-jāti tripuṭa" msgstr "Tiśra-jāti tripuṭa" #: DB:work_attribute_type_allowed_value/value:284 msgctxt "work_attribute_type_allowed_value" msgid "Tiśra-jāti ēka" msgstr "Tiśra-jāti ēka" #: DB:series_type/name:7 msgctxt "series_type" msgid "Tour" msgstr "" #: DB:cover_art_archive.art_type/name:7 msgctxt "cover_art_type" msgid "Track" msgstr "" #: DB:cover_art_archive.art_type/name:9 msgctxt "cover_art_type" msgid "Tray" msgstr "" #: DB:work_attribute_type_allowed_value/value:775 msgctxt "work_attribute_type_allowed_value" msgid "Türk Darbı" msgstr "" #: DB:work_attribute_type_allowed_value/value:776 msgctxt "work_attribute_type_allowed_value" msgid "Türkaksağı" msgstr "" #: DB:work_attribute_type_allowed_value/value:580 msgctxt "work_attribute_type_allowed_value" msgid "Türki Hicaz" msgstr "" #: DB:work_attribute_type_allowed_value/value:799 msgctxt "work_attribute_type_allowed_value" msgid "Türkmen" msgstr "" #: DB:work_attribute_type_allowed_value/value:675 msgctxt "work_attribute_type_allowed_value" msgid "Türkü" msgstr "" #: DB:work_attribute_type_allowed_value/value:581 msgctxt "work_attribute_type_allowed_value" msgid "Tüvanger" msgstr "" #: DB:work_attribute_type/name:5 msgctxt "work_attribute_type" msgid "Tāla (Carnatic)" msgstr "" #: DB:work_attribute_type_allowed_value/value:257 msgctxt "work_attribute_type_allowed_value" msgid "Tōḍi" msgstr "Tōḍi" #: DB:medium_format/name:28 msgctxt "medium_format" msgid "UMD" msgstr "UMD" #: DB:medium_format/name:26 msgctxt "medium_format" msgid "USB Flash Drive" msgstr "" #: DB:work_attribute_type_allowed_value/value:258 msgctxt "work_attribute_type_allowed_value" msgid "Udaya ravicandrika" msgstr "Udaya ravicandrika" #: DB:work_attribute_type/name:17 msgctxt "work_attribute_type" msgid "Usul (Ottoman, Turkish)" msgstr "" #: DB:work_attribute_type_allowed_value/value:676 msgctxt "work_attribute_type_allowed_value" msgid "Uzunhava" msgstr "" #: DB:work_attribute_type_allowed_value/value:601 msgctxt "work_attribute_type_allowed_value" msgid "Uzzal" msgstr "" #: DB:work_attribute_type_allowed_value/value:582 msgctxt "work_attribute_type_allowed_value" msgid "Uşşak" msgstr "" #: DB:work_attribute_type_allowed_value/value:583 msgctxt "work_attribute_type_allowed_value" msgid "Uşşak Ruy-i Nikriz" msgstr "" #: DB:work_attribute_type_allowed_value/value:584 msgctxt "work_attribute_type_allowed_value" msgid "Uşşakaşiran" msgstr "" #: DB:medium_format/name:22 msgctxt "medium_format" msgid "VCD" msgstr "VCD" #: DB:medium_format/name:59 msgctxt "medium_format" msgid "VHD" msgstr "" #: DB:medium_format/name:21 msgctxt "medium_format" msgid "VHS" msgstr "VHS" #: DB:work_attribute_type_allowed_value/value:261 msgctxt "work_attribute_type_allowed_value" msgid "Vakuḷābharaṇaṁ" msgstr "Vakuḷābharaṇaṁ" #: DB:work_attribute_type_allowed_value/value:262 msgctxt "work_attribute_type_allowed_value" msgid "Valaji" msgstr "Valaji" #: DB:work_attribute_type_allowed_value/value:263 msgctxt "work_attribute_type_allowed_value" msgid "Vandanadhāriṇi" msgstr "Vandanadhāriṇi" #: DB:work_attribute_type_allowed_value/value:265 msgctxt "work_attribute_type_allowed_value" msgid "Varamu" msgstr "Varamu" #: DB:work_attribute_type_allowed_value/value:264 msgctxt "work_attribute_type_allowed_value" msgid "Varāḷi" msgstr "Varāḷi" #: DB:work_attribute_type_allowed_value/value:266 msgctxt "work_attribute_type_allowed_value" msgid "Varṇarūpini" msgstr "Varṇarūpini" #: DB:work_attribute_type_allowed_value/value:267 msgctxt "work_attribute_type_allowed_value" msgid "Vasanta" msgstr "Vasanta" #: DB:work_attribute_type_allowed_value/value:268 msgctxt "work_attribute_type_allowed_value" msgid "Vasanta varāḷi" msgstr "Vasanta varāḷi" #: DB:work_attribute_type_allowed_value/value:269 msgctxt "work_attribute_type_allowed_value" msgid "Vasantabhairavi" msgstr "Vasantabhairavi" #: DB:work_attribute_type_allowed_value/value:585 msgctxt "work_attribute_type_allowed_value" msgid "Vecd-i Dil" msgstr "" #: DB:work_attribute_type_allowed_value/value:586 msgctxt "work_attribute_type_allowed_value" msgid "Vech-i Arazbar" msgstr "" #: DB:work_attribute_type_allowed_value/value:587 msgctxt "work_attribute_type_allowed_value" msgid "Vech-i Dil" msgstr "" #: DB:work_attribute_type_allowed_value/value:588 msgctxt "work_attribute_type_allowed_value" msgid "Vech-i Şehnaz" msgstr "" #: DB:place_type/name:2 msgctxt "place_type" msgid "Venue" msgstr "spillested" #: DB:medium_format/description:59 msgctxt "medium_format" msgid "" "Video High Density (VHD) was a videodisc format which was marketed " "predominantly in Japan by JVC." msgstr "" #: DB:work_attribute_type_allowed_value/value:273 msgctxt "work_attribute_type_allowed_value" msgid "Vijayanagari" msgstr "Vijayanagari" #: DB:work_attribute_type_allowed_value/value:274 msgctxt "work_attribute_type_allowed_value" msgid "Vijayasarasvati" msgstr "Vijayasarasvati" #: DB:work_attribute_type_allowed_value/value:275 msgctxt "work_attribute_type_allowed_value" msgid "Vijayaśrī" msgstr "Vijayaśrī" #: DB:medium_format/name:7 msgctxt "medium_format" msgid "Vinyl" msgstr "vinyl" #: DB:medium_format/name:48 msgctxt "medium_format" msgid "VinylDisc" msgstr "" #: DB:work_attribute_type_allowed_value/value:259 msgctxt "work_attribute_type_allowed_value" msgid "Vācaspati" msgstr "Vācaspati" #: DB:work_attribute_type_allowed_value/value:260 msgctxt "work_attribute_type_allowed_value" msgid "Vāgadīśvari" msgstr "Vāgadīśvari" #: DB:work_attribute_type_allowed_value/value:270 msgctxt "work_attribute_type_allowed_value" msgid "Vāsanti" msgstr "Vāsanti" #: DB:work_attribute_type_allowed_value/value:271 msgctxt "work_attribute_type_allowed_value" msgid "Vēgavāhiṇi" msgstr "Vēgavāhiṇi" #: DB:work_attribute_type_allowed_value/value:272 msgctxt "work_attribute_type_allowed_value" msgid "Vēlāvali" msgstr "Vēlāvali" #: DB:work_attribute_type_allowed_value/value:276 msgctxt "work_attribute_type_allowed_value" msgid "Vīra vasantaṁ" msgstr "Vīra vasantaṁ" #: DB:cover_art_archive.art_type/name:13 msgctxt "cover_art_type" msgid "Watermark" msgstr "" #: DB:medium_format/name:14 msgctxt "medium_format" msgid "Wax Cylinder" msgstr "voksrulle" #: DB:instrument_type/name:1 msgctxt "instrument_type" msgid "Wind instrument" msgstr "" #: DB:editor_collection_type/name:3 msgctxt "collection_type" msgid "Wishlist" msgstr "" #: DB:editor_collection_type/name:15 msgctxt "collection_type" msgid "Work" msgstr "" #: DB:series_type/name:4 msgctxt "series_type" msgid "Work" msgstr "" #: DB:work_alias_type/name:1 msgctxt "alias_type" msgid "Work name" msgstr "værknavn" #: DB:work_attribute_type_allowed_value/value:277 msgctxt "work_attribute_type_allowed_value" msgid "Yadukula kāṁbōji" msgstr "Yadukula kāṁbōji" #: DB:work_attribute_type_allowed_value/value:278 msgctxt "work_attribute_type_allowed_value" msgid "Yamuna kalyāṇi" msgstr "Yamuna kalyāṇi" #: DB:work_attribute_type_allowed_value/value:589 msgctxt "work_attribute_type_allowed_value" msgid "Yegah" msgstr "" #: DB:work_attribute_type_allowed_value/value:590 msgctxt "work_attribute_type_allowed_value" msgid "Yegah-ı Acemi" msgstr "" #: DB:work_attribute_type_allowed_value/value:677 #: DB:work_attribute_type_allowed_value/value:777 msgctxt "work_attribute_type_allowed_value" msgid "Yürüksemai" msgstr "" #: DB:work_attribute_type_allowed_value/value:778 msgctxt "work_attribute_type_allowed_value" msgid "Yürüksemai II" msgstr "" #: DB:work_attribute_type_allowed_value/value:779 msgctxt "work_attribute_type_allowed_value" msgid "Yürüksofyan" msgstr "" #: DB:work_attribute_type_allowed_value/value:780 msgctxt "work_attribute_type_allowed_value" msgid "Zafer" msgstr "" #: DB:work_type/name:19 msgctxt "work_type" msgid "Zarzuela" msgstr "zarzuela" #: DB:work_attribute_type_allowed_value/value:591 msgctxt "work_attribute_type_allowed_value" msgid "Zavil" msgstr "" #: DB:work_attribute_type_allowed_value/value:592 msgctxt "work_attribute_type_allowed_value" msgid "Zavilaşiran" msgstr "" #: DB:work_attribute_type_allowed_value/value:781 msgctxt "work_attribute_type_allowed_value" msgid "Zencir" msgstr "" #: DB:work_attribute_type_allowed_value/value:593 msgctxt "work_attribute_type_allowed_value" msgid "Zengule" msgstr "" #: DB:work_attribute_type_allowed_value/value:594 msgctxt "work_attribute_type_allowed_value" msgid "Zengulebuselik" msgstr "" #: DB:work_attribute_type_allowed_value/value:595 msgctxt "work_attribute_type_allowed_value" msgid "Zenguleli Kürdi" msgstr "" #: DB:work_attribute_type_allowed_value/value:597 msgctxt "work_attribute_type_allowed_value" msgid "Zevk-ü Tarab" msgstr "" #: DB:work_attribute_type_allowed_value/value:596 msgctxt "work_attribute_type_allowed_value" msgid "Zevk-ı Dil" msgstr "" #: DB:work_attribute_type_allowed_value/value:678 msgctxt "work_attribute_type_allowed_value" msgid "Zeybek" msgstr "" #: DB:work_attribute_type_allowed_value/value:598 msgctxt "work_attribute_type_allowed_value" msgid "Zilkeş" msgstr "" #: DB:work_attribute_type_allowed_value/value:599 msgctxt "work_attribute_type_allowed_value" msgid "Zirefkend" msgstr "" #: DB:work_attribute_type_allowed_value/value:298 #: DB:work_attribute_type_allowed_value/value:602 #: DB:work_attribute_type_allowed_value/value:681 msgctxt "work_attribute_type_allowed_value" msgid "ambiguous" msgstr "" #: DB:medium_format/name:27 msgctxt "medium_format" msgid "slotMusic" msgstr "slotMusic" #: DB:work_attribute_type_allowed_value/value:340 msgctxt "work_attribute_type_allowed_value" msgid "Çargah" msgstr "" #: DB:work_attribute_type_allowed_value/value:341 msgctxt "work_attribute_type_allowed_value" msgid "Çargah (Yeni)" msgstr "" #: DB:work_attribute_type_allowed_value/value:342 msgctxt "work_attribute_type_allowed_value" msgid "Çehar Agazin" msgstr "" #: DB:work_attribute_type_allowed_value/value:706 msgctxt "work_attribute_type_allowed_value" msgid "Çenber" msgstr "" #: DB:work_attribute_type_allowed_value/value:707 msgctxt "work_attribute_type_allowed_value" msgid "Çeng-i Harbi" msgstr "" #: DB:work_attribute_type_allowed_value/value:708 msgctxt "work_attribute_type_allowed_value" msgid "Çifteaksak" msgstr "" #: DB:work_attribute_type_allowed_value/value:709 msgctxt "work_attribute_type_allowed_value" msgid "Çiftedüyek" msgstr "" #: DB:work_attribute_type_allowed_value/value:710 msgctxt "work_attribute_type_allowed_value" msgid "Çiftesofyan" msgstr "" #: DB:work_attribute_type_allowed_value/value:679 msgctxt "work_attribute_type_allowed_value" msgid "Çiftetelli" msgstr "" #: DB:work_attribute_type_allowed_value/value:609 msgctxt "work_attribute_type_allowed_value" msgid "Çocuk şarkısı" msgstr "" #: DB:work_type/name:20 msgctxt "work_type" msgid "Étude" msgstr "etude" #: DB:work_attribute_type_allowed_value/value:653 msgctxt "work_attribute_type_allowed_value" msgid "Örnek Öz" msgstr "" #: DB:work_attribute_type_allowed_value/value:35 msgctxt "work_attribute_type_allowed_value" msgid "Ābhēri" msgstr "Ābhēri" #: DB:work_attribute_type_allowed_value/value:36 msgctxt "work_attribute_type_allowed_value" msgid "Ābhōgi" msgstr "Ābhōgi" #: DB:work_attribute_type_allowed_value/value:279 msgctxt "work_attribute_type_allowed_value" msgid "Ādi" msgstr "Ādi" #: DB:work_attribute_type_allowed_value/value:290 msgctxt "work_attribute_type_allowed_value" msgid "Ādi (Tiśra naḍe)" msgstr "Ādi (Tiśra naḍe)" #: DB:work_attribute_type_allowed_value/value:37 msgctxt "work_attribute_type_allowed_value" msgid "Āhir bhairav" msgstr "Āhir bhairav" #: DB:work_attribute_type_allowed_value/value:38 msgctxt "work_attribute_type_allowed_value" msgid "Āhiri" msgstr "Āhiri" #: DB:work_attribute_type_allowed_value/value:41 msgctxt "work_attribute_type_allowed_value" msgid "Ānandabhairavi" msgstr "Ānandabhairavi" #: DB:work_attribute_type_allowed_value/value:42 msgctxt "work_attribute_type_allowed_value" msgid "Āndōḷika" msgstr "Āndōḷika" #: DB:work_attribute_type_allowed_value/value:43 msgctxt "work_attribute_type_allowed_value" msgid "Ārabhi" msgstr "Ārabhi" #: DB:work_attribute_type_allowed_value/value:283 msgctxt "work_attribute_type_allowed_value" msgid "Ēka" msgstr "Ēka" #: DB:work_attribute_type_allowed_value/value:436 msgctxt "work_attribute_type_allowed_value" msgid "İbrahimi" msgstr "" #: DB:work_attribute_type_allowed_value/value:740 msgctxt "work_attribute_type_allowed_value" msgid "İki Bir" msgstr "" #: DB:work_attribute_type_allowed_value/value:741 msgctxt "work_attribute_type_allowed_value" msgid "İkiz Aksak" msgstr "" #: DB:work_attribute_type_allowed_value/value:620 msgctxt "work_attribute_type_allowed_value" msgid "İlahi" msgstr "" #: DB:work_attribute_type_allowed_value/value:226 msgctxt "work_attribute_type_allowed_value" msgid "Śankarābharaṇaṁ" msgstr "Śankarābharaṇaṁ" #: DB:work_attribute_type_allowed_value/value:240 msgctxt "work_attribute_type_allowed_value" msgid "Śivaranjani" msgstr "Śivaranjani" #: DB:work_attribute_type_allowed_value/value:241 msgctxt "work_attribute_type_allowed_value" msgid "Śrī" msgstr "Śrī" #: DB:work_attribute_type_allowed_value/value:242 msgctxt "work_attribute_type_allowed_value" msgid "Śrīranjani" msgstr "Śrīranjani" #: DB:work_attribute_type_allowed_value/value:243 msgctxt "work_attribute_type_allowed_value" msgid "Śubhapantuvarāḷi" msgstr "Śubhapantuvarāḷi" #: DB:work_attribute_type_allowed_value/value:245 msgctxt "work_attribute_type_allowed_value" msgid "Śudda sārang" msgstr "Śudda sārang" #: DB:work_attribute_type_allowed_value/value:246 msgctxt "work_attribute_type_allowed_value" msgid "Śudda sāvēri" msgstr "Śudda sāvēri" #: DB:work_attribute_type_allowed_value/value:247 msgctxt "work_attribute_type_allowed_value" msgid "Śuddadhanyāsi" msgstr "Śuddadhanyāsi" #: DB:work_attribute_type_allowed_value/value:248 msgctxt "work_attribute_type_allowed_value" msgid "Śuddasīmantini" msgstr "Śuddasīmantini" #: DB:work_attribute_type_allowed_value/value:254 msgctxt "work_attribute_type_allowed_value" msgid "Śyāṁ kaḷyāṇ" msgstr "Śyāṁ kaḷyāṇ" #: DB:work_attribute_type_allowed_value/value:249 msgctxt "work_attribute_type_allowed_value" msgid "Śūḷiṇi" msgstr "Śūḷiṇi" #: DB:work_attribute_type_allowed_value/value:773 msgctxt "work_attribute_type_allowed_value" msgid "Şark Devr-i Revanı" msgstr "Şark Devr-i Revanı" #: DB:work_attribute_type_allowed_value/value:665 msgctxt "work_attribute_type_allowed_value" msgid "Şarkı" msgstr "Şarkı" #: DB:work_attribute_type_allowed_value/value:549 msgctxt "work_attribute_type_allowed_value" msgid "Şedaraban" msgstr "Şedaraban" #: DB:work_attribute_type_allowed_value/value:550 msgctxt "work_attribute_type_allowed_value" msgid "Şedd-i Saba" msgstr "Şedd-i Saba" #: DB:work_attribute_type_allowed_value/value:551 msgctxt "work_attribute_type_allowed_value" msgid "Şehnaz" msgstr "Şehnaz" #: DB:work_attribute_type_allowed_value/value:554 msgctxt "work_attribute_type_allowed_value" msgid "Şehnaz Nişaburek" msgstr "Şehnaz Nişaburek" #: DB:work_attribute_type_allowed_value/value:552 msgctxt "work_attribute_type_allowed_value" msgid "Şehnazbuselik" msgstr "Şehnazbuselik" #: DB:work_attribute_type_allowed_value/value:553 msgctxt "work_attribute_type_allowed_value" msgid "Şehnazhaveran" msgstr "Şehnazhaveran" #: DB:work_attribute_type_allowed_value/value:555 msgctxt "work_attribute_type_allowed_value" msgid "Şemsefruz" msgstr "Şemsefruz" #: DB:work_attribute_type_allowed_value/value:556 msgctxt "work_attribute_type_allowed_value" msgid "Şeref-i Hamidi" msgstr "Şeref-i Hamidi" #: DB:work_attribute_type_allowed_value/value:557 msgctxt "work_attribute_type_allowed_value" msgid "Şerefnuma" msgstr "Şerefnuma" #: DB:work_attribute_type_allowed_value/value:558 msgctxt "work_attribute_type_allowed_value" msgid "Şevk" msgstr "Şevk" #: DB:work_attribute_type_allowed_value/value:561 msgctxt "work_attribute_type_allowed_value" msgid "Şevk-i Cedid" msgstr "Şevk-i Cedid" #: DB:work_attribute_type_allowed_value/value:563 msgctxt "work_attribute_type_allowed_value" msgid "Şevk-i Cihan" msgstr "Şevk-i Cihan" #: DB:work_attribute_type_allowed_value/value:562 msgctxt "work_attribute_type_allowed_value" msgid "Şevk-i Dil" msgstr "Şevk-i Dil" #: DB:work_attribute_type_allowed_value/value:564 msgctxt "work_attribute_type_allowed_value" msgid "Şevk-i Serab" msgstr "Şevk-i Serab" #: DB:work_attribute_type_allowed_value/value:559 msgctxt "work_attribute_type_allowed_value" msgid "Şevkaver" msgstr "Şevkaver" #: DB:work_attribute_type_allowed_value/value:560 msgctxt "work_attribute_type_allowed_value" msgid "Şevkefza" msgstr "Şevkefza" #: DB:work_attribute_type_allowed_value/value:565 msgctxt "work_attribute_type_allowed_value" msgid "Şevknüma" msgstr "Şevknüma" #: DB:work_attribute_type_allowed_value/value:566 msgctxt "work_attribute_type_allowed_value" msgid "Şevkutarab" msgstr "Şevkutarab" #: DB:work_attribute_type_allowed_value/value:567 msgctxt "work_attribute_type_allowed_value" msgid "Şinaver" msgstr "Şinaver" #: DB:work_attribute_type_allowed_value/value:568 msgctxt "work_attribute_type_allowed_value" msgid "Şiraz" msgstr "Şiraz" #: DB:work_attribute_type_allowed_value/value:569 msgctxt "work_attribute_type_allowed_value" msgid "Şivekar" msgstr "Şivekar" #: DB:work_attribute_type_allowed_value/value:570 msgctxt "work_attribute_type_allowed_value" msgid "Şivekeş" msgstr "Şivekeş" #: DB:work_attribute_type_allowed_value/value:571 msgctxt "work_attribute_type_allowed_value" msgid "Şivenüma" msgstr "Şivenüma" #: DB:work_attribute_type_allowed_value/value:572 msgctxt "work_attribute_type_allowed_value" msgid "Şuri" msgstr "Şuri" #: DB:work_attribute_type_allowed_value/value:666 msgctxt "work_attribute_type_allowed_value" msgid "Şuğul" msgstr "Şuğul" #: DB:work_attribute_type_allowed_value/value:227 msgctxt "work_attribute_type_allowed_value" msgid "Ṣanmukhapriya" msgstr "Ṣanmukhapriya" picard-release-1.4.2/po/attributes/de.po000066400000000000000000004645311310410472100201400ustar00rootroot00000000000000# Translators: # Translators: # nikki, 2014 # nikki2 , 2012 # Daniel Schury , 2011 # Daniel Schury