pax_global_header00006660000000000000000000000064147730411770014525gustar00rootroot0000000000000052 comment=e99d1603f84e816afe9bfb02702cffa728fe9510 ocaml-metadata-0.3.1/000077500000000000000000000000001477304117700143775ustar00rootroot00000000000000ocaml-metadata-0.3.1/.github/000077500000000000000000000000001477304117700157375ustar00rootroot00000000000000ocaml-metadata-0.3.1/.github/workflows/000077500000000000000000000000001477304117700177745ustar00rootroot00000000000000ocaml-metadata-0.3.1/.github/workflows/doc.yml000066400000000000000000000015751477304117700212740ustar00rootroot00000000000000name: Documentation on: push: branches: - main jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup OCaml uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: 5.3 - name: Pin locally run: opam pin -y add -n . - name: Install locally run: opam install -y odoc metadata - name: Build doc run: opam exec dune build @doc - name: Upload website artifact uses: actions/upload-pages-artifact@v2 with: path: _build/default/_doc/_html deploy: needs: build permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Deploy website id: deployment uses: actions/deploy-pages@v2 ocaml-metadata-0.3.1/.github/workflows/main.yml000066400000000000000000000010501477304117700214370ustar00rootroot00000000000000name: Build on: [push] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup OCaml uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: 5.3 - name: Install required packages run: sudo apt install ffmpeg - name: Pin package run: opam pin add -n . - name: Install dependencies run: opam depext -yt metadata - name: Build and install run: opam install -t . - name: Run tests run: eval $(opam env) && dune build @citest ocaml-metadata-0.3.1/.gitignore000066400000000000000000000000211477304117700163600ustar00rootroot00000000000000_build *~ .*.sw* ocaml-metadata-0.3.1/.ocamlformat000066400000000000000000000003371477304117700167070ustar00rootroot00000000000000version=0.27.0 profile = conventional break-separators = after space-around-lists = false doc-comments = before match-indent = 2 match-indent-nested = always parens-ite exp-grouping = preserve module-item-spacing = compact ocaml-metadata-0.3.1/CHANGES.md000066400000000000000000000013141477304117700157700ustar00rootroot000000000000000.3.1 (2024-04-01) 🃏 ===== - Add support for `ogg/flac` metadata. 0.3.0 (2024-03-24) ===== - Add basic example. - Add optional custom parser argument to override the default parsing mechanism. - Add binary format for encoding frames. - Update default metadata mappings to follow musicbrainz's picard mapping. - Add `MIME` module to guess MIME type of files (#4). - Generic RIFF format parser, adds support for wave files (#6). 0.2.0 (2023-07-01) ===== - Add support for FLAC. - id3v2: use "bpm" instead of "tempo". - id3v2: convert "tlen" to "duration". - id3v2: implement partial support for rendering. - Fix charset conversion from utf16. - Fixed MP4 parsing. 0.1.0 (2023-02-08) ===== - Initial release. ocaml-metadata-0.3.1/LICENSE000066400000000000000000001045151477304117700154120ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. 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. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey 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; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. 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. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. 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 state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This 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 3 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program 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, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU 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 Lesser General Public License instead of this License. But first, please read . ocaml-metadata-0.3.1/Makefile000066400000000000000000000001151477304117700160340ustar00rootroot00000000000000all: build build: @dune build doc: @dune build @doc clean: @dune clean ocaml-metadata-0.3.1/README.md000066400000000000000000000015671477304117700156670ustar00rootroot00000000000000The metadata library ==================== A pure OCaml library to read metadata from various formats. For now, are supported: - audio formats: ID3v1 and ID3v2 (for mp3), ogg/vorbis, ogg/opus, flac and wav - image formats: jpeg and png - video formats: mp4 and avi Usage ----- Basic usage is ```ocaml let () = let metadata = Metadata.parse_file "test.mp3" in List.iter (fun (k,v) -> Printf.printf "- %s: %s\n" k v) metadata ``` In the above example, the function `Metadata.parse_file` takes a file name as argument and returns an association list describing its metadata. It consists of pairs of strings being the name of the metadata and its value. Installing ---------- The preferred way is via opam: ```bash opam install metadata ``` Other libraries --------------- - [ocaml-taglib](https://github.com/savonet/ocaml-taglib): for tags from audio files (mp3, ogg, etc.) ocaml-metadata-0.3.1/dune-project000066400000000000000000000006651477304117700167300ustar00rootroot00000000000000(lang dune 3.6) (version 0.3.1) (name metadata) (source (github savonet/ocaml-metadata)) (license GPL-3.0-or-later) (authors "Samuel Mimram ") (maintainers "Samuel Mimram ") (generate_opam_files true) (package (name metadata) (synopsis "Read metadata from various file formats") (description "A pure OCaml library for reading files from various formats.") (depends (ocaml (>= 4.14.0)) ) ) ocaml-metadata-0.3.1/examples/000077500000000000000000000000001477304117700162155ustar00rootroot00000000000000ocaml-metadata-0.3.1/examples/Makefile000066400000000000000000000004671477304117700176640ustar00rootroot00000000000000META = dune exec -- ./meta.exe all: test id3v2 test: @dune test @dune build @citest id3v2: @for i in id3v2/*.mp3; do \ echo; echo Testing $$i; echo; \ $(META) -b "$$i"; \ done test-mp3: @for i in ~/Music/misc/*; do \ echo; echo Testing $$i; echo; \ $(META) -b "$$i"; \ done .PHONY: id3v2 ocaml-metadata-0.3.1/examples/README.md000066400000000000000000000001341477304117700174720ustar00rootroot00000000000000The main example is [meta.ml](meta.ml) which is a program parsing the metadata of any file. ocaml-metadata-0.3.1/examples/artist_title000077500000000000000000000001021477304117700206430ustar00rootroot00000000000000#!/bin/sh dune exec --no-print-directory ./artist_title.exe -- $@ ocaml-metadata-0.3.1/examples/artist_title.ml000066400000000000000000000020441477304117700212560ustar00rootroot00000000000000(** Show artist - title of a (list of) files. *) let () = let fname = ref [] in Arg.parse [] (fun f -> fname := f :: !fname) "artist_title files"; if !fname = [] then ( Printf.eprintf "Please enter a filename.\n%!"; exit 1); let fname = !fname |> List.map (fun f -> if String.contains f '*' then ( let d = Filename.dirname f in let f = Filename.basename f |> Str.global_replace (Str.regexp "\\*") ".*" |> Str.regexp in let files = Sys.readdir d |> Array.to_list |> List.filter (fun s -> Str.string_match f s 0) in List.map (fun f -> d ^ "/" ^ f) files) else [f]) |> List.flatten in List.iter (fun fname -> let m = Metadata.Any.parse_file fname in let artist = List.assoc_opt "artist" m |> Option.value ~default:"?" in let title = List.assoc_opt "title" m |> Option.value ~default:"?" in Printf.printf "%s - %s\n%!" artist title ) fname ocaml-metadata-0.3.1/examples/basic.ml000066400000000000000000000002441477304117700176300ustar00rootroot00000000000000(** Basic usage of the library. *) let () = let metadata = Metadata.parse_file "test.mp3" in List.iter (fun (k, v) -> Printf.printf "- %s: %s\n" k v) metadata ocaml-metadata-0.3.1/examples/dump000077500000000000000000000000721477304117700171070ustar00rootroot00000000000000#!/bin/sh dune exec --no-print-directory ./dump.exe -- $@ ocaml-metadata-0.3.1/examples/dump.ml000066400000000000000000000012471477304117700175200ustar00rootroot00000000000000let () = let fname = ref "" in let format = ref "id3v2" in let outfile = ref None in Arg.parse [ ("-f", Arg.Set_string format, "File format."); ( "-o", Arg.String (fun s -> outfile := Some s), "Output file (default is standard output)." ); ] (fun f -> fname := f) "dump [options] file"; let dump = match !format with | "id3v2" -> Metadata.ID3v2.dump_file | _ -> failwith "Unknown format." in let fname = !fname in if fname = "" then ( Printf.eprintf "Please enter a filename.\n%!"; exit 1); let oc = match !outfile with Some f -> open_out f | None -> stdout in output_string oc (dump fname) ocaml-metadata-0.3.1/examples/dune000066400000000000000000000052201477304117700170720ustar00rootroot00000000000000(executable (name meta) (modules meta) (libraries str metadata)) (executable (name artist_title) (modules artist_title) (libraries str metadata)) (executable (name basic) (modules basic) (libraries metadata)) (executable (name dump) (modules dump) (libraries metadata)) (executable (name mimetype) (modules mimetype) (libraries metadata)) (executable (name test) (modules test) (optional) (libraries metadata)) (rule (alias citest) (deps test.mp3 test.png test.jpg test.avi test.mp4 ; test.mkv ; test.webm test.wav (glob_files id3v2/*.mp3)) (action (progn (run ./basic.exe) (run ./test.exe) (run ./artist_title.exe %{deps}) (run ./meta.exe %{deps})))) (rule (target test.mp3) (action (run ffmpeg -f lavfi -i "sine=frequency=440:sample_rate=44100:duration=10" -f mp3 -write_id3v1 true -metadata "title=The title" -metadata "artist=The artist" -metadata "album=The album" test.mp3))) (rule (target test.wav) (action (run ffmpeg -f lavfi -i "sine=frequency=440:sample_rate=44100:duration=10" -f wav -metadata "title=The title" -metadata "artist=The artist" -metadata "album=The album" test.wav))) (rule (target test.png) (action (run ffmpeg -f lavfi -i color=size=320x240:color=blue -vf "drawtext=fontsize=30:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:text=Testing" -metadata "title=The title" -metadata "artist=The artist" -frames:v 1 test.png))) (rule (target test.jpg) (action (run ffmpeg -f lavfi -i color=size=320x240:color=blue -vf "drawtext=fontsize=30:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:text=Testing" -frames:v 1 test.jpg))) (rule (target test.avi) (action (run ffmpeg -f lavfi -i testsrc=duration=1:size=640x480:rate=25 -metadata "title=The title" -metadata "artist=The artist" -metadata "year=2022" test.avi))) (rule (target test.mp4) (action (run ffmpeg -f lavfi -i testsrc=duration=1:size=640x480:rate=25 -metadata "title=The title" -metadata "artist=The artist" -metadata "year=2022" test.mp4))) (rule (target test.mkv) (action (run ffmpeg -f lavfi -i testsrc=duration=1:size=640x480:rate=25 -metadata "title=The title" -metadata "artist=The artist" -metadata "year=2022" test.mkv))) (rule (target test.webm) (action (run ffmpeg -f lavfi -i testsrc=duration=1:size=640x480:rate=25 -metadata "title=The title" -metadata "artist=The artist" -metadata "year=2022" test.webm))) ocaml-metadata-0.3.1/examples/id3v2/000077500000000000000000000000001477304117700171445ustar00rootroot00000000000000ocaml-metadata-0.3.1/examples/id3v2/230_compressed_frames.mp3000066400000000000000000000332451477304117700236610ustar00rootroot00000000000000ID3mCOMMxx%A0>&F¸2H~(8 _|P,MK%?eއ뿉bo࿁ sU׍ W2 nVT[.A4GEOB2x}RHz7 1KΘ9N+=) N~VPTSUNbBk Lڢ\ZY(.CnH?柿Vpmo~]/_~֣b.~8<5cvߢ B)Ïgׅ m]G^ .U:\1fvvعfGL;oWyap6\yNj@~F"/~g_Nk4.\j6s|E.u 7*c/ظ+f9|/.Ԙ:gWE.\b``O7p?6_0`Qj%x:w__Jע|Q̓*_;>|;c`^1-[-ykwcK!٘xPg&~eVpņ9^./1;>;_lLmqȿW~ ;= _(t3=ez@`gK""~q'3v)7 חi5U)O{nd1_Ly;H׭k 1zgA\уtY!#GR;'ٽ ՟`tC)g[`͇9qMdLHW3 + `i&r%Ud3-b M֟r6&l#CMop#;[AjIWFb4Vc'#uȈj(V0:Inm?*r.Q=ۓFf(;]L'#'Pvk ^Έwm U|Dj\N7i/7%F,9CRNa@ɜf"@7aY>X\.GcmЙ)/I˜f=ж!2t(lrK|SN~qW\NI\J}I\?U5ACFz7bpȌm ߃|#c=e_171wN۫ܡ&($MP=&o9@Λu?Nh9#Z~넺:.%8NJD39vخO]TuV"SF|O㑛1vJ?}SCY`ZEUWnEs,C#b vQ ǃ5 U;RM&kEZ,vErQU#ߞjl@:~GE%`rۜlU@g9ZQ]wu욪Dz -? 5\Jl;*uB^~ޏ >[B\˪aDlS>~M¤GLROĩ5v)Kpb59Xn2/4B W%n >CxNg_`znВmdWxP?FWd$mI}SrLQ;LA;aŒhv|d8_Q(2L!"^v(N␽r1-bFB\vhggjL[ Dkt1* B9.UMHj"épp&-I5j$E:B-P'.k[:O`tGSS7 zxܾk,kSL" 2f}-U&!$A8LOq :%~}wQQ4.}kQ^\7:V< 6}__DZDQٿd'Hml8sTAR[Glk$"|.we@׷8+r @XkA%h>@k 4wNV *mS<J ?Nkvi &DЯkwֈ84O}=F$e?HB$,'kk}Bd k@@CX͗c{c6|T#*h&G͓YO=\-ND I~Z̈́$9Q8q>*WK7YW}C!eG.s1Aus D|\VoS$z_ 9丒B(|=t$]SÈq1E#yVJ/W*7n0$׵Lt/;$EyLc@`[Cxc͹)G& 7υTJ༟=1NlRLsrw`kFf# O| EqSzpeX_:U%wQEUwӊ u ax]TuP`3zl_j[y'Gc5URgY֟g\TuQGcv' f׷FkiWnׇtʎ\i.ˆGnK?ظP1%@xM ] C giNa{ŒU{N1ckgSOR 171)1kav'?w sbo Gv _[3 J3"@Auúw:Ru)Pr89MHQk_k5Aiȗݕ*jo2fT1*I>}Z^hE)}+Len>)HERς1F6,+!׎C ^P}("՘ YXrf" >;Zv$RMqC`/:!\A?NEUiH#;E:!:y1 ij#cr R,qj`5s7G&b;[67sbM<mn^km|x#;*%.==p7Α5<Q?5&;wk@r6*%~퐳nJ) -knT7Ȑ8ڭwT;Xnvd_^RXԟ "ɿm&ёZCɂ`^#attb˜b˯*n0ڧ ?CD# #cAV]#O_ G{>GMmqD"{@'XY]JIBd_<{IqԡdA=2Fsʶi=Ja X,qHRj3,!g](p5:R-U)Ur6㬯NlRnTR zۗ0"Cv09E%N\(9vMU&#\^ =t > eKlx *go2 dvTQ|&Af[>Rlf:O3̬NNnmD:VSscq;)'`,OtsQM7րm_ލH \L}RgMlUǫܮ0h^I-6ꆳk;8 g8MQzyKG C|XFfc٤HwRӇDŽh pD;[/%ַl+FZfdl i&"0;;`{Lm~#l }7Ida۽(|2wK&DQʼn<NS15rޞqԩ q]ܲknl 2N־S1Z9av X(U4e(c+OM:1)ϰ;yL_Q~Rz#lZƆ 6<0](tYڮm:oUw:vQՍ#<iم]boB /dr[tLА*Tz|?n#Ȗ]S s6s-kͶ~5؟]F%97Dܯu'nEOz- k[ 9Hg$.6ůðv > Nʐ<^h*+)tmt7|[QF(%OetR|"ءrI_)աdC > EJ',d^jW\!R>AEF͐<^6U$z-U1"ݖ*Gsïm-]F^*Qo Ñ6DɐgS~b;sa2:ׯt!\+_pݯ<&H:4Ӥ&Rn};-pu>EIs #YM娔0S5% ~[e/p𒈫0Wxg=aŶJ/era`ŕbwӘ9k"mo;TW!y<v$i 3@AtkӲ]D˵hP(%YO-gtNaP]r3!=jl[0i;3+[즙2&޲3}%S|ƥS\'L0V nꖑ(˫?֎~*ܦQ(΀oE&XGW #>veti wl[f#P}`Fvxd}ų>^u+]Jkf*]|gz )֫()ۼ._7bmʕ/ (_ >ϧPd˙ۦIS?Tr?r(téCUs@EO. ;ϵeV/h'vEzE]Suc;H'v> (o]؝f ## v?`2??a)~)~ZaT  gi;Og`Uhڬl_:4 ~ݩ/+(GBAz>6ْJډ3"UwD;fmh="9.ywZNRdՍIA-U2srRz43Wj+ar;{N3M]ͅ#8ƶ/r^vҡ٦)g>ܘ;h0uBV3FP*9Ḃ`ndluvqEEdZɴ^G8ZDw4EUh( Bl~d [Vb(ZAw`U/B0u )C'ʭ dDUdzBNGK,=X(;sÓI*I72&½<+:ڽOXm`vyOljD¶˰杧Bti26C\IV.|)k¹%ݓ ίdv:^ .jهvasP_#I~ ~B+¨4ƭ  Ì,ޮfr'v^ (F~L@7\Qwr9v M;B)|D@`埨e~|Ol >(SENP^h{_*s2)8Rw]Ly>rϥXV9\ckkqtTBj",ovV|fH [M\#ֹ|T~n#tn쮱Z^۱Sz*} [ŬߥJf;b|Z(KQ^? wJo,O;nK7ÖHe>[(Se:~};+p8:0sw:Jٲ2 ZbFgK$%5%ׯ~լ0+#K_ޜ*Q6r&b4TP^Pw1fg61-[? ;:jvv*++[7G\%<\$uю:$n5 91W2/Xo'"k-Pơ@[ow޾{qNARhķMR`8ךcI4t5i$ 4]+tzwƅ6-0F+;}-L+q-yn -d25z<5|+Fj&DxAgM*N+ŅiYkpPlm0Q%/LV#/=` NHH6sGjvws;cg_Nv$bh5m}52KqO{ !nf]|Sv=tZdelkg2(EqZ/:OQ;$J.`.'zRb+kW1ICD%sSz;nwC¸Ɯms!7g.[-;@Q-%]n5֮W8k e)DL`ϱ߷ss\*„m"rـ][fpʏe^ə s:;y `fi-t?Kfd[pBZ;)g\`2g;sveb 0촘/3~]6dƙS4Y9E^t4+ldWhgNK _g+'t.[Cנ]#Xڲ)/ޓQe 77_}NK9KEH:K!K?1H'JhC!)n .sԴ\-+];'f/:]?Е8b:`)-QnFl'g}]뻼"& s ̡Ks .&"~WZC LoO?GܢؑOZ̓:YӝZbCc}ԃԟO)O55ݧdt}Bv9t0m;o8郺Nf8`TD-_o{I<*9}ۭB)o$ mo⼂n*H"3h53#,-6{Oo2c`"%5#>ZM{8 aeRwň 6G.H3yBnB&ax뺹Yy"gZõB)6~{aWsX$NɜKx6e?PJm;ֻ3!ׯ)0z3_aDK&Ys臔|n5Y-ȑ\5|/BW6Q\D|Oc-6*:ulbZX}1g{ů>nEnɆɆ~6<zE~0scv^V*r.C% :/̮MO֛vMU#n! )b+;IEf?x~%ȭA|ߙ:#rmWGٺ6Y1FN8KEQ=Χt:9xB&AS6zQ'ܖNCK&qi%=+bnb5?ֶOvu16>79j kؙ1,ܕF|U=EM̝smsb^HOR c/3M#twѴ!~%4ݮIO: *L=at6`Jcg;3z숏NZ7ڶ4z2[Ϙ kyYm$#uC±\Uuǧ3~1cґK"1"SN{q:ɣ^#m?q0VUB)qvUۋ!;F6\jU {ed%&[cp+Tld[;~'5yK'xbɮmu\kJ^&NkЎmp<`sՋ4,SVҰr}ǼtWU>.JPq~plы ΍ǪvQU7Nj &䙍{BQߚ+&!5r %s{p^/ltD;a]=*OuF]a3Kf"g} :~TЏCUݣ:8;+oer5p7:rv&w4ӨrӡMs:F 'KOLstSv(8`q#؞1|E=n]GC0eqє Y kK*Nr<Ѯq hݞҩݠ~8X_c9 uU(̈9V̝ΣDa9i +XS)a')&Vkq}#9vc2CZ3zt3&ŰAHGH;̹&gp&=q8{殾u ]tP*. hέáv' 7-u}$`&<7qZocaml-metadata-0.3.1/examples/id3v2/230_unsynced_tag.mp3000066400000000000000000000021431477304117700226340ustar00rootroot00000000000000ID3YTALB Album title.TIT2 Track title.PCNTdPOPMfake@fake.comocaml-metadata-0.3.1/examples/id3v2/240_prepended.mp3000066400000000000000000000740751477304117700221350ustar00rootroot00000000000000ID3l3COMMRengID3V2-Comment.This is the comment for this test file. This tag will be split.TALBAlbumAPICqimage/jpegham.jpgJFIF( 12/03/2004 17:49:06 Mode=1 0.1 3.0 C    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?9J^sFps(fN1JzRwC֌t8@v s@1.p) 0zGl\0#8h>}hR >ir098 ~tc(у4ƓsCsq֝8֐8/ӽtqc&j\P188&:q@8֔+P?J1K)p81Kc+ }T2oJé`x_廻AFTbgGқ.|Sґ#ڣ#0BMO+NX #\C8J]ϭ@coNjp6b4?Omngrɩ%<ԍN~РtJۀ">Ib{T d+g@1ޞzZB!͵I5 K1`@Tf36zSFĎiǑiV#Î+R͂[= L# %=k^'84ZbzS&T㓊KׁR۾9*;{"DȪ ?5)Xǽ/KbskJ8F)^ Ӈ b$bAv5nLu5&f t󎦕*cbҲu9"[\֧p M0܁c^p}iE'#Ɲɱ rqE/R ҡlsTvo,c'g@dFjgWL/B0?ZOTW1Pr^d]X`k ql &9=:zgW]ri{t<u):Rw(яƘ#4w4)X=(&s@'9◾i9i ^zqF;Rvbz`AIri{t(}i1 'G?!AP4 NiqA.jL(tCށ/ץw{ьE&uP;tjZLR瞴(sG`(`F+7:tcsgSxt#4ړJ\g԰9K ޺j }iut$\|22^0}*R[)`֮ L:h~<ҶRe;Wk+uy,ь宅O֫ S0{S ; 9#6T8ok#8lq(#Ybisޭ@1x!r1 LC m# j=7$sZ8:nQܼH 01GA$v:MMb M!P`iۏUcElOcNMa>—P!irYϛz҈>1ÜJS'J>^Rv Y'*\bN횦7|݃%9>MAV e,hHCʀc4E9'֙sޝEϘHU09-E8`xisJ9H$rw Q)H9CQ'Љ6p2{H(9=:Sʂ4wcX<)h3Ѡ ^II hG9'.zsIK1qҔc'h:ڌ;R )LP=)\a4֓@9֗hJ@(>cAK#'A@5җG;'4P:^i( %+j0G(xژ(i;qNC?ΝMSO–G@){E\#8'5v~2~!4&t~s3@zg5BM uT+oY=+ 4]#a־vVd@I*Eݚy$cDj[ӗ-WH8:˷Ozqwl V! UaiB湉CrS5ʠ! s!+|ܜԷi܄A&ZLdN;P?@q{6U5ph"NZ!1hwAp @x4 w=iz"@PZ`Z@iGQG|Lg:p:Q֗֍L(qH}裵 w J)?ŽzZ(f)GzR8"<}i1 @4 ޹[~*>gW=!Xe##Bz|ҎԀP;R̍h Jb3涵'"3 [ 1}f5jǐr0+ Fk]yR$uɓ f'-HAQ{՛zkLlWw s:.Gzцܚց8jSKdK)ITm2Ћ,A *LH斡h# ry1Y-~5f@S3ئ,x@;ԙ=;Ҝc4F #\Rydf$g,k[?Q ˏ=u=ޙJ@Rsޯ3 5+2$dX.wl#X_J} Y3dmǮ)glGj Rd*yxEBz9sMJ2zӟ&I0v+NF4v2}j9`n);c`/'qR}3J_܍'K9=M0)8Sڌ4"֌IiOL8&r L`z)3ޗéQAz3)ў(=Mg# `@K:A(鎹1Kތqץ)ցR;Q!֊QZ='iIJ:Q@ ڔ((g>QK8 bJ^@8Ҏ4q(I=iux!ktX^.-O0?h{£;?{3Imk"؉@RkO^ ΍Id&YH 'jɭԞMRɥ^XIcJd:tpI#;f>a=i>OpQ94hZLRX#׵ CIqTof3yyߵe]r34+\i)2u˷\~󃞵VMgN0O=I+H:v"r)=W784ژ\".JyP'ҩ$:'@ZGj ;ۯJE /܇`qO*3ޘpݩ;u3cH0h,z84dRwA}Or4(֎ҘhF3s ޘJ=(*f@iqRl[W#Hx9$DO$68f'JY˓4&W4xS{SaOZhJhQc4up{fNM'Q!X~xt:=DKgSbc'|Pxu^@84bY8%{nb5@Szk\.ڳ>aa,Xy *y )_BE!Nؗz3Lhc'@ ħLG ch[EOypp)kf )*Y ɖO5Z2cT˿0;RdZ zR$)@2F#\zV0Z܃I d$ hZNq;Ǭ@HU-gj7֝<qYZۧ :Z3ڇV{P'aRqJͅ9?ʢw2uOOaVd+B&U'k,^swӂ5V7f20qDg 0N52pi22zzSjfF@QPߵJs=B=6Ny5=;THsLM!:ҧ,އ9a~}GJ{ ^=rizLў3֍3M=})EƅQRdQN;ʁx;SYڑr7ƴY 4AGJv} )`br;Nde`h2h43֐u:=)Ru4=b tZiB) 4oH`9r01N)xWVb"Xb^-B#=;Tn>(w})hO1A'I8R*&Hv'^ CY>kU^s(*7Ne `.B8 Ʀ( +DCU'ޚNzPMW|>8)id9NIUV򊞔TU 뗑V7qul V8qjJ!tۋB%qYh4'ay^=)\]r+`Zodx*, sVCYddݫL1U/PpEYg7c>:q<. y0q4^,[ɤ9⡂S" ԅÓLwnrJ:Uۥ/RHQ4..X\u+ː{t/~j*9v*@Z?*$=*d~9?~^zd0Js7=(1uvR1J3U'^D4FN)JTʠfnı~nXcP㌊ܖFaRQvzJ3ӱdҀI4ƀN7'vM(GR4AJZEE 鑚SQ ?zUu<7Zd|j#UZi)gLZ`%O#4=8oq9La\[jҷ4L?jx=&tP9<W?orUخ=~!d'8QPǯ5:+H)sV)ǀJatDz~w%"NzZ=c4(,1HNc֗қj=UpJ& l/H{rwá2rnwUXՂEz O>'3Hi#=@|S9߽>ܰKR&(QSGdAKjxEE(i%'<9'g8e*F}鄟Zdhqrg+aKj9;p@51yf;P= _hPER\b$؞cbǎ2p<C(UmwR]Ojɖ ~d.Hֵ0ʤdb#mOnǷ OQ 8>VПʝqndum\ojg@c\I6-lzVY~\4yʖq* |.́sJώ3KruLr*Yv3R:D݂E1X,} (r>_AQ4Ʃ u֜dl!B4lQy5$yCM3X"*U8WtU|0,RAHϭT.z{@)/&V(YtW![@2OAH(bօ@#>gv8G2BpT$9&$%Qrԏ"985^PÕ9ylO4.r+` ˺fewzBs+@;O#. w S˽ˇ4$O<ԧ;$P$hJL梅RTÅ$um@".*o7CZ_y0FsQsM xSҭ^M(j1BX׏Z=3Jdi~!${Y)/`<:r#~{0< ؖ8t;F{>r8^9 \F맦(9zGJQ>J8gwZP}=J01:~4E 9J(A3@<ԌS3R2i SwC?[*rc9T:w VWۃϽ!2ǎG5*JPHl<{ Zc݁!զÆ<8ʎsK0i++jd8EѿCJ3#g$ˉ{Sl  0޽i;mtʷb: B*{)J"eZy=(m72OZc Wg~),/)ry[Ίrf"v#c+?[ W P;.#:9ZrpzlѺ7"P}kNfP&coޮy楾2:2y뎴z)7zwC֫#1X\c&vܩSO53HР&ړO,uldv/uG"DgB:y.*)ԤasZDjͿ`?4ٞ޹i#pXAUbNG NM_̌{v`c4% iԻpyl_`"!4۔y\;@Ry^[ uP;GE!ŸjNDG'EiOEؒ&MIn'MwfVGw_Zhϭ;#$i1Ҏ=FRf`@:fqގإwz ^LP:@O#Z?J3NVdTj`\a򞕝:I^;D.H$w׃֨fu+}޶&z`Tq4i- ջ*ƀy@1 /g_(I˅"h!gc⡂Y"hƴ2ç&q= I)m)[5ZQUB>m.ɡ] hUcz{q^1X|ƒ+3ԉOdgޑE?AzsT N88`zY΀ eر8d熰p)BFz\*U(zSx 7J烺.Pkz ;b #Nq ɩ?)1)R@ۢw`֬0Zpdh ִ8*0OEx bx e!T(܂$Uɫ@qcj 8KVRӆA"^#d5 ~\#w$́#6L$Zy9#-rGAHnj1X֝c Ibjy1q[R{[d[寮i@$~5#: e/sV, 9=j d"-Zq$ڟ֡Y71D89'l)ۚQݑABZK$}EDZBj^ g#!YI=IL|\3۩ ïjނ1'2b :eOYt35B 74!!mO^iŲ*;ԪC(B' `f8C$<츲@*rQԆzVjNgIB&X T֝1zbmzK$7T>ISUSLzCDqެKn."`:R"6a84K{vE#.YDsY8n 2"E1\|J$^Hgr@^s6 )`'zU+%wf`_϶M}FQ k͟R1&q;SCE.+@"Tȡ#N9r#8t0i =*r)#L]A2&֪$2ǭ^XsQ$,U0Җg (MvD *qTPjbqii!pG TDc,{Єҷ#hKܭ. E5Ҍwwv4ӌRVKRqM(@(Z(FO4Q^Ͻ/lPi|4NT qG'ҋ2 #?&2 \_Zy?1WL\zt`f{,obY-%U|NWTR:S32%&L5th>z4= +Wi0zbN*`rc")<+?̍B>cj&ۚL*ۑ 8v˷\(]UO()ŠG92wp$*esVf$ڣXp>nKC=lB* B֔\˧$/IZ Ǹp2jDxT{9 _=jP9iy #MӥUɸj[YCAriF,F9OZ2rO@;4#H/{V>Wdƺf,܊k6AP>*jp@`*d65F8ǩ#9 2x5xHzry4I :Չ(FNj X`xP05!pIZ-mƆjͨ̇# T1B2zgjlRҽj9n/q_Ij=oJA^ 9@ӳ4Àsڗ<׽/o˜J8Nh@4~ tw{e\SGӵ!RE/oE  A4ą8ZvJ0q@wdϒG <խ%w F=\2G>b YzS+p?Jr?*pT$>Vq l5 fvJ~^|_pnjB) b) 5M\O P9FwS œRRv+N#eI-cUXDTuKEؒEܸipJzc9@N]Q_Qj4dQq7 '~Xj LK fe!GzkF Pj㊵̊N=^zՈܘEW`7w %%(Ӥ8{=$bfOd,R-N&2E8јpjd [,U~*xQJ2"={UA5pg.ƔaE5Ө4IQnz2h'C`hs KZh8c8uT\ar9bP:Ayǩ60z_ZC P0E 9>J 1K 1(4L PGF;ӹ:9F*xO5 K! ]$[e-gfٽ=19QFQ95Bdr3UW; brjaG5NE$h HYhcU|F6lsKA')Nt,{1#=΅#$aǥ>7.:q@~Jm@Lv.sLt V&~_ʪǡfCr) 0UkM6+tkŸ|*Zp*ȋM!A4I; ջK+e*AyV)1bl1T#ڜo -as75X<sSt#j>G<}j@ɨBh0Hj@5JeLy2$͖rz5 Y@*\n^O"SW᳴ul@`,pOaDۈR?sI2j$A"{SIA$9Nk JrŘy*2F1ZE[P=8c(K0(MJ8'=GwANdR(/AҐgK0);ؤ..?*NG B bJP8'ކPH'$i+8I#-5{"I Urn`ĹSZ U p~Nh4W^d63{ :QbǷjN9u#+M6椐+JyޕI[P"䌚Un”FOIȍ9?1K)bY5742O&oTq[{42͌b08x(rbh`i$im橯xU #~E7P)jGU"X1${-\8 @zB}*j̪ <$ !(Y0~VU'-AT 9 Ӱ^ƌjgϨp*m#wp'19/Xri3nIȤ>Kb;>ŵ}kHWo " CՅrʓ]JJ0Q85lI͐mN]XKRyRkVHۇ+Fh_Ɯ5^m**èRM?Q)e,xsU2 n"sPzOM jzz$"~f!^GA=OCciisR)1})qw#֗J)!4;(zriuR\cd\jUzUKb*园9dG-ҫ'r9'W`4Gݏ&%@1YufGdbz.&pQ}՛jZZJ@j3}꣱#4m&$io Ҿ25sS# :T3ځ|7&415X78ms@ZEi& )&A LqWc}iԴ1$cPp&I3x1DOPD$zH'>H;E]WTC[$VyA{SvM);OA5j9'&f-E}9ev 2}M\/J2qޘ32B~A3{ֲE#+8]ұf㩦fjX{nnL)URL.KuXtf9 tYfLAH9rM: qszzĹBHK?@溿 O8+ȮI-&kÏv(=QT_=1RA ֢cT=ӹ Gy2 w3F&\S:S}&y2qMӁѠr)b)G }E*4pF 8R`杌W@т(&ך\UGbȫm̞cL{3i RV+ިd*z] <~hM:D9 LX0rOZMvJiIVLP+ 2 ֠miq e&5rM3x8d{x[$<p8 $\K c.Ǔ@VPαn `([ϸp95, Gꌂ͸& $$lEMp$֮X?^@hP0p*RB+*[˒{ 3j4,JQ?1کjL`g0EԷo$\(لg='ҳ 8)o @i !W}kayR9j餓)d@ɬH䃅ZhRבֿoV*SX2X|f, gjVTzÍL&`ЬUBSL@=3Lv PsCzR֙s}$;U '\+_H󂱂Y-6$dܡ;*1?xj&,@PTJ6S)Sӭh0eZg秠U`W皉#OIR(ޘTCW =)Hw} hÑRT2=sC+S2j 8ϭv5旭7!{9#LCz8J3ƚ: pVNi;j/93QU'VzѸ,\ORTi 'WyAX6,'n Pln)% j2L4Z7ltQPyiOUN*osrOj]8K0#Pdviljc5PNsqu-ڲ=\6* WZx=9 ␦ T(dMRqN="iFJ94[qҜ7 D P6\Z 8$ltLOS܁5]> @ftɪ3\HVp3U$Ec.K} g\pEi2jCn#iօ7SCFzrCT犬:S=S9(Z;BrsKz?JsϨf}:дA^i)sTDKQ(ATyf$rr7Vy2MC,8F4yaq&b{)1_13H6N0=<n! ;!yjd#gWUm^Ny~BژI0P9٨NuASfܻO#U!M$A(L N6N^CZv|{ӺVUc 7m7$HG5f~`pF*GcBHZv?:As>끜U5rPN=hkY+~U?|:`4`"[Twzh.]R! 8GΤUJ[欠xrKMp<՛TBqEI :F.y5b:0XeH<]UWcM1֠c8}Z@th,LR4N5U;n'jۋ9'X㴸=ij; ~g'')]@''8#魸XSdr#;{ty)#RqM'GjM cAdϙϧj#>*9)vIkV* y,F1i8'Z-IUr;UwsVj؉;[A%m_ZF^63LOJ= "hA ds $ MI8omKB@\k9`Rdٞ=iA?4Ҁ@'^g(S 8ҚCji* R13Pt1Lrz88ԧ㧩-@Jy.ʣZ&F^i2y=i#ǗO& vjX}8  .@SmMU*u;vj j7 sO3Ul=醣d<N@sҐ00@v 9$ŭ sa =*3gsR ֤3fJPcRL$ N* cU+ )5 N9J3 sG 'o€O&/ qP0zҲy[7&v]LluR71T UpӭB˃[Uy=jhdpS?5LmPd/. ޣaSF6:ScFMBrOAMfWA(*@`4mS@Ѓs;(JfMr'tP蹌VC+j0ճԲVctBXF9PKU *gkzgf̛M&8´s ՊU~QT[pqV|WR"!ɚw8JWE&F=k[+/F@D2jd\NCqu !׊> v-- B=MX5 s=Frj w¯PL΄I= .h 2=35 dMaI2* B NV>^ Ϧj$rJ$DکEmʼgikژER /jm*ޚ$ ng7*C ҧ2ץUmAqGQ!OOJ|7$r7zU;pY:O2ꦣ#z=ؘRpxcv#CNОr> L )Qi^Ut]? \gK yw5{~[zU{.@x jvHF^i)sQ;pi2Ey皈O>zy9$YI>Xh/sJ>y #$Z7 L:F=h`p'GsJszLC: kcQpHjhWi&ҫnsS 0=i"˶]LB\ s$~Bs)ۊLCZ_^XʞzgP5[9f$p4V0u28B9%+m9)v3TePi#0ZD:OR1hzcUmɵԆzV%֬]Pa%cKHePX;5+hMĜ,j>cwljad=@?p۰=BgP'_^F3ޓvi];x3dyҞ+rEK4FVJLm{PTɻ jr=;i2aO͟ҦfqTpe=8g*98aޢ3Qe~MȏR]Kă)ĩ=OR \ 2oڤ Cc5!1֣00`Cu4]R]Rȹ Ңa@7"2 )niԮ'ސ ֐ b}* 䎕9ǥ1%IS΀ʫJyudҁjLI냚\L!8>=JFmڐRϵ98ۑ:o@*ˍnjrqRao U+UUc{3)9lUNkڐN#I. LW( FE;OM{'5,Qp*Y uGsךjWaQޯRqTrsڜҜnP%}6ɁF0,v{սUJbH02im$z%p:'ᚬ aD:Բ'ˑuUqOaĀӈ =EfE Ȏܯj;>O O8I%l}E65P,zwxr0Nyw,: BHaߥ@%[{tc10rNACm<ӢeXs֚'}Yj<,t{ XZu黛P{WCari1QoCȫJ +U`CqO5 4#Z\<WLwΙdf T1iA5~^H2wK'Ec*rZYiUhd8zm#ƞ֠[#p6mJCRe\F{L85 qN+s H.pjbo!S *>ekTM.q`OV7 li,=He'b9\9RBA; wEɞ H 󞦛 !5A2;ЯfMVӤS<7F'#e')Yh6HFi-7&OjVI,npr):`8"X We-0)fЖ iBܚiYP|Ǩ4|k(+12J]9P3NrH2U$f$p;cFeW< fR̀xҠ?:LڠbY ,ScN"L;TFyǵE}M+1R1L1TIIpy(BZ,mGZh^e* "aMX۾PSIz6.=8-jD]Ա[`HI"(VE!2{-CVp:U`+c$J#5>j`qڋܫ\ g&Taio§8+!^`7Gң 85\=ƁXTCܜy&\P*]j)hzt*2+.)F?*`iS#?t4\w!b%VѸ$TnĘhOYs8I‚-)mZvAϏ(n;`oԻ)ZO/In!Ai?H(FsSP|̆IIH"sңYdjiUqI"mC\B5+̇^" 2IT$rr° SF9PCqN䞟ZZ ` EVȮ*őI4+֢iIdņ48'0<Ld)AS?+8'4'~ % 82:Ccx5*MV= H5%PNy4 4|OZ]‿b)2p1Zȓ tUGޭ&Gs֮$xb9Ҕ|dL|A  mSSQg9)Fy ǥC D@|2qީn&YG8}h`A f&Y#  81c);^d`8xf^3H;np?A* 8{ QsT!9u(Gz&A/'ιmӁM v4 F;nޞ T63I;O5JIryTb%|*JZR3ޛ#۰i[QKriN_*pH ilUb# )aaߩk4Ƀz{=jsR3nbޝ(  LsU9rOH ݹ5JB3>Ԟ%贒)cҬ[ sU~btzn@CT#Un;kjt3Xۜ4l"kf9lՕv|tU\ HGO_chA"^1=i)h9<džZ~dgFڻ{檰ğ>"_l-_a?J͵e; rLO5"WlE"3"9LT,FV RT}*g#WRd jxᗊ.299Zj$9c|CJsQO==)9\ˑEVV#z~XxwI4I玔7 UrzsRQO$S!Ja1TS 4!nڑ+|LDpyGun&Ú~ŵ1~¤x=EYL6@i_%gtlNz2-0*=iLOBph ɸȼP Ƿ?6y xd:⡙.S/h܃48"K)Z̫Lpi`Uat@=)t["4$ӳiیbvFjן"85Fz:I3Cz7#4;~^O>$oG,6 k]yzQ\<7ɜ4ydHҒMt^A9 = QIBjE oӒ( HT3w9Psc@ڥS4`6G*cXlC}ڿq2GBjho09yzvmlKvdI84 JrAS\*Z+;Fnri. ,Փq3CZuˋE9"irʹƑl3UvR9zkg=inXISH)$dM jNncO:p*ӓ[#5#.|Z_54ʞ Ib8$Pe)$Ĥ˃ڐ AVm8_^L`CJT(@h)i:U{  .CLzrS* pcN\9 HQʠS.Tΐ٣*A1V/#!rzoa; XCsM#SؕU_ʓLd!JqN*`HA΄sS|)vsRiNvՈ<!.EH01MMY :25S`uK 1GJu[a֧@Ɓ"$QGRl @SiXv dI`[@#4 u>*#3W0d T?g5Cjq4x?)FWo9TM1$OHqI<UlgR; Hb} qғ.n]G'6G v qNp)0);R x2+A+T7;`y3QIy;9=ihA!)wt7K]8=)@.@SX`}E?v8ڡb(pD*E=<t[imϭ$b)!VBGTjF7@}j VaԎ)%#Odm8=h {ѯqXQT5c` b(MR[d)$@硪Z⌎*WP#犤=圐iiϠVϰ5,znpxpj%­=ُ*@mX.bN 80-T.; #e=)~mIp1֕wG6rNZځnߖբ7?^܀XF&G K8 nS:'ˬKzpE!O]_츪aV w|lL 2])AJ\iÁOHJn!.x;DE?w6S6 ނ/RR#"WO'-2=\U{TTI<8§~*8QM=CJ%·nOj|e%[ApB޵4cȍWa*YG3* `d.oer')`sպQ~0|;EOzޖEGSYאa*pL*d7\ac`TV˖5yʑ~ClƼ;!aMin >WFeTJaJ,%I RJiyGJxcO )iT=T9K圚VX"d"}#){DCJzxڒ7CV#zSՒ"2i.`z8?tq!U+n)A*OAҤU(*I8$cSm:sU-]x`zG0xWyN3`MxK]JDsch=Xwud b6qw=Ozx/CHxc ' ƛD(ozU!O5C$$L4g<R.YyQIcS2*6|v5WAzQ16Lf'(B *;m>nץL ;mCfLuS@'P{d(gې5?1F7 9Lo ڕq&al0*T<ՐvwM*zҜy\`T;GLTԘ_@DONH4Ԩ -ړ7%#=8u/:~=MCs(jۯUw4 M-<5M ~F*vWV종kRFnvV6ۙ=݌QM#/ 'Z:BgLS (fMǷJ4ة!n 5*ʟ2=:vxhӰ('C#R">H겒qKK-z`jNZ69epygX\UIlwE~8<5cvߢ B)Ïgׅ m]G^ .U:\1fvvعfGL;oWyap6\yNj@~F"/~g_Nk4.\j6s|E.u 7*c/ظ+f9|/.Ԙ:gWE.\b``O7p?6_0`Qj%x:w__Jע|Q̓*_;>|;c`^1-[-ykwcK!٘xPg&~eVpņ9^./1;>;_lLmqȿW~ ;= _(t3=ez@`gK""~q'3v)7 חi5U)O{nd1_Ly;H׭k 1zgA\уtY!#GR;'ٽ ՟`tC)g[`͇9qMdLHW3 + `i&r%Ud3-b M֟r6&l#CMop#;[AjIWFb4Vc'#uȈj(V0:Inm?*r.Q=ۓFf(;]L'#'Pvk ^Έwm U|Dj\N7i/7%F,9CRNa@ɜf"@7aY>X\.GcmЙ)/I˜f=ж!2t(lrK|SN~qW\NI\J}I\?U5ACFz7bpȌm ߃|#c=e_171wN۫ܡ&($MP=&o9@Λu?Nh9#Z~넺:.%8NJD39vخO]TuV"SF|O㑛1vJ?}SCY`ZEUWnEs,C#b vQ ǃ5 U;RM&kEZ,vErQU#ߞjl@:~GE%`rۜlU@g9ZQ]wu욪Dz -? 5\Jl;*uB^~ޏ >[B\˪aDlS>~M¤GLROĩ5v)Kpb59Xn2/4B W%n >CxNg_`znВmdWxP?FWd$mI}SrLQ;LA;aŒhv|d8_Q(2L!"^v(N␽r1-bFB\vhggjL[ Dkt1* B9.UMHj"épp&-I5j$E:B-P'.k[:O`tGSS7 zxܾk,kSL" 2f}-U&!$A8LOq :%~}wQQ4.}kQ^\7:V< 6}__DZDQٿd'Hml8sTAR[Glk$"|.we@׷8+r @XkA%h>@k 4wNV *mS<J ?Nkvi &DЯkwֈ84O}=F$e?HB$,'kk}Bd k@@CX͗c{c6|T#*h&G͓YO=\-ND I~Z̈́$9Q8q>*WK7YW}C!eG.s1Aus D|\VoS$z_ 9丒B(|=t$]SÈq1E#yVJ/W*7n0$׵Lt/;$EyLc@`[Cxc͹)G& 7υTJ༟=1NlRLsrw`kFf# O| EqSzpeX_:U%wQEUwӊ u ax]TuP`3zl_j[y'Gc5URgY֟g\TuQGcv' f׷FkiWnׇtʎ\i.ˆGnK?ظP1%@xM ] C giNa{ŒU{N1ckgSOR 171)1kav'?w sbo Gv _[3 J3"@Auúw:Ru)Pr89MHQk_k5Aiȗݕ*jo2fT1*I>}Z^hE)}+Len>)HERς1F6,+!׎C ^P}("՘ YXrf" >;Zv$RMqC`/:!\A?NEUiH#;E:!:y1 ij#cr R,qj`5s7G&b;[67sbM<mn^km|x#;*%.==p7Α5<Q?5&;wk@r6*%~퐳nJ) -knT7Ȑ8ڭwT;Xnvd_^RXԟ "ɿm&ёZCɂ`^#attb˜b˯*n0ڧ ?CD# #cAV]#O_ G{>GMmqD"{@'XY]JIBd_<{IqԡdA=2Fsʶi=Ja X,qHRj3,!g](p5:R-U)Ur6㬯NlRnTR zۗ0"Cv09E%N\(9vMU&#\^ =t > eKlx *go2 dvTQ|&Af[>Rlf:O3̬NNnmD:VSscq;)'`,OtsQM7րm_ލH \L}RgMlUǫܮ0h^I-6ꆳk;8 g8MQzyKG C|XFfc٤HwRӇDŽh pD;[/%ַl+FZfdl i&"0;;`{Lm~#l }7Ida۽(|2wK&DQʼn<NS15rޞqԩ q]ܲknl 2N־S1Z9av X(U4e(c+OM:1)ϰ;yL_Q~Rz#lZƆ 6<0](tYڮm:oUw:vQՍ#<iم]boB /dr[tLА*Tz|?n#Ȗ]S s6s-kͶ~5؟]F%97Dܯu'nEOz- k[ 9Hg$.6ůðv > Nʐ<^h*+)tmt7|[QF(%OetR|"ءrI_)աdC > EJ',d^jW\!R>AEF͐<^6U$z-U1"ݖ*Gsïm-]F^*Qo Ñ6DɐgS~b;sa2:ׯt!\+_pݯ<&H:4Ӥ&Rn};-pu>EIs #YM娔0S5% ~[e/p𒈫0Wxg=aŶJ/era`ŕbwӘ9k"mo;TW!y<v$i 3@AtkӲ]D˵hP(%YO-gtNaP]r3!=jl[0i;3+[즙2&޲3}%S|ƥS\'L0V nꖑ(˫?֎~*ܦQ(΀oE&XGW #>veti wl[f#P}`Fvxd}ų>^u+]Jkf*]|gz )֫()ۼ._7bmʕ/ (_ >ϧPd˙ۦIS?Tr?r(téCUs@EO. ;ϵeV/h'vEzE]Suc;H'v> (o]؝f ## v?`2??a)~)~ZaT  gi;Og`Uhڬl_:4 ~ݩ/+(GBAz>6ْJډ3"UwD;fmh="9.ywZNRdՍIA-U2srRz43Wj+ar;{N3M]ͅ#8ƶ/r^vҡ٦)g>ܘ;h0uBV3FP*9Ḃ`ndluvqEEdZɴ^G8ZDw4EUh( Bl~d [Vb(ZAw`U/B0u )C'ʭ dDUdzBNGK,=X(;sÓI*I72&½<+:ڽOXm`vyOljD¶˰杧Bti26C\IV.|)k¹%ݓ ίdv:^ .jهvasP_#I~ ~B+¨4ƭ  Ì,ޮfr'v^ (F~L@7\Qwr9v M;B)|D@`埨e~|Ol >(SENP^h{_*s2)8Rw]Ly>rϥXV9\ckkqtTBj",ovV|fH [M\#ֹ|T~n#tn쮱Z^۱Sz*} [ŬߥJf;b|Z(KQ^? wJo,O;nK7ÖHe>[(Se:~};+p8:0sw:Jٲ2 ZbFgK$%5%ׯ~լ0+#K_ޜ*Q6r&b4TP^Pw1fg61-[? ;:jvv*++[7G\%<\$uю:$n5 91W2/Xo'"k-Pơ@[ow޾{qNARhķMR`8ךcI4t5i$ 4]+tzwƅ6-0F+;}-L+q-yn -d25z<5|+Fj&DxAgM*N+ŅiYkpPlm0Q%/LV#/=` NHH6sGjvws;cg_Nv$bh5m}52KqO{ !nf]|Sv=tZdelkg2(EqZ/:OQ;$J.`.'zRb+kW1ICD%sSz;nwC¸Ɯms!7g.[-;@Q-%]n5֮W8k e)DL`ϱ߷ss\*„m"rـ][fpʏe^ə s:;y `fi-t?Kfd[pBZ;)g\`2g;sveb 0촘/3~]6dƙS4Y9E^t4+ldWhgNK _g+'t.[Cנ]#Xڲ)/ޓQe 77_}NK9KEH:K!K?1H'JhC!)n .sԴ\-+];'f/:]?Е8b:`)-QnFl'g}]뻼"& s ̡Ks .&"~WZC LoO?GܢؑOZ̓:YӝZbCc}ԃԟO)O55ݧdt}Bv9t0m;o8郺Nf8`TD-_o{I<*9}ۭB)o$ mo⼂n*H"3h53#,-6{Oo2c`"%5#>ZM{8 aeRwň 6G.H3yBnB&ax뺹Yy"gZõB)6~{aWsX$NɜKx6e?PJm;ֻ3!ׯ)0z3_aDK&Ys臔|n5Y-ȑ\5|/BW6Q\D|Oc-6*:ulbZX}1g{ů>nEnɆɆ~6<zE~0scv^V*r.C% :/̮MO֛vMU#n! )b+;IEf?x~%ȭA|ߙ:#rmWGٺ6Y1FN8KEQ=Χt:9xB&AS6zQ'ܖNCK&qi%=+bnb5?ֶOvu16>79j kؙ1,ܕF|U=EM̝smsb^HOR c/3M#twѴ!~%4ݮIO: *L=at6`Jcg;3z숏NZ7ڶ4z2[Ϙ kyYm$#uC±\Uuǧ3~1cґK"1"SN{q:ɣ^#m?q0VUB)qvUۋ!;F6\jU {ed%&[cp+Tld[;~'5yK'xbɮmu\kJ^&NkЎmp<`sՋ4,SVҰr}ǼtWU>.JPq~plы ΍ǪvQU7Nj &䙍{BQߚ+&!5r %s{p^/ltD;a]=*OuF]a3Kf"g} :~TЏCUݣ:8;+oer5p7:rv&w4ӨrӡMs:F 'KOLstSv(8`q#؞1|E=n]GC0eqє Y kK*Nr<Ѯq hݞҩݠ~8X_c9 uU(̈9V̝ΣDa9i +XS)a')&Vkq}#9vc2CZ3zt3&ŰAHGH;̹&gp&=q8{殾u ]tP*. hέáv' 7-u}$`&<7qZocaml-metadata-0.3.1/examples/id3v2/240_text_encodings.mp3000066400000000000000000000025021477304117700231660ustar00rootroot00000000000000ID3 8COMM3asiThis text has some funky chars ž¬½ŽÇßù.COMMVu16This text has some funky chars .COMMT16bThis text has some funky chars .COMM3ut8This text has some funky chars ž¬½ŽÇßù.ocaml-metadata-0.3.1/examples/id3v2/README.md000066400000000000000000000004571477304117700204310ustar00rootroot00000000000000The files given here only contain the ID3v2 tags, not the actual music. - tests starting with numbers are taken from - `liquidsoapNNNN.tag` tests refer to a particular [Liquidsoap issue](https://github.com/savonet/liquidsoap/issues) ocaml-metadata-0.3.1/examples/id3v2/liquidsoap2453-2.mp3000066400000000000000000000203261477304117700224170ustar00rootroot00000000000000ID3ALCOMM@eng-=- Chemical Music Crew -=-TIT2LeuchtturmTPE1TriolaTRCK04TYER 2002APICimage/jpgJFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?;;g%$'7ح?(oUIXԯ+O>i>߱S; +}`bZϬU(ح?( *Y_V}}`bQEi>߱Gح?*Y_VbF["8 $X,$pKp}; 9kY-Μ̄ z?bX?خW[جDٻmm,3Cԁe͢i3i+o Zُ1,0y'Һ#A~K&7bj}lkIwD!X`N;s3Һ;+DҼ)[UG7jݱNիw _<VfDȷipI4(?`D$ qq)OG|G5-36eI'Թ`<-6XBHt}}c+m%ӢO*(1IMẉ㞝 Ch:MK)gʯݠzxt+O>i>߱^HҮb7vBQ)#ٙp o[[;KeTv,~P`r7ve;-ALA2(s\*',,/bDžr_*j((4S(REPEPJN=3IEHڤFLX!ILŗ;xbQJ xiK["88N/"k3B[]ZOԤ Z(_I#9?1$`a#POWxݜcޟojW1DEڜ4m V'oɬi3Cs?hQAtݿ =rٵW[[}k4Km62Av'n Ի 1'4pqӵ-P qHI'$}MS#EM%6IHF2}MT oU.W%ͱ7|tgZ0%0~+z*Y&ݙ4 UJ$;VU|{rR4rf9)oC.bmS A5*SĬ-QLJ$>jF}KM)r{c.[+L5ݰtuXn'\ErH#uHfr]exe{2?֝T|6= t.eH SiTAȠI'i}v5t}URчT+=Wf|MīA 7OxoP`%XѾQHSzV.[4ĴEPǹ%*sȨWi};%}YKn;G^՟1^TڊZľ74Ss\*g%fGeŅr_*Nj[i"1n!y'hTVJrE&ֵ2B GoEm2A; bԧ6}W^>ά}طIJzRbk%TR*:ޖ*QzT i4fOdѩt%Ӹvc 1?0ϡxȩ8~BܹvJ‚ID!{o\nXcx>nxFb<>\ͩr7]8(hfAi(aO֖kƺ&c@Bs7k.HnbL~ ? 3)+r)9yɎ',B0z渣>fVF=VQE]͂4W<3[髤hv ޕF[va:8JIW iis2D5bk@+Jc DK%z ye*hM9ZΩ⃒8lB*kMJ|kNQ]ӭ!V1XWrclSMI׃JU`3 档ZI=PwqV'.7)L0<"(]:4@lWֱ~5,M[ei V$( ޞ%̻I=A¢GhqStO#^5zM(Gޑi-=DN#j+B X }*%UfYr$ +#JIlmC' rܳfu+!vQ|MJGw?^U+NvR8iaB<)t*^xR:TcPhb9UDщ1OmbDur+/QI$j3jz4pQ4*I"&*`;%kbVv8SNm:5St#V߻gOoZx$ ʹuaug(ʭ;3v>aօٶGƴMFrL+6ku)b0mͭzbVfMJ>k0q&343rsX _ׂ̧RqVսsjzi31;nW;߹u掾DZn&ʚunN34VТ|iRvjhokaj@@$gXA7I:9 U ;A?Z"E($is#떜$+I9-˻UV/{ j'x;V8x[ r{b%e܌ tsV*i Zb1 d#ЛPyDwXXG 7;1GN)'+ jeD*$Vڊnh?u\_A4VY"+.i )ګi2GP5I ױ rA CI4j 6ە'zՈ.B'2o:q\25Ej+ZEoBG! uasd-/*RJ8r*zUeAUVr!RVyaH$U}9ƛpܧ$rݰ(qǚڔViYX֜ybsH l_0sTѹEu4I]YHE<rsbLp*nPd!+ף.h%mQrk 5ԙ -޵cUӴ? OFN=1T 9$pb&{8_EN9=k6prE[Wg{OeaŸֵsMu qW}s ׷)س* ]I$+\hK 34W]:3l3RR2Okin4[VIU@d@ZPn4׏qX{VfEvU:5ff9Rk'En!)P {W34Qgey$s$Aܖխ1ڤSbMδ$֊ƶ|֬AdRU$Ғ)OrvFm5VَZ5:KFC@WcEo8XdUb#:+KPmqIV TQBmc{;E|+zVO6ќMu[nb$;2S 9tu\bS\0'{ 0F3mhR1u튀Mt*= ii DͰ+`Ϛpv43G$]B"H^䌝Bl$I?u::Ý"儑c 0_ָ++y&Gwr*^]i$6ଛz`Tk81T}'xe{XdhE!kӼZ{3R/(6vVJ-2Av+kw`@6VM2[jĢf#5*pd<#G:[)-mxHZ5 H! 1 8`m^TmSDQrK R W^k&[pp3R;2zv^i  kZfo4t)F')PP p*9HHdtuL˸$Ԛauo)BbEk);H_z|2#N9*>KvY=Ţ >Ol=:tS’+wYz򂃄^y##N;zShi3Cץ/42 zkB_ !̙3'&Unaiq*H6ea#.h;?j`Е%ӯt|i!nۗ=mkmn97v+,SEhBzs]0Rk޲,eKZKB)[\L( zm>[3UQ]+TΙtGQ &Qmq]T]!DvHJGZm(7]#0,7vLxjťݵxs}*,@Z]VQΩTa3AӖћk}d_rvU`m H"VzzڜߏinSBק_fԬ|4QX N.BHg[?,EitjVB-=u+_5Ɗ(Q<ηeb?ƛe?vƊ(T%F 4hݿE4ҫ~?A}hu0hX(WZ(3 Q9v?TCON(26)GRP1SSR1ocaml-metadata-0.3.1/examples/id3v2/liquidsoap2453.mp3000066400000000000000000002030551477304117700222620ustar00rootroot00000000000000ID3 #TALBPrpositionTCOP32003 Subsonic ExperienceTIT23Throne In The BackgroundTPE1)Subsonic ExperienceTPE2)Subsonic ExperienceTPUB DiNTRCK 02/07TSRC DiN13TYER 2003APICimage/jpgJFIF``C    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?tkyl;iLlRNqUt>5yyLA>޵x.M=h#rrҹ kY^/0Ws8*TJkCOT~Dr| =muҮj!XMcll]`caޤ- slMXm(jR\)mP}sV&2K#HN }^McڼZZ 33bٴC5'1v=;WK lmC65GYO$a qoc\5pݻGj:֮eӴ?InaZLz- ñ?8ST/iEK ?鷟C[уzK6RHa5,F_u%{$cxBWԯ.oyv{ 3P7~-ka?Q,a{?4TВxUГ|uDvنjxs|.#APZ?ZjPx#vs TcNsD+Z5[GF.rs^k?QeэV4yce< wڂ>mگ nscwyFx@}:ɻtB:CmYMeу7ێkKp71]Oıʚ!ሬW!f 3"obtﰘmb=+:Wr=.IGX=n;[0{0a;:T>%.A'qdc]yw-7;2nsɮ$poڐNh~LO,:Y6`'ֽź=q~ a. A3us!{:H~ɥZ[S1!mZTԅ^-5W ,mV%ᙈ;tڅj7n&B}O~5_,Zzs܈H죮>5hOo6lSMnmL߆>1Χ1KTvg'<.{q^_;Ӎߎ "vp;䎌x*4u#ża!;Vk>C>xVbGk?Zl4>?Ƭ[izu$v?bߏjv `jQB?޴ #"ykk[#U5> fhF ^~7_}KZſݜy??#u)*E(=) v3Ӛxiî(9i2jL1Iڌ(7GΣ'ɼdgX{޴ՆTq6,coh XDоv\H<9o{oo&4ds4ec4Jp+J_C|w74=*BKMFCKtҡԼV!m: jkIW&,c6*81RM}cw12qӎ557V)o.(΅ s}O.<-W5f/̱ob2boAǍRmPksj٫Vl zO~nK9lUܫu^]lGt]GRxD 2{ΦDv{W70đ׭+jTw0υc$ǰ<3.g&2Ao.r=W1-b)-!6PEl[T>mk~#:I٥W>b㜎h`s|O\A3\j@Ѱt n|CĉuVʽW5%kz[ s3H"SOEť^ğuCT9׉N )!2Oc^x7Zĥ.aky%[#ǭt^&5 pm͜[idtpsV{@2 T4;tnmoçܳ2sJ eiV] 2*CͥY$´c ֡Yʸ%>s*Z i89 o2B2ʽg&[#jlrF3kXaޝN{0RNCWN|Ja_jzvܼ4N֪@=9JaQ7E 9m%xobt.v>OǾ)s*GF0{Z+2<ƦrsYmgW~OJ(pL 柩]O!1B[y$_k3WJܕ;<v7‹KdFIcU V\k=`5Ksڴ^޴uh<=?l:-Nov[ [.}j9..S(v&'hQ-;6%)uḥP֭1g7}A~MH+uyyVҹIDv%{f##r̳YcZwn OCqVΫ8j[  OҴ{yI|gvTSH˖Dz|=:tp^T36;?jO>7'/L \ײ^ºndui|qy#2<%}k|/r"Z]V&d4ac`~}kjZ=ڹ'$}km6 C kc S&>[ 44<㰫 b2HW+ʼn? ;Dw0rsݏtu'w]ƅ:'q31K[]6I"KX6:*( >]1@z|ψzo6r#,qG˾%$Y]oz|Ch%w-p~;%.h+^@=#@.8.G_ZN ^ԙ/4sE!=EqoݴeRW u5Og,,ɋ'+ޏNZS5H :8uymGq'޽Z1\iMs4F)cǚA_uzc5sΣ*{RmB$.$*Ö9ʰ5ŝ4Ċ`!i-==km&կ/xy7(=Cz2nlZ%wt5cӮ &gg.ϓӹUO{c)nCMTqƎ8zfj"c+5;ˡn;Xmɬ)e񖐎ZXi7"[r~:48\Cmzdd?>W;Dqq~5kspЫ-٣kHӭV"'(FW)>s,r+Fь7|afy\2i6d3Yhr98k-l`vi{uix5BqZzFfծ# BU+^6vCt(+O`=WBPJNm4&m?L׎kzk: BV$}ryUwjv-‘IW P8'MԤ츑U~Jg% 6/sup`dT%?+4ySo;N{ qtH[@{rQoٴt//Ki C (!F\Ȗ̫ˉ.&K4qԓUZڢxz&By ,6#8lc5MMAflZӅ5]tG$\:4#X'7WHڱ f\|əVEa(z e)ڬ sP9C ,zd' BwLJ8ێ+)Knr{ЭV%EeaW2qRnCa|pzrkU*gsV,:nn tZmMWR sQޣDwm2M_{.Y? r)%|V- )+1\Wr=jMCX Is1˱IX\7s=$cwq^J2[UfGҲuРs:v\ qyl$ƫIؾLn5 RҁoL<]G;ky# s8Tx@&6 ^X-%iC|'[bɆ{O1 W yu.;…@:~XomQ&T/,}+mbooegr+<4=DyuOxt{]80(9#MxW\_F^AygW_9:WtءoAxDv-c~^ιAow{"Wҽº>#݆ 6W Lz-qOԞ qxAZG *F}MVxQR<[|5|Eu6heU؏빽7tе'ִدͤI9>N}3^xo5M{l\fylz>31V%~8_/ۉennX.ӠceMde{>o&xvM?S.(Mf(vvHddP'*~5-*o+=ֹL`ӵ;&2/֓Evlg }A]meH xWrZJ17 ׊]Yxf`s<XVuH4ՙ!l|Hˀ@Z[FH. 2]-mYDGª +<]>@!X@QbVvWWHȖ;LO v˱U';hBe)#Ksc3g$+85-nz<@;AjypA4o&xv R&胜y`n5>7v>bԷ 'lfIU#UK*JXTYvHVQ-NCڝ ҤIp;UK[9.w|䪍I"" #<^ܭq!SvN$Ń2:\`wbAPG@y*t#-)ozTcԃLhk:+-Ixr#ݸ@.AQze#2`zgihO'oI_3h.`>ܣX..bqo֝ZrF;Щr>k\N|ŀeE*^H"0V1.xSa`zI=PZ8ֲw qIf!{}k9-K,O{2zT;Vi08_֤u  :sֳgwg 9;2􎨞u2NX㊁Dp9>9'҇-/'RMhKwԈ)4{VD% s[V eAS?)zrz^ټdIVh\X1ˍ#;<yn>Vc5+-/R,!Տ9ɻiPy:v>d#6TtZwMwZdz [jDr3 -bLy<}x5m6i.6C7Ҹ[ՙM+!vQ],; y%xV #y`:ЯK>xtg' ^X衂l;@+D|Y5ɸd/Muh^Եk;Xoʊ`sw_ڼO3I{dWUx<Ҕ!ʛD=c|.NCm^?+Ƶh5XqtZpˆ@{2q_Ok^ӵ;h@XzeߑiF1*?h#G&×3O=í1iPKi3E> 3ST[۩@FAATUP&:'mo[Z߯^#Sh&5U86FkiuE`(ҹ~YWRZ"Y8#7U,RN4onQTYBNs+!G^HG<\SMIEFq^agOdFz1+ҧQ, 5󮿯#XuYnyWniqRC j38x21}U^ 94[\] U4&Q%̟1}pzc֠0\3y«*1=#(>GdU:.8.zH{sެx-v6j##gƫǪC-<}iQK,P0W Fc 6$s]Oo*dQWUZm[LPA3w<1 2Q~;BWdfXdd83ozeĖveȘ".;UKImŹq<ѥݢj,6=Kq,!%x4[VӺ14އ9ni2ҌD> O ޝ)BN:֍b;Q[6]R0JYYf1+䖆Њ"N1 :m8lIOkOWP<[A5̽d~5'S)wY~Qs^t+JU/-4C)fd Vm@,0Xt?JȻpqLX$e;3ר5,)([Q\YO4Üml:UWH+Nڙr^Ǐ^jB09v:ݼghȡ/%R{*o#P޻GjpSvm~n+DO+Гݍz*[҈>bgI$:N"Vfwջv{U>TZbe̓ 9'=qUbI 9qt9&9'&솕~b 9")`CW{Kd Kt5Gkx3L uRGM ^RUZtB=wlH6G=@r9=Xh-d9SN2]K:9|d3amc7ɀ~z\M!\DǷX/IvױQK] Ny5n9''jgy3ICVՑsyG|1xR:M@7;}X@[ᆆ+ܡ_0y0z}+\;S?$( z94!EjCV|]@Ԛm`K{X#XOMnhd*=F ǰ gLCZ0yH' p: av\E);\gsӺmL qRf=bX9T*ǂݜ^zӲ)H~U+VB#rs?NU,A;\qVpAVGƮ y[QFU^$x=ku".+y|>jWL]xm1 3aT/Vo9VcW9.pHqAX՚*kVroOs[z 7ْegƩ[[/kHq[Ү/k|y8ՉW*j s5$#0W^׋|#!mQ5ge#=J|;=c_]rCa#v3޽H:r{ aIl_:m 6Xȹ-FJᏅ5+I!p^F_pU}=Qxs:WoY 9z(c#W/4I#U$SN Oॆࡒ[c'X]_#$wۃR|z;.t~\Ú=5Myw|gưgrGz%em{]O4xeK=9U8+롋bKe(2YO*x]犖 ei-HH88oYZOsygnci~"MIk{Eaӭ-#*"U>T5I */)tԱ4GsSK[FO2AQ)eoU#Mzm(JD'~i^1כBKM,1]9d>8m]ׇ>j7P^88ڀkVH]_cus.j0'7t4ќ`q\Ɠk+xʊMcKRLO}?y0ivtwh@'GTϵlG@zWGx2NԦ.Wy>+c]Yq:A'uk Ki^5b$85k>"uմD$On?Gpjm@'ڜ19SeZ w2B9nƑf= Sq]l6vHؕ3߸}*]JHp@5̇ hMTBq;tLm`eD81(ֺ Y6%x-~n ?QZ4plh&}OIsP̨M7x{dk[/ͷFQJ4VhL 87rK)hiu9]O[V,jSfN7~meWX@J`)cqMRrsbjpK.z-^YGlr՚7)=3]2iZGV}>1<'zqѳW9#\.I=sNxJg zXt+="V+ "Ȝa|He)hZZlRկcUzmV[ )^iQ<%G5䑇ębSҳ!c&X3^f\p4pTo#\kRcG;P;=uvڭF jQ7$鸜6֝ zv[H rk/ Eh*֫5=~iEg xS:: M5F)!Ԟ©3lB78 ^BV ɐ9aߍl>{KG&0~lbC9kJ*YpR?Z-^oN|-,/ZK-oǶzfG&0w6jKQ1? |/%oT{Nޡ⯋xzx|E^pA54N Zk*"EۼSm![KH Rs QZ,LQ$eΔV%lA))#m]璄ր'7] UkvP@-3޹|@;iWVB0HALtg'њu5FǷyƛOﶙ/wSO_?XThX#oe|{/4ے|T'Ҥ _n ˢΛ|&Z{|Qncx' clm>'ӊ'YWšnkm:0܅C})Z|[^_cp:dH^ߥr.W@}VHqZ?nQQTse9/YPn;1V~$i)S*YgD9Jԕ>˧hM{UKV𼶩B#T"XNAoSo?YclEUtWiPX cs9T᳞բw [xZĐjEZm*8gQ:ݶooї;UEyWW~4OGGn<=H\oFpn^?r9"t;&z]q( sqql%N>“VK[t=_zQC-iq5#;Q)\-JɑzTRԮmͲ*fCR6E 1LK?b"9Pmhl ˱G"/q9XTIT[;#,*Rò9U֮J#̠?Kw&ygFҷz&fx~M'Si'IbD3E1~ I dsȋM_BȏQn#P7d9qy>U&⍲+ҵ7U oq,Cf$ѬX,Yg2K/q< V4*jZ~09"KX b#lOVt'= ?B-/Y@6-B8{4 /c3AQjE-kqvu}꿋t %||I$WTNԭ ڍ٤mH$q `Օ f6FgjM^t[8]mC<1zWL[F~C;X|!pAsbʴry!aI %1a-N::}֯5l WӶ+<|4e]B4dJ*߳tR[SOi.7lV?tqZS7w̗SM<*_Wms,~`}EO|QoA!P tӌoQ6I$qwm4QݩxZEqO|3fIF>xm#K [ z+A\O91Lq{׫uV1z>>]%$qVw~C,p/=^in.XuS*P2vMy^sKƒ{Ոq!nu+&=E{_> 5o.-ט>ӴZ_.p#/~\ ߫WTuxe̷>]cW#ކu_kCK]7ʖ)8l/JckBm:Ԏ&$Cz~&>I-S,W E5I_Ak tQdgA-:ť]y/uY.)b-.sX?<;‘D3IϽv^g -[}I=3ǥvakă_a5ޟ}qks]ta+sSמqGJBI5+'40<'Nq6yOJ5:8>p*]ĭ&Mo gM!FYWx GPu>XvYz1cA9 6m$zË&HK@ȮhdeQ^}D'f-ߛO%ǧJMrքSizaj$71\Xݝ<\vdt9VihpKlxkVb Z. GRHy屙25eѭ#uݠ m#ol4+NV[h[uyk.}JIC.Ȯ$$::VFȖS"ckR;x}1ᶞ]>d+{=K - >.Flw3 5= U'Lw~uϮHni"#xWGL׬䲿{kv*ɫ9V9'MfqXdӺ;? Z4(Ҵ@gMrޙ 8NYneiZm7u>Vn5sQufU(>a'ZM]0ٰ]ҘF۽UHY:`*T>ЇE E0=rY˧JUWG}Otf3k;a\ML D˟\n۬]ҁ$S{׍Rkm ԷuKLgm~\ mc.'QD5 ZLZXe臰k˿jXܫ1&F[GS-G A-VG0A.qur@NOҧc虵PjFc8»0xel-MDe\G$-HFvk~qkP\!67OgtXbPdf%}jA!?])T甛3:WƓ>"g| ҵm?76H+e+;&,5(F1??JcO>9u>ƇJII7-V)~[UĭcIlO&mB!MSJRla$1ۻ=%@r ֵ4Tz|Gj^(v99+:u4;JT՝;V.T={XNxsegI^Ope>U_17eM]7,f\<φtm_:l%2aSMz~xC4H~+V@=%}yln}B}0H>Hao]һ9Ht[O;y%m\_ kũjif>=^tZk"MP"9lw5Y#G۾袅5{7Фci M2XWXN+kƶq4WRg8"Z]&':ϯk<ӟDj;/} 1[jFS@QѴ-3)eXk acԚF1K 9JJBϦMyYj2u͖9pt^3dh:o-[AQ53K3'zlEo M3q ;N* 2=~C[`tk.X.Pg >x?žK}Qڧ kAeP}ޗk-OpUݔOtY:kX{y w\8,)TsP1BQAoJscԅ~tKRҬ[9-nS yH<8|Q,1YH&!N2FlZ(h)xMnl?`*9ʋ/۵'Vm@ϥZTUL7iq3Fu W ]F8_ 70-hXc2˒=<7v_uJT -O"'ugK(gǰ]ĥNsH`z YyP:я뺷i:e xIEɫdqMaUr7Q~,hZ 7R6-݆=K h`r^u;i +~'5'R>ymB/S]j{mdu)#/V2tx:Ηki+$iطbHc7 PjwAfd>@lΩgku,S@>w:|8$~g}I>!5yasw_ܟ_Zni<6,ιWו_$32p3c#1lñɒxVVBɬE'\ggq#A E8oʽVS6Ϥb'?W{=$7cDkQ+8ڢ;.tk3ˈm=_+N=X%I،`t v&dۅÐ~u>(^\̣U3zÓTuK}#EoE 8]q\ E#;\7%:c&hXGGkm5-ljZ*J,$_Z˨@OנHYlͶ,7+!Q7}kyգ_.(9?_J»#7ZX3' s74-J69?Y֩R+; /#F{f]A~j 0?* [}/~u>$ƙ|fV9e(>5tkƫщfҊEv$#ص/[snZՋp֤䟧j8uVRάJ1diIF8 Ȕ? +٫ݗť:vtQUQ^M\cTGj@o+uh Ao$8`'!^Uރjo2 J)!Uϗٱ0=+߈x{Ms{|~VrRO:VӋjx͍r#B9<~{/. Ĕ7e=O/o b-YbKwh5ͨԢ D(ad,ܱ_j9vqC WCѵ%y앷<YEwO4#yوh#֪+ߨJihuwO}8Oƺ'^=rꚔ6Eh#Bsy]w<`wGw  Aks#?M\j^VfS2BYVltһ [sx[H=I-uoie?Lw5k7>u>xpn𡷽m{[䥰?Ÿ]Fw=1Yn1LjTiNQFr2́z/>)wgԮSl/惷־wklfS|OZȎ-K*z6J{An>iA8o3o޻ҹfze3wI#WO-;)VTdzqBԨ9됡$t#+m>%1[+!r)فisk>8EQ"wm<X Iږ8M,hxC3p5~U=Bu':Oi&@5ePj&PFQgb,3uCԱ<@.vZ#d⤕f{c1LMخiy?Za'9o&EnH钵(%47v$gq\׆$9m mF ja#i.ߍyY\KҭilBǹ=zkZ &"~Nu{{RfhV,^LAYWzJPԒFT8s qҬ棙-Z4Q3ُˑJy&F${(3x5MapiŦ'd+Ҫ/!?L1T+.5B!XrG[Sun>'r Ki6z087[u?Hmiq2 y{R~hI>^LzOz9ˌWS Ժy&[k˗1LU ;+BȷUi68Q99:u+_>&xd /lYtCUt֭4ZJr$w7=rk|M{?[KџXrwH$/.ysOqEk+GlØhxח-)FW7uGq ` Q]'!%jZʓJaұݬ 5R<l@&^?/KA4}5g[rmlVwzmtTdR3j^]M#[Ǚ9V(lw;194_c,!p}Iȸ4ქE{ MT 뚹$k> aQh dq]0'q"+Rp:0!G8;:Ta+^T31Yu c=+Qn\4ly_ :*ehVm@{VbFy9.9>&"[{+X"U@b"R[^>ީ-؊Q.[{1HzNf88+GzkmckZMwW?t~;*\4ɌZ+ٳ:Zەnc˞w ׼m_^\` sfhY`!KZUҼo*,%ܣqk_o_~q mE` +DŽ2xQ>ٜyM_C7 -2 嬳΢#[M2]GUdˋjr<jk^g4me s}BHu&Aj;hT1W_jwPQ&U=@$VP6|K[L-YS =Ts0sMLYZ&yϥeYrz'{-ƍ\!t}?PGE4eU3jnLda^rr~ԛ =5\IlYkkNZ_eSFL~#n)l!m~oBk'=j&\;Sf~1P1ZZ ;樆ۏzsM.jlpL!9mDZvl t^71D|rm?JV|xzbѴUe_rnrLVΪ&T%7.MR5 um1K#= k}ݮtX8{v(O;׭i !]!בX4vaiFQ2qўEw}yz"[&&kqkQCe{M%r)f^Vu?Jxm>I<!N+#]2O6NZCDzwK}F?=`q!A]g^u XlXשf/MJ &Sa q\lyL=:nr=r~+\ 5|I<(u]^gAKIv8yyS[V.QA>>Y}k%qΕ,RtrB9Ζ2ﶲD`m _O!z)?E6iA}~V=A5T'6ݰf+ w& PP[JM+ ɵ5qI%24Vԓ ˥uv^ 𾝦4&2 OֽKd-)sSo~Ƴյ8G_oso֞~Y"*cWk?Y.E-^ʲ~l27k7|A}QpraJ ?b7լVF^W#8"|ѵy^Hm*hN3~-oBZQ Cv{]r+%k\]Kֽv&(v;Hg>7"!sX~>F{:5C K(זj,ӵNml֗('5]x&)Lצܮ;",7L0}{)Ï̢+MeR2fp>o A.E/BJ!m} Cj[CZܙT6C+ UԀ8.sW)RxDK[6DZT^UՒG]|X"CDJ̓Gz/}RRi|l e<+^Ŋ8;W#R]}6q޺ԕȂiJ[mV^§ 6JֺMn#+]ҼR85; D|zqmRAsRJ; HV[xprX[ 4 nbT1fO$אΝ-%1wJ;Zխ!y3 `JƮh`*6 !חxދ.nH5k7[ld '9cIlM:](L<KgZOpcBǶtLR8ti:}ڃІQc:(9)t`.B9m-gL{Kw``$!R\[4$l|׃\򗙤f[5t6wc:Pp,fƷ(V"cz{}kc22.iA)>kGI/wku. 崋li$ˉ {Z-*y :_3VdUb~:U8<9C،bXy.XiQ-#OHWani\rzV5.m4i(r]FO*;kZ,L vLQXseY2Ā@'iàeG+ q#NJu>H }uev֑ȑ45g /esG~kn|Egqu?0<]>`FIg9jI  D#s}jpF<']@%@̧ }Et_/OE#Ӌ|䫺޳^Պ{Xb3&~ZҢ> $דkg.<י$޺˻[i3j++jO~53:UB=qŹy[VFCLuTF%6HG޽WD%O"ˑ0{CoyhМVL0-2Ll+iojDOu3}̐6[^X9XHA k٭dрOXũ)6!@9iko]M< ZzeIe}lA V#޸_Y^dWጌx>/[*f$PF-UXa$ZDW9$Gzٵ_Kzv=gN[6v"`Gb ]̻\gu+븮YW!$>p#Vg lj6yǥVԆM'9#'r8YfU =_81ϥRՒYZergӬpiAFhA֨MO[W?x&sֺ+)4|I>vʷ)r6s,3,h7 c[oδ0;zʹԒW;=wW\]}3ZopVƴ\XŲ?۴55 HJ:aZsjTW\ac5\ŊI5mO-s.S֩H8ȭ}:MR;1"ƭ#k"Iԡc-Ɏ,tP&-#5mNi&bO ϧw>Kk ke_&Ϻi0@ pZO֧#MxpzƝs7Ͳ4f٧Z!#f2zsWXf͈x, >\}-g8%(t9ξ[Ʈj]ђJJ#5v>/|Gm LWр=}~kޠkٟ&iZg=k:8|W*Pi\K J]CdϝWF3<` up/5H*l}3^/tŧ#PӼ5*Fɹ뚚[5WHQUpj%{>>h>VGiPخ Z[̮}qԚm<-q\3I>ґ\b֗%$UDzPaīw5 ,v%?I#Z}a iwV\L}At"Fׅy23! uhKgDGL)1OAjmxWƚu .~Bs!B:½GEj7eYjbǮzxڭΡy׻n6?"G"6ScggS8J[hd}Zזqsdy֣2c̡-VOKjG,0(^[h"/ZYi B0cR@~ټP!rNA` 2iX dA\߈eۛ:{{q$g8׊vW%]|AZ6MFF8@#ֵm=B;sj7ڼh@cb8Lkm"+g~I {Elqw է \/Fp3W #;[ mɮV)$2Mw 2j.DpNc)5&%{[$lÓU$st$Ĵ[x.H񏠩K2̹x3}hؒd:r:9'$vuչa"iWKw"5dX\ 犧oZG) TB&eH jCԒJqۃ 8RgRͶ%*F)'>M*)Fm͛[rܳ !ةec#%Zg_1}֪O4` YK1iBT?^`j̥bY7MSR ]v\5\5zz\-́n8$p}*[icV ^+SIt9j\tvM'.ظȢ90ɪ6ey9YcqPHH!liS#sǠ&ijl.f^1  azvZ=:so##'޴vԵM$tM!|OpĪ0E8rZ moĞk6 %a']=ƮjmO#n>:VԡN̎Ӧ6Ila9=Vu툄?&=Vm&3Q0t* er;jDgWt/'cבەK $LT^yF)2}y|#{ c0pM6G,2Iq! Zπ%45+[0YZI}zWj t {3NI-dͪwq&˸m#'G$Ɯs'#sytшE* ǻvP\mL1doľ(&}Y]L6 C'^=vbPUI#'\CJ3j t&wrUk<k{8`}[G/4Xg-gq,Aប(5ʺ~{܈l-s5u $wD$׊7ģ^!$A m'U5+ocP>FLch;ak|jZ׭۬ڣ㹸IB\=s0pNǘk k"k.Wag7Hk&l@ J0,VRNnV2mqH5[QvzxoXյ=ƭe{'=;L_?ŷ˟RvrGaq[[i;&k|(LFb;֊dƔPGf{V&ZYܛK8.5-ATz-s^>'t+[w0K2Wgi6ڤ0!$)I5QKV 7՞ڟΟ`g.iB/coOvQ\E)Q0~0g4:wlY#?NxGW^;/Mx'a:V/i]}oQxuB0f$ۂAc9-Tg֮Řh2tJf$9ҠٍUt@>a 8իڸ'hkQk"9uwR6fY"[ԂIBg"qXBw8H9#>UrD<-0qP\0*Zwz쉑kh#l4ya_W92_CCEFhxᔌk{ P7%A4=>H3'[TH&8O ʫuxv>TT!B޴qEHqQ[$v`*eRxb&s:Y6%cx⼛[ YbJ5I,'xw)5š4kFKr[dNd%'fjC+ej! \߈u[7_H}WԮd`dWsR.*:i.c34 U,~ -&dF5CJ@-ī a֣yDT*ՆyI{۩T$rEz<~9Dphn,ĩh,nK y6 ʹt{R*K[bQEJF jZX.ēgޙӟZidѩZy^DҲ'M1\pj@Zud$4zsK!F!#=ȫKKSjzks?2y2gƜ*EW4r^ӯ}cs{֕ DK9f|wT<=:kۍ2Xw)imrVK6a0y'ӎ*ܔiDoPcr^?=@V6nT.WN kVn+;Hmya ūIӑu%e^7 Tbe"H ݓZnn?va3ـ\Z[-xU.Hz֬\C<:d POcQrdxmWGϖ5Uܷy0A-Qij6rLgmc[v7da}h^䶻 ivb0fYzr S (-S,N5B֗-,xZPpM3 11呵y+A Vs$mgi^Z~cX;kOŒ^*QkTe&_%ݽݲ[Π]-@ћ8,B6p:Vr9qP2ELM0;Rl[k \'=A:y$1'`eC̽K:%Yu0JPjΞ؁֗\$O@k%}:\K֢Dy;[>T.xꜗQ;^K<)?iه2=vLʛVdgt$Lwc%& ~5"6~7X5R+<_#G*uB%)t!k߇hm'mYr45ҹ CW֌j.B}Uk@ bpkXq܎.R{rv׉yڳcƽn 뻆C՛WV) aR hӭDkSd \hZeUA+Di?eV8DD"=&X1k\lQƦ4O_]ݿXʽ2J|cmUO?$nԜ︘7  LҮ{'c:Ӽ:S|,kw7+Q5GY-!?0 ;Bhޛi'u-2&bՒe/0zvVmU 4)㴓k$}pqbFXC`jz2!-$9=jY#/ގ^N0`} c_c]xSZޤr'_w>_iw>|v(e)8`LRg#)A8֢,}\J cvaks۬UJN?ĦGGx(g? rn.O">EQtO=ǥ#`cZLdwz}ZNJWC/#he%vSɟlU='FvrrSڍU|Hx mkA[InEf~KBfZ皅:|;|RduHmUF(\dhm[vcrZ?C5ٴa8""e|&$Æwպ{T{BV|f-fqcb oǺ奈"#؁]T^EuuӦPB}9W/&o i9A 3r={sG>8\d6G,o!Av85 +[iUMwͰ{IRZ|9ExමY瑂 F#P2R[[C"S;p HtV*Jwn(7|Ћ/,kKvn-y08Uú+f\Lusڱ'4i9[, due,<[QQQI{kXl7\7w>5KPކ{կH΂J̋\e=Hc[1]VsjԳ  E8N =RE9fjnRN#֭NmX,GInD"?0C#V%;V;гe`x>oV$9!#d jXϘ_6e[M6l41h2EYD(DJ2)mLCg3ISԚ}>|n]P1fL{nչFy9+DDNڶe.~TIԯy>ĎMc1mǯ5|޵Q5/&K'=)IVwhDiZ(*̄|Gݪ,=*OL2ʟu+F2 gm4!gSQܤR 15YC\&yR[LxR:T NSցc)*Zc֚ < /S֦>0Q85p[/1m*[FK;5C$Wd_ٷzk/%yJb"W𩯧~ӴpOq.2<^l2L hˬX2@݀=uKyy-eOuڻF0@.0;{]A"eEk&WItc? znOᭉW(ٝANKntr}1Gr/uuX{c+ 嵻&hG2FUWQ!6! -{}[ Z)S p 9MYx[u_=v,~kqO7=&"`xy֥ =]a>Qj oAKT$ʒ)Mb$+)F@_/|964+CB}sVCFkQ-<zVbggX,Fd|4^NQ۝/Z) `Mwm*]J@%I} ubDџʗⷅ^^j ׍*q+ֵ[mbeU@g8׾-:&MÊ/x'eӃ+A˨L8J( oxCľҶjlQYY,ݰÜW42–$NpbX}z{_^<_nRvzןxaz5jIkZ6|Ev/'Ԟ/ƕ̟g֓`g.%zU.=kм'N !sy\yb7^-[ɷ,i7(k٢|O$ CS4j)VܦLnysR[r?OڅPD . 8RӮ-M͔V'd<~5Sė7^5m̞T=O΂)-5m+HgmYPz5mY+Χ H.مr$B?\.u$Td<2? \`LfΎO}VxZHKJӵ)ZvNu)z*JAj2VQc%u-ڇO\m< X'{xgSi5y(IF䢠,g 2,*&s+4[F.uUkplByCPʼoJ-JHryR,u&NHϵ%{}R($h$|. MRM\e:tR[6=89m~prF`\bGW_9J1me)~W}N{Okw$E-· j}뫒h,;tH<L" :3rj>RqQ*[uvV`8 ,Oޘ$Y^8ѓZt(iYK0OtyS5fPC c<dnr= -ׇeG8-dEF-mPAv`6C-P=}*.T#8b^El0?MWn|Ar\f3v.#6emV|b W޹,m$E%N!隻{dq&f$oa8wd9=j5KB J--r"{<*ʊCڴ 'X8A__ֵjiiHaR^θ#>ڬ"W?ZǼ`8)IU]xB[OJdXr{ @A v80[=+]UɩKK2ĄN:IIU#GI%Ǚ1cUd]D=&I!22_ Ӵ'gơq~LU50\%̱YbC7pUF7+Niy}zFgAE#5W_R7 ۇHQ 乐?ҡ4.%[[?2n1#?kMD1nL#iߒFaF"a.Ö~UZ)Cok\i3:^_#p\ \_IoŒqζ)8Ms^}u?MnXܨZΏ⑋XSnځ$q>ޔ -B\͝lbf~ {Of%v mn#Ov'߰)zW+w2i+wytw{-)gҤ"[ęd*35 &RS2sּQF6Y'bQ٦XWo}Jِ#ZmK\uVcy{,Nvg>P8\1ִTPNm줜Wyh^%ox,-0k7ƞۮo}+"\4Z9;ɖgbiƿu W_|8u=*.푮[lP?_Ƣ󲶩kh+ ;^&]'Ivǟf=떶%BJj']ܚ|wC٥ ,M:h-QO=="].uh5[gYS0[,S<۾.+=yi}gNf 85}NkR+_ m- OwKu{iqU*J-69X֎YxcEk}ġ1dފ߁,R. ڊ,tej8k7֥&?Mic"ް}\D3-ijJZv2䚐jWqB`GֹS,~9+,I[dhVBн_ "4n+ l)4m?XMZY^^ܩzd>O"#E'iN1skAٽFw5Ɲxk{@Br:G SZ^j K@ _`ZotcUGw}CžΙ )uIȋv#-|Ev"YY^5`<"C?֮YǞ*ϷpZwkSë{p3̑OGZzO7s ƑGAYCƺ4t,3};~Mo/mȥI`[gZJGbVڊJلS]$>aQ"Wlc&NMxͷ.ju1[B<;;=i7N 2~N[FN(x(;;hol~63";}o -vWVx:%S_=!bߊxxo^vv;̎vlsIۅRDkkgpyN!Gyi(uO )zENdF|۹̝H֋h\7jMl[K BuvpGs$7,-1)a"))wZ)#bCҺ<=a2 Tn#Xgp;[T?)@+W:+#Iu "V&82킿c"Fְ\Mrm+WMc\_;TdvOҸY~楨.-VhtpiEͭ,!,w¦Ifo~UGq{i'+"<"5N~~b_2ӡ]O{9TgۊMvI3nKE1a1FY$2S]4eFT NY$ђd*ՋaU;=NK# nK1u&'hPnd_;\-K?W*Yvo}+J/t:Y"=;ZJw 7`Nw d?|sҭY&53ҙ0Y@=E## sӜ?7uThܶKny'\)`HQzqUYGOJ/,$drrJ3Ն}KMc Ƭ[hr(VͬF.x ƝiI'XN sgRFQe%[u&x {5ǀH#Iv jrq݆a]-T6&OI+zV2EM[܁u\G8uJ80:`bԴb(緌yVi_ƶSj,m%X`_&472E;{k hUKLjܻ?'7񞉠KFNdE,O7č94Y=)#yB2h9:WN0ӼC\̀BW3h:~uײ_ 2)kWJ9iqפ? a}sqY]3~ҹfVjyGUO rsFx9博@z(T%dFa[-4u\52hMm< xtޤ:vO6MtnulE* TA뺼ʩ\L4Eim@˕xU'dO\K%qq?f@YO]Yk(n!HL {Oq3Zivlr +jtg*K:s [K HOz,+k]}P0V^dh^Q QUbSeyg xR}sVpSR$$JgbZ|d1Һ঳r+k?ҽRkF(<rWEg.tJq}" OZa WCo<)OHr}+#` yαQ-%~?J˸EM~HZ`*"HipNsG%ͧn7+mfp:N^Fk-C=yFڴKr";&TM׼ҝ$9*;V߈}+SOڲο;#U>j{ڲ /]AXڲ%Sjzi`ln0 ^WꚆ-ʡdQힵ IկU?vI#>1/sb*ؖ\( )*G-QV~)SPՈ+5kwZ~jm<=#0\q;q}W"-n!@yV)72ԑY«ߠ] ,<Fv>E;OԼUNc{KvY/`ȭEҤbIV_'7Gƺ: 5vFp{2n[zږt>okB]G&v#70}'wLV]du= l%ŜD>E6R{ ڰqUr#W4cu3TibMH>\JG݈P2)y^? tmJi߄SHEf>h>44um2]bFezt +j9 ӗadKa藶 }H2+xc{fh6,:\=ͼDe[ڳN\?\e$Œ[ڷ0bw'l5y= {8!yqzG2Z{8IS@*js^>LQhXM&pغ.HW|YE{$0z2x28i\芓kC*ЬPY$⣰i.Ieg9=x5b(޽.[/j Cn6 :wxH$"X$m9U[`vrK? iK4<>V籨&I:m8aLNA;u_s~ƜS@I?jO?Z~^OXK4(9w) H+$b4g98s1sLxY$1J6<%~-K(@AV-ӂsTkk `e~ՉiI[-:Pǒ;IbΞu \XQ]yrŏIڦmZ\=NQCہZ\5,$}? Gz[+ 죸BPRqWxÚ߃^%.d+*Zy=xm[Ь%yL`\5?4R̍:+?Jxq3}hN+lnLgL'gַ"F< E]Avi &HYNNTyƹB'ǵtBNysOk~ƍhӺ]DR'<L9=+M ľ&UȳZy7@R3jQ55"\t? :Y3ܰC`@j;xI1oʓM=vd.Fn cXz=|^Տ^Ի)M5b>5MBNcel|F鞵ΌWuM7Qm:_=մ|vJlEpF\2DQH4Mqaybğ@5׾5&[ vr?x/5>yem6ݻz?oݥvX>ǥyƣ;w.7zMjS$ /l۵E{׶!DbA"Ct^s*ėYt[$u_. cePz+VYk֎Ypzb)Ћ~V:e}}lGUFR}xYZyͼ.ߞ+ #T>Kb[kn. TաNzy #.M&[{-B&p^}e.ut{P8Wi7&m"pc筴[ u t#:\,G_UƔZZcǬ5b(mV8E\E?MNr?ÈKo+l+ƺ#ukvAӭ?~~{mGV ^w26?=jm}Fi"}TSMgJrkVZ1.Rt:M5yLl+״4mdv/&B[X#z֔b_/c2qryr$Gn41ʷdG[|?89Y4Ԟ'7*v ߆:D*FzHۡA<[ռwڏ54hv\MOֹ[ma}MtO _k4Nj{"q TN>~vQӡ l^doP楻LD]x3@Tҷ|:ư^ I% R:^g9mou / RJEtyW5i- Jn+OK{mF\ӶNI Q(Y̾"4W RG*gj|/[Rػ֚UlŧC9'C]vX ֚vkdVwo6mkjr RFpG5i:Sr'{]:2 .]vrOִt_WQ4aVhMǶ:^UJ3^XY.$;[Ttt_ kQ`$FWxH m,-?e^qKӮͶ Ոw=>aoN׎n?Jȉal|k p\Րw3f/8ܑIg"Kkx76u2|v ?h#NI4mRRXrk1}jZfo(zAXê;3?48P5 4\#rq>O}WJۢi >=ukZ ,`[:r?Ȭ8d5JXB)--ڡTRr}>VP^,/H"e]QVQ$IոRԾ#_inL#A?v>1W|qieB AB]hW2-n.BbB?5SCI=nOM׋OOlxS2d7QZlOC2^v2ГхvkH5;nfMYw,r6Vqs-wt|I'ڴ'V_%8rw\tmomJ 3ħ𞵬)(f͡vpy^H*El\9VTkQjvѴH=~V=ޘ<+ep |Zj35¢/B֩]9k0K'\t9D*ӛ+4ͲsWpκew?.F8SjJmcb+1uֽSQn`OuOi%ݭIF }A"oPJ-SJܶwĎY ܤ} vWkdhQsJõhfROsL OZsg"xQ"MH*Em61Gމ o"K.KĹ?f%מ}+@SRen!LuJ*{ٵ-"8d;S*;(B:lhwfm 𖛫,ڝ%$C-vCmp>IS {{mO]_%\Ү|ȤHs>Ҽ*n-P㴵ur޻TqXrh;4՞KGqU5^ho^9\h,t ҵtӭ5  CrJ:u)]hi~'$]Nme9=:?xcMm,rt>M#UnV[:i߬m#F1WoZ4Ѝ3ȤmaIBf6-Yܼ6iA׾jе=>$y8XCm9Dڍ+Q.zv5Gs] `*AtTQ-qHu7.PҊMFwL* gB&Qj^y[0}ֳM6&i9u_GZKV-*Yv'p#:.$63_O% [ ^7kxZ[ہm% H ΰbAsc#d#T+@:__<ҙ<!鎦W.@vSC,GZe#W,wsu)g+(+?"$n}?j7dJ XE\<y,tYlgB*to]K0Gn}MU'/&FYRkIκhW9j%cuo[Gc I9FpI04>Ԗ}œJ%I5zZ[`'(]i9Q+_ :[bic5m r^'nu+`W%l%I.Zvt{YZ.Ԗl=Oa?4X-&PD͸W4Ėv'M7XnHܓxDq=*OOBmZpPIu5v֮O01~VuofҾЎG⛐TiXV=ZxIM&:Q[v\V}/ =j|0}kҞ:*(=#S{8x.z\.^jfQ$m(PR&=*H89cG60[CmRY<6[EQpIIoІ9WqInO|U;R.My4g VSLj/ΥVZH Z 4>cVxzW3屸z?qZJc!7kK (;O8h5i3=ؕvdȹdy70!QҰj欐Myu'!y_8: U%Iou?~C~n|trߚ)|ФХ-Q r 8-+Zkn+[+Ե7˼ki695OkKm}wGTiOLokuWIiXz]5Unv/eS FG^Efkz>oqȖeSׯn5۫ˉ$8;R>[Mi$K-uf93n.dO̎ N_HxBլ0 f!a"'{%lG,{+- ;̣vƬXMk oylnmP#C 'pc~Z:db(ħ]}=(rñhVzxu4{Y%YlDZ# ~{WhzĚK"] + e6{WkIkv#.-lDyЙ wV C#Ey<5-oWKeK #jbI|?Bzb[VDIN~e$Ns_Jvѱ=><"NܫO}e7rp<2=y-MX2A6|7VHH](iU`I sdҢ=hVkD]2ȱT$gsc󫍥 {&"*4: @$գh:%{+I%XԻ0^zRm-JLBb]6:1’@\éNA翛͸`8P8*MW=(ґGLҖ,I dq@q돭Oiew} k X+狓D&4]"yHKh>0jvдv>n@O+14!gRY@$mu&K/zыv6i/ V6 SحJFō/wZoљ/+h^`vmZIl=7L>0YČC' "b ]&NӶqI#lP{;ҊQvQjj)mq2c "CܓⱯ>)xWX[H*Pud;G.74$q8[wm?Leٽ)Yu{nzV /xu%.rN!~|dVv 8cַ5ocek"E;SzlZ%Ԗ29F8NN%ȍuxq<|u?)gX# Yu;+I&-)=iO<35O}ہGʹRDvQ_SlVn{r3\Kc.b(ƴy9]6-zHm~LcP85uS33\}!wm*}A=|=6,CG$G29tA7ʜ#u>7ѷkPN>t5mɻ5 2nyc$!oS7.E$~".;(-O"9 Cg5D y늉bFONozl>򸗌csMŏ=Yk [eƜosǖ{0hKo'֖gc{;@/V _z6NĹ.Lo" 3~p:\s^EKK=:.mWI5i=&ѥt_(Y6IyGyn/U ~{>w1}+\D]X5~JҸeԭmL+ݏwxI3p#ik='!cA@z鰩w8BzW{'|g̖G'G+^BMGq#\][vis]fOi7_hJ*ekR%Kx Pġ'xI;q8JڜʥA>94lև{eA;lmo]GpfTHׇ4mcl3ޯ>+o,!W Gn$zVb6]Gxz;YCI#D6٦9JVl{0Lw657h7esͭ~xFř˭hu ȟ\q+FmלשWO#|-IZ`Z& tkB񟀫"?t(bm5]d fw:iVo2)%\>4.vZҮW K*E뵚9."pˍV5AXb?: Fy 0\7!5OM1t8#۪OK5R]ViC7b1J&@md }?TXӁ$JC`E.eѭou*eW 0Ƹy,JpWOk.z,Z+zZO!;|dƸjxz/f9-MM9sW4-ZMXaHa=Bmf fS3FD*q:0F3]¼oL3|oh# ΏkDH;kwx'@U~Y{mm1yXHқS}b [2>}fZI:_h,뫕sXTZׁd6ךbБ$f?^էxo?R¯rURO_M:W~,/3; :x|{}\-U2}j-/M56{sgkV兎nP~e0=j!Z7uGۇjmbz$HxO jr\5[PH+KG rn ŵGGՕ>qZFtؗ =^I XL5M$o.X>Jnr1ZGnBLxYjtjޖ]eWqRO`;C#]pZMӼmokK9"|$wRF8Esi{$w~d.JpƥNHt%+4QzI(1>]]Əa5PZ(ojͽ78j$21|[ ϡj=x:eY判SGGL|MBxN$s\| d?4/o#=1ڶ&Ѭ,<:}v10Ô ?ɯ.xt߳GLauvS:L͊ϪZCiv%crʧ޲m2&n#nUvT,hLzcSGyu7޷]կQ5Vjݵ7`OIu#ggCm2G}Xγim.S`-??uܗF>5Y^\wyqHk JWӻ% 5 9]z؋u6Î{jIuz|ZHǶyɨֵ5?0\*1m=^(Sk]H<=*V!STifF=(;Mb#2*]>".ϨBCgTpzPYef2lӷX,dfw2Eŷ׽8p*{ ܬ 属:UwC]; ;m!hv!3ι} Jq`gd/$\&,!N[h7,B]k dzQmN쫞c<̗^ugd4׆bb=֮rʹN$N0Â(:ū-11[>G=2 |BHdmd W}&xdʹ k amPUzi;kw6K]f-K섃!OY?hdZȹۀ"?]k!ԭ$C-G{NBH'?'$dXh;Rw⾐ܨt&$䆈TgXbE'v#U9y;iڛG-$j3 S.?ssLc^`zWC4I8}t\e[e#<*\%cZQx8*@5݊|U~VVo9#EwIU,u d[g ? TE2]W|q2 2g?;c?yuzfiO?Z1|cd'bO95YiP.U=©³zcu`|B@> :y_J8hj›XC[v\})\ޗqF)SUbЦ1O4y9?m't1sڭ6aE`2soj˂QfOۤX[}OW/#["]PaVZu7tNZ= *Zpj<6 0Z;I3e?5H m~_wiSD#aΝi%6Q>vypTͱ \엒51ݞ{SY{p9܍jͤ@m +R|uvבK Fz;CF6=Dm:-剋Cyel-/nff:55ޠ.Xkn-`Ac=Ζn5-8w˸H}9xWg- ^Yd(±mm⸻l@C~&xwW6v%u޲}X2,D [\ ~THP-)nox6mCKK(4yA' >݅ImOHIRV/O[>W1h~ y޵,oWIVS}tNU\eKgNefmZPBx.{5}2_SOI<.&߅P]YT 0OgŤ u",sqrm's=+h3k-ckr<ӹ_^$\2O "MhNԀx\i:maVj`IIjoYmA;#fSJؗ'}! {9nURHU{{+`.. {W)7BmowL|AnۏLZ]k}̾J6;rѧJmE>i- pNEA}6 $8'=)!{ɳDN=P/5^iu8dĒ$W^Y UyÑ4.cnl!-9ӽOS]I\O%O5w-ܸF+Zk[(b=Ϋ69Dn-&nOFI'sk/yw8v/<דjױ, ?(+v֋& Kns^Œye/;e<9-Kx}F?0A隹 -"i FiU WHK<=/*(=5&ռw7P7ٶsV (\E}%Ҥvaeg'9#6.5$hV < ~팂Grۼ%.=+mdgjl©rZ긜?72wly|%]X8q4`J+o|c}r$10A}֡/ -ګx^$]~3Jz69\S0SEGm4Q5q-/bWId]3V8ը`G_\Zǩ۽]+2y$(@C]Thhhg?O+[Z+@4&EM&F5%; =hK64Yq5_-Xq$=&Dy$XK;4E$2R$p!96zԿ5+/WEנ0+Zj2M&q%r!2?8JQmǯkMߊmivZltޢԼU%I@ {7G5;4T CE^QLcmݩHGszF$]& !lUdBMz֖ akA)!dPpr5I쎌X`VtCRIY )EN9#kY[v *NKHwj|HYY$Gh\^iZ@\FpSUBu|C<{39㧵ZoKv>$e@}c hǵ5|9ҞBSl?F1М<ܳ,]SP{{Fq}8oDPmfbbO`{i5f+%[Y`v}Z7|BBs.El浘%BY,=%.t+iiޒIdPz֎iʺܒ?,>@^ϣ?\k o}7Q)6U7Nϖ\>zj\Z ..jWxX$Ufs+ v8eq嶅{I^ Gq #EweeGd/Lh'"3ozZJ/'??'+h)? n iEB)h(E)(<'iWtW^aF\c[jz"բ9Xmb8 n(w5$}*{+{>nHM\צM}>OӤ2@ӞiMկ67p$-9 u=]ɧʗF^=@Lw, t)={ƛqiVvVY%Sbķ7MM^x&Fq7aƑ> ڗms`6FI {>K5P۫$u9ҳln,LQ#UˊnR8;Yxk{[5m/ 6WSw`yjh:/t{˝Fɵ;]V[S17U& Ks [EgL3)|‡kq+uWvo19]W ~ GdM#kD@i u$3 >Oz7 d;?tzuriYmTk@nj}ޛ~_jP+I E.v#c@v ۝vxiȒ.c o> 41Ewa9%6۱¦?ۥs8Io-$U2A=3W4OO&*I :S/O?ǵ غmiYigt7/p_ i$>K߰%c}*,t=>ËYuUq9U vu':%.%w~Rv~5KFf1c?8 }B=KN7Mn䯗!9X As%՝WA c@htcE.4;{i,mnḒ9Kg;:Iۉ?,aP-q&ՉK/1ۤC513 ̭;Q`63þ/t++)wY%2Fݸ3}NsK70ZVBE盔'0îGՙ7&[#idvHrd`-1ޜU-֚-$r-W=iWַXh+M"bC:\&~лU}>>o[i}孼Q r~4|WYqqnvo`{ߦ+#¾z̚c̱þZ#NYWWǗӵ[+Kh%QˑT`1 ↣^O6e}f_|bIsۯ8<`="~i-yqgwY. up}}k#SqSSj>lnf$;’}:bOvaokibnc5iV>4~>x0kG>VuO}Gݜ}ΘoJ xyejhEB_cyhp׍?a:ޕYi6_!t"EbsGhм1}Ƕn5+8-]HifC?1^>xE#HnĎX4OMx\Am383< 4oۋ ;.~t =iUûO|Q>}y%@$Vu xB_ ifA{9q_xma$A1ʙjg⾧[Yiz~op.M$pw|4XNVܥ$+98BլIq :Ξl|BN~ם,Hx.ňА_:oSo #?B-Cx»SIGתvAaqZDžZDžvdQ>Sw q:Dž/<+^8GxQ} %^o<(OЮΌqGתvA{`j?B GxWiG; Wp޿5?G<+^G} ?5?]8tǭ^}ۨ7+mEO]*2|*?:З bYTVaa]8|D^燚e# m꺟)Ÿ?'|%?*~-ԯM:-=A$h+[B[ )L7cZs#Ox@?Sh?w@85>KrUnA.o!&[Q-hc 1;MWN3 [RϞOx ?ƏS;@O:ƹ5Ol#WƭJn& ?Ɨ%%OŒ )3;%;/O_օG҂ҎfGw@ /)v%5(=?J9X/?ƏS;'@Ok@v`g) I w֛F:Qg;h/?N2 J AG3 #S<'=IM69Y&ž$Ty@ UQҎfGɃd?; _Y즐=(acS;ƏS;_XTztw)OI {ǟGWAGJ6O)OӇ/ƾŸ3{u֕* 02A* @ԀQ(/9a zYYʣ\QOٗ~7I}$޿v,@=6B;ϏqO667kL'?J{`&v NrGҹjeh&\MW0җo7]zh͂FI4 x=H`&>)NLA+Կa k]%{<6b)fen MNseaWC46r~uMDYTZ&ĸ~&@BB1>Xm˸0犾$coP_ѷKzNc6iR6xI\WG,S+j)vro 6 i˧2Ґ8Pި;T7[M{vMc\k8ӯS[H? O5ϳc{֥WEBh峔S5kp (#+ԠKy#\q"!nUՋg3cKySO_m'>ph=Zˤ[eyXֳxH+e<&bvXie5 y6Mi*7\=L1J85~M>Bl,y rʔb~'#oiMo"F+30 pmoHr=`AWaFk\>J,}Yv+A8hbhw#c4\}:?3> Ip2x)|'SjZ&lw^Q6u;yLJ1`uzuw$kقkib)ӏ4㧖qkIߙ' GfkDKpyI+]:hlut=x?J ^]F㹃$~$^F?l2XAgU͆*@5 }NYQX=Zޙ=0+WTZAl rtjxNMwt%dM _/n״mu/3gl kwHhFYq?5+$-ySf1K\kYiA񯆁՘"[DMOĆ1W#"9ǵwЯcv}W<93Zf=Nʾh֛^jBݤpںM;ÖQK$ZA"݅*بvzu;ukf (- Vؒqƴ]wFia-7*-V{'Ja_Cww>bgV$`\.[IJK{ #}͌"N%.nࣘǖ:քdVI-xIbMi(_J"Fo֜_/hZ 8;N3Skcd:u7iz폸4r>MgNݥon rN~О_]^ _-|7$z aoN#mV{fΩk؇mT-HjxD=58? ZL~$j$&|FB0T$j=ܾ z5eZ( L饱*=xG#OExW8ոE<) )y[ iWBF7 {n0}1PN *'zqҚ[m;ʾZ~$Ёq=*Vu5.U[s2*V9Oj"%b8`pT}hy _M82WZܭ+S3U%FyUnM,&bC㟝g4)(3.eo1c?Dug#<cҨy m9uBM{KYYO2[x;u-. YVC}e=wMX6lk$:oS[HuBX3Nڊc 彪G"4W9ɪZDrD|ewUu PSK, A^RV#hu:E1 d 9P<+ !HbX.ZM[r13,y<#=)mb]5knP6kԧdw1nW)xzm]<[8@-$:zf /lby+j"i~4ГgOU ]5M*Ŀz=҄g:T>crޣ,͈݈pW^kpsRLN_l,,hL˽+5ҚEoTsz5hxkI%ۂUJxC_.a>n_=+a֢hG9$ڵo2vJHtWX֓޿oýn._w֬E8C20@MmUI m^JO7H5, yqwŶ7ѵbxd$횡kթMBx \>f8FwGgkV8fM(\5{c|Evv10ڸhD21:Z6r`P C С+vZ1(bsɞv~$]ZNktw$RvD u]^ UM.y2gۃϚRx̊v.5̗ʅOP*V"#6q'"JoZ=܊݃Tԩ}b$m1L ,t@ «Gz<umWvFY]z.a]4N3}EX'I&)qF=y}#K@8׸k6|X8nG*(xgF=A|bO+t1·ڮ̶>Lda OkQubЏֹxgDrSJJM-E26z8b."dK(7v?<^*;٦݁"̇*+-]ԑ9![EoT\id2d5gi4ZFLrNK}jPޔs=P*%3T +ܨS *sw>N Ym*T5)PwzqH̍6 @VZJ_&J}aH-9>B&{֌oڵNx\ĩM iRiA.X3BƝ<&0Dyl`g8$*Ǒ=BkV]77cib>rT3=;q5ؘ-$h.ӧqRXv$w xZtmcnxsL\*Bܩ$\jw}gyly?Z[-].Uf~S2-qY0*IʬG)$c %đ}p k\myCI#בke-@rxlbx|%0vGN ]³ݲzEk٠+C~'s셥$,x_ʺK}s}4 Qj5m"}\z*tNE< B0`5ȴ{yXZc&HQv$Awyn#@0ԑyc5ׅQ'-dv^͡Cmi9a1}vhc]#[V5,*%Α2I7]pNxoB㪱&2[A5YHVUXZw3e<>[O.e'5^YY 圈 { o 2pXJ}FH[kVso돦bjq%Ѹ|NX²Y\:nX}Zk+U\FiW]s {$>aJ>\\iwulTJ\d VŅƵ]Tl&q|]M-PX$ V>un[ "v]u-foeqD2vr5YhX,=*mڞX\*ì޶?Cs^iY^/" \Dp?fD*upzVi嗎'#v#؞<j3js.ֹJZزvboUw AbvK4 'lvV8=M/[5jog-pC]dgeezחky>l,iysıM~]B $.|+dunswzz5)b5DzL֙.@I[ k [~ sOVf1ȋ;=MlM: 7xugxMՠ=NN>5[·WN΢O4HNGZ_>.N+r?4Kmg83$^Lf{r-mf|`يg&[=y"rfU62/,kJ\RJh(/N+дw21ΤrĬHEuGU]-y3FEyNUc,tgyY,ܚ+ԯuK$w c t]+F~>@42f!CaG O=3E,kTeF༊(pvPc7 @Ҋ)*; _R EUmVIpK̍ɜF x22~r4QY֜'f++58sK;nj(j|+Z!2O Vֳg $EqoEWx9(c (gmʶpWjX J(Oqe^lff,O5P`O3EM%k =4[ 0 Kwu$[}0cEuCXlZ|Nq(>]>h!0g*-iXu41ĪU*NF+KVqKq,TgQ^)(ӹ4!mel!D%ʎOT4 2? PuL,ʒQt\<DN#Ijb;dE~#Fv>즎mBUg8Mǀ~CEjwo6a~Q^UI?o'}1SY̴x+Fb+ɺ+?ҐQ^IjtQ05?d6]Jq3f*+EF/XuGJ3,_LEeTk6`WAPZ(eV\+Kɝس(tٔ5nMJKeeh@ Š*lT9n)wnhD)! 8Q@J_>78%((%zx|8̱)ּjɟ94QMK:k ҺX&p(4 i61WpYIfrhv3{WCtQ(h?TCON(26)GRP1SSR1ocaml-metadata-0.3.1/examples/id3v2/liquidsoap2738-2.mp3000066400000000000000000000024461477304117700224300ustar00rootroot00000000000000ID3 TIT2'Sekernup KingerpTPE1%Jens Hendriksenip NipillersortartueTRCK1TALBVaigatip Kitigiai VaigatmusikTXXX year1973RVA2 album D RVA2 track ,TXXX CountryGLTXXX LABELDemosTSSELavf59.16.100TXXXCATALOGNUMBERDEMOS14TXXXcommentCreated with ArdourTXXXREPLAYGAIN_ALBUM_PEAK0.5316TXXXREPLAYGAIN_TRACK_PEAK0.3488TXXXREPLAYGAIN_ALBUM_GAIN5.00 dBTXXXREPLAYGAIN_TRACK_GAIN5.79 dBocaml-metadata-0.3.1/examples/id3v2/liquidsoap2738.mp3000066400000000000000000000023441477304117700222660ustar00rootroot00000000000000ID3 ZTIT2UkiakTPE1SumeTRCK10TALBSumutTXXX year1973RVA2 album7RVA2 track 'TXXX CountryGLTXXX LABELDemosTSSELavf59.16.100TXXXCATALOGNUMBERDemos 13TXXXcommentCreated with ArdourTXXXREPLAYGAIN_ALBUM_PEAK0.4338TXXXREPLAYGAIN_TRACK_PEAK0.3116TXXXREPLAYGAIN_ALBUM_GAIN3.91 dBTXXXREPLAYGAIN_TRACK_GAIN6.54 dBocaml-metadata-0.3.1/examples/id3v2/todo/000077500000000000000000000000001477304117700201115ustar00rootroot00000000000000ocaml-metadata-0.3.1/examples/id3v2/todo/230_appended.tag000066400000000000000000000721071477304117700227610ustar00rootroot00000000000000ID3d3COMMRengID3V2-Comment.This is the comment for this test file. This tag will be split.TALBAlbumAPICc=image/jpegham.jpgJFIF( 12/03/2004 17:49:06 Mode=1 0.1 3.0 C    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?9J^sFps(fN1JzRwC֌t8@v s@1.p) 0zGl\0#8h>}hR >ir098 ~tc(у4ƓsCsq֝8֐8/ӽtqc&j\P188&:q@8֔+P?J1K)p81Kc+ }T2oJé`x_廻AFTbgGқ.|Sґ#ڣ#0BMO+NX #\C8J]ϭ@coNjp6b4?Omngrɩ%<ԍN~РtJۀ">Ib{T d+g@1ޞzZB!͵I5 K1`@Tf36zSFĎiǑiV#Î+R͂[= L# %=k^'84ZbzS&T㓊KׁR۾9*;{"DȪ ?5)Xǽ/KbskJ8F)^ Ӈ b$bAv5nLu5&f t󎦕*cbҲu9"[\֧p M0܁c^p}iE'#Ɲɱ rqE/R ҡlsTvo,c'g@dFjgWL/B0?ZOTW1Pr^d]X`k ql &9=:zgW]ri{t<u):Rw(яƘ#4w4)X=(&s@'9◾i9i ^zqF;Rvbz`AIri{t(}i1 'G?!AP4 NiqA.jL(tCށ/ץw{ьE&uP;tjZLR瞴(sG`(`F+7:tcsgSxt#4ړJ\g԰9K ޺j }iut$\|22^0}*R[)`֮ L:h~<ҶRe;Wk+uy,ь宅O֫ S0{S ; 9#6T8ok#8lq(#Ybisޭ@1x!r1 LC m# j=7$sZ8:nQܼH 01GA$v:MMb M!P`iۏUcElOcNMa>—P!irYϛz҈>1ÜJS'J>^Rv Y'*\bN횦7|݃%9>MAV e,hHCʀc4E9'֙sޝEϘHU09-E8`xisJ9H$rw Q)H9CQ'Љ6p2{H(9=:Sʂ4wcX<)h3Ѡ ^II hG9'.zsIK1qҔc'h:ڌ;R )LP=)\a4֓@9֗hJ@(>cAK#'A@5җG;'4P:^i( %+j0G(xژ(i;qNC?ΝMSO–G@){E\#8'5v~2~!4&t~s3@zg5BM uT+oY=+ 4]#a־vVd@I*Eݚy$cDj[ӗ-WH8:˷Ozqwl V! UaiB湉CrS5ʠ! s!+|ܜԷi܄A&ZLdN;P?@q{6U5ph"NZ!1hwAp @x4 w=iz"@PZ`Z@iGQG|Lg:p:Q֗֍L(qH}裵 w J)?ŽzZ(f)GzR8"<}i1 @4 ޹[~*>gW=!Xe##Bz|ҎԀP;R̍h Jb3涵'"3 [ 1}f5jǐr0+ Fk]yR$uɓ f'-HAQ{՛zkLlWw s:.Gzцܚց8jSKdK)ITm2Ћ,A *LH斡h# ry1Y-~5f@S3ئ,x@;ԙ=;Ҝc4F #\Rydf$g,k[?Q ˏ=u=ޙJ@Rsޯ3 5+2$dX.wl#X_J} Y3dmǮ)glGj Rd*yxEBz9sMJ2zӟ&I0v+NF4v2}j9`n);c`/'qR}3J_܍'K9=M0)8Sڌ4"֌IiOL8&r L`z)3ޗéQAz3)ў(=Mg# `@K:A(鎹1Kތqץ)ցR;Q!֊QZ='iIJ:Q@ ڔ((g>QK8 bJ^@8Ҏ4q(I=iux!ktX^.-O0?h{£;?{3Imk"؉@RkO^ ΍Id&YH 'jɭԞMRɥ^XIcJd:tpI#;f>a=i>OpQ94hZLRX#׵ CIqTof3yyߵe]r34+\i)2u˷\~󃞵VMgN0O=I+H:v"r)=W784ژ\".JyP'ҩ$:'@ZGj ;ۯJE /܇`qO*3ޘpݩ;u3cH0h,z84dRwA}Or4(֎ҘhF3s ޘJ=(*f@iqRl[W#Hx9$DO$68f'JY˓4&W4xS{SaOZhJhQc4up{fNM'Q!X~xt:=DKgSbc'|Pxu^@84bY8%{nb5@Szk\.ڳ>aa,Xy *y )_BE!Nؗz3Lhc'@ ħLG ch[EOypp)kf )*Y ɖO5Z2cT˿0;RdZ zR$)@2F#\zV0Z܃I d$ hZNq;Ǭ@HU-gj7֝<qYZۧ :Z3ڇV{P'aRqJͅ9?ʢw2uOOaVd+B&U'k,^swӂ5V7f20qDg 0N52pi22zzSjfF@QPߵJs=B=6Ny5=;THsLM!:ҧ,އ9a~}GJ{ ^=rizLў3֍3M=})EƅQRdQN;ʁx;SYڑr7ƴY 4AGJv} )`br;Nde`h2h43֐u:=)Ru4=b tZiB) 4oH`9r01N)xWVb"Xb^-B#=;Tn>(w})hO1A'I8R*&Hv'^ CY>kU^s(*7Ne `.B8 Ʀ( +DCU'ޚNzPMW|>8)id9NIUV򊞔TU 뗑V7qul V8qjJ!tۋB%qYh4'ay^=)\]r+`Zodx*, sVCYddݫL1U/PpEYg7c>:q<. y0q4^,[ɤ9⡂S" ԅÓLwnrJ:Uۥ/RHQ4..X\u+ː{t/~j*9v*@Z?*$=*d~9?~^zd0Js7=(1uvR1J3U'^D4FN)JTʠfnı~nXcP㌊ܖFaRQvzJ3ӱdҀI4ƀN7'vM(GR4AJZEE 鑚SQ ?zUu<7Zd|j#UZi)gLZ`%O#4=8oq9La\[jҷ4L?jx=&tP9<W?orUخ=~!d'8QPǯ5:+H)sV)ǀJatDz~w%"NzZ=c4(,1HNc֗қj=UpJ& l/H{rwá2rnwUXՂEz O>'3Hi#=@|S9߽>ܰKR&(QSGdAKjxEE(i%'<9'g8e*F}鄟Zdhqrg+aKj9;p@51yf;P= _hPER\b$؞cbǎ2p<C(UmwR]Ojɖ ~d.Hֵ0ʤdb#mOnǷ OQ 8>VПʝqndum\ojg@c\I6-lzVY~\4yʖq* |.́sJώ3KruLr*Yv3R:D݂E1X,} (r>_AQ4Ʃ u֜dl!B4lQy5$yCM3X"*U8WtU|0,RAHϭT.z{@)/&V(YtW![@2OAH(bօ@#>gv8G2BpT$9&$%Qrԏ"985^PÕ9ylO4.r+` ˺fewzBs+@;O#. w S˽ˇ4$O<ԧ;$P$hJL梅RTÅ$um@".*o7CZ_y0FsQsM xSҭ^M(j1BX׏Z=3Jdi~!${Y)/`<:r#~{0< ؖ8t;F{>r8^9 \F맦(9zGJQ>J8gwZP}=J01:~4E 9J(A3@<ԌS3R2i SwC?[*rc9T:w VWۃϽ!2ǎG5*JPHl<{ Zc݁!զÆ<8ʎsK0i++jd8EѿCJ3#g$ˉ{Sl  0޽i;mtʷb: B*{)J"eZy=(m72OZc Wg~),/)ry[Ίrf"v#c+?[ W P;.#:9ZrpzlѺ7"P}kNfP&coޮy楾2:2y뎴z)7zwC֫#1X\c&vܩSO53HР&ړO,uldv/uG"DgB:y.*)ԤasZDjͿ`?4ٞ޹i#pXAUbNG NM_̌{v`c4% iԻpyl_`"!4۔y\;@Ry^[ uP;GE!ŸjNDG'EiOEؒ&MIn'MwfVGw_Zhϭ;#$i1Ҏ=FRf`@:fqގإwz ^LP:@O#Z?J3NVdTj`\a򞕝:I^;D.H$w׃֨fu+}޶&z`Tq4i- ջ*ƀy@1 /g_(I˅"h!gc⡂Y"hƴ2ç&q= I)m)[5ZQUB>m.ɡ] hUcz{q^1X|ƒ+3ԉOdgޑE?AzsT N88`zY΀ eر8d熰p)BFz\*U(zSx 7J烺.Pkz ;b #Nq ɩ?)1)R@ۢw`֬0Zpdh ִ8*0OEx bx e!T(܂$Uɫ@qcj 8KVRӆA"^#d5 ~\#w$́#6L$Zy9#-rGAHnj1X֝c Ibjy1q[R{[d[寮i@$~5#: e/sV, 9=j d"-Zq$ڟ֡Y71D89'l)ۚQݑABZK$}EDZBj^ g#!YI=IL|\3۩ ïjނ1'2b :eOYt35B 74!!mO^iŲ*;ԪC(B' `f8C$<츲@*rQԆzVjNgIB&X T֝1zbmzK$7T>ISUSLzCDqެKn."`:R"6a84K{vE#.YDsY8n 2"E1\|J$^Hgr@^s6 )`'zU+%wf`_϶M}FQ k͟R1&q;SCE.+@"Tȡ#N9r#8t0i =*r)#L]A2&֪$2ǭ^XsQ$,U0Җg (MvD *qTPjbqii!pG TDc,{Єҷ#hKܭ. E5Ҍwwv4ӌRVKRqM(@(Z(FO4Q^Ͻ/lPi|4NT qG'ҋ2 #?&2 \_Zy?1WL\zt`f{,obY-%U|NWTR:S32%&L5th>z4= +Wi0zbN*`rc")<+?̍B>cj&ۚL*ۑ 8v˷\(]UO()ŠG92wp$*esVf$ڣXp>nKC=lB* B֔\˧$/IZ Ǹp2jDxT{9 _=jP9iy #MӥUɸj[YCAriF,F9OZ2rO@;4#H/{V>Wdƺf,܊k6AP>*jp@`*d65F8ǩ#9 2x5xHzry4I :Չ(FNj X`xP05!pIZ-mƆjͨ̇# T1B2zgjlRҽj9n/q_Ij=oJA^ 9@ӳ4Àsڗ<׽/o˜J8Nh@4~ tw{e\SGӵ!RE/oE  A4ą8ZvJ0q@wdϒG <խ%w F=\2G>b YzS+p?Jr?*pT$>Vq l5 fvJ~^|_pnjB) b) 5M\O P9FwS œRRv+N#eI-cUXDTuKEؒEܸipJzc9@N]Q_Qj4dQq7 '~Xj LK fe!GzkF Pj㊵̊N=^zՈܘEW`7w %%(Ӥ8{=$bfOd,R-N&2E8јpjd [,U~*xQJ2"={UA5pg.ƔaE5Ө4IQnz2h'C`hs KZh8c8uT\ar9bP:Ayǩ60z_ZC P0E 9>J 1K 1(4L PGF;ӹ:9F*xO5 K! ]$[e-gfٽ=19QFQ95Bdr3UW; brjaG5NE$h HYhcU|F6lsKA')Nt,{1#=΅#$aǥ>7.:q@~Jm@Lv.sLt V&~_ʪǡfCr) 0UkM6+tkŸ|*Zp*ȋM!A4I; ջK+e*AyV)1bl1T#ڜo -as75X<sSt#j>G<}j@ɨBh0Hj@5JeLy2$͖rz5 Y@*\n^O"SW᳴ul@`,pOaDۈR?sI2j$A"{SIA$9Nk JrŘy*2F1ZE[P=8c(K0(MJ8'=GwANdR(/AҐgK0);ؤ..?*NG B bJP8'ކPH'$i+8I#-5{"I Urn`ĹSZ U p~Nh4W^d63{ :QbǷjN9u#+M6椐+JyޕI[P"䌚Un”FOIȍ9?1K)bY5742O&oTq[{42͌b08x(rbh`i$im橯xU #~E7P)jGU"X1${-\8 @zB}*j̪ <$ !(Y0~VU'-AT 9 Ӱ^ƌjgϨp*m#wp'19/Xri3nIȤ>Kb;>ŵ}kHWo " CՅrʓ]JJ0Q85lI͐mN]XKRyRkVHۇ+Fh_Ɯ5^m**èRM?Q)e,xsU2 n"sPzOM jzz$"~f!^GA=OCciisR)1})qw#֗J)!4;(zriuR\cd\jUzUKb*园9dG-ҫ'r9'W`4Gݏ&%@1YufGdbz.&pQ}՛jZZJ@j3}꣱#4m&$io Ҿ25sS# :T3ځ|7&415X78ms@ZEi& )&A LqWc}iԴ1$cPp&I3x1DOPD$zH'>H;E]WTC[$VyA{SvM);OA5j9'&f-E}9ev 2}M\/J2qޘ32B~A3{ֲE#+8]ұf㩦fjX{nnL)URL.KuXtf9 tYfLAH9rM: qszzĹBHK?@溿 O8+ȮI-&kÏv(=QT_=1RA ֢cT=ӹ Gy2 w3F&\S:S}&y2qMӁѠr)b)G }E*4pF 8R`杌W@т(&ך\UGbȫm̞cL{3i RV+ިd*z] <~hM:D9 LX0rOZMvJiIVLP+ 2 ֠miq e&5rM3x8d{x[$<p8 $\K c.Ǔ@VPαn `([ϸp95, Gꌂ͸& $$lEMp$֮X?^@hP0p*RB+*[˒{ 3j4,JQ?1کjL`g0EԷo$\(لg='ҳ 8)o @i !W}kayR9j餓)d@ɬH䃅ZhRבֿoV*SX2X|f, gjVTzÍL&`ЬUBSL@=3Lv PsCzR֙s}$;U '\+_H󂱂Y-6$dܡ;*1?xj&,@PTJ6S)Sӭh0eZg秠U`W皉#OIR(ޘTCW =)Hw} hÑRT2=sC+S2j 8ϭv5旭7!{9#LCz8J3ƚ: pVNi;j/93QU'VzѸ,\ORTi 'WyAX6,'n Pln)% j2L4Z7ltQPyiOUN*osrOj]8K0#Pdviljc5PNsqu-ڲ=\6* WZx=9 ␦ T(dMRqN="iFJ94[qҜ7 D P6\Z 8$ltLOS܁5]> @ftɪ3\HVp3U$Ec.K} g\pEi2jCn#iօ7SCFzrCT犬:S=S9(Z;BrsKz?JsϨf}:дA^i)sTDKQ(ATyf$rr7Vy2MC,8F4yaq&b{)1_13H6N0=<n! ;!yjd#gWUm^Ny~BژI0P9٨NuASfܻO#U!M$A(L N6N^CZv|{ӺVUc 7m7$HG5f~`pF*GcBHZv?:As>끜U5rPN=hkY+~U?|:`4`"[Twzh.]R! 8GΤUJ[欠xrKMp<՛TBqEI :F.y5b:0XeH<]UWcM1֠c8}Z@th,LR4N5U;n'jۋ9'X㴸=ij; ~g'')]@''8#魸XSdr#;{ty)#RqM'GjM cAdϙϧj#>*9)vIkV* y,F1i8'Z-IUr;UwsVj؉;[A%m_ZF^63LOJ= "hA ds $ MI8omKB@\k9`Rdٞ=iA?4Ҁ@'^g(S 8ҚCji* R13Pt1Lrz88ԧ㧩-@Jy.ʣZ&F^i2y=i#ǗO& vjX}8  .@SmMU*u;vj j7 sO3Ul=醣d<N@sҐ00@v 9$ŭ sa =*3gsR ֤3fJPcRL$ N* cU+ )5 N9J3 sG 'o€O&/ qP0zҲy[7&v]LluR71T UpӭB˃[Uy=jhdpS?5LmPd/. ޣaSF6:ScFMBrOAMfWA(*@`4mS@Ѓs;(JfMr'tP蹌VC+j0ճԲVctBXF9PKU *gkzgf̛M&8´s ՊU~QT[pqV|WR"!ɚw8JWE&F=k[+/F@D2jd\NCqu !׊> v-- B=MX5 s=Frj w¯PL΄I= .h 2=35 dMaI2* B NV>^ Ϧj$rJ$DکEmʼgikژER /jm*ޚ$ ng7*C ҧ2ץUmAqGQ!OOJ|7$r7zU;pY:O2ꦣ#z=ؘRpxcv#CNОr> L )Qi^Ut]? \gK yw5{~[zU{.@x jvHF^i)sQ;pi2Ey皈O>zy9$YI>Xh/sJ>y #$Z7 L:F=h`p'GsJszLC: kcQpHjhWi&ҫnsS 0=i"˶]LB\ s$~Bs)ۊLCZ_^XʞzgP5[9f$p4V0u28B9%+m9)v3TePi#0ZD:OR1hzcUmɵԆzV%֬]Pa%cKHePX;5+hMĜ,j>cwljad=@?p۰=BgP'_^F3ޓvi];x3dyҞ+rEK4FVJLm{PTɻ jr=;i2aO͟ҦfqTpe=8g*98aޢ3Qe~MȏR]Kă)ĩ=OR \ 2oڤ Cc5!1֣00`Cu4]R]Rȹ Ңa@7"2 )niԮ'ސ ֐ b}* 䎕9ǥ1%IS΀ʫJyudҁjLI냚\L!8>=JFmڐRϵ98ۑ:o@*ˍnjrqRao U+UUc{3)9lUNkڐN#I. LW( FE;OM{'5,Qp*Y uGsךjWaQޯRqTrsڜҜnP%}6ɁF0,v{սUJbH02im$z%p:'ᚬ aD:Բ'ˑuUqOaĀӈ =EfE Ȏܯj;>O O8I%l}E65P,zwxr0Nyw,: BHaߥ@%[{tc10rNACm<ӢeXs֚'}Yj<,t{ XZu黛P{WCari1QoCȫJ +U`CqO5 4#Z\<WLwΙdf T1iA5~^H2wK'Ec*rZYiUhd8zm#ƞ֠[#p6mJCRe\F{L85 qN+s H.pjbo!S *>ekTM.q`OV7 li,=He'b9\9RBA; wEɞ H 󞦛 !5A2;ЯfMVӤS<7F'#e')Yh6HFi-7&OjVI,npr):`8"X We-0)fЖ iBܚiYP|Ǩ4|k(+12J]9P3NrH2U$f$p;cFeW< fR̀xҠ?:LڠbY ,ScN"L;TFyǵE}M+1R1L1TIIpy(BZ,mGZh^e* "aMX۾PSIz6.=8-jD]Ա[`HI"(VE!2{-CVp:U`+c$J#5>j`qڋܫ\ g&Taio§8+!^`7Gң 85\=ƁXTCܜy&\P*]j)hzt*2+.)F?*`iS#?t4\w!b%VѸ$TnĘhOYs8I‚-)mZvAϏ(n;`oԻ)ZO/In!Ai?H(FsSP|̆IIH"sңYdjiUqI"mC\B5+̇^" 2IT$rr° SF9PCqN䞟ZZ ` EVȮ*őI4+֢iIdņ48'0<Ld)AS?+8'4'~ % 82:Ccx5*MV= H5%PNy4 4|OZ]‿b)2p1Zȓ tUGޭ&Gs֮$xb9Ҕ|dL|A  mSSQg9)Fy ǥC D@|2qީn&YG8}h`A f&Y#  81c);^d`8xf^3H;np?A* 8{ QsT!9u(Gz&A/'ιmӁM v4 F;nޞ T63I;O5JIryTb%|*JZR3ޛ#۰i[QKriN_*pH ilUb# )aaߩk4Ƀz{=jsR3nbޝ(  LsU9rOH ݹ5JB3>Ԟ%贒)cҬ[ sU~btzn@CT#Un;kjt3Xۜ4l"kf9lՕv|tU\ HGO_chA"^1=i)h9<džZ~dgFڻ{檰ğ>"_l-_a?J͵e; rLO5"WlE"3"9LT,FV RT}*g#WRd jxᗊ.299Zj$9c|CJsQO==)9\ˑEVV#z~XxwI4I玔7 UrzsRQO$S!Ja1TS 4!nڑ+|LDpyGun&Ú~ŵ1~¤x=EYL6@i_%gtlNz2-0*=iLOBph ɸȼP Ƿ?6y xd:⡙.S/h܃48"K)Z̫Lpi`Uat@=)t["4$ӳiیbvFjן"85Fz:I3Cz7#4;~^O>$oG,6 k]yzQ\<7ɜ4ydHҒMt^A9 = QIBjE oӒ( HT3w9Psc@ڥS4`6G*cXlC}ڿq2GBjho09yzvmlKvdI84 JrAS\*Z+;Fnri. ,Փq3CZuˋE9"irʹƑl3UvR9zkg=inXISH)$dM jNncO:p*ӓ[#5#.|Z_54ʞ Ib8$Pe)$Ĥ˃ڐ AVm8_^L`CJT(@h)i:U{  .CLzrS* pcN\9 HQʠS.Tΐ٣*A1V/#!rzoa; XCsM#SؕU_ʓLd!JqN*`HA΄sS|)vsRiNvՈ<!.EH01MMY :25S`uK 1GJu[a֧@Ɓ"$QGRl @SiXv dI`[@#4 u>*#3W0d T?g5Cjq4x?)FWo9TM1$OHqI<UlgR; Hb} qғ.n]G'6G v qNp)0);R x2+A+T7;`y3QIy;9=ihA!)wt7K]8=)@.@SX`}E?v8ڡb(pD*E=<t[imϭ$b)!VBGTjF7@}j VaԎ)%#Odm8=h {ѯqXQT5c` b(MR[d)$@硪Z⌎*WP#犤=圐iiϠVϰ5,znpxpj%­=ُ*@mX.bN 80-T.; #e=)~mIp1֕wG6rNZځnߖբ7?^܀XF&G K8 nS:'ˬKzpE!O]_츪aV w|lL 2])AJ\iÁOHJn!.x;DE?w6S6 ނ/RR#"WO'-2=\U{TTI<8§~*8QM=CJ%·nOj|e%[ApB޵4cȍWa*YG3* `d.oer')`sպQ~0|;EOzޖEGSYאa*pL*d7\ac`TV˖5yʑ~ClƼ;!aMin >WFeTJaJ,%I RJiyGJxcO )iT=T9K圚VX"d"}#){DCJzxڒ7CV#zSՒ"2i.`z8?tq!U+n)A*OAҤU(*I8$cSm:sU-]x`zG0xWyN3`MxK]JDsch=Xwud b6qw=Ozx/CHxc ' ƛD(ozU!O5C$$L4g<R.YyQIcS2*6|v5WAzQ16Lf'(B *;m>nץL ;mCfLuS@'P{d(gې5?1F7 9Lo ڕq&al0*T<ՐvwM*zҜy\`T;GLTԘ_@DONH4Ԩ -ړ7%#=8u/:~=MCs(jۯUw4 M-<5M ~F*vWV종kRFnvV6ۙ=݌QM#/ 'Z:BgLS (fMǷJ4ة!n 5*ʟ2=:vxhӰ('C#R">H겒qKK-z`jNZ69epygX\UIlwEߘ`:z9ٖ݇4V!' az;bZn|X-VrPLt*`{3l>kjy#S^_K5e`SпmuNXX\~E=OpGEOB2x}RHz7 1KΘ9N+=) N~VPTSUNbBk Lڢ\ZY(.CnH?柿Vpmo~]/_~֣b.~8<5cvߢ B)Ïgׅ m]G^ .U:\1fvvعfGL;oWyap6\yNj@~F"/~g_Nk4.\j6s|E.u 7*c/ظ+f9|/.Ԙ:gWE.\b``O7p?6_0`Qj%x:w__Jע|Q̓*_;>|;c`^1-[-ykwcK!٘xPg&~eVpņ9^./1;>;_lLmqȿW~ ;= _(t3=ez@`gK""~q'3v)7 חi5U)O{nd1_Ly;H׭k 1zgA\уtY!#GR;'ٽ ՟`tC)g[`͇9qMdLHW3 + `i&r%Ud3-b M֟r6&l#CMop#;[AjIWFb4Vc'#uȈj(V0:Inm?*r.Q=ۓFf(;]L'#'Pvk ^Έwm U|Dj\N7i/7%F,9CRNa@ɜf"@7aY>X\.GcmЙ)/I˜f=ж!2t(lrK|SN~qW\NI\J}I\?U5ACFz7bpȌm ߃|#c=e_171wN۫ܡ&($MP=&o9@Λu?Nh9#Z~넺:.%8NJD39vخO]TuV"SF|O㑛1vJ?}SCY`ZEUWnEs,C#b vQ ǃ5 U;RM&kEZ,vErQU#ߞjl@:~GE%`rۜlU@g9ZQ]wu욪Dz -? 5\Jl;*uB^~ޏ >[B\˪aDlS>~M¤GLROĩ5v)Kpb59Xn2/4B W%n >CxNg_`znВmdWxP?FWd$mI}SrLQ;LA;aŒhv|d8_Q(2L!"^v(N␽r1-bFB\vhggjL[ Dkt1* B9.UMHj"épp&-I5j$E:B-P'.k[:O`tGSS7 zxܾk,kSL" 2f}-U&!$A8LOq :%~}wQQ4.}kQ^\7:V< 6}__DZDQٿd'Hml8sTAR[Glk$"|.we@׷8+r @XkA%h>@k 4wNV *mS<J ?Nkvi &DЯkwֈ84O}=F$e?HB$,'kk}Bd k@@CX͗c{c6|T#*h&G͓YO=\-ND I~Z̈́$9Q8q>*WK7YW}C!eG.s1Aus D|\VoS$z_ 9丒B(|=t$]SÈq1E#yVJ/W*7n0$׵Lt/;$EyLc@`[Cxc͹)G& 7υTJ༟=1NlRLsrw`kFf# O| EqSzpeX_:U%wQEUwӊ u ax]TuP`3zl_j[y'Gc5URgY֟g\TuQGcv' f׷FkiWnׇtʎ\i.ˆGnK?ظP1%@xM ] C giNa{ŒU{N1ckgSOR 171)1kav'?w sbo Gv _[3 J3"@Auúw:Ru)Pr89MHQk_k5Aiȗݕ*jo2fT1*I>}Z^hE)}+Len>)HERς1F6,+!׎C ^P}("՘ YXrf" >;Zv$RMqC`/:!\A?NEUiH#;E:!:y1 ij#cr R,qj`5s7G&b;[67sbM<mn^km|x#;*%.==p7Α5<Q?5&;wk@r6*%~퐳nJ) -knT7Ȑ8ڭwT;Xnvd_^RXԟ "ɿm&ёZCɂ`^#attb˜b˯*n0ڧ ?CD# #cAV]#O_ G{>GMmqD"{@'XY]JIBd_<{IqԡdA=2Fsʶi=Ja X,qHRj3,!g](p5:R-U)Ur6㬯NlRnTR zۗ0"Cv09E%N\(9vMU&#\^ =t > eKlx *go2 dvTQ|&Af[>Rlf:O3̬NNnmD:VSscq;)'`,OtsQM7րm_ލH \L}RgMlUǫܮ0h^I-6ꆳk;8 g8MQzyKG C|XFfc٤HwRӇDŽh pD;[/%ַl+FZfdl i&"0;;`{Lm~#l }7Ida۽(|2wK&DQʼn<NS15rޞqԩ q]ܲknl 2N־S1Z9av X(U4e(c+OM:1)ϰ;yL_Q~Rz#lZƆ 6<0](tYڮm:oUw:vQՍ#<iم]boB /dr[tLА*Tz|?n#Ȗ]S s6s-kͶ~5؟]F%97Dܯu'nEOz- k[ 9Hg$.6ůðv > Nʐ<^h*+)tmt7|[QF(%OetR|"ءrI_)աdC > EJ',d^jW\!R>AEF͐<^6U$z-U1"ݖ*Gsïm-]F^*Qo Ñ6DɐgS~b;sa2:ׯt!\+_pݯ<&H:4Ӥ&Rn};-pu>EIs #YM娔0S5% ~[e/p𒈫0Wxg=aŶJ/era`ŕbwӘ9k"mo;TW!y<v$i 3@AtkӲ]D˵hP(%YO-gtNaP]r3!=jl[0i;3+[즙2&޲3}%S|ƥS\'L0V nꖑ(˫?֎~*ܦQ(΀oE&XGW #>veti wl[f#P}`Fvxd}ų>^u+]Jkf*]|gz )֫()ۼ._7bmʕ/ (_ >ϧPd˙ۦIS?Tr?r(téCUs@EO. ;ϵeV/h'vEzE]Suc;H'v> (o]؝f ## v?`2??a)~)~ZaT  gi;Og`Uhڬl_:4 ~ݩ/+(GBAz>6ْJډ3"UwD;fmh="9.ywZNRdՍIA-U2srRz43Wj+ar;{N3M]ͅ#8ƶ/r^vҡ٦)g>ܘ;h0uBV3FP*9Ḃ`ndluvqEEdZɴ^G8ZDw4EUh( Bl~d [Vb(ZAw`U/B0u )C'ʭ dDUdzBNGK,=X(;sÓI*I72&½<+:ڽOXm`vyOljD¶˰杧Bti26C\IV.|)k¹%ݓ ίdv:^ .jهvasP_#I~ ~B+¨4ƭ  Ì,ޮfr'v^ (F~L@7\Qwr9v M;B)|D@`埨e~|Ol >(SENP^h{_*s2)8Rw]Ly>rϥXV9\ckkqtTBj",ovV|fH [M\#ֹ|T~n#tn쮱Z^۱Sz*} [ŬߥJf;b|Z(KQ^? wJo,O;nK7ÖHe>[(Se:~};+p8:0sw:Jٲ2 ZbFgK$%5%ׯ~լ0+#K_ޜ*Q6r&b4TP^Pw1fg61-[? ;:jvv*++[7G\%<\$uю:$n5 91W2/Xo'"k-Pơ@[ow޾{qNARhķMR`8ךcI4t5i$ 4]+tzwƅ6-0F+;}-L+q-yn -d25z<5|+Fj&DxAgM*N+ŅiYkpPlm0Q%/LV#/=` NHH6sGjvws;cg_Nv$bh5m}52KqO{ !nf]|Sv=tZdelkg2(EqZ/:OQ;$J.`.'zRb+kW1ICD%sSz;nwC¸Ɯms!7g.[-;@Q-%]n5֮W8k e)DL`ϱ߷ss\*„m"rـ][fpʏe^ə s:;y `fi-t?Kfd[pBZ;)g\`2g;sveb 0촘/3~]6dƙS4Y9E^t4+ldWhgNK _g+'t.[Cנ]#Xڲ)/ޓQe 77_}NK9KEH:K!K?1H'JhC!)n .sԴ\-+];'f/:]?Е8b:`)-QnFl'g}]뻼"& s ̡Ks .&"~WZC LoO?GܢؑOZ̓:YӝZbCc}ԃԟO)O55ݧdt}Bv9t0m;o8郺Nf8`TD-_o{I<*9}ۭB)o$ mo⼂n*H"3h53#,-6{Oo2c`"%5#>ZM{8 aeRwň 6G.H3yBnB&ax뺹Yy"gZõB)6~{aWsX$NɜKx6e?PJm;ֻ3!ׯ)0z3_aDK&Ys臔|n5Y-ȑ\5|/BW6Q\D|Oc-6*:ulbZX}1g{ů>nEnɆɆ~6<zE~0scv^V*r.C% :/̮MO֛vMU#n! )b+;IEf?x~%ȭA|ߙ:#rmWGٺ6Y1FN8KEQ=Χt:9xB&AS6zQ'ܖNCK&qi%=+bnb5?ֶOvu16>79j kؙ1,ܕF|U=EM̝smsb^HOR c/3M#twѴ!~%4ݮIO: *L=at6`Jcg;3z숏NZ7ڶ4z2[Ϙ kyYm$#uC±\Uuǧ3~1cґK"1"SN{q:ɣ^#m?q0VUB)qvUۋ!;F6\jU {ed%&[cp+Tld[;~'5yK'xbɮmu\kJ^&NkЎmp<`sՋ4,SVҰr}ǼtWU>.JPq~plы ΍ǪvQU7Nj &䙍{BQߚ+&!5r %s{p^/ltD;a]=*OuF]a3Kf"g} :~TЏCUݣ:8;+oer5p7:rv&w4ӨrӡMs:F 'KOLstSv(8`q#؞1|E=n]GC0eqє Y kK*Nr<Ѯq hݞҩݠ~8X_c9 uU(̈9V̝ΣDa9i +XS)a')&Vkq}#9vc2CZ3zt3&ŰAHGH;̹&gp&=q8{殾u ]tP*. hέáv' 7-u}$`&<7qZocaml-metadata-0.3.1/examples/id3v2/todo/240_compressed_frames_unsynced_frames.mp3000066400000000000000000000332641477304117700300750ustar00rootroot00000000000000ID3m*COMM !x%A0 ?rO w4nbq8w=H1o{-g4aab[E3E@UB(Uc=*Pw;!:GOM9^GEOBd x}RHz7 1KΘ9N+=) N~VPTSUNbBk Lڢ\ZY(.CnH?柿Vpmo~]/_~֣b.~8<5cvߢ B)Ïgׅ m]G^ .U:\1fvvعfGL;oWyap6\yNj@~F"/~g_Nk4.\j6s|E.u 7*c/ظ+f9|/.Ԙ:gWE.\b``O7p?6_0`Qj%x:w__Jע|Q̓*_;>|;c`^1-[-ykwcK!٘xPg&~eVpņ9^./1;>;_lLmqȿW~ ;= _(t3=ez@`gK""~q'3v)7 חi5U)O{nd1_Ly;H׭k 1zgA\уtY!#GR;'ٽ ՟`tC)g[`͇9qMdLHW3 + `i&r%Ud3-b M֟r6&l#CMop#;[AjIWFb4Vc'#uȈj(V0:Inm?*r.Q=ۓFf(;]L'#'Pvk ^Έwm U|Dj\N7i/7%F,9CRNa@ɜf"@7aY>X\.GcmЙ)/I˜f=ж!2t(lrK|SN~qW\NI\J}I\?U5ACFz7bpȌm ߃|#c=e_171wN۫ܡ&($MP=&o9@Λu?Nh9#Z~넺:.%8NJD39vخO]TuV"SF|O㑛1vJ?}SCY`ZEUWnEs,C#b vQ ǃ5 U;RM&kEZ,vErQU#ߞjl@:~GE%`rۜlU@g9ZQ]wu욪Dz -? 5\Jl;*uB^~ޏ >[B\˪aDlS>~M¤GLROĩ5v)Kpb59Xn2/4B W%n >CxNg_`znВmdWxP?FWd$mI}SrLQ;LA;aŒhv|d8_Q(2L!"^v(N␽r1-bFB\vhggjL[ Dkt1* B9.UMHj"épp&-I5j$E:B-P'.k[:O`tGSS7 zxܾk,kSL" 2f}-U&!$A8LOq :%~}wQQ4.}kQ^\7:V< 6}__DZDQٿd'Hml8sTAR[Glk$"|.we@׷8+r @XkA%h>@k 4wNV *mS<J ?Nkvi &DЯkwֈ84O}=F$e?HB$,'kk}Bd k@@CX͗c{c6|T#*h&G͓YO=\-ND I~Z̈́$9Q8q>*WK7YW}C!eG.s1Aus D|\VoS$z_ 9丒B(|=t$]SÈq1E#yVJ/W*7n0$׵Lt/;$EyLc@`[Cxc͹)G& 7υTJ༟=1NlRLsrw`kFf# O| EqSzpeX_:U%wQEUwӊ u ax]TuP`3zl_j[y'Gc5URgY֟g\TuQGcv' f׷FkiWnׇtʎ\i.ˆGnK?ظP1%@xM ] C giNa{ŒU{N1ckgSOR 171)1kav'?w sbo Gv _[3 J3"@Auúw:Ru)Pr89MHQk_k5Aiȗݕ*jo2fT1*I>}Z^hE)}+Len>)HERς1F6,+!׎C ^P}("՘ YXrf" >;Zv$RMqC`/:!\A?NEUiH#;E:!:y1 ij#cr R,qj`5s7G&b;[67sbM<mn^km|x#;*%.==p7Α5<Q?5&;wk@r6*%~퐳nJ) -knT7Ȑ8ڭwT;Xnvd_^RXԟ "ɿm&ёZCɂ`^#attb˜b˯*n0ڧ ?CD# #cAV]#O_ G{>GMmqD"{@'XY]JIBd_<{IqԡdA=2Fsʶi=Ja X,qHRj3,!g](p5:R-U)Ur6㬯NlRnTR zۗ0"Cv09E%N\(9vMU&#\^ =t > eKlx *go2 dvTQ|&Af[>Rlf:O3̬NNnmD:VSscq;)'`,OtsQM7րm_ލH \L}RgMlUǫܮ0h^I-6ꆳk;8 g8MQzyKG C|XFfc٤HwRӇDŽh pD;[/%ַl+FZfdl i&"0;;`{Lm~#l }7Ida۽(|2wK&DQʼn<NS15rޞqԩ q]ܲknl 2N־S1Z9av X(U4e(c+OM:1)ϰ;yL_Q~Rz#lZƆ 6<0](tYڮm:oUw:vQՍ#<iم]boB /dr[tLА*Tz|?n#Ȗ]S s6s-kͶ~5؟]F%97Dܯu'nEOz- k[ 9Hg$.6ůðv > Nʐ<^h*+)tmt7|[QF(%OetR|"ءrI_)աdC > EJ',d^jW\!R>AEF͐<^6U$z-U1"ݖ*Gsïm-]F^*Qo Ñ6DɐgS~b;sa2:ׯt!\+_pݯ<&H:4Ӥ&Rn};-pu>EIs #YM娔0S5% ~[e/p𒈫0Wxg=aŶJ/era`ŕbwӘ9k"mo;TW!y<v$i 3@AtkӲ]D˵hP(%YO-gtNaP]r3!=jl[0i;3+[즙2&޲3}%S|ƥS\'L0V nꖑ(˫?֎~*ܦQ(΀oE&XGW #>veti wl[f#P}`Fvxd}ų>^u+]Jkf*]|gz )֫()ۼ._7bmʕ/ (_ >ϧPd˙ۦIS?Tr?r(téCUs@EO. ;ϵeV/h'vEzE]Suc;H'v> (o]؝f ## v?`2??a)~)~ZaT  gi;Og`Uhڬl_:4 ~ݩ/+(GBAz>6ْJډ3"UwD;fmh="9.ywZNRdՍIA-U2srRz43Wj+ar;{N3M]ͅ#8ƶ/r^vҡ٦)g>ܘ;h0uBV3FP*9Ḃ`ndluvqEEdZɴ^G8ZDw4EUh( Bl~d [Vb(ZAw`U/B0u )C'ʭ dDUdzBNGK,=X(;sÓI*I72&½<+:ڽOXm`vyOljD¶˰杧Bti26C\IV.|)k¹%ݓ ίdv:^ .jهvasP_#I~ ~B+¨4ƭ  Ì,ޮfr'v^ (F~L@7\Qwr9v M;B)|D@`埨e~|Ol >(SENP^h{_*s2)8Rw]Ly>rϥXV9\ckkqtTBj",ovV|fH [M\#ֹ|T~n#tn쮱Z^۱Sz*} [ŬߥJf;b|Z(KQ^? wJo,O;nK7ÖHe>[(Se:~};+p8:0sw:Jٲ2 ZbFgK$%5%ׯ~լ0+#K_ޜ*Q6r&b4TP^Pw1fg61-[? ;:jvv*++[7G\%<\$uю:$n5 91W2/Xo'"k-Pơ@[ow޾{qNARhķMR`8ךcI4t5i$ 4]+tzwƅ6-0F+;}-L+q-yn -d25z<5|+Fj&DxAgM*N+ŅiYkpPlm0Q%/LV#/=` NHH6sGjvws;cg_Nv$bh5m}52KqO{ !nf]|Sv=tZdelkg2(EqZ/:OQ;$J.`.'zRb+kW1ICD%sSz;nwC¸Ɯms!7g.[-;@Q-%]n5֮W8k e)DL`ϱ߷ss\*„m"rـ][fpʏe^ə s:;y `fi-t?Kfd[pBZ;)g\`2g;sveb 0촘/3~]6dƙS4Y9E^t4+ldWhgNK _g+'t.[Cנ]#Xڲ)/ޓQe 77_}NK9KEH:K!K?1H'JhC!)n .sԴ\-+];'f/:]?Е8b:`)-QnFl'g}]뻼"& s ̡Ks .&"~WZC LoO?GܢؑOZ̓:YӝZbCc}ԃԟO)O55ݧdt}Bv9t0m;o8郺Nf8`TD-_o{I<*9}ۭB)o$ mo⼂n*H"3h53#,-6{Oo2c`"%5#>ZM{8 aeRwň 6G.H3yBnB&ax뺹Yy"gZõB)6~{aWsX$NɜKx6e?PJm;ֻ3!ׯ)0z3_aDK&Ys臔|n5Y-ȑ\5|/BW6Q\D|Oc-6*:ulbZX}1g{ů>nEnɆɆ~6<zE~0scv^V*r.C% :/̮MO֛vMU#n! )b+;IEf?x~%ȭA|ߙ:#rmWGٺ6Y1FN8KEQ=Χt:9xB&AS6zQ'ܖNCK&qi%=+bnb5?ֶOvu16>79j kؙ1,ܕF|U=EM̝smsb^HOR c/3M#twѴ!~%4ݮIO: *L=at6`Jcg;3z숏NZ7ڶ4z2[Ϙ kyYm$#uC±\Uuǧ3~1cґK"1"SN{q:ɣ^#m?q0VUB)qvUۋ!;F6\jU {ed%&[cp+Tld[;~'5yK'xbɮmu\kJ^&NkЎmp<`sՋ4,SVҰr}ǼtWU>.JPq~plы ΍ǪvQU7Nj &䙍{BQߚ+&!5r %s{p^/ltD;a]=*OuF]a3Kf"g} :~TЏCUݣ:8;+oer5p7:rv&w4ӨrӡMs:F 'KOLstSv(8`q#؞1|E=n]GC0eqє Y kK*Nr<Ѯq hݞҩݠ~8X_c9 uU(̈9V̝ΣDa9i +XS)a')&Vkq}#9vc2CZ3zt3&ŰAHGH;̹&gp&=q8{殾u ]tP*. hέáv' 7-u}$`&<7qZocaml-metadata-0.3.1/examples/id3v2/todo/240_compressed_frames_unsynced_frames.tag000066400000000000000000000332641477304117700301510ustar00rootroot00000000000000ID3m*COMM !x%A0 ?rO w4nbq8w=H1o{-g4aab[E3E@UB(Uc=*Pw;!:GOM9^GEOBd x}RHz7 1KΘ9N+=) N~VPTSUNbBk Lڢ\ZY(.CnH?柿Vpmo~]/_~֣b.~8<5cvߢ B)Ïgׅ m]G^ .U:\1fvvعfGL;oWyap6\yNj@~F"/~g_Nk4.\j6s|E.u 7*c/ظ+f9|/.Ԙ:gWE.\b``O7p?6_0`Qj%x:w__Jע|Q̓*_;>|;c`^1-[-ykwcK!٘xPg&~eVpņ9^./1;>;_lLmqȿW~ ;= _(t3=ez@`gK""~q'3v)7 חi5U)O{nd1_Ly;H׭k 1zgA\уtY!#GR;'ٽ ՟`tC)g[`͇9qMdLHW3 + `i&r%Ud3-b M֟r6&l#CMop#;[AjIWFb4Vc'#uȈj(V0:Inm?*r.Q=ۓFf(;]L'#'Pvk ^Έwm U|Dj\N7i/7%F,9CRNa@ɜf"@7aY>X\.GcmЙ)/I˜f=ж!2t(lrK|SN~qW\NI\J}I\?U5ACFz7bpȌm ߃|#c=e_171wN۫ܡ&($MP=&o9@Λu?Nh9#Z~넺:.%8NJD39vخO]TuV"SF|O㑛1vJ?}SCY`ZEUWnEs,C#b vQ ǃ5 U;RM&kEZ,vErQU#ߞjl@:~GE%`rۜlU@g9ZQ]wu욪Dz -? 5\Jl;*uB^~ޏ >[B\˪aDlS>~M¤GLROĩ5v)Kpb59Xn2/4B W%n >CxNg_`znВmdWxP?FWd$mI}SrLQ;LA;aŒhv|d8_Q(2L!"^v(N␽r1-bFB\vhggjL[ Dkt1* B9.UMHj"épp&-I5j$E:B-P'.k[:O`tGSS7 zxܾk,kSL" 2f}-U&!$A8LOq :%~}wQQ4.}kQ^\7:V< 6}__DZDQٿd'Hml8sTAR[Glk$"|.we@׷8+r @XkA%h>@k 4wNV *mS<J ?Nkvi &DЯkwֈ84O}=F$e?HB$,'kk}Bd k@@CX͗c{c6|T#*h&G͓YO=\-ND I~Z̈́$9Q8q>*WK7YW}C!eG.s1Aus D|\VoS$z_ 9丒B(|=t$]SÈq1E#yVJ/W*7n0$׵Lt/;$EyLc@`[Cxc͹)G& 7υTJ༟=1NlRLsrw`kFf# O| EqSzpeX_:U%wQEUwӊ u ax]TuP`3zl_j[y'Gc5URgY֟g\TuQGcv' f׷FkiWnׇtʎ\i.ˆGnK?ظP1%@xM ] C giNa{ŒU{N1ckgSOR 171)1kav'?w sbo Gv _[3 J3"@Auúw:Ru)Pr89MHQk_k5Aiȗݕ*jo2fT1*I>}Z^hE)}+Len>)HERς1F6,+!׎C ^P}("՘ YXrf" >;Zv$RMqC`/:!\A?NEUiH#;E:!:y1 ij#cr R,qj`5s7G&b;[67sbM<mn^km|x#;*%.==p7Α5<Q?5&;wk@r6*%~퐳nJ) -knT7Ȑ8ڭwT;Xnvd_^RXԟ "ɿm&ёZCɂ`^#attb˜b˯*n0ڧ ?CD# #cAV]#O_ G{>GMmqD"{@'XY]JIBd_<{IqԡdA=2Fsʶi=Ja X,qHRj3,!g](p5:R-U)Ur6㬯NlRnTR zۗ0"Cv09E%N\(9vMU&#\^ =t > eKlx *go2 dvTQ|&Af[>Rlf:O3̬NNnmD:VSscq;)'`,OtsQM7րm_ލH \L}RgMlUǫܮ0h^I-6ꆳk;8 g8MQzyKG C|XFfc٤HwRӇDŽh pD;[/%ַl+FZfdl i&"0;;`{Lm~#l }7Ida۽(|2wK&DQʼn<NS15rޞqԩ q]ܲknl 2N־S1Z9av X(U4e(c+OM:1)ϰ;yL_Q~Rz#lZƆ 6<0](tYڮm:oUw:vQՍ#<iم]boB /dr[tLА*Tz|?n#Ȗ]S s6s-kͶ~5؟]F%97Dܯu'nEOz- k[ 9Hg$.6ůðv > Nʐ<^h*+)tmt7|[QF(%OetR|"ءrI_)աdC > EJ',d^jW\!R>AEF͐<^6U$z-U1"ݖ*Gsïm-]F^*Qo Ñ6DɐgS~b;sa2:ׯt!\+_pݯ<&H:4Ӥ&Rn};-pu>EIs #YM娔0S5% ~[e/p𒈫0Wxg=aŶJ/era`ŕbwӘ9k"mo;TW!y<v$i 3@AtkӲ]D˵hP(%YO-gtNaP]r3!=jl[0i;3+[즙2&޲3}%S|ƥS\'L0V nꖑ(˫?֎~*ܦQ(΀oE&XGW #>veti wl[f#P}`Fvxd}ų>^u+]Jkf*]|gz )֫()ۼ._7bmʕ/ (_ >ϧPd˙ۦIS?Tr?r(téCUs@EO. ;ϵeV/h'vEzE]Suc;H'v> (o]؝f ## v?`2??a)~)~ZaT  gi;Og`Uhڬl_:4 ~ݩ/+(GBAz>6ْJډ3"UwD;fmh="9.ywZNRdՍIA-U2srRz43Wj+ar;{N3M]ͅ#8ƶ/r^vҡ٦)g>ܘ;h0uBV3FP*9Ḃ`ndluvqEEdZɴ^G8ZDw4EUh( Bl~d [Vb(ZAw`U/B0u )C'ʭ dDUdzBNGK,=X(;sÓI*I72&½<+:ڽOXm`vyOljD¶˰杧Bti26C\IV.|)k¹%ݓ ίdv:^ .jهvasP_#I~ ~B+¨4ƭ  Ì,ޮfr'v^ (F~L@7\Qwr9v M;B)|D@`埨e~|Ol >(SENP^h{_*s2)8Rw]Ly>rϥXV9\ckkqtTBj",ovV|fH [M\#ֹ|T~n#tn쮱Z^۱Sz*} [ŬߥJf;b|Z(KQ^? wJo,O;nK7ÖHe>[(Se:~};+p8:0sw:Jٲ2 ZbFgK$%5%ׯ~լ0+#K_ޜ*Q6r&b4TP^Pw1fg61-[? ;:jvv*++[7G\%<\$uю:$n5 91W2/Xo'"k-Pơ@[ow޾{qNARhķMR`8ךcI4t5i$ 4]+tzwƅ6-0F+;}-L+q-yn -d25z<5|+Fj&DxAgM*N+ŅiYkpPlm0Q%/LV#/=` NHH6sGjvws;cg_Nv$bh5m}52KqO{ !nf]|Sv=tZdelkg2(EqZ/:OQ;$J.`.'zRb+kW1ICD%sSz;nwC¸Ɯms!7g.[-;@Q-%]n5֮W8k e)DL`ϱ߷ss\*„m"rـ][fpʏe^ə s:;y `fi-t?Kfd[pBZ;)g\`2g;sveb 0촘/3~]6dƙS4Y9E^t4+ldWhgNK _g+'t.[Cנ]#Xڲ)/ޓQe 77_}NK9KEH:K!K?1H'JhC!)n .sԴ\-+];'f/:]?Е8b:`)-QnFl'g}]뻼"& s ̡Ks .&"~WZC LoO?GܢؑOZ̓:YӝZbCc}ԃԟO)O55ݧdt}Bv9t0m;o8郺Nf8`TD-_o{I<*9}ۭB)o$ mo⼂n*H"3h53#,-6{Oo2c`"%5#>ZM{8 aeRwň 6G.H3yBnB&ax뺹Yy"gZõB)6~{aWsX$NɜKx6e?PJm;ֻ3!ׯ)0z3_aDK&Ys臔|n5Y-ȑ\5|/BW6Q\D|Oc-6*:ulbZX}1g{ů>nEnɆɆ~6<zE~0scv^V*r.C% :/̮MO֛vMU#n! )b+;IEf?x~%ȭA|ߙ:#rmWGٺ6Y1FN8KEQ=Χt:9xB&AS6zQ'ܖNCK&qi%=+bnb5?ֶOvu16>79j kؙ1,ܕF|U=EM̝smsb^HOR c/3M#twѴ!~%4ݮIO: *L=at6`Jcg;3z숏NZ7ڶ4z2[Ϙ kyYm$#uC±\Uuǧ3~1cґK"1"SN{q:ɣ^#m?q0VUB)qvUۋ!;F6\jU {ed%&[cp+Tld[;~'5yK'xbɮmu\kJ^&NkЎmp<`sՋ4,SVҰr}ǼtWU>.JPq~plы ΍ǪvQU7Nj &䙍{BQߚ+&!5r %s{p^/ltD;a]=*OuF]a3Kf"g} :~TЏCUݣ:8;+oer5p7:rv&w4ӨrӡMs:F 'KOLstSv(8`q#؞1|E=n]GC0eqє Y kK*Nr<Ѯq hݞҩݠ~8X_c9 uU(̈9V̝ΣDa9i +XS)a')&Vkq}#9vc2CZ3zt3&ŰAHGH;̹&gp&=q8{殾u ]tP*. hέáv' 7-u}$`&<7qZocaml-metadata-0.3.1/examples/meta000077500000000000000000000000721477304117700170700ustar00rootroot00000000000000#!/bin/sh dune exec --no-print-directory ./meta.exe -- $@ ocaml-metadata-0.3.1/examples/meta.ml000066400000000000000000000043431477304117700175010ustar00rootroot00000000000000let () = let binary = ref false in let fname = ref [] in let format = ref "" in Arg.parse [ ("-b", Arg.Set binary, "Show binary contents of tags."); ("--format", Arg.Set_string format, "File format."); ("-f", Arg.Set_string format, "File format."); ] (fun f -> fname := f :: !fname) "meta [options] file"; let parser = match !format with | "id3" | "mp3" -> Metadata.ID3.parse_file | "id3v1" -> Metadata.ID3v1.parse_file | "id3v2" -> fun ?custom_parser f -> Metadata.ID3v2.parse_file ?custom_parser f | "ogg" -> Metadata.OGG.parse_file | "mp4" -> Metadata.MP4.parse_file | "" -> Metadata.Any.parse_file | _ -> failwith "Unknown format." in let fname = !fname in if fname = [] then ( Printf.eprintf "Please enter a filename.\n%!"; exit 1); let fname = List.map (fun f -> if String.contains f '*' then ( let d = Filename.dirname f in let f = Filename.basename f |> Str.global_replace (Str.regexp "\\*") ".*" |> Str.regexp in let files = Sys.readdir d |> Array.to_list |> List.filter (fun s -> Str.string_match f s 0) in List.map (fun f -> d ^ "/" ^ f) files) else [f]) fname |> List.flatten in List.iter (fun fname -> Printf.printf "\n# Metadata for %s\n\n%!" fname; (* Store "APIC" as custom tag. *) let apic_tag = ref None in let custom_parser { Metadata.read_ba; label; _ } = match (label, read_ba) with | "APIC", Some r -> apic_tag := Some (r ()) | _ -> () in let m = parser ~custom_parser fname in List.iter (fun (k, v) -> let v = match k with | "APIC" -> assert false | "PIC" | "metadata_block_picture" | "RVA2" -> "" | _ -> v in Printf.printf "- %s: %s\n%!" k v; if !binary then Printf.printf " %s: %S\n%!" k v) m; match !apic_tag with | None -> () | Some tag -> Printf.printf "- APIC: \n" (Bigarray.Array1.dim tag)) fname ocaml-metadata-0.3.1/examples/mimetype000077500000000000000000000000761477304117700177770ustar00rootroot00000000000000#!/bin/sh dune exec --no-print-directory ./mimetype.exe -- $@ ocaml-metadata-0.3.1/examples/mimetype.ml000066400000000000000000000002101477304117700203710ustar00rootroot00000000000000let () = let fname = Sys.argv.(1) in let mime = try Metadata.MIME.of_file fname with Not_found -> "unknown" in print_endline mime ocaml-metadata-0.3.1/examples/mp4/000077500000000000000000000000001477304117700167155ustar00rootroot00000000000000ocaml-metadata-0.3.1/examples/mp4/base_tags.m4a000066400000000000000000000037101477304117700212510ustar00rootroot00000000000000ftypM4A M4A isomiso2freewmdatLavc60.3.1000])d-Sz3}n%ʕ;' ADqH$"#&>= '!Ij"$"$A@lɘ3 (KgO]DCS Mw w 䝍tu~ߝmNj˸Ų-[nN;m۵ܫ]ʱnu^unvwnq;rܫ5jZ֮5jZVi_____________(((((((((rK8)Ø(((. =6CŘZhWS﫽_l?֮O֭^ X]qswĿ\w7]y5iZgR0_\s/#^m!?ZUHzmU_04JiAS"7.. AJpj@{uiij`r>WMtO>sOs|[8-f`?~~8V$j݄ q 3RbдEpwFXe1FRpr.+5.=պU?ūVcT3&͛(0 YadD;3d .N! V.T} `ݲyo>\MU>խJA9;#0f7󸻏{,Z)ף/q)1Nh Ch2d{^וϖ4h`ٷoܳ`ѣEۛ, 4hÈB.,[3.'eÑ._3c,9TioA<1o]lFGMtFFG4U@6?ܹ@5 #- V Y3Ei |?xuFm-Rr56Orøp-moovlmvhdd@=trak\tkhdd@$edtselstdmdia mdhdD:U-hdlrsounSoundHandler`minfsmhd$dinfdref url $stbljstsdZmp4aD6esds%@V stts:stsc,stsz.stco,sgpdrollsbgproll|udtatmeta!hdlrmdirapplGilstnamdatafoo$toodataLavf60.3.100ocaml-metadata-0.3.1/examples/mp4/base_tags.ml000066400000000000000000000002031477304117700211720ustar00rootroot00000000000000open Metadata let () = let metadata = Reader.with_file MP4.parse Sys.argv.(1) in assert (List.assoc "title" metadata = "foo") ocaml-metadata-0.3.1/examples/mp4/dune000066400000000000000000000002661477304117700175770ustar00rootroot00000000000000(executable (name base_tags) (libraries metadata)) (rule (alias runtest) (deps (:base_tags ./base_tags.exe) ./base_tags.m4a) (action (run %{base_tags} ./base_tags.m4a))) ocaml-metadata-0.3.1/examples/test.ml000066400000000000000000000023431477304117700175300ustar00rootroot00000000000000let p name m = let m = List.map (fun (l, v) -> "- " ^ l ^ " : " ^ v) m |> String.concat "\n" in Printf.printf "# Testing %s\n\n%s\n\n%!" name m (* Test parsing of metadata. *) let () = p "mp3v2" (Metadata.ID3v2.parse_file "test.mp3"); p "mp3v1" (Metadata.ID3v1.parse_file "test.mp3"); p "mp3" (Metadata.ID3.parse_file "test.mp3"); p "wav" (Metadata.WAV.parse_file "test.wav"); p "png" (Metadata.PNG.parse_file "test.png"); p "jpg" (Metadata.JPEG.parse_file "test.jpg"); p "avi" (Metadata.AVI.parse_file "test.avi"); p "mp4" (Metadata.MP4.parse_file "test.mp4") (* Test failures. *) let () = Printf.printf "# Testing failures\n\n%!"; let test t s f file = Printf.printf "- parsing %s as %s: %!" s t; try ignore (f file); assert false with Metadata.Invalid -> Printf.printf "failed as expected\n%!" in test "png" "jpg" Metadata.PNG.parse_file "test.jpg"; test "jpg" "png" Metadata.JPEG.parse_file "test.png"; test "mp3v2" "png" Metadata.ID3v2.parse_file "test.png"; test "mp3v1" "png" Metadata.ID3v1.parse_file "test.png"; test "mp3" "png" Metadata.ID3.parse_file "test.png"; test "mp4" "mp3" Metadata.MP4.parse_file "test.mp3"; test "avi" "mp3" Metadata.MP4.parse_file "test.avi" ocaml-metadata-0.3.1/metadata.opam000066400000000000000000000014061477304117700170360ustar00rootroot00000000000000# This file is generated by dune, edit dune-project instead opam-version: "2.0" version: "0.3.1" synopsis: "Read metadata from various file formats" description: "A pure OCaml library for reading files from various formats." maintainer: ["Samuel Mimram "] authors: ["Samuel Mimram "] license: "GPL-3.0-or-later" homepage: "https://github.com/savonet/ocaml-metadata" bug-reports: "https://github.com/savonet/ocaml-metadata/issues" depends: [ "dune" {>= "3.6"} "ocaml" {>= "4.14.0"} "odoc" {with-doc} ] build: [ ["dune" "subst"] {dev} [ "dune" "build" "-p" name "-j" jobs "@install" "@runtest" {with-test} "@doc" {with-doc} ] ] dev-repo: "git+https://github.com/savonet/ocaml-metadata.git" ocaml-metadata-0.3.1/src/000077500000000000000000000000001477304117700151665ustar00rootroot00000000000000ocaml-metadata-0.3.1/src/dune000066400000000000000000000001051477304117700160400ustar00rootroot00000000000000(library (name metadata) (public_name metadata) (libraries unix)) ocaml-metadata-0.3.1/src/metadata.ml000066400000000000000000000042061477304117700173020ustar00rootroot00000000000000module CharEncoding = MetadataCharEncoding module MIME = MetadataMIME module Make (E : CharEncoding.T) = struct include MetadataBase module ID3v1 = MetadataID3v1 module ID3v2 = MetadataID3v2 module OGG = MetadataOGG module FLAC = MetadataFLAC module JPEG = MetadataJPEG module PNG = MetadataPNG module AVI = MetadataAVI module MP4 = MetadataMP4 module WAV = MetadataWAV module RIFF = MetadataRIFF (** Charset conversion function. *) let recode = E.convert module ID3 = struct let parse f = let failure, v2 = try (false, ID3v2.parse ~recode f) with _ -> (true, []) in let v1 = try Reader.reset f; ID3v1.parse ~recode f with _ -> if failure then raise Invalid else [] in v2 @ v1 let parse_file ?custom_parser file = Reader.with_file ?custom_parser parse file end let rec first_valid l file = match l with | f :: l -> ( try f file with Invalid -> Reader.reset file; first_valid l file) | [] -> raise Invalid module Audio = struct let parsers = [ID3.parse; OGG.parse; FLAC.parse; WAV.parse] let parse = first_valid parsers let parse_file ?custom_parser file = Reader.with_file ?custom_parser parse file end module Image = struct let parsers = [JPEG.parse; PNG.parse] let parse = first_valid parsers let parse_file ?custom_parser file = Reader.with_file ?custom_parser parse file end module Video = struct let parsers = [AVI.parse; MP4.parse] let parse = first_valid parsers let parse_file ?custom_parser file = Reader.with_file ?custom_parser parse file end module Any = struct let parsers = Audio.parsers @ Image.parsers @ Video.parsers @ [RIFF.parse] (** Genering parsing of metadata. *) let parse = first_valid parsers let parse_file ?custom_parser file = Reader.with_file ?custom_parser parse file (** Parse the metadatas of a string. *) let parse_string ?custom_parser file = Reader.with_string ?custom_parser parse file end include Any end include Make (CharEncoding.Naive) ocaml-metadata-0.3.1/src/metadata.mli000066400000000000000000000113041477304117700174500ustar00rootroot00000000000000(** Read metadata from various file formats. *) (** Functions for handling charset conversion. *) module CharEncoding = MetadataCharEncoding (** Guess the MIME type of a file. *) module MIME : sig (** Guess the MIME type from file contents. Raises [Not_found] if none was found. *) val of_string : string -> string (** Same as [of_string] but takes a file name as argument. *) val of_file : string -> string end (** Generate metadata parsers given functions for converting charsets. *) module Make : functor (_ : CharEncoding.T) -> sig (** Raised when the metadata is not valid. *) exception Invalid (** Metadata are represented as association lists (name, value). *) type metadata = (string * string) list (** Bigarray representation of (large) tags. *) type bigarray = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t (** When used, a custom parser can override the default parsing mechanism. It is passed the metadata label (without normalization), the expected length of the data, a regular read function an an optional bigarray read function. The custom parser can call any of the read function to get the corresponding tag's value. After doing so, the tag is ignored by the regular parsing process. Currently only supported for: ID3v2, MP4 and [metadata_block_picture] in FLAC metadata. *) type parser_handler = MetadataBase.parser_handler = { label : string; length : int; read : unit -> string; read_ba : (unit -> bigarray) option; skip : unit -> unit; } (** A custom parser, see [parser_handler]. *) type custom_parser = parser_handler -> unit (** Abstractions for reading from various sources. *) module Reader : sig (** A function to read taking the buffer to fill the offset and the length and returning the number of bytes actually read. *) type t = MetadataBase.Reader.t = { read : bytes -> int -> int -> int; read_ba : (int -> MetadataBase.bigarray) option; custom_parser : custom_parser option; seek : int -> unit; size : unit -> int option; reset : unit -> unit; } (** Go back at the beginning of the stream. *) val reset : t -> unit (** Specialize a parser to operate on files. *) val with_file : ?custom_parser:custom_parser -> (t -> metadata) -> string -> metadata (** Specialize a parser to operate on strings. *) val with_string : ?custom_parser:custom_parser -> (t -> metadata) -> string -> metadata end (** ID3v1 metadata.*) module ID3v1 = MetadataID3v1 (** ID3v2 metadata. *) module ID3v2 = MetadataID3v2 (** OGG metadata. *) module OGG = MetadataOGG (** Flac metadata. *) module FLAC = MetadataFLAC (** Jpeg metadata. *) module JPEG = MetadataJPEG (** PNG metadata. *) module PNG = MetadataPNG (** AVI metadata. *) module AVI = MetadataAVI (** MP4 metadata. *) module MP4 = MetadataMP4 (** WAV metadata. *) module WAV = MetadataWAV (** RIFF metdata. *) module RIFF = MetadataRIFF (** Convert the charset encoding of a string. *) val recode : ?source:[ `ISO_8859_1 | `UTF_16 | `UTF_16BE | `UTF_16LE | `UTF_8 ] -> ?target:[ `UTF_16 | `UTF_16BE | `UTF_16LE | `UTF_8 ] -> string -> string (** ID3v1 and ID3v2 metadata. *) module ID3 : sig val parse : Reader.t -> (string * string) list val parse_file : ?custom_parser:custom_parser -> string -> (string * string) list end (** Return the first application which does not raise invalid. *) val first_valid : (Reader.t -> metadata) list -> Reader.t -> metadata (** Audio file formats. *) module Audio : sig val parse : Reader.t -> MetadataBase.metadata val parse_file : ?custom_parser:custom_parser -> string -> MetadataBase.metadata end (** Image file formats. *) module Image : sig val parse : Reader.t -> MetadataBase.metadata val parse_file : ?custom_parser:custom_parser -> string -> MetadataBase.metadata end (** Video file formats. *) module Video : sig val parse : Reader.t -> MetadataBase.metadata val parse_file : ?custom_parser:custom_parser -> string -> MetadataBase.metadata end (** All supported file formats. *) module Any : sig (** Generic metadata parsing. *) val parse : Reader.t -> MetadataBase.metadata (** Parse the metadata from a file. *) val parse_file : ?custom_parser:custom_parser -> string -> MetadataBase.metadata (** Parse the metadata from a string containing the contents of a file. *) val parse_string : ?custom_parser:custom_parser -> string -> MetadataBase.metadata end include module type of Any end include module type of Make (CharEncoding.Naive) ocaml-metadata-0.3.1/src/metadataAVI.ml000066400000000000000000000001471477304117700176420ustar00rootroot00000000000000let parse = MetadataRIFF.parse ~format:"AVI " let parse_file = MetadataRIFF.parse_file ~format:"AVI " ocaml-metadata-0.3.1/src/metadataBase.ml000066400000000000000000000110021477304117700200650ustar00rootroot00000000000000(** Raised when the format is invalid. *) exception Invalid type bigarray = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t type metadata = (string * string) list type endianness = Big_endian | Little_endian type parser_handler = { label : string; length : int; read : unit -> string; read_ba : (unit -> bigarray) option; skip : unit -> unit; } type custom_parser = parser_handler -> unit module Reader = struct (** A function to read taking the buffer to fill the offset and the length and returning the number of bytes actually read. *) type t = { read : bytes -> int -> int -> int; read_ba : (int -> bigarray) option; custom_parser : custom_parser option; seek : int -> unit; size : unit -> int option; reset : unit -> unit; } (** Make a reading function retry until buffer is filled (or an error occurs). *) let retry read buf off len = let r = ref 0 in let loop = ref true in while !loop do let n = read buf (off + !r) (len - !r) in r := !r + n; loop := !r <> 0 && !r < len && n <> 0 done; !r let read f n = let s = Bytes.create n in let k = retry f.read s 0 n in if k <> n then raise Invalid; Bytes.unsafe_to_string s let read_tag ~length ~label f = let is_custom = match f.custom_parser with | None -> false | Some custom_parser -> let is_custom = ref false in let skip () = is_custom := true; f.seek length in let read () = is_custom := true; read f length in let read_ba = Option.map (fun read_ba () -> is_custom := true; read_ba length) f.read_ba in custom_parser { read_ba; read; skip; length; label }; !is_custom in if is_custom then None else Some (read f length) let drop f n = f.seek n let byte f = int_of_char (read f 1).[0] let uint8 f = byte f let int16_be f = let b0 = byte f in let b1 = byte f in (b0 lsl 8) + b1 let int16_le f = let b0 = byte f in let b1 = byte f in (b1 lsl 8) + b0 let uint16_le = int16_le let int16 = function Big_endian -> int16_be | Little_endian -> int16_le let int24_be f = let b0 = byte f in let b1 = byte f in let b2 = byte f in (b0 lsl 16) + (b1 lsl 8) + b2 let int32_le f = let b0 = byte f in let b1 = byte f in let b2 = byte f in let b3 = byte f in (b3 lsl 24) + (b2 lsl 16) + (b1 lsl 8) + b0 let uint32_le = int32_le let int32_be f = let b0 = byte f in let b1 = byte f in let b2 = byte f in let b3 = byte f in (b0 lsl 24) + (b1 lsl 16) + (b2 lsl 8) + b3 let size f = f.size () let reset f = f.reset () let with_file ?custom_parser f fname = let fd = Unix.openfile fname [Unix.O_RDONLY; Unix.O_CLOEXEC] 0o644 in let file = let read = Unix.read fd in let read_ba len = let pos = Int64.of_int (Unix.lseek fd 0 Unix.SEEK_CUR) in let ba = Bigarray.array1_of_genarray (Unix.map_file ~pos fd Bigarray.char Bigarray.c_layout false [| len |]) in ignore (Unix.lseek fd len Unix.SEEK_CUR); ba in let seek n = ignore (Unix.lseek fd n Unix.SEEK_CUR) in let size () = try let p = Unix.lseek fd 0 Unix.SEEK_CUR in let n = Unix.lseek fd 0 Unix.SEEK_END in ignore (Unix.lseek fd p Unix.SEEK_SET); Some n with _ -> None in let reset () = ignore (Unix.lseek fd 0 Unix.SEEK_SET) in { read; read_ba = Some read_ba; seek; size; reset; custom_parser } in try let ans = f file in Unix.close fd; ans with e -> let bt = Printexc.get_raw_backtrace () in Unix.close fd; Printexc.raise_with_backtrace e bt let with_string ?custom_parser f s = let len = String.length s in let pos = ref 0 in let read b ofs n = let n = min (len - !pos) n in String.blit s !pos b ofs n; pos := !pos + n; n in let seek n = pos := !pos + n in let reset () = pos := 0 in let size () = Some len in f { read; read_ba = None; seek; size; reset; custom_parser } end module Int = struct include Int let find p = let ans = ref 0 in try while true do if p !ans then raise Exit else incr ans done; assert false with Exit -> !ans end ocaml-metadata-0.3.1/src/metadataCharEncoding.ml000066400000000000000000000060241477304117700215470ustar00rootroot00000000000000type recode = ?source:[ `ISO_8859_1 | `UTF_8 | `UTF_16 | `UTF_16LE | `UTF_16BE ] -> ?target:[ `UTF_8 | `UTF_16 | `UTF_16LE | `UTF_16BE ] -> string -> string module type T = sig val convert : ?source:[ `ISO_8859_1 | `UTF_8 | `UTF_16 | `UTF_16LE | `UTF_16BE ] -> ?target:[ `UTF_8 | `UTF_16 | `UTF_16LE | `UTF_16BE ] -> string -> string end module Naive : T = struct let convert ?(source = `UTF_8) ?(target = `UTF_8) s = match (source, target) with | `UTF_8, `UTF_8 | `UTF_16, `UTF_16 | `UTF_16LE, `UTF_16LE | `UTF_16BE, `UTF_16BE | _ -> let buf = Buffer.create 10 in let len = String.length s in let add_unicode_char = match target with | `UTF_8 -> Buffer.add_utf_8_uchar | `UTF_16BE -> Buffer.add_utf_16be_uchar | _ -> Buffer.add_utf_16le_uchar in let unicode_byte_length = match source with | `ISO_8859_1 -> fun _ -> 1 | `UTF_8 -> Uchar.utf_8_byte_length | _ -> Uchar.utf_16_byte_length in let s, get_unicode_char = match source with | `ISO_8859_1 -> (s, fun s pos -> Uchar.of_char s.[pos]) | `UTF_8 -> ( s, fun s pos -> Uchar.utf_decode_uchar (String.get_utf_8_uchar s pos) ) | `UTF_16BE -> ( s, fun s pos -> Uchar.utf_decode_uchar (String.get_utf_16be_uchar s pos) ) | `UTF_16LE -> ( s, fun s pos -> Uchar.utf_decode_uchar (String.get_utf_16le_uchar s pos) ) | `UTF_16 -> let default = ( "", fun s pos -> Uchar.utf_decode_uchar (String.get_utf_16be_uchar s pos) ) in if len < 2 then default else ( let rem = String.sub s 2 (len - 2) in match (s.[0], s.[1]) with | '\xfe', '\xff' -> ( rem, fun s pos -> Uchar.utf_decode_uchar (String.get_utf_16be_uchar s pos) ) | '\xff', '\xfe' -> ( rem, fun s pos -> Uchar.utf_decode_uchar (String.get_utf_16le_uchar s pos) ) | _ -> default) in if target = `UTF_16 then add_unicode_char buf Uchar.bom; let len = String.length s in let rec f pos = if pos = len then Buffer.contents buf else ( let c = get_unicode_char s pos in add_unicode_char buf c; f (pos + unicode_byte_length c)) in f 0 end ocaml-metadata-0.3.1/src/metadataCharEncoding.mli000066400000000000000000000011171477304117700217160ustar00rootroot00000000000000(** Charset conversion. *) (** Type of functions for converting charset. *) type recode = ?source:[ `ISO_8859_1 | `UTF_8 | `UTF_16 | `UTF_16LE | `UTF_16BE ] -> ?target:[ `UTF_8 | `UTF_16 | `UTF_16LE | `UTF_16BE ] -> string -> string (** Type of modules for specifying charset conversion. *) module type T = sig (** Convert charset. *) val convert : recode end (** Basic charset conversion. The conversion routine implemented in this module is not able to detect encoding. We recommend using a library such as camomile for a more complete solution. *) module Naive : T ocaml-metadata-0.3.1/src/metadataFLAC.ml000066400000000000000000000050611477304117700177300ustar00rootroot00000000000000open MetadataBase module R = Reader let parse f : metadata = let id = R.read f 4 in if id <> "fLaC" then raise Invalid; let tags = ref [] in let rec block () = let n = R.uint8 f in let last = n land 0b10000000 <> 0 in let block_type = n land 0b01111111 in let len = R.int24_be f in (match block_type with | 4 -> (* Vorbis comment *) let n = ref 0 in let vendor_len = R.uint32_le f in let vendor = R.read f vendor_len in n := !n + 4 + vendor_len; tags := ("vendor", vendor) :: !tags; let list_len = R.uint32_le f in n := !n + 4; for _ = 1 to list_len do let len = R.uint32_le f in let tag = R.read f len in n := !n + 4 + len; match String.index_opt tag '=' with | Some k -> let field = String.sub tag 0 k |> String.lowercase_ascii in let value = String.sub tag (k + 1) (len - (k + 1)) in tags := (field, value) :: !tags | None -> () done; R.drop f (len - !n) | 6 -> ( (* Picture *) match R.read_tag ~length:len ~label:"metadata_block_picture" f with | None -> () | Some picture -> tags := ("metadata_block_picture", picture) :: !tags) | _ -> R.drop f len); if not last then block () in block (); List.rev !tags let parse_file ?custom_parser file = R.with_file ?custom_parser parse file type picture = { picture_type : int; picture_mime : string; picture_description : string; picture_width : int; picture_height : int; picture_bpp : int; picture_colors : int; picture_data : string; } let parse_picture p = let n = ref 0 in let int () = let ans = (int_of_char p.[!n] lsl 24) + (int_of_char p.[!n + 1] lsl 16) + (int_of_char p.[!n + 2] lsl 8) + int_of_char p.[!n + 3] in n := !n + 4; ans in let string len = let ans = String.sub p !n len in n := !n + len; ans in let picture_type = int () in let mime_len = int () in let mime = string mime_len in let desc_len = int () in let desc = string desc_len in let width = int () in let height = int () in let bpp = int () in let colors = int () in let len = int () in let data = string len in { picture_type; picture_mime = mime; picture_description = desc; picture_width = width; picture_height = height; picture_bpp = bpp; picture_colors = colors; picture_data = data; } ocaml-metadata-0.3.1/src/metadataFLAC.mli000066400000000000000000000006331477304117700201010ustar00rootroot00000000000000val parse : MetadataBase.Reader.t -> MetadataBase.metadata val parse_file : ?custom_parser:MetadataBase.custom_parser -> string -> MetadataBase.metadata type picture = { picture_type : int; picture_mime : string; picture_description : string; picture_width : int; picture_height : int; picture_bpp : int; picture_colors : int; picture_data : string; } val parse_picture : string -> picture ocaml-metadata-0.3.1/src/metadataID3v1.ml000066400000000000000000000024411477304117700200500ustar00rootroot00000000000000open MetadataBase module R = Reader let trim s = match String.index_opt s '\000' with Some n -> String.sub s 0 n | None -> s (** Parse ID3v1 tags. *) let parse ?(recode = MetadataCharEncoding.Naive.convert) f : metadata = let size = match R.size f with Some n -> n | None -> raise Invalid in R.drop f (size - 128); if R.read f 3 <> "TAG" then raise Invalid; let title = R.read f 30 |> trim |> recode ~source:`ISO_8859_1 in let artist = R.read f 30 |> trim |> recode ~source:`ISO_8859_1 in let album = R.read f 30 |> trim |> recode ~source:`ISO_8859_1 in let year = R.read f 4 |> trim |> recode ~source:`ISO_8859_1 in let comment = R.read f 30 in let comment, track, genre = if comment.[27] = '\000' then (trim comment, int_of_char comment.[28], int_of_char comment.[29]) else (trim comment, 0, 0) in let comment = recode comment in let track = if track = 0 then "" else string_of_int track in let genre = if genre = 255 then "" else string_of_int genre in let genre = recode genre in [ ("title", title); ("artist", artist); ("album", album); ("year", year); ("comment", comment); ("track", track); ("genre", genre); ] |> List.filter (fun (_, v) -> v <> "") let parse_file ?custom_parser file = R.with_file ?custom_parser parse file ocaml-metadata-0.3.1/src/metadataID3v1.mli000066400000000000000000000003111477304117700202130ustar00rootroot00000000000000val parse : ?recode:MetadataCharEncoding.recode -> MetadataBase.Reader.t -> MetadataBase.metadata val parse_file : ?custom_parser:MetadataBase.custom_parser -> string -> MetadataBase.metadata ocaml-metadata-0.3.1/src/metadataID3v2.ml000066400000000000000000000362121477304117700200540ustar00rootroot00000000000000open MetadataBase module R = Reader let read_size ~synch_safe f = let s = R.read f 4 in let s0 = int_of_char s.[0] in let s1 = int_of_char s.[1] in let s2 = int_of_char s.[2] in let s3 = int_of_char s.[3] in if synch_safe then ( if s0 lor s1 lor s2 lor s3 land 0b10000000 <> 0 then raise Invalid; (s0 lsl 21) + (s1 lsl 14) + (s2 lsl 7) + s3) else (s0 lsl 24) + (s1 lsl 16) + (s2 lsl 8) + s3 let read_size_v2 f = let s = R.read f 3 in let s0 = int_of_char s.[0] in let s1 = int_of_char s.[1] in let s2 = int_of_char s.[2] in (s0 lsl 16) + (s1 lsl 8) + s2 (** Remove trailing nulls. *) let unterminate encoding s = let n = String.length s in match encoding with | 0 | 3 -> if String.length s > 0 && s.[n - 1] = '\000' then String.sub s 0 (n - 1) else s | 1 | 2 -> if String.length s >= 2 && s.[n - 1] = '\000' && s.[n - 2] = '\000' then String.sub s 0 (n - 2) else s | _ -> failwith (Printf.sprintf "Unknown encoding: %d." encoding) (** Find the index of the substring after the first null-terminated substring. *) let next_substring encoding ?(offset = 0) s = let ans = ref 0 in let utf16 = encoding = 1 || encoding = 2 in try if utf16 then for i = offset to (String.length s / 2) - 1 do if s.[2 * i] = '\000' && s.[(2 * i) + 1] = '\000' then ( ans := (2 * i) + 2; raise Exit) done else for i = offset to String.length s - 1 do if s.[i] = '\000' then ( ans := i + 1; raise Exit) done; raise Not_found with Exit -> !ans let normalize_id = function | "COMM" -> "comment" | "TALB" -> "album" | "TBPM" -> "bpm" | "TCOM" -> "composer" | "TCON" -> "genre" | "TCOP" -> "copyright" | "TDAT" -> "date" | "TDOR" -> "original release time" | "TDRC" -> "recording time" | "TENC" -> "encodedby" | "TEXT" -> "lyricist" | "TIT1" -> "grouping" | "TIT2" -> "title" | "TIT3" -> "subtitle" | "TKEY" -> "key" | "TLAN" -> "language" | "TLEN" -> "length" | "TMED" -> "media" | "TOAL" -> "originalalbum" | "TOFN" -> "originalfilename" | "TOPE" -> "originalartist" | "TPOS" -> "discnumber" | "TPE1" -> "artist" | "TPE2" -> "albumartist" | "TPE3" -> "conductor" | "TPE4" -> "remixer" | "TPUB" -> "label" | "TRCK" -> "tracknumber" | "TSOA" -> "albumsort" | "TSO2" -> "albumartistsort" | "TSOT" -> "titlesort" | "TSRC" -> "isrc" | "TSSE" -> "encoder" | "TSST" -> "discsubtitle" | "TYER" -> "year" | "WOAR" -> "website" | "WXXX" -> "url" | id -> id let make_recode recode = let recode = Option.value ~default:MetadataCharEncoding.Naive.convert recode in let recode : int -> string -> string = function | 0 -> recode ~source:`ISO_8859_1 ~target:`UTF_8 | 1 -> recode ~source:`UTF_16 ~target:`UTF_8 | 2 -> recode ~source:`UTF_16 ~target:`UTF_8 | 3 -> recode ~source:`UTF_8 ~target:`UTF_8 (* Invalid encoding. *) | _ -> fun s -> s in fun encoding s -> recode encoding (unterminate encoding s) (** Parse ID3v2 tags. *) let parse ?recode f : metadata = let recode = make_recode recode in let id = R.read f 3 in if id <> "ID3" then raise Invalid; let version = let v1 = R.byte f in let v2 = R.byte f in [| 2; v1; v2 |] in let v = version.(1) in if not (List.mem v [2; 3; 4]) then raise Invalid; let id_len, read_frame_size = if v = 2 then (3, read_size_v2) else (4, read_size ~synch_safe:(v > 3)) in let flags = R.byte f in let unsynchronization = flags land 0b10000000 <> 0 in if unsynchronization then failwith "Unsynchronized headers not handled."; let extended_header = flags land 0b1000000 <> 0 in let size = read_size ~synch_safe:true f in let len = ref size in if extended_header then ( let size = read_size ~synch_safe:(v > 3) f in let size = if v = 3 then size else size - 4 in len := !len - (size + 4); ignore (R.read f size)); let tags = ref [] in while !len > 0 do try (* We can have 3 null bytes in the end even if id is 4 bytes. *) let id_len = min !len id_len in let id = R.read f (min !len id_len) in if id = "\000\000\000\000" || id = "\000\000\000" then len := 0 (* stop tag *) else ( let size = read_frame_size f in (* make sure that we remain within the bounds in case of a problem *) let size = min size (!len - 10) in let flags = if v = 2 then None else Some (R.read f 2) in let compressed = match flags with | None -> false | Some flags -> int_of_char flags.[1] land 0b10000000 <> 0 in let encrypted = match flags with | None -> false | Some flags -> int_of_char flags.[1] land 0b01000000 <> 0 in if compressed || encrypted then ( ignore (R.read f size); len := !len - (size + 10); raise Exit); let tag = R.read_tag ~label:id ~length:size f in len := !len - (size + 10); if tag = None then raise Exit; let data = Option.get tag in let len = String.length data in if List.mem id ["SEEK"] then () else if id = "TXXX" then ( let encoding = int_of_char data.[0] in let data = String.sub data 1 (len - 1) in let recode = recode encoding in let id, data = let n = next_substring encoding data in (String.sub data 0 n, String.sub data n (String.length data - n)) in let id = recode id in let data = recode data in tags := (id, data) :: !tags) else if id = "COMM" then ( let encoding = int_of_char data.[0] in let recode = recode encoding in let data = String.sub data 1 (len - 1) in (* We ignore the language description of the comment. *) let n = try next_substring encoding data with Not_found -> 0 in let data = String.sub data n (String.length data - n) |> recode in tags := ("comment", data) :: !tags) else if id.[0] = 'T' then ( let encoding = int_of_char data.[0] in let recode = recode encoding in let data = String.sub data 1 (len - 1) |> recode in tags := (normalize_id id, data) :: !tags) else tags := (normalize_id id, data) :: !tags) with Exit -> () done; List.rev !tags let parse_file ?recode ?custom_parser = R.with_file ?custom_parser (parse ?recode) (** Dump ID3v2 header. *) let dump f = let id = R.read f 3 in if id <> "ID3" then raise Invalid; let v1 = R.byte f in let _v2 = R.byte f in if not (List.mem v1 [2; 3; 4]) then raise Invalid; let _flags = R.byte f in let size = read_size ~synch_safe:true f in R.reset f; R.read f (10 + size) let dump_file = R.with_file dump (** APIC data. *) type apic = { mime : string; picture_type : int; description : string; data : string; } type pic = { pic_format : string; pic_type : int; pic_description : string; pic_data : string; } (** Parse APIC data. *) let parse_apic ?recode apic = let recode = make_recode recode in let text_encoding = int_of_char apic.[0] in let text_bytes = if text_encoding = 1 || text_encoding = 2 then 2 else 1 in let recode = recode text_encoding in let n = String.index_from apic 1 '\000' in let mime = String.sub apic 1 (n - 1) in let n = n + 1 in let picture_type = int_of_char apic.[n] in let n = n + 1 in let l = Int.find (fun i -> i mod text_bytes = 0 && apic.[n + i] = '\000' && (text_bytes = 1 || apic.[n + i + 1] = '\000')) in let description = recode (String.sub apic n l) in let n = n + l + text_bytes in let data = String.sub apic n (String.length apic - n) in { mime; picture_type; description; data } let parse_pic ?recode pic = let recode = make_recode recode in let text_encoding = int_of_char pic.[0] in let text_bytes = if text_encoding = 1 || text_encoding = 2 then 2 else 1 in let recode = recode text_encoding in let pic_format = String.sub pic 1 3 in let pic_type = int_of_char pic.[4] in let l = Int.find (fun i -> i mod text_bytes = 0 && pic.[5 + i] = '\000' && (text_bytes = 1 || pic.[5 + i + 1] = '\000')) in let pic_description = recode (String.sub pic 5 l) in let n = 5 + l + text_bytes in let pic_data = String.sub pic n (String.length pic - n) in { pic_format; pic_type; pic_description; pic_data } type frame_id = [ `AENC | `APIC | `COMM | `COMR | `ENCR | `EQUA | `ETCO | `GEOB | `GRID | `IPLS | `LINK | `MCDI | `MLLT | `OWNE | `PCNT | `POPM | `POSS | `PRIV | `RBUF | `RVAD | `RVRB | `SYLT | `SYTC | `TALB | `TBPM | `TCOM | `TCON | `TCOP | `TDAT | `TDLY | `TENC | `TEXT | `TFLT | `TIME | `TIT1 | `TIT2 | `TIT3 | `TKEY | `TLAN | `TLEN | `TMED | `TOAL | `TOFN | `TOLY | `TOPE | `TORY | `TOWN | `TPE1 | `TPE2 | `TPE3 | `TPE4 | `TPOS | `TPUB | `TRCK | `TRDA | `TRSN | `TRSO | `TSIZ | `TSRC | `TSSE | `TXXX | `TYER | `UFID | `USER | `USLT | `WCOM | `WCOP | `WOAF | `WOAR | `WOAS | `WORS | `WPAY | `WPUB | `WXXX ] let binary_frame = function | `AENC | `ENCR | `EQUA | `ETCO | `GRID | `LINK | `MCDI | `MLLT | `PRIV | `PCNT | `POPM | `POSS | `RBUF | `RVAD | `RVRB | `SYTC | `UFID -> true | _ -> false let frame_id_of_string = function | "AENC" -> Some `AENC | "APIC" -> Some `APIC | "COMM" -> Some `COMM | "COMR" -> Some `COMR | "ENCR" -> Some `ENCR | "EQUA" -> Some `EQUA | "ETCO" -> Some `ETCO | "GEOB" -> Some `GEOB | "GRID" -> Some `GRID | "IPLS" -> Some `IPLS | "LINK" -> Some `LINK | "MCDI" -> Some `MCDI | "MLLT" -> Some `MLLT | "OWNE" -> Some `OWNE | "PCNT" -> Some `PCNT | "POPM" -> Some `POPM | "POSS" -> Some `POSS | "PRIV" -> Some `PRIV | "RBUF" -> Some `RBUF | "RVAD" -> Some `RVAD | "RVRB" -> Some `RVRB | "SYLT" -> Some `SYLT | "SYTC" -> Some `SYTC | "TALB" -> Some `TALB | "TBPM" -> Some `TBPM | "TCOM" -> Some `TCOM | "TCON" -> Some `TCON | "TCOP" -> Some `TCOP | "TDAT" -> Some `TDAT | "TDLY" -> Some `TDLY | "TENC" -> Some `TENC | "TEXT" -> Some `TEXT | "TFLT" -> Some `TFLT | "TIME" -> Some `TIME | "TIT1" -> Some `TIT1 | "TIT2" -> Some `TIT2 | "TIT3" -> Some `TIT3 | "TKEY" -> Some `TKEY | "TLAN" -> Some `TLAN | "TLEN" -> Some `TLEN | "TMED" -> Some `TMED | "TOAL" -> Some `TOAL | "TOFN" -> Some `TOFN | "TOLY" -> Some `TOLY | "TOPE" -> Some `TOPE | "TORY" -> Some `TORY | "TOWN" -> Some `TOWN | "TPE1" -> Some `TPE1 | "TPE2" -> Some `TPE2 | "TPE3" -> Some `TPE3 | "TPE4" -> Some `TPE4 | "TPOS" -> Some `TPOS | "TPUB" -> Some `TPUB | "TRCK" -> Some `TRCK | "TRDA" -> Some `TRDA | "TRSN" -> Some `TRSN | "TRSO" -> Some `TRSO | "TSIZ" -> Some `TSIZ | "TSRC" -> Some `TSRC | "TSSE" -> Some `TSSE | "TXXX" -> Some `TXXX | "TYER" -> Some `TYER | "UFID" -> Some `UFID | "USER" -> Some `USER | "USLT" -> Some `USLT | "WCOM" -> Some `WCOM | "WCOP" -> Some `WCOP | "WOAF" -> Some `WOAF | "WOAR" -> Some `WOAR | "WOAS" -> Some `WOAS | "WORS" -> Some `WORS | "WPAY" -> Some `WPAY | "WPUB" -> Some `WPUB | "WXXX" -> Some `WXXX | _ -> None let string_of_frame_id = function | `AENC -> "AENC" | `APIC -> "APIC" | `COMM -> "COMM" | `COMR -> "COMR" | `ENCR -> "ENCR" | `EQUA -> "EQUA" | `ETCO -> "ETCO" | `GEOB -> "GEOB" | `GRID -> "GRID" | `IPLS -> "IPLS" | `LINK -> "LINK" | `MCDI -> "MCDI" | `MLLT -> "MLLT" | `OWNE -> "OWNE" | `PCNT -> "PCNT" | `POPM -> "POPM" | `POSS -> "POSS" | `PRIV -> "PRIV" | `RBUF -> "RBUF" | `RVAD -> "RVAD" | `RVRB -> "RVRB" | `SYLT -> "SYLT" | `SYTC -> "SYTC" | `TALB -> "TALB" | `TBPM -> "TBPM" | `TCOM -> "TCOM" | `TCON -> "TCON" | `TCOP -> "TCOP" | `TDAT -> "TDAT" | `TDLY -> "TDLY" | `TENC -> "TENC" | `TEXT -> "TEXT" | `TFLT -> "TFLT" | `TIME -> "TIME" | `TIT1 -> "TIT1" | `TIT2 -> "TIT2" | `TIT3 -> "TIT3" | `TKEY -> "TKEY" | `TLAN -> "TLAN" | `TLEN -> "TLEN" | `TMED -> "TMED" | `TOAL -> "TOAL" | `TOFN -> "TOFN" | `TOLY -> "TOLY" | `TOPE -> "TOPE" | `TORY -> "TORY" | `TOWN -> "TOWN" | `TPE1 -> "TPE1" | `TPE2 -> "TPE2" | `TPE3 -> "TPE3" | `TPE4 -> "TPE4" | `TPOS -> "TPOS" | `TPUB -> "TPUB" | `TRCK -> "TRCK" | `TRDA -> "TRDA" | `TRSN -> "TRSN" | `TRSO -> "TRSO" | `TSIZ -> "TSIZ" | `TSRC -> "TSRC" | `TSSE -> "TSSE" | `TXXX -> "TXXX" | `TYER -> "TYER" | `UFID -> "UFID" | `USER -> "USER" | `USLT -> "USLT" | `WCOM -> "WCOM" | `WCOP -> "WCOP" | `WOAF -> "WOAF" | `WOAR -> "WOAR" | `WOAS -> "WOAS" | `WORS -> "WORS" | `WPAY -> "WPAY" | `WPUB -> "WPUB" | `WXXX -> "WXXX" type frame_flag = [ `Tag_alter_perservation of bool | `File_alter_preservation of bool ] let default_flags = function | `AENC | `ETCO | `EQUA | `MLLT | `POSS | `SYLT | `SYTC | `RVAD | `TENC | `TLEN | `TSIZ -> [`Tag_alter_perservation true; `File_alter_preservation false] | _ -> [`Tag_alter_perservation true; `File_alter_preservation true] type text_encoding = [ `ISO_8859_1 | `UTF_8 | `UTF_16 | `UTF_16LE | `UTF_16BE ] type frame_data = [ `Text of text_encoding * string | `Binary of string ] type frame = { id : frame_id; data : frame_data; flags : frame_flag list } let write_string ~buf = Buffer.add_string buf let write_int32 ~buf n = Buffer.add_int32_be buf (Int32.of_int n) let write_int16 ~buf n = Buffer.add_int16_be buf n let write_int ~buf n = Buffer.add_char buf (char_of_int n) let write_size ~buf n = if 0x0fffffff < n then raise Invalid; for i = 0 to 3 do let n = n lsr (7 * (3 - i)) in Buffer.add_char buf (char_of_int (n land 0x7f)) done let render_frame_data ~version = function | `Binary b -> b | `Text (encoding, data) -> let encoding, data = match encoding with | `ISO_8859_1 -> (0, data) | `UTF_16 -> (1, data) | `UTF_16BE when version >= 3 -> (2, data) | `UTF_8 when version >= 3 -> (3, data) | source -> ( 1, MetadataCharEncoding.Naive.convert ~source ~target:`UTF_16 data ) in Printf.sprintf "%c%s" (Char.chr encoding) data let render_frames ~version frames = let buf = Buffer.create 1024 in List.iter (fun { id; data; flags } -> write_string ~buf (string_of_frame_id id); let data = render_frame_data ~version data in let frame_length = String.length data in if version < 4 then write_int32 ~buf frame_length else write_size ~buf frame_length; write_int16 ~buf (List.fold_left (fun flags flag -> flags lor match flag with | `Tag_alter_perservation true -> 0b10000000 lsl 8 | `File_alter_preservation true -> 0b01000000 lsl 8 | _ -> 0) 0 flags); write_string ~buf data) frames; buf let make ~version frames = let buf = Buffer.create 1024 in write_string ~buf "ID3"; write_int ~buf version; write_int ~buf 0; let tags = 0 in write_int ~buf tags; let frame_content = render_frames ~version frames in write_size ~buf (Buffer.length frame_content); Buffer.add_buffer buf frame_content; Buffer.contents buf ocaml-metadata-0.3.1/src/metadataID3v2.mli000066400000000000000000000051121477304117700202200ustar00rootroot00000000000000(** Parse the ID3v2 header. *) val parse : ?recode:MetadataCharEncoding.recode -> MetadataBase.Reader.t -> MetadataBase.metadata (** Parse the ID3v2 header from a file. *) val parse_file : ?recode:MetadataCharEncoding.recode -> ?custom_parser:MetadataBase.custom_parser -> string -> MetadataBase.metadata (** Dump the ID3v2 header. *) val dump : MetadataBase.Reader.t -> string (** Dump the ID3v2 header from a file. *) val dump_file : string -> string type apic = { mime : string; picture_type : int; description : string; data : string; } type pic = { pic_format : string; pic_type : int; pic_description : string; pic_data : string; } (** Parse an APIC tag (containing album art). *) val parse_apic : ?recode:MetadataCharEncoding.recode -> string -> apic (** Parse a PIC tag (containing album art). *) val parse_pic : ?recode:MetadataCharEncoding.recode -> string -> pic (** Frame identifier. *) type frame_id = [ `AENC | `APIC | `COMM | `COMR | `ENCR | `EQUA | `ETCO | `GEOB | `GRID | `IPLS | `LINK | `MCDI | `MLLT | `OWNE | `PCNT | `POPM | `POSS | `PRIV | `RBUF | `RVAD | `RVRB | `SYLT | `SYTC | `TALB | `TBPM | `TCOM | `TCON | `TCOP | `TDAT | `TDLY | `TENC | `TEXT | `TFLT | `TIME | `TIT1 | `TIT2 | `TIT3 | `TKEY | `TLAN | `TLEN | `TMED | `TOAL | `TOFN | `TOLY | `TOPE | `TORY | `TOWN | `TPE1 | `TPE2 | `TPE3 | `TPE4 | `TPOS | `TPUB | `TRCK | `TRDA | `TRSN | `TRSO | `TSIZ | `TSRC | `TSSE | `TXXX | `TYER | `UFID | `USER | `USLT | `WCOM | `WCOP | `WOAF | `WOAR | `WOAS | `WORS | `WPAY | `WPUB | `WXXX ] (** String representation of a frame identifier. *) val string_of_frame_id : frame_id -> string (** Parse a string into a frame id. *) val frame_id_of_string : string -> frame_id option (** Does a frame contain binary data? *) val binary_frame : frame_id -> bool (** Charset for encoding text. *) type text_encoding = [ `ISO_8859_1 | `UTF_16 | `UTF_16BE | `UTF_16LE | `UTF_8 ] (** Data contained in a frame. *) type frame_data = [ `Text of text_encoding * string | `Binary of string ] type frame_flag = [ `File_alter_preservation of bool | `Tag_alter_perservation of bool ] (** Default flags for a frame. *) val default_flags : frame_id -> frame_flag list (** A ID3 frame. *) type frame = { id : frame_id; data : frame_data; flags : frame_flag list } (** Create an ID3v2 header. Consistency between [frame_id] and [frame_data] is not enforced and left to the user to check. *) val make : version:int -> frame list -> string ocaml-metadata-0.3.1/src/metadataJPEG.ml000066400000000000000000000031521477304117700177470ustar00rootroot00000000000000open MetadataBase module R = Reader let parse f : metadata = (* Start of image *) if R.read f 2 <> "\xff\xd8" then raise Invalid; let metadata = ref [] in let add l v = metadata := (l, v) :: !metadata in let rec read_maker () = if R.byte f <> 0xff then raise Invalid; let kind = R.byte f in let len = R.int16_be f in (* Printf.printf "Marker: %x (len: %d)\n%!" kind len; *) match kind with (* | 0xe0 -> *) (* Printf.printf "JFIF\n%!"; *) (* if R.read f 5 <> "JFIF\x00" then raise Invalid; *) (* let _ (\* major version *\) = R.byte f in *) (* let _ (\* minor version *\) = R.byte f in *) (* | 0xe1 -> *) (* Printf.printf "EXIF\n%!"; *) (* if R.read f 6 <> "Exif\x00\x00" then raise Invalid; *) (* let endianness = R.read f 2 in *) (* let endianness = if endianness = "MM" then Big_endian else if endianness = "II" then Little_endian else raise Invalid in *) (* let int16 = R.int16 endianness in *) (* if int16 f <> 0x2a then raise Invalid; *) (* let skip = int16 f in *) (* let _ = R.read f (skip - 8) in *) (* let entries = int16 f in *) | 0xc0 | 0xc2 -> let _ (* precision *) = R.byte f in let height = R.int16_be f in let width = R.int16_be f in add "width" (string_of_int width); add "height" (string_of_int height); (* Ignore after that. *) () | _ -> let _ = R.read f (len - 2) in read_maker () in read_maker (); List.rev !metadata let parse_file ?custom_parser file = R.with_file ?custom_parser parse file ocaml-metadata-0.3.1/src/metadataJPEG.mli000066400000000000000000000002341477304117700201160ustar00rootroot00000000000000val parse : MetadataBase.Reader.t -> MetadataBase.metadata val parse_file : ?custom_parser:MetadataBase.custom_parser -> string -> MetadataBase.metadata ocaml-metadata-0.3.1/src/metadataMIME.ml000066400000000000000000000025241477304117700177530ustar00rootroot00000000000000(** Guess the mime-type of a file. *) module String = struct include String let contains_at offset ~substring s = let n = String.length substring in if String.length s < offset + n then false else String.sub s offset n = substring end let prefixes = [ "ID3", "audio/mpeg"; "OggS", "audio/ogg"; "%PDF-", "application/pdf"; "\137PNG\013\010\026\010", "image/png"; ] let advanced = let wav s = String.starts_with ~prefix:"RIFF" s && String.contains_at 8 ~substring:"WAVEfmt " s in let avi s = String.starts_with ~prefix:"RIFF" s && String.contains_at 8 ~substring:"AVI " s in [ wav, "audio/wav"; avi, "video/x-msvideo" ] let of_string s = let ans = ref "" in try List.iter (fun (f, mime) -> if f s then ( ans := mime; raise Exit ) ) advanced; List.iter (fun (prefix, mime) -> if String.starts_with ~prefix s then ( ans := mime; raise Exit ) ) prefixes; raise Not_found with | Exit -> !ans let of_file fname = let len = 16 in let buf = Bytes.create len in let ic = open_in fname in let n = input ic buf 0 len in let buf = if n = len then buf else Bytes.sub buf 0 n in let s = Bytes.unsafe_to_string buf in of_string s ocaml-metadata-0.3.1/src/metadataMP4.ml000066400000000000000000000037341477304117700176300ustar00rootroot00000000000000open MetadataBase module R = Reader let tagn = [ ("\xa9nam", "title"); ("\xa9ART", "artist"); ("cprt", "copyright"); ("\xa9too", "encoder"); ("\xa9day", "date"); ("\xa9cpy", "copyright"); ("\xa9gen", "genre"); ("\xa9wrt", "composer"); ("\xa9alb", "album"); ("\xa9des", "description"); ("\xa9cmt", "comment"); ] let parse f : metadata = let len = R.int32_be f in if R.read f 4 <> "ftyp" then raise Invalid; R.drop f (len - 8); let ans = ref [] in let rec chunk l = let len = R.int32_be f in let tag = R.read f 4 in let tags = tag :: l in (match tags with | ["moov"] | ["udta"; "moov"] | ["meta"; "udta"; "moov"] | ["ilst"; "meta"; "udta"; "moov"] | [_; "ilst"; "meta"; "udta"; "moov"] -> let remaining = ref (len - 8) in if List.hd tags = "meta" then ( R.drop f 4; remaining := !remaining - 4); (* version and flags for metadata *) while !remaining > 0 do remaining := !remaining - chunk (tag :: l) done | ["data"; tag; "ilst"; "meta"; "udta"; "moov"] -> ( if len < 16 then raise Invalid; let data_type = R.int32_be f in let _ = R.read f 4 in match R.read_tag ~label:tag ~length:(len - 16) f with | None -> () | Some value -> ( match (data_type, List.assoc_opt tag tagn) with | 1, Some tag -> ans := (tag, value) :: !ans | 2, Some tag -> ans := ( tag, MetadataCharEncoding.Naive.convert ~source:`UTF_16BE value ) :: !ans | _ -> ())) | _ -> R.drop f (len - 8)); len in try while true do ignore (chunk []) done; assert false with _ -> List.rev !ans let parse_file ?custom_parser file = R.with_file ?custom_parser parse file ocaml-metadata-0.3.1/src/metadataMP4.mli000066400000000000000000000002341477304117700177710ustar00rootroot00000000000000val parse : MetadataBase.Reader.t -> MetadataBase.metadata val parse_file : ?custom_parser:MetadataBase.custom_parser -> string -> MetadataBase.metadata ocaml-metadata-0.3.1/src/metadataOGG.ml000066400000000000000000000066211477304117700176420ustar00rootroot00000000000000open MetadataBase module R = Reader let parse f : metadata = (* Packetized reading *) let f, peek = (* Current page *) let page = ref "" in (* Read a page *) let fill () = if R.read f 4 <> "OggS" then raise Invalid; (* stream_structure_version *) ignore (R.read f 1); (* header_type_flag *) ignore (R.read f 1); (* absolute granule position *) ignore (R.read f 8); (* stream serial number *) ignore (R.read f 4); (* page sequence no *) ignore (R.read f 4); (* page checksum *) ignore (R.read f 4); let segments = R.uint8 f in let lacing = List.init segments (fun _ -> R.uint8 f) in let n = List.fold_left ( + ) 0 lacing in page := !page ^ R.read f n in let ensure len = while String.length !page < len do fill () done in let read b off len = ensure len; Bytes.blit_string !page 0 b off len; page := String.sub !page len (String.length !page - len); len in let seek n = assert (n >= 0); ensure n; page := String.sub !page n (String.length !page - n) in let peek n = ensure n; let buf = Bytes.create n in Bytes.blit_string !page 0 buf 0 n; Bytes.unsafe_to_string buf in ( { R.read; read_ba = None; custom_parser = None; seek; size = (fun () -> None); reset = (fun () -> assert false); }, peek ) in let comments () = let string () = let n = R.uint32_le f in R.read f n in let vendor = string () in let n = R.uint32_le f in let comments = List.init n (fun _ -> string ()) in let comments = List.filter_map (fun c -> match String.index_opt c '=' with | Some n -> Some ( String.sub c 0 n, String.sub c (n + 1) (String.length c - (n + 1)) ) | None -> None) comments in ("vendor", vendor) :: comments in let stream_type = match (peek 8, peek 7, peek 5) with | "OpusHead", _, _ -> `Opus | _, "\001vorbis", _ -> `Vorbis | _, _, "\127FLAC" -> `Flac | _ -> `Unknown in match stream_type with | `Opus -> R.drop f 8; (* version *) let v = R.uint8 f in if v <> 1 then raise Invalid; (* output channels *) let c = R.uint8 f in (* pre-skip *) ignore (R.uint16_le f); (* input samplerate *) ignore (R.uint32_le f); (* output gain *) ignore (R.uint16_le f); (* mapping family *) let mapping_family = R.uint8 f in if mapping_family <> 0 then ( (* stream count *) ignore (R.uint8 f); (* coupled count *) ignore (R.uint8 f); (* channel mapping *) ignore (R.read f c)); if R.read f 8 <> "OpusTags" then raise Invalid; comments () | `Vorbis -> R.drop f 7; R.drop f (4 + 1 + 4 + 4 + 4 + 4 + 2); (* comment header *) assert (R.uint8 f = 3); if R.read f 6 <> "vorbis" then raise Invalid; comments () | `Flac -> R.drop f 51; (* comment header *) assert (R.uint8 f land 0x7ff = 4); R.drop f 3; comments () | `Unknown -> [] let parse_file ?custom_parser file = R.with_file ?custom_parser parse file ocaml-metadata-0.3.1/src/metadataOGG.mli000066400000000000000000000002341477304117700200050ustar00rootroot00000000000000val parse : MetadataBase.Reader.t -> MetadataBase.metadata val parse_file : ?custom_parser:MetadataBase.custom_parser -> string -> MetadataBase.metadata ocaml-metadata-0.3.1/src/metadataPNG.ml000066400000000000000000000010231477304117700176410ustar00rootroot00000000000000open MetadataBase module R = Reader let parse f : metadata = if R.read f 8 <> "\x89PNG\x0d\x0a\x1a\x0a" then raise Invalid; let _ = R.int32_be f in if R.read f 4 <> "IHDR" then raise Invalid; let width = R.int32_be f in let height = R.int32_be f in let bit_depth = R.byte f in let _ (* color_type *) = R.byte f in [ ("width", string_of_int width); ("height", string_of_int height); ("bit_depth", string_of_int bit_depth); ] let parse_file ?custom_parser file = R.with_file ?custom_parser parse file ocaml-metadata-0.3.1/src/metadataPNG.mli000066400000000000000000000002341477304117700200150ustar00rootroot00000000000000val parse : MetadataBase.Reader.t -> MetadataBase.metadata val parse_file : ?custom_parser:MetadataBase.custom_parser -> string -> MetadataBase.metadata ocaml-metadata-0.3.1/src/metadataRIFF.ml000066400000000000000000000034201477304117700177460ustar00rootroot00000000000000open MetadataBase module R = Reader (* Tag normalization. *) let tagn = [ ("IART", "artist"); ("ICMT", "comment"); ("ICOP", "copyright"); ("ICRD", "date"); ("ICRD", "date"); ("IGNR", "genre"); ("INAM", "title"); ("IPRD", "album"); ("IPRT", "track"); ("ISFT", "encoder"); ("ITRK", "track"); ] let parse ?format f : metadata = if R.read f 4 <> "RIFF" then raise Invalid; let _ (* file size *) = R.int32_le f in if format <> None && Some (R.read f 4) <> format then raise Invalid; let ans = ref [] in let chunk () = let tag = R.read f 4 in let size = R.int32_le f in if tag <> "LIST" then R.drop f size else ( let subtag = R.read f 4 in match subtag with | "INFO" -> let remaining = ref (size - 4) in while !remaining > 0 do let tag = R.read f 4 in let size = R.int32_le f in match R.read_tag ~length:(size - 1) ~label:tag f with | None -> () | Some s -> R.drop f 1; (* null-terminated *) let padding = size mod 2 in R.drop f padding; remaining := !remaining - (8 + size + padding); let tag = match List.assoc_opt tag tagn with | Some tag -> tag | None -> tag in ans := (tag, s) :: !ans done | "movi" -> raise Exit (* stop parsing there *) | _ -> R.drop f (size - 4)) in try while true do chunk () done; assert false with _ -> List.rev !ans let parse_file ?format ?custom_parser file = R.with_file ?custom_parser (parse ?format) file ocaml-metadata-0.3.1/src/metadataRIFF.mli000066400000000000000000000002761477304117700201250ustar00rootroot00000000000000val parse : ?format:string -> MetadataBase.Reader.t -> MetadataBase.metadata val parse_file : ?format:string -> ?custom_parser:MetadataBase.custom_parser -> string -> MetadataBase.metadata ocaml-metadata-0.3.1/src/metadataWAV.ml000066400000000000000000000001471477304117700176600ustar00rootroot00000000000000let parse = MetadataRIFF.parse ~format:"WAVE" let parse_file = MetadataRIFF.parse_file ~format:"WAVE" ocaml-metadata-0.3.1/test/000077500000000000000000000000001477304117700153565ustar00rootroot00000000000000ocaml-metadata-0.3.1/test/dune000066400000000000000000000001451477304117700162340ustar00rootroot00000000000000(executable (name test) (libraries metadata)) (rule (alias citest) (action (run ./test.exe))) ocaml-metadata-0.3.1/test/test000077500000000000000000000000721477304117700162620ustar00rootroot00000000000000#!/bin/sh dune exec --no-print-directory ./test.exe -- $@ ocaml-metadata-0.3.1/test/test.ml000066400000000000000000000044021477304117700166670ustar00rootroot00000000000000let () = (* assert (Metadata.ID3v2.unterminate 2 "\000ab\000de\000\000" = "\000ab\000de"); *) (* Little endian. *) assert ( Metadata.CharEncoding.Naive.convert ~source:`UTF_16LE "a\x00b\x00c\x00" = "abc"); assert ( Metadata.CharEncoding.Naive.convert ~source:`UTF_16 "\xff\xfea\x00b\x00c\x00" = "abc"); (* Big endian. *) assert ( Metadata.CharEncoding.Naive.convert ~source:`UTF_16BE "\x00a\x00b\x00c" = "abc"); assert ( Metadata.CharEncoding.Naive.convert ~source:`UTF_16 "\xfe\xff\x00a\x00b\x00c" = "abc") let () = List.iter (fun version -> let tag = Metadata.ID3v2.make ~version Metadata.ID3v2. [ { id = `TIT2; data = `Text (`UTF_8, "foobar😅"); flags = default_flags `TIT2; }; { id = `TALB; data = `Text (`UTF_8, "Let's go get them ⚡️"); flags = []; }; ] in ignore (Metadata.Reader.with_string (fun reader -> let tags = Metadata.ID3v2.parse reader in assert (List.assoc "title" tags = {|foobar😅|}); assert (List.assoc "album" tags = {|Let's go get them ⚡️|}); tags) tag)) [3; 4] let () = let tag = Metadata.ID3v2.make ~version:4 Metadata.ID3v2. [ { id = `TIT2; data = `Text (`UTF_8, "foobar😅"); flags = default_flags `TIT2; }; { id = `TALB; data = `Text (`UTF_8, "Let's go get them ⚡️"); flags = []; }; ] in let custom_parser_labels = ref [] in let custom_parser { Metadata.read; length; label; _ } = custom_parser_labels := label :: !custom_parser_labels; match label with | "TIT2" -> let s = read () in assert (length = String.length s) | _ -> () in ignore (Metadata.Reader.with_string ~custom_parser (fun reader -> let tags = Metadata.ID3v2.parse reader in assert (tags = [("album", {|Let's go get them ⚡️|})]); tags) tag); assert (!custom_parser_labels = ["TALB"; "TIT2"])